Django tips: A simple AJAX example, part 1

An entry published by James Bennett on July 31, 2006, Part of the categories Django, JavaScript and Programming. 14 comments posted.

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.

On July 31, 2006, Pete Crosier said:

Hey James, a promising start!

One thing I noticed is that the form is accepting things it shouldn’t do for a ‘name’, I can put..

.. as my name and it validates, then shows my name as the little blue RSS symbol!

On July 31, 2006, Pete Crosier said:

Hah, well that foiled me too ; ) I can put the img tag with the src as that picture’s location (which is what I typed for that comment), and then it comes back with that image as my name.

On July 31, 2006, James Bennett said:

Dang it, stop trying to hack my ultra-basic example!

;)

And yeah, it’ll work fine in comments here; I probably should filter a lot more than I do, but thus far I’m trusting my readers with the privilege of throwing random HTML into their comments.

On July 31, 2006, Joseph Rose said:

James,

Thanks for this tutorial. I am looking forward to part 2 (where the Javascript must come in). I think this will allow me to do a better apples-to-apples comparison of Ajax in RoR and Django.

On July 31, 2006, Ryan Pitts said:

Just want to chime in and thank you for the walkthroughs you’ve been doing. I’ve been sucking up the knowledge eagerly — and biding my time until I can get my site converted over to Django power. I really appreciate the time you spend sharing all this.

On July 31, 2006, Ian Holsman said:

cool example James.

I’m just wondering why you are recreating what the basic manipulators do?

wouldn’t it be easier to just dump the ‘form’ object which is done in the basic add/change manipulator?

(again it’s off topic of the ajax example)

On July 31, 2006, Pete said:

This is excellent. Your efforts are both appreciated and worthwhile!

On July 31, 2006, Andrew said:

Django will gain much more publicity with AJAX oriented tutorials such as these. Good step in the right direction!

On August 1, 2006, David, biologeek said:

Your Django tips are really helpful, thanks again.

Now, one of my goals in life at the moment is to try to fill in the gaps in Django’s documentation, so…

Just to know, why don’t you contribute to the django doc on the django site?

On August 1, 2006, James Bennett said:

Ian, I tried to address that in one of the notes interspersed in the article; basically, it’s because the point was to quickly whip up a view and show ways to adapt it for AJAX use. In any “real world” situation I’d be using a manipulator to do the validation.

On August 1, 2006, James Bennett said:

David, we’re getting there :)

Mostly, I like using my blog to post at least the initial versions of some of these articles, because it’s much easier to edit them and respond to feedback here; once I can get some of them into relatively “final” forms, I’ll see about contributing them to the offiicial documentation.

On August 1, 2006, Jon said:

I also just wanted to thank you for the great django tutorials you have been providing. The django documentation is decent but I have learned a lot more by viewing and implementing your real world examples.

Keep up the great work and I will be looking forward to part 2.

On August 2, 2006, Dan Shafer said:

James,

Thanks for a great tutorial on this subject. And I like your suggested convention, too. So much so that I’m going to use it. I’m actually not in your primary target audience; I know JavaScript quite well but I’m new to Django and only a moderately experienced Python coder who’s learning more every second.

Keep up the great work!

On August 9, 2006, Konstantin said:

Sorry for offtopic. It’s a comment on http://www.b-list.org/weblog/2006/05/21/django-gzip-and-wsgi (since there are no comments and no comment form, but the question is not answered).

Content-Type, Content-Length and Content-Encoding (with appropriate Accept and Accept-Encoding) headers are considered applications’ responsibility, while Transfer-Encoding (and TE) is a transfer-agents’ responsibility. While browser is both user application and transfer agent, web servers and proxies in general are only transfer agents (consider earlier concept of separate MUA, MSA, MTA and MDA in E-mail handling). HTTP 1.1 server should use ‘Transfer-Encoding: chunked’ in the absence of Content-Length as only way to signal end of content with keep-alive connections.

So, it is legal to set Content-Encoding in the application (as well as Content-Type), but application (or middleware) must keep an eye on Content-Length, if it is set (it must indicate length after encoding applied), and set proper ‘Vary: Accept-Encoding’ header to notify caches. Setting Transfer-Encoding: gzip is both illegal and inpractical (most browsers doesn’t understand it).

Comments for this entry are closed. If you'd like to share your thoughts on this entry with me, please contact me directly.