Posts Tagged http

Django RESTful resources

Last year I blogged about a neat trick in Django to have multiple views per HTTP verb. Since then I’ve been playing with RESTful applications and decided to see if there was a nicer way to expose “resources” in Django. The following is what I’ve come up with so far.

Listing: router.py

from django.http import Http404, HttpResponseNotAllowed

def get_handler_method(request_handler, http_method):
    try:
        handler_method = getattr(request_handler, http_method.lower())
        if callable(handler_method):
            return handler_method
    except AttributeError:
        pass

class Resource:

    http_methods = ['GET', 'POST', 'HEAD', 'PUT', 'DELETE', 'OPTIONS', 'TRACE']

    @classmethod
    def dispatch(cls, request, *args, **kwargs):
        request_handler = cls()

        if request.method in cls.http_methods:
            handler_method = get_handler_method(request_handler, request.method)
            if handler_method:
                return handler_method(request, *args, **kwargs)

        methods = [method for method in cls.http_methods if get_handler_method(request_handler, method)]
        if len(methods) > 0:
            return HttpResponseNotAllowed(methods)
        else:
            raise Http404

Listing: views.py

from django.shortcuts import render_to_response, get_object_or_404
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from router import Resource
from models import Document
from django import forms
import identity

class DocumentForm(forms.Form):
    one = forms.CharField()
    two = forms.CharField()
    date = forms.CharField()

def index(request):
    document_links = []
    for document in Document.objects.all():
        url = reverse('document', args=[document.id])
        document_links.append({ 'identity': document.id, 'url': url })

    new_form_url = reverse('document', args=[identity.NEW])
    model = { 'document_links': document_links, 'new_form_url': new_form_url }
    return render_to_response('index.html', model)

def success(request, document_id):
    document = get_object_or_404(Document, pk=document_id)
    form = DocumentForm(document.get_values())
    form.is_valid()

    model = { 'form': form.cleaned_data, 'index_url': reverse('index') }
    return render_to_response('success.html', model)

class DocumentView(Resource):

    def get(self, request, document_id):
        if identity.is_new(document_id):
            form = DocumentForm()
        else:
            document = get_object_or_404(Document, pk=document_id)
            form = DocumentForm(document.get_values())

        model = { 'form': form, 'index_url': reverse('index') }
        return render_to_response('form.html', model)

    def post(self, request, document_id):
        if identity.is_new(document_id):
            document = Document()
        else:
            document = get_object_or_404(Document, pk=document_id)

        document.set_values(request.POST)
        document.save()

        form = DocumentForm(document.get_values())
        if form.is_valid():
            url = reverse('success', args=[document.id])
        else:
            url = reverse('document', args=[document.id])

        return HttpResponseRedirect(url)

Listing: urls.py

from django.conf.urls.defaults import *
from django.conf import settings
import os, views

urlpatterns = patterns('',
    url(r'^document/(?P<document_id>[A-Za-z0-9\-]+)/$', views.DocumentView.dispatch, name='document'),
    url(r'^success/(?P<document_id>[A-Za-z0-9\-]+)/$', views.success, name='success'),
    url(r'^$', views.index, name='index'),
)

if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^static/(?P<path>.*)$', 'django.views.static.serve',
            {'document_root': os.path.join(os.path.dirname(__file__), 'static')}),
    )

It does add a little bit more magic to the views (since you don’t actually see the dispatch method at all) but it benefits from simpler mapping in urls.py, as well as an easier way to figure out what instance method on a Resource subclass gets called for each HTTP verb. Never felt that comfortable with popping-off of kwargs in last year’s post.

Tags: , , , , ,

HTTP method primer for RESTful web services

The following is just a reminder to stop me from getting confused ;-)

GET

  • Used to fetch a resource.
  • The server sends back a representation of the resource in the response body.
  • Safe operation (see below).

DELETE

  • Used to delete a resource.
  • The response from a server may contain a status message or nothing at all. It is usually nice to send back at least a 204 (No Content).
  • Idempotent operation (see below).

