jump to navigation

jQuery Django REST 11 September 2009

Posted by manniwood in Uncategorized.
trackback

While working on a webapp, I had a situation where a handful of items of the same type were presented on the same page, and all were editable, for the user’s convenience.

In the old days, I would have wrapped the handful of items in a single form, encouraging the user to edit all of the items in one go, and submit the bunch all at once. I would either report back with a “your changes have been saved” page (really old school) or reloaded the page with a “your changes have been saved” notification, and the forms filled out with the new edits.

But with AJAX now a robust and well-supported technology (especially under the spiffy new frameworks), I figured why not

  • put each item in its own form
  • allow each item to be submitted separately
  • use AJAX to notify the user of form submission success/failure using the current page, so that no full form-submission/round-trip/page-load would be necessary?

And if I was going to write back-end code to support this sort of item update, why not see how RESTful I could make it?

First off, let me say that the solution I came up with is only RESTful; maybe only REST-like or REST-ish.

I’ve been doing some poking around on REST and what it really is, and it seems that it is an architecture more than a specification, which is kind of nice, because you don’t have to adopt all of it, especially if it gets in the way of solving your problem.

(Aside: How I Explained REST to My Wife is the best explanation I’ve read of REST.)

Anyway, let me first dispense with two things I did in my code that were decidedly not RESTful.

First, because this work was in the context of a larger application, I required a user to be logged in to perform the updates on the items. My understanding of REST is that it should be stateless. My takeaway is that login credentials would have to be provided with each individual action to enable true statelessness. I ignored this.

Second, true RESTful resources are accessible individually through regular URLs without appended key/value pairs. Hence, the ideal format of URLs to my items would have been


https://myserver.com/items/1234

https://myserver.com/items/2345

whereas I was going to still access my items like so:


https://myserver.com/itemEditRest/?id=1234

https://myserver.com/itemEditRest/?id=2345

And, in fact, even that is not true, because what I was really going to do was pass the form data in the body of the request (POST-like data, if you will) and not even in the URL.

I chose to do this out of a desire for simplicity: all the other attributes of the items were already going to be passed in the body of the request, and were already going to be parsed out and incorporated into my SQL update statement. So getting all of the attributes from one source (the request body) instead of two sources (ID from the URL but other attributes from request body) seemed a better idea, for my purposes.

This still left me with some interesting RESTful stuff to do. First off, because my items were being updated (rather than created or destroyed), I would use the recommended HTTP PUT method, because apparently, PUT is the HTTP method that RESTful services use (by convention) to allow updates on items.

My first goal was to see if I could send a PUT request through jQuery (and, by extension, the XMLHTTPRequest facility provided by all the major browsers).

It turns out I could! Whenever one of my forms’ save buttons (all sharing the same class=”saveButton”) was clicked, I would serialise that form’s data and send it to my server in the request body using PUT:

$('.saveButton').click(function() {
    // code omitted here, but basically just determining
    // the ID of the form that the
    // submit button was in, and assigning it to formID
    var formDataString = $(formID).serialize();
    $.ajax( { url:  '/itemEditRest',
        type: 'PUT',
        data: formDataString,  // data is request body
        processData: false,
        dataType: 'text',  // could also be "json"
                     // but I'll parse return data manually
        success: function(data, status) {
            // code omitted here;
            // do whatever I do when I'm successful
        },
        error: function(xhr, text_status, error_thrown) {
            var status = xhr.status;
            if (status == '400') {
                // code omitted;
                // handle bad form input
            } else if (status == '410') {
                // code omitted;
                // handle item no longer there
            } else {
                // code omitted;
                // handle any other truly
                // unexpected problem
            }
        }
      });
});

Some notes on the above code:

How cool is $(formID).serialize()? My understanding of PUT is that anything can be in a PUT’s request body: a text file, a PDF, a PNG, anything. I happened to want to put my form data in there (rather like a POST request). jQuery’s .serialize() method will take a locator that resolves to a form tag, and serialise all of that form’s “foo=bar, one=two” form inputs to the expected “foo=bar&one=two” query string. Very nice!

One thing that’s interesting is how little information I decided to send back from my server. For instance, if the data were successfully saved, I figured I may as well just send back a response with HTTP status code 204 (which means ‘no content’). jQuery correctly takes any 2xx response and calls its success handler! Very nice.

