mattdorn.com

Generously funded by Matt Dorn

RESTful Web apps with Django, Piston and Ext JS

with 3 comments

Piston appears to have emerged as the preferred method for giving your Django applications a RESTful API. While there are any number interesting things you might want to want to do with such an API, this post is about using it to give your Django app an attractive, Ajax-y, Ext JS interface.

Getting Started

I’m going to proceed with two assumptions: 1) You’ve got a Django project up and running, and 2) You’ve installed the django-piston package (as in easy_install django-piston). If you’re just getting started with Django, the official tutorial is a great place to start. Additionally, you may want to take advantage of virtualenv for managing Python packages used by your application.

From there, you’ll need to install the Ext JS library. In order to develop your Django app using JavaScript, you’ll need to put your JavaScript files in a media directory which will be handled differently from your other application files. In the root of my project directory, I have a directory called media where static files such as images, CSS, and JavaScript files will be stored. To make use of this directory in development, take a look at "How to serve static files", available on the Django documentation site.

So, download the latest version of Ext JS, and extract it into a place that makes sense in your static media folder. In my Django project, the relevant part of my directory structure looks like this:

myproject/
    media/
        css/
        img/
        js/
            ext/
    todos/
        models.py
        [etc...]

You’ll probably want to remove the version number from the name of the root Ext JS directory, as I’ve done here.

Note that I’ve included my todos Django application in this listing, since in this tutorial we’ll be using a "to-do" list application to illustrate the tools under consideration.

Piston Basics

Before we do anything with Ext JS, let’s make make sure we have a basic understanding of how Piston interacts with your Django application.

My application has a single, very simple model, representing an item on a to-do list.

So, my models.py in its entirety looks like this:

from django.db import models

class Task(models.Model):
    name = models.CharField(max_length=50)
    complete = models.BooleanField(default=False, null=False)

    def __unicode__(self):
        return self.name

What we want to do is expose this model via a RESTful API, which is exactly what Piston will enable us to do. Most of what follows does not deviate significantly from what you’ll find in the Piston documentation.

First, create an api directory in your project’s root. At a minimum, you will need __init__.py, but also urls.py and handlers.py. Let’s look at handlers.py first.

Here is where you need to do the minimal work of associating one of your project’s models with a Piston "handler" class that will define how basic CRUD operations are handled in your application:

from piston.handler import BaseHandler
from myproject.todos.models import Task

class TaskHandler(BaseHandler):
    model = Task

In urls.py, you’ll find a configuration very similar to what you need to do to connect your regular Django views with URLs to access them:

from django.conf.urls.defaults import *
from piston.resource import Resource
from piston_demo.api.handlers import TaskHandler

task_resource = Resource(TaskHandler)

urlpatterns = patterns('',
   url(r'^tasks/(?P<id>\d+)$', task_resource),
   url(r'^tasks$', task_resource),
)

Here the url pattern takes a Piston Resource as an argument, which in turn takes the Piston "handler" class that we just defined.

With this minimum bit of work, our to-do list app has a basic RESTful API. Below is an example of posting data from the command line using cURL:

mdorn@ubuntu:~$ curl -i -H "Accept: application/json" -X POST -d "name=To-do%20#1&complete=false" http://ubuntu:8000/api/tasks
HTTP/1.0 200 OK
Date: Sun, 20 Dec 2009 18:48:53 GMT
Server: WSGIServer/0.1 Python/2.5.2
Vary: Authorization
Content-Type: application/json; charset=utf-8

{
    "name": "To-do #1",
    "complete": "true"
}

After posting a second to-do item, if we perform a GET request against the resource’s URL, we’ll see:

mdorn@ubuntu:~$ curl -i -X GET http://ubuntu:8000/api/tasks
HTTP/1.0 200 OK
Date: Sun, 20 Dec 2009 18:50:40 GMT
Server: WSGIServer/0.1 Python/2.5.2
Vary: Authorization
Content-Type: application/json; charset=utf-8

[
    {
        "name": "To-do #1",
        "complete": false
    },
    {
        "name": "To-do #2",
        "complete": false
    }
]

We can also delete and update by using the DELETE and PUT HTTP verbs, respectively.

