Build a simple address book in Python

Hydrangeas

It is not uncommon to propose an exercise in Python to construct an address book display. It should display a list of names and allow you to view the details of any individual, as well as being able to add, edit to update entries.

In this article we take a design proposed by Data Flair and expand on it by using Python layout managers and some simple classes to organize the program. Our basic data is just an array of two-element arrays. It could be a dictionary or a database: the code would be much the same.

contactlist = [
['Perry Mason', '3176738493'],
['David Smith', '2684430000'],
['Malcolm Kane', '4338354432'],
['Peter Maur', '6834552341'],
['Roger Krager', '5234852689'],
['Johanna Shaw', '2119876543'],
]

Classes we use for the data

This is pretty simple. We create a Person class which stores the name and phone number.

# One person
class Person():
def __init__(self, nameArray):
self.name = nameArray[0]
self.phone = nameArray[1]

Of course, it could contain a street and E-mail address as well and would work the same way.

Then we create a NameList class which holds an array of Person objects and allows you to add more and delete them one by one if you need to:

# a collection of address entries
class NameList():
def __init__(self, narray):
self.names = []
for nm in narray:
p = Person(nm)
self.names.append(p)
# add a person
def add(self, person):
self.names.append(person)
# delete a person
def delete(self, index):
self.names.pop(index)

Visual design

Our design consists of a listbox of names on the right side, and entry fields for name and phone number on the left side. There are also buttons to add, delete, and update the names. You can also clear the name fields and reset the list to its original six names as shown below:

Address book design

The screen is constructed from a grid layout three columns wide and 2 rows deep. In the left row, we create two Frames which themselves contain grid layout. This simple trick keeps the components together within each Frame. This is illustrated in the following layout diagram.

Grid layout of the address book

Ignoring coding niceties for a minute, if we put the labels, entries, buttons and listbox into the layout it will look like this, with variable size buttons:

Address book with uneven buttons

because the buttons are no larger than needed by the text you put into them. What we need to do is to create a derived button that has a fixed width of, say, 8 characters.

We’ll also create the convenience methods enable and disable which are easier to type. We’ll be making use of those below. The buttons are now all the same width as you can see here.

# derived button is always 8 chars wide
# with simplified enable and disable methods
class DButton(Button):
def __init__(self, master=None, **kwargs):
super().__init__(master, width=8, **kwargs)

def enable(self):
self['state'] = NORMAL

def disable(self):
self['state'] = DISABLED

Likewise, since the labels are blue, we can derive a BlueLabel class:

# all these labels are blue
class BlueLabel(Label):
def __init__(self, root, nm):
super().__init__(root, text=nm, fg='blue')

Here is the code for creating the widgets in the top frame:

# top left frame holds labels
# and entry fields
fr1 = Frame(self.root)
fr1.grid(row=0, column=0)

self.nmList = NameList(Mediator.contactlist)
lb1 = BlueLabel(fr1, "Name")
lb1.grid(row=0, column=0, sticky="E", pady=5, padx=10)
self.nmentry = Entry(fr1)
self.nmentry.grid(row=0, column=1, sticky="W", padx=10, pady=5)

lb2 = BlueLabel(fr1, "Phone")
lb2.grid(row=1, column=0, sticky="E", pady=5, padx=10)
self.phnentry = Entry(fr1)
self.phnentry.grid(row=1, column=1, sticky="W", padx=10, pady=5)

And the code below creates the listbox in column 2. Note that we give it room by having it span 4 rows.

# The listbox goes in column 2
self.lbox = Listbox(self.root)
self.lbox.grid(row=0, column=2, rowspan=4)

Using a Mediator

All of the screen widgets: 5 buttons, the 2 fields and the listbox interact with each other. For example, clicking on a name in the listbox copies the name and the phone number into the entry fields, where you can edit it and store the revised name by clicking on the Update button. So it would seem that the list, the fields and the buttons need to know about each other. This can get rather convoluted, so instead we create a Mediator class that knows about all the widgets. So, with that in mind, we create the layout and tell the Mediator about the fields and buttons.

We start by creating the Mediator and telling it about the listbox and the two entry fields:

# create Mediator and tell it about entry fields
# and the listbox
med = Mediator(self.lbox)
med.setEntries(self.nmentry, self.phnentry)
med.setNamelist(self.nmList)
self.lbox.bind("<<ListboxSelect>>", med.listClicked)
med.refreshList()

