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.
#1 by Andriy Drozdyuk on 23 November 2010 - 4:18 am
Quote
Nice post.
Have you looked at the built-in django filter require_http_methods?
It is in the first accepted answer on stackoverflow:
http://stackoverflow.com/questions/1013587/http-verb-decorator-for-django
#2 by Tom on 24 November 2010 - 8:39 pm
Quote
@Andriy Thanks for the link. What I was after was not a filter but a way of dispatching the same URL to different handler methods based on a HTTP verb. If I wanted to restrict which HTTP verbs I support on a single handler method then the filter makes great sense.