Two things to note here: 1) The default MIME type for Piston responses is JSON. 2) If we want the list of returned records to include the record’s primary key, we’d need to explicitly specify it in our handler definition:

class TaskHandler(BaseHandler):
    fields = ('id', 'name', 'complete')
    model = Task

Basics of Ajax with Ext JS

Next we’ll take a look at the Ajax functionality provided by Ext JS, and how to connect it with our Django app. Create an HTML template in your Django app that looks like this:

<html>
<head>
    <title>Basic Example</title>

    <!-- Include Ext and app-specific scripts: -->
    <script type="text/javascript" src="/static/js/ext/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="/static/js/ext/ext-all-debug.js"></script>
    <script type="text/javascript" src="/static/js/basic.js"></script>

    <!-- Include Ext/app-specific stylesheets here: -->
    <link rel="stylesheet" type="text/css" href="/static/js/ext/resources/css/ext-all.css" />
    <link rel="stylesheet" type="text/css" href="/static/css/basic.css" />

</head>
<body>
    <h1>Basic Ext JS Ajax Example</h1>

    <div id="content">
            <div id="myDiv">Watch this space.</div>
            <input type="button" id="myButton" value="Click me" />
    </div>

</body>
</html>

I’ll assume you can wire this template up to your urls.py on your own, using direct_to_template, since we won’t be using any application view.

Note that in addition to the Ext JS and CSS files being referenced, you’ll need basic.js:

Ext.onReady(function() {
    var buttonClicked = function() {
        Ext.Ajax.defaultHeaders = {
            'Accept': 'application/json'
        };
        var resp = Ext.Ajax.request({
            url: '/api/tasks',
            method: 'GET',
            success: function(resp) {
                var item = Ext.decode(resp.responseText)[0].name;
                var myDiv = Ext.get(’myDiv’);
                myDiv.update(’The first item in the to-do list is: <br />’ + item);
            },
            failure: function() {
                Ext.Msg.alert(’Failed’);
            },
        });

    }
    Ext.get(’myButton’).on(’click’, buttonClicked);
});

Optionally, the CSS that’s in basic.css will make this demo a bit prettier. I won’t list the source here, but it’s in the download for this tutorial (see below).

Now when you navigate to the URL that renders the template in your browser, the code in basic.js executes when the onReady event is fired, which happens when the page is loaded. Here we’re defining a callback function to be executed when the button whose id attribute is "myButton". The action to be performed is to use an Ext JS DOM manipulation function to replace the contents of the "div" tag with id myDiv with a response from the server, in this case the first item we added to our to-do list using our new RESTful API.

Note that once you start working with Ext JS widgets, you won’t often use the Ext.Ajax class, as the widgets generally abstract it away for you. If you’re new to Ext JS, I recommend taking a look at the the Introduction to Ext 2.0 tutorial, to get a better of understanding of what’s going on here.

Using Ext JS Widgets

Now let’s look at how Ext JS’s RESTful store and grid widget can interface with our Django to-do list app to give it a very attractive and responsive Ajax interface.

I won’t list most of the source code for the widget here, but you can find it in the Conclusion below.

To populate the grid with our to-do list, our Ext.data.Store instance will need an Ext.data.HttpProxy object pointing to our resource’s URL:

var proxy = new Ext.data.HttpProxy({
    url: '/api/tasks'
});

After we define which columns should be included in the grid and set a few other attributes, that should be all we need to instantiate the grid and populate it.

But it doesn’t populate. What’s wrong? If we open Firebug to take a look at the response when we load the grid, we see the following:

[
    {
        "name": "To-do #2",
        "complete": true
    },
    {
        "name": "To-do #1",
        "complete": true
    }
]

This may be the expected result based on our experiments above, but the problem is that the Grid is expecting the response to be formatted in a different way. Specifically it expects a JSON dictionary which includes this array of dictionaries as a value assigned to the data key, like so:

{
    "data": [
        {
            "id": 2,
            "name": "Take out the trash",
            "complete": true
        },
        {
            "id": 3,
            "name": "Another try",
            "complete": false
        }
    ],
    "success": true,
    "message": "",
}

As we can see, it also expects a success flag, and a message, if any.

Fortunately, Piston can be extended to accommodate this requirement. Let’s take a look at how to do that.

