Environment Setup
Editor: Sublime Text3 Operating System: Windows 10 Python Version: Python3
Required Libraries:
- PyQt5: For creating the graphical user interface.
- PyQt5-tools: For converting UI files designed in QT Creator to Python files.
- sqlite3: For database operations (optional, as PyQt5's QtSql module also supports SQLite).
- Pillow (Image): For handling image resources and application icons.
- PyInstaller: For packaging the application into an executable file.
Installation commands:
pip3 install pyqt5
pip3 install pyqt5-tools
pip3 install sqlite3
pip3 install Pillow
pip3 install pyinstaller
Database Loading with SQLite
SQLite is chosen for its lightweight nature and portability. Basic SQL knowledge is sufficient for this implementation.
Initializing the Database
The initDB function initializes the database by checking for an existing file, creating it if necessary, and loading data.
def initialize_database(self):
home_directory = os.path.expanduser('~')
config_folder = '.PasswordManager'
if config_folder not in os.listdir(home_directory):
os.mkdir(os.path.join(home_directory, config_folder))
database_path = os.path.join(home_directory, config_folder, 'passwords.db')
self.connection = sqlite3.connect(database_path)
self.connection.isolation_level = None
if not os.path.exists(database_path):
self.connection.execute('''CREATE TABLE credentials
(id INTEGER PRIMARY KEY AUTOINCREMENT,
website TEXT,
username TEXT,
password TEXT,
url TEXT)''')
cursor = self.connection.cursor()
cursor.execute('SELECT * FROM credentials')
self.loaded_data = cursor.fetchall()
cursor.close()
self.total_entries = len(self.loaded_data)
Displaying Data in the Table
The initGrid function sets up a QTableWidget to display the loaded data base records.
def setup_table(self):
self.table = QTableWidget()
self.setCentralWidget(self.table)
self.table.setColumnCount(4)
self.table.setRowCount(0)
column_widths = [100, 150, 200, 150]
for col in range(4):
self.table.setColumnWidth(col, column_widths[col])
headers = ['Website', 'Username', 'Password', 'URL']
self.table.setHorizontalHeaderLabels(headers)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
for row_index, record in enumerate(self.loaded_data):
self.table.insertRow(row_index)
for col_index in range(4):
cell_value = record[col_index + 1]
table_item = QTableWidgetItem(cell_value)
self.table.setItem(row_index, col_index, table_item)
Adding New Antries
The newAction_def function inserts a new record into the database and updates the table.
def add_entry(self):
entry_data = self.display_input_dialog()
if entry_data[0]:
self.total_entries += 1
self.connection.execute("INSERT INTO credentials VALUES(NULL, ?, ?, ?, ?)",
(entry_data[1], entry_data[2], entry_data[3], entry_data[4]))
self.table.insertRow(self.total_entries - 1)
for col in range(4):
new_item = QTableWidgetItem(entry_data[col + 1])
self.table.setItem(self.total_entries - 1, col, new_item)
Editing Existing Entries
The editAction_def function updates a selected record in the database and refreshes the table.
def edit_entry(self):
selected_items = self.table.selectedItems()
if selected_items:
row_to_edit = self.table.row(selected_items[0])
existing_data = [self.table.item(row_to_edit, col).text() for col in range(4)]
updated_data = self.display_input_dialog(*existing_data)
if updated_data[0]:
self.connection.execute('''UPDATE credentials SET
website = ?, username = ?,
password = ?, url = ?
WHERE id = ?''',
(updated_data[1], updated_data[2], updated_data[3], updated_data[4], row_to_edit + 1))
for col in range(4):
updated_item = QTableWidgetItem(updated_data[col + 1])
self.table.setItem(row_to_edit, col, updated_item)
else:
self.show_warning_message()
Deleting Entries
The delAction_def function removes a selected record and adjusts subsequent IDs.
def delete_entry(self):
selected_items = self.table.selectedItems()
if selected_items:
row_to_delete = self.table.row(selected_items[0])
self.table.removeRow(row_to_delete)
self.connection.execute("DELETE FROM credentials WHERE id = ?", (row_to_delete + 1,))
for subsequent_id in range(row_to_delete + 2, self.total_entries + 1):
self.connection.execute("UPDATE credentials SET id = ? WHERE id = ?",
(subsequent_id - 1, subsequent_id))
self.total_entries -= 1
else:
self.show_warning_message()
Note: After deletion, IDs of subsequent entries are decremented to maintain sequential order.
Summary
This implementation covers core database operations for a password manager. The next step involves adding backup functionality, which will be addressed separate due to its complexity involving email integration.
Complete Code Example
import sys
import sqlite3
import os
from PyQt5.QtWidgets import *
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import *
class PasswordManager(QMainWindow):
def __init__(self):
super().__init__()
self.setup_toolbar()
self.initialize_database()
self.setup_table()
self.setGeometry(300, 300, 700, 400)
self.setWindowTitle('Password Manager')
self.setWindowIcon(QIcon('icon.png'))
def setup_toolbar(self):
add_action = QAction(QIcon('add.png'), 'Add Ctrl+N', self)
edit_action = QAction(QIcon('edit.png'), 'Edit Ctrl+E', self)
delete_action = QAction(QIcon('delete.png'), 'Delete', self)
backup_action = QAction(QIcon('backup.png'), 'Backup Ctrl+B', self)
add_action.setShortcut('Ctrl+N')
edit_action.setShortcut('Ctrl+E')
delete_action.setShortcut('Delete')
backup_action.setShortcut('Ctrl+B')
add_action.triggered.connect(self.add_entry)
edit_action.triggered.connect(self.edit_entry)
delete_action.triggered.connect(self.delete_entry)
backup_action.triggered.connect(self.backup_data)
self.addToolBar('Add').addAction(add_action)
self.addToolBar('Edit').addAction(edit_action)
self.addToolBar('Delete').addAction(delete_action)
self.addToolBar('Backup').addAction(backup_action)
def backup_data(self):
pass
def initialize_database(self):
home_directory = os.path.expanduser('~')
config_folder = '.PasswordManager'
if config_folder not in os.listdir(home_directory):
os.mkdir(os.path.join(home_directory, config_folder))
database_path = os.path.join(home_directory, config_folder, 'passwords.db')
self.connection = sqlite3.connect(database_path)
self.connection.isolation_level = None
if not os.path.exists(database_path):
self.connection.execute('''CREATE TABLE credentials
(id INTEGER PRIMARY KEY AUTOINCREMENT,
website TEXT,
username TEXT,
password TEXT,
url TEXT)''')
cursor = self.connection.cursor()
cursor.execute('SELECT * FROM credentials')
self.loaded_data = cursor.fetchall()
cursor.close()
self.total_entries = len(self.loaded_data)
def setup_table(self):
self.table = QTableWidget()
self.setCentralWidget(self.table)
self.table.setColumnCount(4)
self.table.setRowCount(0)
column_widths = [100, 150, 200, 150]
for col in range(4):
self.table.setColumnWidth(col, column_widths[col])
headers = ['Website', 'Username', 'Password', 'URL']
self.table.setHorizontalHeaderLabels(headers)
self.table.setEditTriggers(QAbstractItemView.NoEditTriggers)
self.table.setSelectionBehavior(QAbstractItemView.SelectRows)
for row_index, record in enumerate(self.loaded_data):
self.table.insertRow(row_index)
for col_index in range(4):
cell_value = record[col_index + 1]
table_item = QTableWidgetItem(cell_value)
self.table.setItem(row_index, col_index, table_item)
def add_entry(self):
entry_data = self.display_input_dialog()
if entry_data[0]:
self.total_entries += 1
self.connection.execute("INSERT INTO credentials VALUES(NULL, ?, ?, ?, ?)",
(entry_data[1], entry_data[2], entry_data[3], entry_data[4]))
self.table.insertRow(self.total_entries - 1)
for col in range(4):
new_item = QTableWidgetItem(entry_data[col + 1])
self.table.setItem(self.total_entries - 1, col, new_item)
def edit_entry(self):
selected_items = self.table.selectedItems()
if selected_items:
row_to_edit = self.table.row(selected_items[0])
existing_data = [self.table.item(row_to_edit, col).text() for col in range(4)]
updated_data = self.display_input_dialog(*existing_data)
if updated_data[0]:
self.connection.execute('''UPDATE credentials SET
website = ?, username = ?,
password = ?, url = ?
WHERE id = ?''',
(updated_data[1], updated_data[2], updated_data[3], updated_data[4], row_to_edit + 1))
for col in range(4):
updated_item = QTableWidgetItem(updated_data[col + 1])
self.table.setItem(row_to_edit, col, updated_item)
else:
self.show_warning_message()
def delete_entry(self):
selected_items = self.table.selectedItems()
if selected_items:
row_to_delete = self.table.row(selected_items[0])
self.table.removeRow(row_to_delete)
self.connection.execute("DELETE FROM credentials WHERE id = ?", (row_to_delete + 1,))
for subsequent_id in range(row_to_delete + 2, self.total_entries + 1):
self.connection.execute("UPDATE credentials SET id = ? WHERE id = ?",
(subsequent_id - 1, subsequent_id))
self.total_entries -= 1
else:
self.show_warning_message()
def show_warning_message(self):
warning_box = QMessageBox()
warning_box.setText('No row selected!')
warning_box.addButton(QMessageBox.Ok)
warning_box.exec_()
def display_input_dialog(self, website='', username='', password='', url=''):
dialog = QDialog(self)
group_box = QGroupBox('Edit Information', dialog)
website_label = QLabel('Website:', group_box)
website_input = QLineEdit(group_box)
website_input.setText(website)
username_label = QLabel('Username:', group_box)
username_input = QLineEdit(group_box)
username_input.setText(username)
password_label = QLabel('Password:', group_box)
password_input = QLineEdit(group_box)
password_input.setText(password)
url_label = QLabel('URL:', group_box)
url_input = QLineEdit(group_box)
url_input.setText(url)
ok_button = QPushButton('OK', dialog)
cancel_button = QPushButton('CANCEL', dialog)
ok_button.clicked.connect(dialog.accept)
ok_button.setDefault(True)
cancel_button.clicked.connect(dialog.reject)
layout = QVBoxLayout()
widgets = [website_label, website_input,
username_label, username_input,
password_label, password_input,
url_label, url_input]
for widget in widgets:
layout.addWidget(widget)
group_box.setLayout(layout)
group_box.setFixedSize(group_box.sizeHint())
button_layout = QHBoxLayout()
button_layout.addWidget(ok_button)
button_layout.addWidget(cancel_button)
dialog_layout = QVBoxLayout()
dialog_layout.addWidget(group_box)
dialog_layout.addLayout(button_layout)
dialog.setLayout(dialog_layout)
dialog.setFixedSize(dialog.sizeHint())
if dialog.exec_():
return True, website_input.text(), username_input.text(), password_input.text(), url_input.text()
return False, None, None, None, None
if __name__ == '__main__':
app = QApplication(sys.argv)
manager = PasswordManager()
manager.show()
app.exec_()
manager.connection.close()
sys.exit(0)