How to do it...

To add new contacts to our database, we will define a Toplevel subclass that reuses ContactForm to instantiate a new contact:

class NewContact(tk.Toplevel):
def __init__(self, parent):
super().__init__(parent)
self.contact = None
self.form = ContactForm(self)
self.btn_add = tk.Button(self, text="Confirm",
command=self.confirm)
self.form.pack(padx=10, pady=10)
self.btn_add.pack(pady=10)

def confirm(self):
self.contact = self.form.get_details()
if self.contact:
self.destroy()

def show(self):
self.grab_set()
self.wait_window()
return self.contact

The following top-level window will be displayed on top of the main window and returns the focus once the dialog is confirmed or closed:

We will also extend our ContactForm class with two additional buttons—one for updating the contact information, and another one for deleting the selected contact:

class UpdateContactForm(ContactForm):
def __init__(self, master, **kwargs):
super().__init__(master, **kwargs)
self.btn_save = tk.Button(self, text="Save")
self.btn_delete = tk.Button(self, text="Delete")

self.btn_save.pack(side=tk.RIGHT, ipadx=5, padx=5, pady=5)
self.btn_delete.pack(side=tk.RIGHT, ipadx=5, padx=5, pady=5)

def bind_save(self, callback):
self.btn_save.config(command=callback)

def bind_delete(self, callback):
self.btn_delete.config(command=callback)

The bind_save and bind_delete methods allow us to attach a callback to the corresponding button's command.

To integrate all these changes, we will add the following code to our App class:

class App(tk.Tk):
def __init__(self, conn):
super().__init__()
self.title("SQLite Contacts list")
self.conn = conn
self.selection = None
self.list = ContactList(self, height=15)
self.form = UpdateContactForm(self)
self.btn_new = tk.Button(self, text="Add new contact",
command=self.add_contact)
self.contacts = self.load_contacts()

for contact in self.contacts:
self.list.insert(contact)
self.list.pack(side=tk.LEFT, padx=10, pady=10)
self.form.pack(padx=10, pady=10)
self.btn_new.pack(side=tk.BOTTOM, pady=5)

self.list.bind_doble_click(self.show_contact)
self.form.bind_save(self.update_contact)
self.form.bind_delete(self.delete_contact)

We also need to modify the load_contacts method to create the contacts from a query result:

    def load_contacts(self):
contacts = []
sql = """SELECT rowid, last_name, first_name, email, phone
FROM contacts"""
for row in self.conn.execute(sql):
contact = Contact(*row[1:])
contact.rowid = row[0]
contacts.append(contact)
return contacts

def show_contact(self, index):
self.selection = index
contact = self.contacts[index]
self.form.load_details(contact)

To add a contact to the list, we will instantiate a NewContact dialog and call its show method to get the details of the new contact. If these values are valid, we will store them in a tuple in the same order as they are specified in our INSERT statement:

    def to_values(self, c):
return (c.last_name, c.first_name, c.email, c.phone)

def add_contact(self):
new_contact = NewContact(self)
contact = new_contact.show()
if not contact:
return
values = self.to_values(contact)
with self.conn:
cursor = self.conn.cursor()
cursor.execute("INSERT INTO contacts VALUES (?,?,?,?)",
values)
contact.rowid = cursor.lastrowid
self.contacts.append(contact)
self.list.insert(contact)

Once a contact is selected, we can update its details by retrieving the current form values. If they are valid, we execute an UPDATE statement to set the columns of the record with the specified rowid.

Since the fields of this statement are in the same order as the INSERT statement, we reuse the to_values method to create a tuple from the contact instance—the only difference is that we have to append the substitution parameter for rowid:

    def update_contact(self):
if self.selection is None:
return
rowid = self.contacts[self.selection].rowid
contact = self.form.get_details()
if contact:
values = self.to_values(contact)
with self.conn:
sql = """UPDATE contacts SET
last_name = ?,
first_name = ?,
email = ?,
phone = ?
WHERE rowid = ?"""
self.conn.execute(sql, values + (rowid,))
contact.rowid = rowid
self.contacts[self.selection] = contact
self.list.update(contact, self.selection)

To delete the selected contact, we get its rowid to replace it in our DELETE statement. Once the transaction is committed, the contact is removed from the GUI by clearing the form and deleting it from the list. The selection attribute is also set to None to avoid performing operations over an invalid selection:

    def delete_contact(self):
if self.selection is None:
return
rowid = self.contacts[self.selection].rowid
with self.conn:
self.conn.execute("DELETE FROM contacts WHERE rowid = ?",
(rowid,))
self.form.clear()
self.list.delete(self.selection)
self.selection = None

Finally, we will wrap the code to initialize our application in a main function:

def main():
with sqlite3.connect("contacts.db") as conn:
app = App(conn)
app.mainloop()

if __name__ == "__main__":
main()

With all these changes, our complete application will look as follows:

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset