weblog.mattdorn.com

Generously funded by Matt Dorn

RESTful Web apps with Django, Piston and Ext JS

with 21 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 , , , , ,

21 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

  4. I’ve de same problem. Any idea?

    Jars

    16 Mar 10 at 2:39 pm

  5. Sorry I haven’t had time to look into this — if anyone else has an idea, feel free to post here. Otherwise, hopefully in a couple of weeks I’ll get a chance to take a look.

    mdorn

    23 Mar 10 at 11:39 pm

  6. Hello,
    In most of the case that come to my mind I want to restrict the POST, DELETE to authenticated users. Authenticated user in the django sense (@login_required). I would be really interested to hear from you how you enforce this restriction.
    Thanks for sharing your experience with django-piston and Ext JS.
    Regards,
    –yml

    yml

    1 May 10 at 12:38 am

  7. The reason for the 400 errors is that ExtJS is appending a charset onto the Content-Type field, which causes piston to not interpret the Content-Type correcly. There is an open issue for it at http://bitbucket.org/jespern/django-piston/issue/121/content-type-is-not-being-split-against. I was able to get the example working after I applied the patch and did an easy_install.

    Gerrit Kitts

    24 May 10 at 1:59 am

  8. [...] Piston – It’s not magic though, you still need some coding for each model to hook it in: Overview and Tutorial [...]

  9. In api/handlers.py one can add to the TaskHandler the following missing crud methods:

    def delete(self, request, id):
    if not self.has_model():
    return rc.NOT_IMPLEMENTED
    try:
    inst = self.model.objects.get(id=id)
    inst.delete()
    return rc.DELETED
    except self.model.MultipleObjectsReturned:
    return rc.DUPLICATE_ENTRY
    except self.model.DoesNotExist:
    return rc.NOT_HERE

    def read(self, request, id):
    if not self.has_model():
    return rc.NOT_IMPLEMENTED
    try:
    inst = self.model.objects.get(id=id)
    return inst
    except self.model.MultipleObjectsReturned:
    return rc.DUPLICATE_ENTRY
    except self.model.DoesNotExist:
    return rc.NOT_HERE

    Then one can test it with curl:
    delete the task with id=1:
    curl -i -H “Accept: application/json” -X DELETE ‘http://localhost:8000/api/tasks/1′

    read the task with id=2:
    curl -i -H “Accept: application/json” -X GET ‘http://localhost:8000/api/tasks/2′

    Hope this helps.

    Orges

    18 Aug 10 at 3:47 pm

  10. Well, I’m still getting the 400 BAD REQUEST on POSTs.

    I’m running Piston 0.2.2 and that patch doesn’t seem to apply to this version.

    My request headers from the POST do contain: application/json; charset=UTF-8 (according to firefox 3.6.7/firebug).

    I wish I knew what what happening here. I’m somewhat committed to Piston!

    Ryan Allen

    4 Sep 10 at 12:06 am

  11. I’ve discovered what’s going on (for me, at least). Django seems to be choking on content-type being ‘application/x-www-form-urlencoded;charset=UTF-8′; I had to cut it down to ‘application/x-www-form-urlencoded’ to get it working. I achieved this manually with Tamper Data, I still have to find out how to do this in the ajax request.

    185346

    8 Sep 10 at 2:57 pm

  12. Discovered something else too. For me, the uwsgi_params file had a line that mentioned the contenttype field. Took out the line, everything magically worked! My guess is that either nginx or uwsgi was mangling it somewhere along the way, and when I took out that line, everything just starting using their default settings.

    185346

    9 Sep 10 at 5:21 pm

  13. I am getting the BAD REQUEST on PUT. 185346: could you please tell me where you found the uwsgi_params file? I am running on OS X and cannot find this file under the django installation. Any other ways of fixing this problem?

    Mach

    1 Nov 10 at 1:29 pm

  14. Got it working! It was a mistake in my code.

    Mach

    2 Nov 10 at 3:18 pm

  15. I was mainly interested in the custom emitters and tried to add emitters.py, but didn’t get it working until I added this to __init__.py

    from app_name.api.emitters import *

    Thanks for the article helped a ton

    jondkoon

    2 Mar 11 at 5:39 pm

  16. I believe that the error with the inability to ‘PUT’ or ‘POST’ comes from – in this example anyway – from the fact that the Grid has a form that opens to allow editing of the fields in the grid, on double-click, BUT the grid is set up to use a GridEditor, so there is no need for the form1. By removing the form1 and the ‘this.destroy()’ the processes for ‘PUT’ and ‘POST’ now work as Dorn wrote them to. Pretty impressive stuff though! I LIKE IT!!!!

    Paul Welby

    5 May 11 at 3:07 am

  17. RESTful Web apps with Django, Piston and Ext JS…

    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 a…

  18. Great article and great technologies combination with django piston for easy restful applications. Tested it and working great

    Thank you Matt

    Daniel

    29 Sep 11 at 9:53 am

  19. Hi im having a problem regarding to that example above
    the grid wont display the records from my database.

    John

    1 Nov 11 at 1:52 pm

  20. [...] i just followed this tutorial and the example is good. http://weblog.mattdorn.com/content/restful-web-apps-with-django-piston-and-ext-js/ [...]

  21. I have problem in update and delete. how you connect the ext-js to the django, i mean the restful.js to the handlers.py? here is the link of my problem,
    http://stackoverflow.com/questions/8147542/unable-to-update-put-and-delete-delete-data-in-django-piston

    thanks in advance

    gadss

    17 Nov 11 at 12:47 am

Leave a Reply