Django tips: A simple AJAX example, part 1
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:
-
Fetching data that needs to be regularly refreshed, without the need for manual reloads or (worse)
meta
refreshing. - 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:
-
If the request wasn’t a
POST
, we can go straight to the template with an empty context. -
If it was a
POST
, we grab thename
andtotal
submitted, grabbing a default value ofFalse
if they weren’t submitted. - Go ahead and stick them in a dictionary for later use in creating a context.
-
Make sure
total
is something that converts to anint
; if it doesn’t, set it toFalse
(which will coerce to zero). -
Do the actual checking: we make sure
name
andtotal
actually had values, and if they don’t, create error messages saying they’re required. And iftotal
was submitted but wasn’t 10, add an error saying it’s incorrect. If everything’s OK, add asuccess
variable to the dictionary we’ll be using for context. -
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.