2012-06-19
How I'm doing AJAX with Django in my web app
Django has native AJAX support for serializing model objects and query results to and from JSON (or XML); various people have then added various featureful packages on top of this. For my web app all of this was overkill and too complex. What I needed for my limited client side form validation was a simple AJAX endpoint service, one that reported whether or not a potential login name was acceptable by returning a JSON dictionary with both a validity flag and a message to display to the user about the situation.
(I decided that I wanted to explicitly mark whether the login was valid or not rather than have client side code try to infer it from, say, whether or not there was a message. The latter seemed excessively fragile when being explicit was simple and cheap.)
So here's what I did (with the disclaimer that this may horrify
experienced Django developers). First off, for these form fields you're
going to want to pull as much of the field validation logic as possible
out of check_<field>
functions in your regular form classes and into
reusable functions somewhere. Fortunately I was already mostly doing
this because I had several forms that all had a login field that they
had to validate.
(I couldn't extract all of the logic because I'd made the login field
a RegexField
with a maximum length. This may have been a mistake;
automatic field validation is very convenient until it isn't.)
The main validation endpoint is exposed as a REST-style URL that
looks like '/check/login/<proposed login>'. This is connected to
a plain Django view function with a single parameter, the login.
The view function duplicates the RegexField
's regular expression
check then if that passes calls my general login field validation
function, sets up a dictionary based on the result, turns it into JSON
with simplejson.dumps()
(simplejson
is from django.utils
), and
finally returns that as the HTTP response (with the content type set
appropriately). The client side code picks it up from there and does
appropriate client side magic.
(Jquery normally wants to form parameterized JSON URLs using a query parameter, ie something like '/check/login/?<proposed login>'. Since I didn't know how to handle that in Django and didn't want to work it out, I punted and forced a more REST-ish URL with some hackery. I actually like the REST-ish URL better than the Jquery one.)
This left me with a small problem: how was the client side JavaScript
code supposed to know the (base) URL for the validation endpoint? My
solution is a hack. First, I created a second 'endpoint' view that
is attached to the plain '/check/login/' URL (without a login
parameter). This view does nothing, because its only purpose in life is
being a valid argument for the url
tag in templates. Then in the HTML
template for the form page I added a JavaScript snippet that just had:
var loginBaseUrl = "{% url requests.views.checklogin_url %}";
The actual JavaScript code (which comes from a separate file at a
separate URL) then uses loginBaseUrl
instead of trying to hard-code
the URL of the endpoint. The advantage of this hack is that the endpoint
URL is automatically adjusted for whatever environment the Django app is
running in (production, my testing, whatever) without my file of actual
JavaScript code having to be run through Django template processing or
any other form of (pre)processing.
(If you had a lot of endpoints you could come up with a more elaborate JavaScript data structure to hold this information and maybe a more elaborate scheme for propagating it around. Or at least something that doesn't involve random global JavaScript variables stuffed into your page HTML. However, this was a quick hack and it works, and I'm new to JavaScript so I get to do things the crude and direct way for at least a bit.)
PS: both my JavaScript code and our environment are so small that I'm not bothering to do any minimization, bundling, or the like of the code right now. In a larger environment I imagine that such 'ready it for deployment' processing of the JavaScript would be the natural place to insert endpoint URLs and the like.