For this example, I'm going to use the classic Django polls tutorial project -- specifically starting from Daniel Lindsley's awesome guide to testing project. The source is available on GitHub. This version is mostly identical to the one in the Django tutorial, except it has:
- tests
- form validation
We will enhance this polls app to have a more interactive feel, appropriate for a live voting situation.
The first thing I want to do is get the initial code. It's all available on GitHub:
git clone git://github.com/mjumbewu/guide-to-testing-in-django.git
Of course, I want to set up an environment and actually install Django:
cd guide-to-testing-in-django virtualenv env source env/bin/activate pip install django
(We will try to keep the dependency list short for this project.)
Initialize the project, and then check to make sure that everything is working as expected. There is a SQLite database bundled with the project (username: admin, password: abc123), so you should just be able to start running. First, make sure the tests pass:
./manage.py test polls
You should get output that looks like:
Creating test database for alias 'default'... ............ ---------------------------------------------------------------------- Ran 12 tests in 0.398s OK Destroying test database for alias 'default'...
Now, run the server:
./manage.py runserver
In your browser, go to http://localhost:8000/polls/ and click around for a while. If everything looks alright, let's continue.
- **Up until this point, the project code corresponds to the tag bb01-initial.
- The next few sections describe the bb02-explore_models tag.**
We will have at least three JavaScript library dependencies:
- Backbone.js, which in turn depends on both
- Underscore.js, and (optionally)
- jQuery
Underscore.js fills in the gaps in JavaScript. It is "a utility-belt library for JavaScript that provides a lot of the functional programming support that you would expect ... but without extending any of the built-in JavaScript objects." It's especially great for achieving list comprehension-like things in JavaScript.
jQuery has many strengths, two of which we will take advantage of here either directly or indirectly through Backbone.js:
- DOM selection and manipulation (easily finding and modifying elements on the page), and
- Ajax handling (making asynchronous requests to the server without a page refresh)
So let's go ahead and download each of these into our project (NOTE: If you
prefer, you can use a CDN such as cdnjs. If you do not use a
CDN, you should use a merger and minifier to combine and compress your assets.
Django-compressor is a good one to consider). First, create a reasonable
structure for your static assets. I like to create libs
folders for 3rd-
party assets, and an additional folder for app-specific assets (we'll come back
to that later):
mkdir static cd static mkdir libs polls
Remember to add your static folder to the STATICFILES_DIRS
setting, if it
is not within an app directory.
When downloading the 3rd-party libraries remember, wget
is your friend:
cd libs wget http://underscorejs.org/underscore.js wget http://backbonejs.org/backbone.js wget http://code.jquery.com/jquery-1.8.0.js -O jquery.js
You may want to download a library for writing automated tests as well. I find QUnit to work well, and if you're familiar with xUnit testing frameworks (like the Python unittest package), then it'll make a lot of sense to you. However, some prefer Jasmine.
To set up QUnit, first download the library:
wget http://code.jquery.com/qunit/qunit-1.9.0.js -O qunit.js wget http://code.jquery.com/qunit/qunit-1.9.0.css -O qunit.css
Then set up a test template:
templates/test/index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <link rel="stylesheet" href="{{ STATIC_URL }}libs/qunit.css"> <script src="{{ STATIC_URL }}libs/qunit.js"></script> <!-- Your project-specific JavaScript imports will go here <script src="{{ STATIC_URL }}polls/models.js"></script> <script src="{{ STATIC_URL }}polls/views.js"></script> --> </head> <body> <div id="qunit"></div> <!-- Your test files will go here <script src="{{ STATIC_URL }}polls/tests.js"></script> --> </body> </html>
In the interest of simplicity, the polls
tutorial omits the HTML
scaffolding from its templates. It is going to be in our interest to include
this scaffolding. Let's create a super-simple base template for our app.
templates/polls/base.html:
<html> <head> <script src="{{ STATIC_URL }}libs/jquery.js"></script> <script src="{{ STATIC_URL }}libs/underscore.js"></script> <script src="{{ STATIC_URL }}libs/backbone.js"></script> </head> <body> {% block content %} {% endblock %} </body> </html>
Next, modify each of index.html, detail.html, and results.html to extend the base. Though we will be creating a single-page app, we will still be using each of these templates:
{% extend "polls/base.html" %} {% block content %} [...original template content...] {% endblock %}
Now we're ready to start with Backbone!
For something simple and low-security like this polling app, we may not really need a full-featured API framework, but we'll use one anyway, for demonstration. Every so often someone writes a good roundup of the options in this regard on their blog, on some mailing list, or on Stack Overflow. The most recent good one that I've come across is on Daniel Greenfield's (@pydanny) post Choosing an API framework for Django. Danny recommends TastyPie and Django REST Framework.
We'll use Django REST Framework (DRF), but keep it as simple as we can. First,
install DRF using the install instructions on Read the Docs. Now create an
app for the API called polls_api
. In the polls_api.views
module,
enter the following:
from django.shortcuts import get_object_or_404 from djangorestframework import views from polls.models import Poll class PollResults (views.View): def get(self, request, poll_id): poll = get_object_or_404(Poll.objects.all(), pk=poll_id) results = { 'question': poll.question, 'choices': [{ 'id': choice.id, 'choice': choice.choice, 'votes': choice.votes } for choice in poll.choice_set.all()] } return results poll_results_view = PollResults.as_view()
Let's break this down. Django REST Framework uses an interface similar to Django's core class-based views. Here we define a view class that supports one HTTP method: GET.
... class PollResults (views.View): def get(self, request, poll_id): ...
Next, we get the requested poll object, and construct a dictionary of data that represents what we want to return to the client.
results = { ... } return results
Note that our view method then just returns this dictionary, not an HttpResponse. DRF alows us to simple return the data we want represented by the API. This data will be encoded as JSON or JSON-P or XML, ..., depending on what is requested by the client.
One thing I've been experimenting with is using the same templating language on both the client and the server. I have been working on a Django template adapter for the PyBars project (djangobars), with the intention of using Handlebars in both places. With Handlebars, it would be possible to still use many of Dajngo's template tags and filters in the templates.
Though I like this approach, some potential downsides include:
- having to implement Django's filters in Javascript as well, if I really want to use the templates without modification on both ends of the pipe
I've recently built support for Django's makemessages
command in to
django-mustachejs. I find this to work pretty well.