Python Forum
Python code review | Tkinter gui application
Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Python code review | Tkinter gui application
#20
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


Reply


Messages In This Thread
RE: Python code review | Tkinter gui application - by menator01 - Dec-03-2023, 10:24 PM

Possibly Related Threads…
Thread Author Replies Views Last Post
  GUI application - code review Sr999 3 897 Jan-06-2024, 10:14 PM
Last Post: Sr999
  Code review of my rock paper scissors game Milan 0 2,099 May-25-2022, 06:59 AM
Last Post: Milan
  Review on (new) Python module: Function but Lazy Python jeertmans 3 2,505 Nov-01-2021, 06:57 PM
Last Post: ndc85430
  First time python user - Calculator code review Steamy 1 2,286 Jul-22-2020, 05:59 PM
Last Post: Larz60+

Forum Jump:

User Panel Messages

Announcements
Announcement #1 8/1/2020
Announcement #2 8/2/2020
Announcement #3 8/6/2020