RapidSMS Tutorial Part 3¶
So far, nothing we’ve done really requires Django. Let’s create a RapidSMS application that uses Django’s abilities to store data.
We’ll create an extremely simple voting application. It will understand
two messages: VOTE <choice>
will add a vote for the specified
choice, and RESULTS
will respond with the current number of votes
for each choice.
(Please notice that this application is more appropriate for a group to choose a place to go to lunch than for anything serious. It makes no attempt whatsoever to enforce any of the controls a real election would need.)
Create the application¶
Create a new Django application. Let’s call it “voting”:
$ python manage.py startapp voting
The models¶
This application is so simple that we’ll only need one model. We’ll
call it Choice
, and there will be one instance for each possible
choice. Each record will contain the name of the choice and the number
of votes for it so far.
# voting/models.py
from django.db import models
class Choice(models.Model):
name = models.CharField(max_length=40, unique=True)
votes = models.IntegerField(default=0)
Application Design¶
Even a simple application like this can demonstrate an important design principle for RapidSMS applications.
Instead of adding to a candidate’s vote count each time a vote arrived, we could instead have created a Vote model and stored a record of each vote. That seems like a little simpler way to handle an incoming vote.
However, if we did that, whenever we needed the results we would have to query every record in our database to count up the votes for each choice. There are SQL queries that can simplify doing that, but the database still has to look at every record. And the next time we wanted the results, we’d have to do that again.
We’re better off doing a little more processing on each message, if that can save us a lot of work later on.
Admin¶
We’ll want to use the Django admin to set up our choices. Typically you would
need to make a few simple changes to your project’s urls.py
, but the
RapidSMS project template has already done that for us. So all we need to
do is add admin.py
to our application:
# voting/admin.py
from django.contrib import admin
from .models import Choice
admin.site.register(Choice)
The results handler¶
Let’s start with the simpler message to handle, RESULTS
. This is
easily implemented as a RapidSMS keyword handler. Let’s create a file
handlers.py
to contain our handlers, and write a handler that responds
with the current votes.
# voting/handlers.py
from rapidsms.contrib.handlers import KeywordHandler
from .models import Choice
class ResultsHandler(KeywordHandler):
keyword = "results"
def help(self):
"""help() gets invoked when we get the ``results`` message
with no arguments"""
# Build the response message, one part per choice
parts = []
for choice in Choice.objects.all():
part = "%s: %d" % (choice.name, choice.votes)
parts.append(part)
# Combine the parts into the response, with a semicolon after each
msg = "; ".join(parts)
# Respond
self.respond(msg)
def handle(self, text):
"""This gets called if any arguments are given along with
``RESULTS``, but we don't care; just call help() as if they
passed no arguments"""
self.help()
If the choices are “Moe”, “Larry”, and “Curly”, the response to a
RESULTS
message might look like Moe: 27; Larry: 15; Curly: 98
.
The vote handler¶
The VOTE
message is slightly more work. If we receive VOTE xxxx
where xxx is one of the choices (case-insensitive), we want to increment
the votes for choice xxx
and respond telling the user that their
vote has been counted. If we receive any other message starting with VOTE
,
we’ll respond with some help to tell them how the command works and what
the choices are.
# voting/handlers.py (continued)
from django.db.models import F
class VoteHandler(KeywordHandler):
keyword = "vote"
def help(self):
"""Respond with the valid commands. Example response:
``Valid commands: VOTE <Moe|Larry|Curly>``
"""
choices = "|".join(Choice.objects.values_list('name', flat=True))
self.respond("Valid commands: VOTE <%s>" % choices)
def handle(self, text):
text = text.strip()
# look for a choice that matches the attempted vote
try:
choice = Choice.objects.get(name__iexact=text)
except Choice.DoesNotExist:
# Send help
self.help()
else:
# Count the vote. Use update to do it in a single query
# to avoid race conditions.
Choice.objects.filter(name__iexact=text).update(votes=F('votes')+1)
self.respond("Your vote for %s has been counted" % text)
Settings¶
We need to add our Django app to INSTALLED_APPS
and our
handlers to RAPIDSMS_HANDLERS
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | INSTALLED_APPS = ( [...] # RapidSMS "voting", [...] "rapidsms.contrib.default", # Must be last ) RAPIDSMS_HANDLERS = [ [...] "voting.handlers.ResultsHandler", "voting.handlers.VoteHandler", [...] ] |
Update database¶
We’ve added a new model, so we need to update our database to include it:
$ python manage.py syncdb
Syncing...
Creating tables ...
Creating table voting_choice
[... rest of output omitted ...]
Create some choices¶
Now it’s time to start our application and create some choices to vote for.
$ python manage.py runserver
Validating models...
0 errors found
May 07, 2013 - 08:28:44
Django version 1.5.1, using settings 'rapidsms_tut.settings'
Development server is running at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Go to http://127.0.0.1:8000/admin/voting/choice/, login as the superuser you created in part 1 of the tutorial, and you should be able to add some choices.
Vote¶
Let’s start by checking that there are no votes. Go to the message tester
application (http://127.0.0.1:8000/httptester/) and send the message
RESULTS
. You should see a response showing no votes, something like
this:
05/07/2013 8:30 a.m. 349911« Moe: 0; Larry: 0; Curly: 0
05/07/2013 8:30 a.m. 349911» RESULTS
(Recall that the messages are shown in reverse order.)
Now let’s cast a vote. Send VOTE Moe
and you should see something
like:
05/07/2013 8:32 a.m. 349911« Your vote for Moe has been counted
05/07/2013 8:32 a.m. 349911» VOTE Moe
and if you check the results again:
05/07/2013 8:33 a.m. 349911« Moe: 1; Larry: 0; Curly: 0
05/07/2013 8:33 a.m. 349911» RESULTS
Continue with RapidSMS Tutorial Part 4.