PUT

  • Used to create or modify a resource. If your server should not permit users to determine the URI of a new resource then you may need to create it via a POST to a factory service (see below).
  • The request body contains the proposed new representation of the resource.
  • From my reading there seems to be some contention as to whether the request body should contain a full representation of the resource or a delta when modifying an existing resource. My personal preference is that the request should contain a representation of the whole resource, and use a POST method to the resource to update part of a resource. Alternatively, if possible, you can PUT to a sub-resource (e.g. PUT to /user/12345/address if all I want to do is modify the user’s address).
  • The response from a server may contain a status message or nothing at all. It is usually nice to at least return a 200 (OK).
  • Idempotent operation.

POST

  • Used to create subordinate resources: resources that exist in relation to some other parent resource.
    • For example: POST /weblogs/myweblog with the request body containing the contents of a new weblog entry would create an entry called /weblogs/myweblog/entries/SS0093WSA.
    • Response to such a POST request usually has a status of 201 (Created) and a HTTP response “Location” header that has the URI of the created resource.
  • POST can also be used to append to an existing resource or to update an existing resource.
    • For example: POST to /log adds a log message to the /log resource.
  • Not safe or idempotent. Expect side-effects. “Here be dragons”

HEAD

  • Used to retrieve metadata about a resource, rather than the resource itself.
  • Can be used to check if the resource exists, or if a newer version of the resource is available.
  • Gives you the same HTTP header as the response to a GET request, just without the response body.
  • Safe operation.

OPTIONS and TRACE

  • Currently not really used by most RESTful systems. Nobody can really figure out how to use them properly.

OPERATION TYPES

  • Safe operation
    • Should not change any server state – or at least any state that matters as even GET requests can get logged or access counters can become incremented.
  • Idempotent operation
    • Has the same effect irrespective of the number of times its applied. In other words, the second and subsequent requests leave the resource in exactly the same state as the first request did.

Tags: , ,

Django and multiple methods per url pattern

HTTP verbs are not used to determine the route to a view method by deliberate design in Django. Sometimes I find it useful to be able to specify different methods for the same url pattern – one per HTTP verb. The Django book contains an interesting example of how this can be done using the django.conf.urls.defaults.url method to separate POST from GET processing. I’ve extended the example to provide handling to cover the standard HTTP verbs.

Listing: router.py

from django.http import Http404, HttpResponseNotAllowed
from django.conf.urls.defaults import url

GET = 'GET'
PUT = 'PUT'
POST = 'POST'
HEAD = 'HEAD'
TRACE = 'TRACE'
DELETE = 'DELETE'
OPTIONS = 'OPTIONS'

HTTP_METHODS = (GET, POST, PUT, HEAD, DELETE, OPTIONS, TRACE)

def dispatch(request, *args, **kwargs):
    view = kwargs.pop(request.method, None)
    if view:
        for method in HTTP_METHODS:
            kwargs.pop(method, None)
        return view(request, *args, **kwargs)
    else:
        allowed_methods = []
        for method in HTTP_METHODS:
            if kwargs.pop(method, None):
                allowed_methods.append(method)
        if allowed_methods:
            return HttpResponseNotAllowed(allowed_methods)
        else:
            raise Http404

def mapping(regex, viewname, get = None, post = None, put = None, delete = None, \
            head = None, options = None, trace = None):
    views = { GET: get, POST: post, PUT: put, DELETE: delete, HEAD: head, \
              OPTIONS: options, TRACE: trace }
    return url(regex, dispatch, views, viewname)

Listing: urls.py

from django.conf.urls.defaults import *
from router import mapping
import views

urlpatterns = patterns('',
    (r'^$', views.index),
    mapping(r'^document/(?P<document_id>[A-Za-z0-9]+)/$', 'single_document',
            views.single_document_get, views.single_document_post),
)

Works for me.

UPDATE: There is also a more object-oriented/RESTful way to do this, as outlined in my Django RESTful resources blog post.

Tags: , , ,