Dec-03-2023, 10:24 PM
The final version. May still be a bug or two.
# Do the imports import tkinter as tk from tkinter import ttk from random import sample from string import ascii_letters, digits import os import sqlite3 as sq import hashlib from PIL import Image # Get path path = os.path.realpath(os.path.dirname(__file__)) # Characters for random password use characters = ascii_letters+digits+'#&!*' class Data: ''' Class will handle all database interaction ''' def __init__(self): self.connect = sq.connect(f'{path}/resources/PasswordManager/password.db') self.cursor = self.connect.cursor() def create(self): query = ''' create table if not exists users ( id integer primary key, level integer default 5, website text not null, username text not null, password text not null, date default current_timestamp ); ''' self.connect.execute(query) self.connect.commit() # Check if we have any entries. If not create the admin entry query = 'select exists(select 1 from users);' result = self.cursor.execute(query).fetchone() if result[0] == 0: self.insert(level=1,website='None',username='admin',password='admin') def insert(self, level=5, edit=False, **args): ''' Method for inserting users ''' level = int(level) website = args['website'] username = args['username'] password = args['password'] if 'id' in args.keys(): id = args['id'] # Hash password # password = args['password'].encode() # password = hashlib.sha256(password).digest() if edit: query = f'update users set website="{website}", username="{username}", password="{password}" where id="{id}";' self.cursor.execute(query) else: query = ''' insert into users (level, website, username, password) values (?,?,?,?); ''' self.cursor.execute(query, (level, website, username, password)) self.connect.commit() def getuser(self, username, password): ''' Method for getting single user ''' username = username.strip() password = password.strip() # password = password.encode() # password = hashlib.sha256(password).digest() query = f'select username, level, password from users where username="{username}"' cursor = self.cursor.execute(query).fetchone() if cursor: if cursor[2] == password: return cursor return False def getall(self): ''' Method for getting all users ''' query = 'select * from users' result = self.cursor.execute(query).fetchall() return result def delete(self, id): if id: query = f'delete from users where id="{id}";' self.cursor.execute(query) return True return False class Page(tk.Frame): ''' Base class for pages ''' def __init__(self, *args, **kwargs): tk.Frame.__init__(self, *args, **kwargs) class LoginForm(Page): ''' Page for the login view ''' def __init__(self, *args, **kwargs): Page.__init__(self, *args, **kwargs) def form(self, parent): self.parent = parent container = tk.Frame(parent) container.grid(column=0, row=2, sticky = 'new', padx=5, pady=5) self.msg = tk.Label(container, anchor='w', padx=5, pady=5) self.msg.grid(column=0, columnspan=2, row=0, sticky='new', padx=(280,5), pady=1) self.msg.grid_forget() label = tk.Label(container, text='Username:') label.grid(column=0, row=1, sticky='new', padx=(275,5), pady=5) self.username = tk.Entry(container) self.username.focus() self.username.grid(column=1, row=1, sticky='new', padx=5, pady=5) label = tk.Label(container, text='Password:') label.grid(column=0, row=2, sticky='new', padx=(275,5), pady=5) self.password = tk.Entry(container, show='*') self.password.grid(column=1, row=2, sticky='new', padx=5, pady=5) btnframe = tk.Frame(container) btnframe.grid(column=0, columnspan=2, row=3, sticky='new', padx=(250,5), pady=5) self.btn = tk.Button(btnframe, text='Login', cursor='hand2') self.btn.grid(column=0, row=0, sticky='new', padx=(150,0), pady=5) self.cancel = tk.Button(btnframe, text='Cancel', cursor='hand2') self.cancel.grid(column=1, row=0, sticky='new', padx=(5,0), pady=5) class MainView(Page): ''' Page for the main view ''' def __init__(self, *args, **kwargs): Page.__init__(self, *args, **kwargs) def view(self, parent): self.container = tk.Frame(parent) self.container.grid(column=0, row=2, sticky='news', padx=5, pady=5) self.container.grid_columnconfigure(0, weight=3) self.container.grid_rowconfigure(0, weight=0) self.container.grid_rowconfigure(1, weight=3) self.msg = tk.Label(self.container, padx=5, pady=5) self.msg.grid(column=0, row=0, sticky='new', padx=5, pady=1) self.msg.grid_forget() contentframe = tk.Frame(self.container) contentframe.grid(column=0, row=1, sticky='news', padx=5, pady=1) contentframe.grid_columnconfigure(0, weight=3) contentframe.grid_rowconfigure(0, weight=3) columns = ('id', 'website', 'username', 'password', 'date') # columns = ('id', 'level', 'website', 'username', 'password', 'date') self.tree = ttk.Treeview(contentframe, columns=columns, show='headings', selectmode='browse') for column in columns: self.tree.heading(column, text=column.title()) width = 50 if column in ('id', 'level') else 50 stretch = 'no' if column == 'id' else 'yes' self.tree.column(column, minwidth=50, width=width, stretch=stretch) self.tree.grid(column=0, row=0, sticky='news', padx=5, pady=5) self.linkframe = tk.Frame(self.container) self.linkframe.grid(column=0, row=2, sticky='new', padx=5, pady=5) for i in range(4): self.linkframe.columnconfigure(i, weight=3, uniform='links') self.add_label = tk.Label(self.linkframe, text='Add', fg='blue', cursor='hand2') self.add_label.grid(column=0, row=0, sticky='new', padx=5, pady=5) self.add_label.bind('<Enter>', lambda event: self.on_enter(self.add_label)) self.add_label.bind('<Leave>', lambda event: self.on_exit(self.add_label)) self.edit_label = tk.Label(self.linkframe, text='Edit', fg='blue', cursor='hand2') self.edit_label.grid(column=1, row=0, sticky='new', padx=5, pady=5) self.edit_label.bind('<Enter>', lambda event: self.on_enter(self.edit_label)) self.edit_label.bind('<Leave>', lambda event: self.on_exit(self.edit_label)) self.delete_label = tk.Label(self.linkframe, text='Delete', fg='blue', cursor='hand2') self.delete_label.grid(column=2, row=0, sticky='new', padx=5, pady=5) self.delete_label.bind('<Enter>', lambda event: self.on_enter(self.delete_label)) self.delete_label.bind('<Leave>', lambda event: self.on_exit(self.delete_label)) self.exit_label = tk.Label(self.linkframe, text='Quit', fg='firebrick', cursor='hand2') self.exit_label.grid(column=3, row=0, sticky='new', padx=5, pady=5) self.exit_label.bind('<Enter>', lambda event: self.on_enter(self.exit_label)) self.exit_label.bind('<Leave>', lambda event: self.on_quit(self.exit_label)) def on_enter(self, link): link['fg'] = 'red' def on_exit(self, link): link['fg'] = 'blue' def on_quit(self, link): link['fg'] = 'firebrick' class DataForm(Page): ''' Page for the data form ''' def __init__(self, *args, **kwargs): Page.__init__(self, *args, **kwargs) def view(self, parent, data=None, edit=False): container = tk.Frame(parent, padx=200) container.grid(column=0, row=2, sticky='news', padx=5, pady=5) container.grid_columnconfigure(0, weight=1) container.grid_columnconfigure(1, weight=3) self.msg = tk.Label(container, anchor='w', padx=5, pady=5) self.msg.grid(column=0, columnspan=2, row=0, sticky='new', padx=5,pady=5) self.msg.grid_forget() label = tk.Label(container, text='Website:', anchor='w') label.grid(column=0, row=1, sticky='new', padx=5, pady=5) self.website = tk.Entry(container) self.website.grid(column=1, row=1, sticky='new', padx=5, pady=5) label = tk.Label(container, text='Username:', anchor='w') label.grid(column=0, row=2, sticky='new', padx=5, pady=5) self.username = tk.Entry(container) self.username.grid(column=1, row=2, sticky='new', padx=5, pady=5) label = tk.Label(container, text='Password:', anchor='w') label.grid(column=0, row=3, sticky='new', padx=5, pady=5) self.password = tk.Entry(container) self.password.grid(column=1, row=3, sticky='new', padx=5, pady=5) self.gen_btn = tk.Button(container, text='Generate Password') self.gen_btn.grid(column=0, row=4, sticky='new', padx=5, pady=5) self.add_btn = tk.Button(container, text='Add Data') self.add_btn.grid(column=1, row=4, sticky='new', padx=5, pady=5) if edit: self.website.insert(0, data[1]) self.username.insert(0, data[2]) self.password.insert(0, data[3]) class Window(tk.Frame): ''' Container class ''' def __init__(self, parent, *args, **kwargs): tk.Frame.__init__(self, *args, **kwargs) parent.columnconfigure(0, weight=1) parent.rowconfigure(0, weight=1) parent.geometry('800x600') parent.resizable(False, False) self.parent = parent self.container = tk.Frame(parent) self.container.grid(column=0, row=0, sticky='news') self.container.columnconfigure(0, weight=1) self.container.rowconfigure(1, weight=0) self.container.rowconfigure(2, weight=3) img = f'{path}/resources/PasswordManager/logo2.png' image = tk.PhotoImage(file=img) image.backup = image label = tk.Label(self.container, image=image) label.grid(column=0, row=0, sticky='new', padx=5, pady=5) class Controller: ''' Controller class handles communictaions between all other classes ''' def __init__(self, data, window): self.data = data self.window = window # Create the database table if not exists self.data.create() # Pages self.loginform = LoginForm() self.loginform.form(self.window.container) self.mainview = MainView() self.dataform = DataForm() # Button Commands self.loginform.btn['command'] = self.login self.loginform.cancel['command'] = self.window.parent.destroy # Binds self.window.parent.bind('<Return>', lambda event: self.login()) self.window.parent.bind('<KP_Enter>', lambda event: self.login()) def _mainview(self, msg=None): ''' Method for calling the main view ''' self.mainview.view(self.window.container) self.mainview.tree.bind('ButtonRelease-1>', self.edit) self.mainview.add_label.bind('<Button-1>', lambda event: self.addform()) self.mainview.edit_label.bind('<Button-1>', lambda event: self.edit()) self.mainview.delete_label.bind('<Button-1>', lambda event: self.delete()) self.mainview.exit_label.bind('<Button-1>', lambda event: self.window.parent.destroy()) # If there is a message display it. else forget it if msg: self.mainview.msg.config(text=msg, bg='darkgreen', fg='lime') self.mainview.msg.grid(column=0, row=0, sticky='new', padx=5, pady=1) self.window.parent.after(3000, self._mainview) else: self.mainview.msg.grid_forget() # Configure the display data = self.data.getall() self.mainview.tree.tag_configure('odd', background='#fffeee') self.mainview.tree.tag_configure('even', background='#eeeeef') self.mainview.tree.tag_configure('admin', background='pink') # Loop through and insert data into treeview for index, entry in enumerate(data): if entry[3] == 'admin': self.mainview.tree.insert('', index, values=(entry[0], entry[2], entry[3], entry[4], entry[5]), tags=('admin', )) elif index % 2 == 0: self.mainview.tree.insert('', index, values=(entry[0], entry[2], entry[3], entry[4], entry[5]), tags=('even', )) else: self.mainview.tree.insert('', index, values=(entry[0], entry[2], entry[3], entry[4], entry[5]), tags=('odd', )) def addform(self): ''' Method for calling the add data form ''' self.dataform.view(self.window.container) self.dataform.gen_btn['command'] = self.gen self.dataform.add_btn['command'] = self.add self.window.parent.bind('<Return>', lambda event: self.add()) self.window.parent.bind('<KP_Enter>', lambda event: self.add()) def gen(self): ''' Method for generating a 10 character password ''' password = ''.join(sample(characters, k=10)) self.dataform.password.delete(0, tk.END) self.dataform.password.insert(0, password) def add(self, id=None): ''' Method for adding data to database ''' website = self.dataform.website.get().strip() username = self.dataform.username.get().strip() password = self.dataform.password.get().strip() # Set variables for error checking error = 0 messages = [] # Do error checking self.dataform.msg['fg'] = 'red' if not website: error += 1 messages.append('Website') if not username: error += 1 messages.append('Username') if not password: error += 1 messages.append('Password') # If there is an error, display message if error > 0: msg = 'fields' if error > 1 else 'field' self.dataform.msg.grid(column=0, columnspan=2, row=0, sticky='new', padx=5,pady=5) self.dataform.msg.config(text=f'Required {msg}: {", ".join(messages)}', bg='pink', fg='red') else: # Else everything is ok, continue if id: self.data.insert(website=website, username=username, password=password, id=id, edit=True) else: self.data.insert(website=website, username=username, password=password) # Destroy maiview container and redisplay self.mainview.container.destroy() self.mainview.container = tk.Frame(self.window.container) self.mainview.container.grid(column=0, row=2, sticky='news', padx=5, pady=5) self.mainview.container.grid_columnconfigure(0, weight=3) self.mainview.container.grid_rowconfigure(0, weight=3) msg = f'Data for {username} updataed.' if id else 'Successfully added data.' self._mainview(msg) def login(self): ''' Method for getting user login info ''' username = self.loginform.username.get().strip() password = self.loginform.password.get().strip() # Check for empty fields if not username or not password: self.loginform.msg.grid(column=0, columnspan=2, row=0, sticky='new', padx=(280,5), pady=1) self.loginform.msg.config(text='All fields are required', fg='red', bg='pink') else: # Else make query call result = self.data.getuser(username, password) # See if we got a match in the query call if result: self._mainview('You are now logged in.') else: # No match found, display message self.loginform.msg.grid(column=0, columnspan=2, row=0, sticky='new', padx=(280,5), pady=1) self.loginform.msg.config(text=' That username and password\ncombination does not exist.', fg='red', bg='pink') def edit(self): ''' Method for editing user information ''' # Get the current focused item current = self.mainview.tree.focus() item = self.mainview.tree.item(current)['values'] # If there is focus if item: self.dataform.view(self.window.container, item) self.dataform.add_btn['text'] = 'Edit' self.dataform.gen_btn['command'] = self.gen self.dataform.website.insert(0, item[1]) self.dataform.username.insert(0, item[2]) self.dataform.password.insert(0, item[3]) id = item[0] # Go to add data form and populate self.dataform.add_btn['command'] = lambda: self.add(id) def delete(self): ''' Method for deleting records ''' # Get current focused item current = self.mainview.tree.focus() item = self.mainview.tree.item(current)['values'] # Make the database query action = self.data.delete(item[0]) # True returned, record deleted if action: self.mainview.container.destroy() self.mainview.container = tk.Frame(self.window.container) self.mainview.container.grid(column=0, row=2, sticky='news', padx=5, pady=5) self.mainview.container.grid_columnconfigure(0, weight=3) self.mainview.container.grid_rowconfigure(0, weight=3) self._mainview('Record has been deleted.') if __name__ == '__main__': root = tk.Tk() controller = Controller(Data(), Window(root)) root.mainloop()
I welcome all feedback.
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags
The only dumb question, is one that doesn't get asked.
My Github
How to post code using bbtags