I figured if my form data were bad (for instance, the user input text where a number should be) I’d use the HTTP return status of 400 (which means bad request). What’s really cool is that an HTTP 400 response can have a body, so I actually send back JSON in the response body. The JSON contains a map of form field names and their associated human readable errors (such as “{ ‘transfer_amount’: ‘Not a valid monetary value’ }”). But it turns out I ignore the JSON in practice, because I’m already doing JavaScript validation on the client side, so the bad fields are already being called out. (In essence, I’m just having the server protect itself from garbage data if a user hits the “Save” button, ignoring the bad input warnings.)

Likewise I use status code 410 for missing data.

Remaining 4xx status codes, and all 5xx status codes can be handled by the “else” part of my error hanlder. Brilliant!

Of course, none of this is any good if my server side cannot produce this output. But with Django, I can.

First off, in my urls.py, I map my URL to my module and function that will handle my RESTful call. Note that all HTTP method calls to itemEditRest will go to item_edit.rest: GET, POST, PUT, DELETE; they will all go to item_edit.rest:

(r'^itemEditRest$', item_edit.rest),

So, here’s what item_edit.rest looks like:

# decorater that requires we be logged in;
# not very RESTful, I know...
@logon.require_login_rest
def rest(request):
    allowed = ['PUT']  # extend this
                       # as we add more
    if request.method == 'PUT':
        return restful_update(request)
    elif request.method == 'POST':
        #not implemented yet
        return HttpResponseNotAllowed(allowed)  # status 405
    elif request.method == 'GET':
        #not implemented yet
        return HttpResponseNotAllowed(allowed)  # status 405
    else:
        return HttpResponseNotAllowed(allowed)  # status 405

Unlike handling a GET/POST directly, as you normally would in Django, you instead look at the request method, and call the appropriate handler from there. (There’s even an HTTP response code for unsupported methods! Very cool…)

Here’s my handler for the PUT method:

(But first, at the top of my file, I have to handle some changes from Python 2.5 [which I use in production] and 2.5 [which I'm using in dev]):

local_parse_qsl = None
import urlparse
if hasattr(urlparse, 'parse_qsl'):
    local_parse_qsl = urlparse.parse_qsl  # Python v. >= 2.6
else:
    import cgi
    local_parse_qsl = cgi.parse_qsl  # Python v. < 2.6

try:
    import json  # Python version >= 2.6
except ImportError:
    import simplejson as json  # Python version < 2.6

OK, on to my handler function:

def restful_update(request):
    key_val_pairs = local_parse_qsl(request.raw_post_data)
    form_values = {}
    for kvp in key_val_pairs:
        form_values[kvp[0]] = kvp[1]

    error_messages = {}
    # validate function populates error_messages
    # dict with form field names as keys, and
    # human-readable error messages as values,
    # e.g. { 'phone': 'Not a valid phone number' }
    # If error_messages remains empty, it means
    # all form fields were good.
    validate(form_values, error_messages)

    if len(error_messages) != 0:
        # status 400 means bad request
        return HttpResponse(json.dumps(error_messages),
                            status=400,
                            mimetype='application/json')

    # code omitted; detect if item
    # or one of its parents is deleted
    if item['is_deleted'] == True:
        # HttpResponseGone is like HttpResponse
        # but uses a 410 status code
        return HttpResponseGone("{ 'details':
               'Item parent deleted.'}",
               mimetype='application/json')

    app_config.SQL_MAP.execute_commit(
        file='items/update.pgsql',
        map=form_values)

    # status 204 means no content,
    # which is very useful
    # we just need to indicate successful save
    return HttpResponse(status=204)

There are a few interesting things to note. First, Django will not parse out the form values for you, because this is not a GET or a POST. This is what all the local_parse_qsl stuff is about above. (Note, too, that because I am not using duplicate form field names, I can confidently turn my form values into a regular dict, rather than resorting to a dict of lists, “just in case” one of my form values is a multiple value.)

Another interesting thing is that although I’m returning some nice JSONified info in the request bodies, my front-end currently does not bother using the data, using only the return codes to decide what to do next. On the other hand, and future RESTful client may find the info useful.

Finally, my return code is not a typical HTTP 200 result; it is an explicit body-less 204 result. It’s lean, it’s mean, and it’s all that’s required to indicate a successful save.

As I’ve been happy to admit, this isn’t completely RESTful, but this dabbling has taught me a lot, and I now know jQuery and Django give me the tools I need to create full-blown RESTful services in the future, should I need to do so.

Now… go and get some REST.

Comments»

No comments yet — be the first.