Extending Piston

Custom Emitters

Here we’ll introduce a new file to the api folder that contains our Piston configuration, emitters.py. Earlier, without realizing it we were making use of Piston’s default "emitter" for returning responses. We need to define and register a custom emitter class to return data in a format understood by our Ext JS widget(s):

from django.utils import simplejson
from django.core.serializers.json import DateTimeAwareJSONEncoder

from piston.emitters import Emitter

class ExtJSONEmitter(Emitter):
    """
    JSON emitter, understands timestamps, wraps result set in object literal
    for Ext JS compatibility
    """
    def render(self, request):
        cb = request.GET.get('callback')
        ext_dict = {'success': True, 'data': self.construct(), 'message': 'Something good happened on the server!'}
        seria = simplejson.dumps(ext_dict, cls=DateTimeAwareJSONEncoder, ensure_ascii=False, indent=4)

        # Callback
        if cb:
            return '%s(%s)' % (cb, seria)

        return seria

Emitter.register('ext-json', ExtJSONEmitter, 'application/json; charset=utf-8')

Notice that in registering this emitter, we’ve given it the title ext-json. We’ll now need to modify our api/urls.py to pass this information as well:

urlpatterns = patterns('',
   url(r'^tasks/(?P<id>\d+)$', task_resource,  {'emitter_format': 'ext-json'}),
   url(r'^tasks$', task_resource, {'emitter_format': 'ext-json'}),
)

With those simple changes, we can now reload our grid and see it populated with our to-do list.

Overriding Handler Methods

However, we run into a similar problem when POSTing data to create a new to-do item or update an existing item. The Ext JS widget sends a request containing a single POST variable called data containing a JSON-formatted dictionary of the values to be posted. Piston expects a dictionary containing key-value pairs that map directly onto the fields in our model.

Here we can override the methods of our BaseHandler-derived class in handlers.py to deal with this unfortunate turn of events:

class TaskHandler(BaseHandler):
    fields = ('id', 'name', 'complete')
    model = Task

    def create(self, request, *args, **kwargs):
        if not self.has_model():
            return rc.NOT_IMPLEMENTED

        attrs = self.flatten_dict(request.POST)
        if attrs.has_key('data'):
            ext_posted_data = simplejson.loads(request.POST.get('data'))
            attrs = self.flatten_dict(ext_posted_data)

        try:
            inst = self.model.objects.get(**attrs)
            return rc.DUPLICATE_ENTRY
        except self.model.DoesNotExist:
            inst = self.model(**attrs)
            inst.save()
            return inst
        except self.model.MultipleObjectsReturned:
            return rc.DUPLICATE_ENTRY

Note that this is mostly a copy of the default implementation of the method with the exception of the three lines of code following if attrs.has_key(’data’):, inclusive.

If we do something similar to the update, method, our application is now able to create and update records when the widget POSTs or PUTs data.

Conclusion

You can download the source code referenced in this tutorial. It’s a complete Django app; you’ll just need to syncdb and install Ext JS as explained above.

Written by mdorn

December 20th, 2009 at 10:52 pm

Posted in technology

Tagged with , , , , ,

3 Responses to 'RESTful Web apps with Django, Piston and Ext JS'

Subscribe to comments with RSS or TrackBack to 'RESTful Web apps with Django, Piston and Ext JS'.

  1. Social comments and analytics for this post…

    This post was mentioned on Twitter by pnendick: “RESTful Web apps with Django, Piston and Ext JS” http://bit.ly/8mJ5Dw @aquamatt @garethr – any experience with piston? http://bit.ly/4n7oFP...

  2. Nice tutorial. Thanks a lot!

    JAVH

    27 Dec 09 at 6:47 pm

  3. I haven’t been able to get this demo to work with ExtJS 3.1. I get 400 BAD REQUEST error anytime I try a POST or PUT request. I was having the same problem with my own code, which is why I installed your demo in the first place. I can get all CRUD methods to work with cUrl technique, and with basic web form requests, but it stops working when I wire up ExtJS grid.

    Any ideas? Thanks for any help you can offer.

    –ch

    Chris Hughes

    3 Feb 10 at 10:07 pm

Leave a Reply