Django tips: A simple AJAX example, part 1

Published July 31, 2006. Filed under: Django, JavaScript, Programming.

One thing that’s come up over and over again in the Django IRC channel and on the mailing lists is the need for good examples of “how to do AJAX with Django”. Now, one of my goals in life at the moment is to try to fill in the gaps in Django’s documentation, so…

Over the next couple of entries we’re going to walk through a very simple form, which will submit via AJAX to a Django view and handle the result without a page refresh. If you’d like, you can go ahead and take a look at the form we’ll be building; it’s nothing fancy, but it’s enough to cover most of the bases. Make sure to try leaving fields blank, filling in an incorrect total in the second field, etc. And try it without JavaScript enabled — it’ll submit just like any “normal” HTML form.

Note: if you’re already pretty handy with both Django and JavaScript, you might just want to skim this article and look at the sample code; while I’d like to think anybody can learn a thing or two from this, my target audience here is people who know Django but don’t necessarily know much JavaScript, and so I’m going to spend a little time covering basics and general best practices as I go.

Now, let’s dive in.

First things first: think about what you’re going to do

The single most important question to ask when you’re thinking about AJAX is whether you should be using it at all; whole books could be written about when to use it and when not to use it. In general, I think AJAX is at its best for two main types of tasks:

  1. Fetching data that needs to be regularly refreshed, without the need for manual reloads or (worse) meta refreshing.
  2. Submitting forms which don’t need to send you to a new URL when they’re done processing (for example: posting a comment, or editing part of a page’s content in-place).

The key theme there, you’ll notice, is that in both cases it makes sense for the browser to remain at the same URL during and after the AJAX effect; if you need to boil this down to a single general rule of thumb, that’s the one I’d recommend.

Now, think about it some more

The second step, equally important, is to plan out the way your effect would work without the AJAX, because that’s what you should write first. In this case, it means we’ll write a view and a template, and make sure that they work on their own, before adding any JavaScript at all. Jeremy Keith, who’s a man worth listening to, calls this technique “Hijax”, and I love that term. So let’s write our view to work normally first, and add the necessary AJAX support as we go; as it turns out, that’s going to be really easy.

A view to a form

The view is pretty simple; we want to verify that you’ve filled in both fields, and that you’ve figured out the correct answer to a little math problem. And regardless of whether you filled things in correctly or not, we want to preserve the things you entered across submissions.

Note: If I really wanted to be pedantic in this view, I’d do this using a custom manipulator and get all the validation for free (remember that manipulators don’t have to create or update instances of models, but can be used for any form you want validation on; the login view in django.contrib.auth, for example, uses a manipulator this way). But this is just a quick and dirty view to support the AJAX example and manipulators are going to be changing soon anyway, so I’ll skip out on that for now.

So our view (let’s call it ajax_example) might look something like this:

def ajax_example(request):
    if not request.POST:
        return render_to_response('weblog/ajax_example.html', {})
    response_dict = {}
    name = request.POST.get('name', False)
    total = request.POST.get('total', False)
    response_dict.update({'name': name, 'total': total})
    if total:
        try:
            total = int(total)
        except:
            total = False
    if name and total and int(total) == 10:
        response_dict.update({'success': True })
    else:
        response_dict.update({'errors': {}})
        if not name:
            response_dict['errors'].update({'name': 'This field is required'})
        if not total and total is not False:
            response_dict['errors'].update({'total': 'This field is required'})
        elif int(total) != 10:
            response_dict['errors'].update({'total': 'Incorrect total'})
    return render_to_response('weblog/ajax_example.html', response_dict)

Here’s how it breaks down:

  1. If the request wasn’t a POST, we can go straight to the template with an empty context.
  2. If it was a POST, we grab the name and total submitted, grabbing a default value of False if they weren’t submitted.
  3. Go ahead and stick them in a dictionary for later use in creating a context.
  4. Make sure total is something that converts to an int; if it doesn’t, set it to False (which will coerce to zero).
  5. Do the actual checking: we make sure name and total actually had values, and if they don’t, create error messages saying they’re required. And if total was submitted but wasn’t 10, add an error saying it’s incorrect. If everything’s OK, add a success variable to the dictionary we’ll be using for context.
  6. Wrap that up in a render_to_response.

The template is pretty easy, too; you can just view source on the example to see what it looks like. The only thing you can’t see in there is that the name and total fields have {{ name }} and {{ total }}, respectively, inside their value attributes, so if the form’s already been submitted they’ll be pre-filled.

Also, you might notice that the error messages look suspiciously like the ones you’d get in the Django admin; I’ve cribbed a little from the admin application’s stylesheet and icons to get that effect. Check the block of CSS at the top of the template to see how it’s done.

Adapting the view for AJAX

We’re going to need something on the backend which will respond to the AJAX submission, and from a logical point of view it makes sense to have it be the same view — why repeat the logic when the only change will be in the output?

Now, there’s no standard convention thus far in the Django world for distinguishing between “normal” and “AJAX’ submissions to a view, but in a few things I’ve worked on I’ve gotten into the habit of using an extra parameter in the URL called xhr (mnemonic for “XMLHttpRequest”). So where the form’s “normal” submission URL is /examples/ajax/1/, the AJAX submission URL will be /examples/ajax/1/?xhr. In addition to being easy to check for from the view, this has the advantage of not requiring any changes to your site’s URL configuration.

And once we know whether we’re reponding to an AJAX submission or not, the response is extremely easy to generate; a Django template context is just a Python dictionary, and a Python dictionary translates almost perfectly into JSON, which is one of the simplest possible formats for an AJAX response. And best of all, Django includes, as part of its serialization framework, the simplejson library, which provides an easy way to convert between Python objects and JSON.

So let’s take a look at the updated view:

from django.utils import simplejson
    
def ajax_example(request):
    if not request.POST:
        return render_to_response('weblog/ajax_example.html', {})
    xhr = request.GET.has_key('xhr')
    response_dict = {}
    name = request.POST.get('name', False)
    total = request.POST.get('total', False)
    response_dict.update({'name': name, 'total': total})
    if total:
        try:
            total = int(total)
        except:
            total = False
    if name and total and int(total) == 10:
        response_dict.update({'success': True})
    else:
        response_dict.update({'errors': {}})
        if not name:
            response_dict['errors'].update({'name': 'This field is required'})
        if not total and total is not False:
            response_dict['errors'].update({'total': 'This field is required'})
        elif int(total) != 10:
            response_dict['errors'].update({'total': 'Incorrect total'})
    if xhr:
        return HttpResponse(simplejson.dumps(response_dict), mimetype='application/javascript')
    return render_to_response('weblog/ajax_example.html', response_dict)

The only thing that’s changed here, really, is that we test for the xhr parameter in the URL and, if it’s present, we return an HttpResponse whose content is the JSON translation of the dictionary we would have used for template context. We use application/javascript for the response’s Content-Type header because the default — text/html — can open up security holes.

And yes, it’s that easy to support AJAX on the server side.

In part 2, which will show up tomorrow or Wednesday, we’ll walk through writing the JavaScript side of things; go ahead and take a look at it if you’re curious, and hopefully any questions you might have about it will soon be answered.