SMS Shopping List, Part 1
The Problem
I have 3 kids. And a wife. We lead pretty hectic lives, and there doesn't seem to be an end in sight to the insanity. Given all of that, it seems as if we always have a list of stuff that we need from the grocery store.
More than once, I've been at the grocery store fulfilling the needs on the pre-written list only to be bombarded with text messages asking for this and that. Sometimes I get lucky and manage to get everything on the lists (the written lists and all of the impromptu SMS lists). More often than not I end up overlooking something because it isn't all in one place. Quite frankly it doesn't help that I'm easily distracted in the grocery store.
There has to be a better way.
The Solution
Obviously, we need a centralized mobile list that is easy for everyone to add to and access. Also, since everyone, myself excluded, prefers to communicate via text message, it seems like I should probably use that.
Recently I've been playing around in Python. One day I stumbled across Charles Leifer's post about writing a note taking app in Python with Flask. I thought the idea was pretty brilliant, and it hit me that I could do something like that to solve my shopping list problem.
The requirements, as I saw them:
A centralized page to view the current list.
Ability to add items to the list via SMS
Ability to see who had added a specific item, and when
At work we use Twilio for web based telephony, so I had a basic understanding of how it worked. I had already been through the Twilio python tutorials and knew how to do some pretty basic stuff in Python with Twilio. The tutorials even used Flask. So the plan was decided.
The implementation
Set up
For the most part, I used Twilio's documentation about setting up the dev environment. The gist of it was something like this:
- Install python and some tools:
brew installl pythonbrew doctorFix any changes from above
easy_install pippip install virtualenv
- Create a virtual environment for holding all of my stuff:
virtualenv --no-site-packages SMShoppingListcd SMShoppingListsource bin/activate
- Install the packages I need to get started
pip install Flask SQLAlchemy twilio Flask-Migrate Flask-Script WebHelpers
The last thing I did was to set up git with a .gitignore file. Nothing magical
here -- just git init and then pull down the sample python .gitignore file
from Github.
Getting started
I started out with some basic stuff. First I made the directory structures that I needed
mkdir -p app/static app/templates and then set up the init for the app:
Basic models:
Basic views:
|
# app/views.py
|
|
import os
|
|
from flask import render_template, request
|
|
from app import app
|
|
|
|
@app.route('/')
|
|
def index():
|
|
return render_template('index.html')
|
A config file
The last thing I wanted was a Django-style manage.py. I'm not sure why I do it this way, to be honest. It works for me, so I just roll with it. Until I find a better way ....
Adding some style & pages
Since I have practically 0 design skills, I decided from the beginning that I would use Twitter's Bootstrap to just make a lot of that stuff work. I downloaded the zip, and copied the dist/{css,js} into app/static.
With Bootstrap in place, I could start adding some basic pages. I started with a basic HTML template to define the basic look of all of the pages of the site.
I'm not going to go through this line by line. Suffice to say that this template defines a horizontal nav bar that persists across all of the pages, and a persistent header & footer.
Modelling it all out
With the basic site layout defined, it was time to get to some functionality. I defined some models. From my requirements, I had 2 basic objects I was going to need to deal with, a person and a list item.
Starting with a person, I knew that, at least at the beginning, I didn't want to design a complicated authentication system. So with that, I really only needed to know the persons name & mobile phone number. Here is what I ended up with.
The person class requires that a mobile phone number be unique. Since the primary communication will be happening via SMS, duplicate mobile phone numbers would be way too hard to deal with.
Aside from the basic class, I've added a couple of class methods. The all()
class method will return a list of all of the people in the table. The
find_by_mobile() is a shortcut way to find a person by their mobile number.
Lastly, I defined a class property (you may need to scroll down in the code window) that is an easy way to get someone's name in the way I want to display it on the screen.
Thinking about an item in the list, I wanted to know the following things about it:
what is it
who added it
when was it added
has it been purchased yet
Given that, I ended up with a model that looks like this:
Again I created a couple of class methods and properties to make my life easier down the road. They should all be pretty self explanatory. You can see the complete file, including all of the Python imports on github.
Displaying the list
Next I needed some HTML to display the list.
This jinja2 template will process a Python dictionary passed in that is called
my_list. It draws a table, and iterates over my_list for each row in the
table. The last thing I need for a basic test is a way to generate the list to pass in.
The views
I knew that I needed to define the index view, and I quickly learned that I needed to define every view that I reference in the layout.html file.
For right now, the /people and /help pages will display an empty index page. We'll get those pages fixed in a bit. The important bit in that file is the index route. When the index.html template is rendered, it is passed in all of the open list items.
Testing
At this point, it seemed like a good time to test. If you've taken the time to read this far, then I think it is only fair that I admit that I am writing this post several months after I wrote the code. I've reconcstucted a lot of what I did, and how I did it from my git commit logs, and my general memory of that day. At this point, I think that you should be able to run this thing, but it will be pretty lack luster. If you are entering in the code line by line, then the next steps may not work. If that is the case, I do apologize up front.
Before we can fire the whole thing up, need to build the database. Because of the way that I have the manage.py file set up, it should be accomplishable with the following commands:
|
python manage.py db init
|
|
python manage.py db migrate
|
|
python manage.py db upgrade
|
|
python manage.py runserver -r -d
|
The first command (db init) will create a migrations folder, and create the database file itself. You only need to run this command one time.
The second command (db migrate) will cause the models that are defined in the models.py to be converted into database scripts. Since this is the first migration, essentially what will happen is that the create scripts for the 2 tables will be created.
The 3rd command (db upgrade) will apply the scripted changes, which are sitting in the newly minted migrations folder, to the actual database.
The final command (runserver) will cause the Flask web server to start up. The -r flag will make it look for changes in the underlying Python files. If changes are detected, then the Flask server will reload itself automatically. The -d flag causes it to run in debug mode, which means that you will get useful debugging output in the web browser when a non-fatal error is encoutnered.
At this point, if all went well, you should be able to open a web browser and browse to localhost:5000 and see the basic layout of the site. Of course, the list is empty so there isn't much to see.
Adding some items by hand
Without the SMS stuff set up, we need to resort to a couple of tricks to get
some data into the database so we can see what it looks like on the page. Go
ahead and fire up another terminal and cd into your project directory.
Activate the virtual environment with source bin/activate. Once you are in
the virtual environment, you can get an interactive shell with python manage.py shell.
The commands below will walk through getting some basic stuff set up inside your app.
If you want, you can close out of the shell with Ctrl-D. Or you can leave it open to continue adding items. First a couple of comments on what we just did.
We started by defining a person, and saving it in the database. We needed to create the person first since the ListItem class requires a person for the 'created_by' field.
You may have noticed some special formatting on the phone number. If you
don't work with telephony very much, then it will look strange. That format however
is a pretty standard way to represent phone numbers in the world. This is the
format that Twilio uses and it is the format that I've chosen to stick with.
You can make it work differently, but you will need to make some changes in the
next article, when the Twilio bits are implemented. Twilio expects this format,
so I recommend that you save yourself the headache and implement it now. If you
did it incorrectly, and you have't yet closed your shell, then you can fix it
with p.mobile = '+1XXXXXXXXXX' and then db.session.add(p) and
db.session.commit() to write the changes.
But the cool part is that if you refresh the page in your broswer, you will see the newly added list item.
In the next installment, I'll walk through wrapping it all up. I'll cover adding in the SMS functionalty, creating the People & Help pages, and adding in some jquery to remove items from the list when you tick the box. Since I had some challenges deploying this to a production environment, I'll aslo walk through how I deployed everything, and then what I forsee the future bringing for this app.
by Kirk Gleason