Note that we bind the ListboxSelect event to a method in the mediator.

# Clicking the list puts the name and phone in entry fields
def listClicked(self, evt):
index = self.listbox.curselection()
if len(index) > 0:
i = int(index[0])
self.curindex = i
person = self.nameList.names[i]
self.clearFields()
self.nmEntry.insert(END, person.name)
self.phEntry.insert(END, person.phone)

Creating all the buttons

We put the five buttons into a 2 x 3 grid, using the DButton class we just created:

# lower frame holds all the buttons
fr2 = Frame(self.root)
fr2.grid(row=3, column=0)
# create the buttons and assign them commands
self.clrButton = DButton(fr2, text="Clear", command=med.clearFN)
self.clrButton.grid(row=3, column=0, padx=10)

self.addButton = DButton(fr2, text="Add", command=med.addName)
self.addButton.grid(row=3, column=1, padx=10)

self.updateButton = DButton(fr2, text="Update", command=med.updatePerson)
self.updateButton.grid(row=4, column=0, pady=5)

self.deleteButton = DButton(fr2, text="Delete", command=med.deletePerson)
self.deleteButton.grid(row=4, column=1, pady=5)

self.resetButton = DButton(fr2, text="Reset", command=med.resetNames)
self.resetButton.grid(row=5, column=0)

# tell the Mediator about all the buttons
med.setButtons(self.clrButton, self.addButton, self.updateButton,
self.deleteButton, self.resetButton)

Note that in every case, the button command is a call to a method in the Mediator class. This puts all visual object manipulation inside the Mediator, so that controls do not need to know about each other.

Enabling and disabling buttons

We now have the program working as we want it to. The addition and deletion of addresses are handled in a few lines in the Mediator, as is the editing of any name or number.

But one problem remains. If you bring up the program and click on the Delete or Update button, nothing good will happen because you haven’t selected a person in the list to delete or update. So, the ideal solution is to disable those buttons until a person is selected, and then enable them. So when we first pass the buttons to the Mediator, we disable Delete and Update:

# copy all button references into Mediator
def setButtons(self, clear, add, update, delete, reset):
self.clear = clear
self.add = add
self.update = update
self.delete = delete
self.reset = reset
self.delete.disable()
self.update.disable()

Likewise, if you have a person selected and you select Add, it would add another copy of the same name. So once you select a person, you need to disable the Add button until the Clear button is selected, which clears the fields and enables the Add button. All of this is handled in a few lines of code within the listClicked method within the mediator:

self.delete.enable()
self.update.enable()
self.add.disable()

You can see the two cases displayed below:

Updating a Person

The code in the Mediator for updating a Person in the list is incredibly simple:

# changes one name
def updatePerson(self):
person = self.nameList.names[self.curindex]
person.name = self.nmEntry.get()
person.phone = self.phEntry.get()
self.refreshList()

Note that we fetch the person and update its fields, but we never have to put it back in the List, because If you change the contents of an object in an array, you have changed the original: it is not a copy.

Conclusion

We have shown how you can use classes to simplify the address book application and improve the objects in the user interface. Overall, we created a Person class, a NameList class, a DButton derived button class and a BlueLabel class to make all the labels blue. And by using a Mediator class (and Design Pattern) we simplified the potential tangle of click events that could otherwise make the program hard to understand. So again, classes simply your code, because Python is an object-oriented language through and through.

The complete code for this program can be downloaded from GitHub, under jameswcooper/articles/AddressBook.

--

--

--

is the author of “Python Programming with Design Patterns” and 20 previous books.

Love podcasts or audiobooks? Learn on the go with our new app.

Recommended from Medium

AIOps — the Basics

It’s the Final Countdown

Powerful Twitter Sentiment Analysis in Under 35 Lines of Code

7 Ways to Prepare for a Coding Test

Memory/Storage

How to add any network to Metamask using Chainlist

Read-later with send2kindle

How to upload a library to maven central using gradle

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
James Cooper

James Cooper

is the author of “Python Programming with Design Patterns” and 20 previous books.

More from Medium

Tic Tac Toe — Python Tkinter GUI

Accessing NetCDF Nested Groups Data in Python

Making a python function into a tkinter App

Python Language: Intro [Py-Series-1]