How to do it...

Firstly, we extract all the pieces of code that interact with the database into a separate class. This will allow us to hide the implementations details of our persistence layer, only exposing the four necessary methods, get_contacts, add_contact, update_contact, and delete_contact:

class ContactsRepository(object):
def __init__(self, conn):
self.conn = conn

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

def get_contacts(self):
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]
yield contact

def add_contact(self, contact):
sql = "INSERT INTO contacts VALUES (?, ?, ?, ?)"
with self.conn:
cursor = self.conn.cursor()
cursor.execute(sql, self.to_values(contact))
contact.rowid = cursor.lastrowid
return contact

def update_contact(self, contact):
rowid = contact.rowid
sql = """UPDATE contacts
SET last_name = ?, first_name = ?, email = ?,
phone = ?
WHERE rowid = ?"""
with self.conn:
self.conn.execute(sql, self.to_values(contact) + (rowid,))
return contact

def delete_contact(self, contact):
sql = "DELETE FROM contacts WHERE rowid = ?"
with self.conn:
self.conn.execute(sql, (contact.rowid,))

This, alongside the Contact class, will compose our model.

Now, our view will simply contain the sufficient code to display the GUI and the methods to let the controller update it. We will also rename the class to ContactsView to better express its purpose:

class ContactsView(tk.Tk):
def __init__(self):
super().__init__()
self.title("SQLite Contacts list")
self.list = ContactList(self, height=15)
self.form = UpdateContactForm(self)
self.btn_new = tk.Button(self, text="Add new 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)

def set_ctrl(self, ctrl):
self.btn_new.config(command=ctrl.create_contact)
self.list.bind_doble_click(ctrl.select_contact)
self.form.bind_save(ctrl.update_contact)
self.form.bind_delete(ctrl.delete_contact)

def add_contact(self, contact):
self.list.insert(contact)

def update_contact(self, contact, index):
self.list.update(contact, index)

def remove_contact(self, index):
self.form.clear()
self.list.delete(index)

def get_details(self):
return self.form.get_details()

def load_details(self, contact):
self.form.load_details(contact)

Note that user input is handled by the controller, so we added a set_ctrl method to connect it to the Tkinter callbacks.

Our ContactsController class will now contain all code missing from our initial App class, that is, the interactions between interface and persistence with the selection and contacts attributes:

class ContactsController(object):
def __init__(self, repo, view):
self.repo = repo
self.view = view
self.selection = None
self.contacts = list(repo.get_contacts())

def create_contact(self):
new_contact = NewContact(self.view).show()
if new_contact:
contact = self.repo.add_contact(new_contact)
self.contacts.append(contact)
self.view.add_contact(contact)

def select_contact(self, index):
self.selection = index
contact = self.contacts[index]
self.view.load_details(contact)

def update_contact(self):
if not self.selection:
return
rowid = self.contacts[self.selection].rowid
update_contact = self.view.get_details()
update_contact.rowid = rowid

contact = self.repo.update_contact(update_contact)
self.contacts[self.selection] = contact
self.view.update_contact(contact, self.selection)

def delete_contact(self):
if not self.selection:
return
contact = self.contacts[self.selection]
self.repo.delete_contact(contact)
self.view.remove_contact(self.selection)

def start(self):
for c in self.contacts:
self.view.add_contact(c)
self.view.mainloop()

We will create a __main__.py script that will allow us not only to Bootstrap our application, but also to be able to launch it from a zipped file or with the name of the containing directory:

# Suppose that __main__.py is in the directory chapter5_05
$ python chapter5_05
# Or if we compress the directory contents
$ python chapter5_05.zip
..................Content has been hidden....................

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