Skip to content

Latest commit

 

History

History
300 lines (212 loc) · 9.49 KB

README.rst

File metadata and controls

300 lines (212 loc) · 9.49 KB

Guide to Django+Backbone.js

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.

Step 0: Getting Set Up

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.**

Step 1: Setting Up the JavaScript Dependencies

We will have at least three JavaScript library dependencies:

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:

  1. DOM selection and manipulation (easily finding and modifying elements on the page), and
  2. Ajax handling (making asynchronous requests to the server without a page refresh)

Downloading the Libraries

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

Automated Testing

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>

Setting Up the Templates

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!

Exposing an API

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.

A11y - Hijacking References and Submissions

Client-side Templating

Further Exploration

DRYness

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

I18n

I've recently built support for Django's makemessages command in to django-mustachejs. I find this to work pretty well.