Django and AJAX

The other day I fiddled a tiny bit with AJAX in Django (is that a nerdly way to spend New Year’s Day, or what?). I have no use at all for it now, but (i) I might someday, and (ii) it’s cool.

Brief summary, for n00bs like me. The “usual” way a browser communicates with a server is to send HttpRequests, to which the server responds with some response, generally a whole page of HTML which the browser loads. Loading a whole page, though, is inefficient if the browser only wants to change its existing page a little, and doesn’t need much new data to do it. So the point of AJAX is to ask only for the new data, and update the page in place. Also, the browser doesn’t need to wait for the data to come back. Instead, it can supply a callback function to do the updating whenever the data is available.

For a better example than mine of how this works in Django, see this.

It turns out to be a lot simpler than I had expected–I think I’ve spent more time writing about it than actually doing it. By using a javascript toolkit you can avoid the more disgusting details. I used Dojo, although really for my quick test it didn’t matter at all; I just needed the “xhrGET” function, an equivalent of which would be in any toolkit.

[What, by the way, should one look for in a js toolkit? I have very little idea.]

As an exercise, I rewrote the search results page from my music website, so that the search results would be updated in place in the “onchange” method of the search box. That’s a pretty silly use of AJAX, actually, and I won’t be putting it in the real site, but it served as practice.

So what to do? The existing page is is rendered by a search_results view, which finds all the objects (of models Work, Author, and Collection) that match the search criteria (passed in GET data), and renders them to a template. To AJAXify that, I added an onchange handler to the search input, which sends and XML request to retrieve the new search results, supplying a callback to update the page. The URL those requests go to is just the existing search page’s URL, with “xhr” added to the GET data. I could have created a new URL/view for that, but the existing one already did almost exactly what this needs; see below. In a more typical case, I think the same strategy would also work, but more logic in the view would be dependent on the type of the request.

I had to do very little to the view. The AJAX request is expected to return just the “guts” of the page–the lists of matches–which can be done just by using a different template.

It may be more typical, and in some ways is preferable, for AJAX requests to return not html but data in json format. In this case, that would require javascript code to attach the data to the html, which would duplicate the existing template code in a nasty way. Maybe there is some clever way to avoid that code duplication…

Anyway, here’s the code:

def search_results(request):
    works = []
    people = []
    collections = []
    searchwords = ""
    try:
        searchwords = request.GET['searchwords']
        works = utils.searchfields(Work, searchwords)
        people = utils.searchfields(Author, searchwords)
        collections = utils.searchfields(Collection, searchwords)
    except KeyError:
        pass

    if request.has_key('xhr'):
        templatename = 'music/searchresults.html'
    else:
        templatename = 'music/search.html'

    return render_to_response(
            templatename,
            { 'searchwords' : searchwords,
              'works' : works,
              'people' : people,
              'collections' : collections })

The template needed more work. First, of course, it needs a js function to respond to fire off the AJAX request. That I cribbed from the DOJO documentation. Then, to avoid code duplication, I refactored the page a bit. I abstracted the html code that displays the search results out into its own tag, used in the view function above, and put the tag inside a new div. The tag is just a simple inclusion/context tag, using the same template as the view above used for xhr requests. On a normal HTTP request the tag is rendered using the data passed in from the view function; on an AJAX/xhr request the view function generates exactly the same html as the tag does initially (if given the same searchwords), and the callback shoves it into the div. And that’s pretty much it. The code:

{% extends "music/base.html" %}
{% block scripts %}
<script type="text/javascript"
    src="http://o.aolcdn.com/dojo/1.0.0/dojo/dojo.xd.js"></script%gt;
<script type="text/javascript">
    function updateajax() {
      dojo.xhrGet( {
        url: "?searchwords=" + dojo.byId("searchwords").value + ";xhr",
        handleAs: "text",

        timeout: 5000, // Time in milliseconds

        load: function(response, ioArgs) {
          dojo.byId("searchresults").innerHTML = response;
          return response;
        },

        error: function(response, ioArgs) {
          console.error("HTTP status code: ", ioArgs.xhr.status);
          return response;
          }
        });
      }
</script>
{% endblock %}

{% block content_search %}
{% simple_search '/music/search/' searchwords %}
{% endblock %}

{% block content %}
<h2>Search Results</h2>
<div>
    {% searchresults %}
</div>
<script type="text/javascript">
    dojo.byId("searchwords").onchange = updateajax
</script>
{% endblock %}

Some thoughts on What I Learned From This to follow.

Advertisements

Tags: ,

One Response to “Django and AJAX”

  1. Some AJAX in Django « Michael Lauer’s Weblog Says:

    […] AJAX in Django Months ago I started looking into doing AJAXy things within Django, and (typically for me) never actually did any of […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s


%d bloggers like this: