Sun 7 Oct 2007
Django Tip: HTTP verb dispatching
Posted at 5:38 +1000
A recent thread on django-developers discussed one style of laying out views, whereby the handling of different request methods to the same URL end up in different functions. This isn't a requirement or even a good idea in some cases (since there's frequently a lot of common code), but when it is a good solution, doing it frequently can become repetitive.
Fortunately, adding any extra code to Django's core isn't necessary to handle this. We can write a helper method that's less than twenty lines of code (and could be much shorter if I wanted to be less readable). This article is about the design of such a feature (at least, about my design, since different people want different hings). Code is included.
The Use-Cases
Always a good idea to work out what we're really trying to build. So let me set up the scope of this piece of code.
REST theory advocates using the HTTP verbs (the request methods — GET, POST, PUT, DELETE, etc) as one aspect of determining the action to take. The URI identifies the resource and the verb indicates what to do to that resource. Retrieving an article (via GET) is different from deleting it (DELETE) or editing it (POST), even though it's the same article and could easily be referenced by the same URL all the time.
If that last paragraph makes no sense at all and you think it should, it's time to start reading a bit more about REST-style interactions. The RESTful Web Services book by Leonard Richardson and Sam Ruby is a good reference if you like hard-copy. The RESTWiki isn't a bad starting place for online browsing, if you want a holistic theoretical introduction.
Sometimes (not always, as indicated in the introduction), the work you want to do for different methods is varied enough and complex enough that it makes sense to write multiple functions: one for each verb (or perhaps groups of verbs).
Since we're operating on the principle that the same URL is used to identify the resource in each case, it's not unreasonable to say that our views will each receive the same information from the URL parsing. Extra information might also be available in the request, depending on the method. I'm thinking, in particular, of submitted data for PUT and POST requests here. However, that information doesn't play a role in URL resolving, so we can ignore it here. What we're really trying to do is generalise this type of code pattern:
def my_view(request, arg1, arg2):
if request.method == 'GET':
return get_view(request, arg1, arg2)
if request.method == 'POST':
return post_view(request, arg1, arg2)
Another question that was raised in the thread that triggered this is what to do about reverse URL resolving. I suspect that's a bit of a non-issue. If you're trying to automatically insert a URL in your template, you don't have reference to the method the caller is going to be using anyway. If it's a link, they'll use GET by default. If it's a form action, the method is specified elsewhere. So the standard url template tag isn't going to be useful in any natural sense here, except in the same way it's used now. If you have different URLs to use for retrieval versus update then they will be different entries in your application's URLConf and so you can just refer to them as you would name (giving each pattern a name makes things easier to read later).
So I can't see that we need any extra support for generic reverse resolving, since the normal tag works as well as it's ever going to here, given the information it has available to it. If more context is required than the name and some parameters, it's time to write a custom tag. At that point you're into deep specialisation (and probably want to think hard about whether you've designed yourself into a corner if this isn't a legacy system).
The API
The previous section was possibly a very long-winded way of getting to the point where we can write down what this helper might look like when it's used. I think the real clue is the fragment of code we're trying to generalise. The generalisation is to pass it a mapping of functions to call for each possible method we want to handle. Sounds like a Python dictionary will be the right input structure.
To make things convenient, we'll say that any methods we don't specify are forbidden by default (will return a 405 HTTP status code). Also, let's allow for a default specification; any methods not otherwise handled are sent to the default view. This way, you could write a special view for, say, the DELETE method and use common code for PUT, POST and DELETE. Finally, since some methods might be sent to the same view, we'll allow sequences of methods as the keys in the dictionaries, as well as single strings. That makes it slightly simpler to map PUT and POST to the same view, for example.
The code we're generalising was a Django view. That is, it's called from URLConf. It makes sense to create the wrapper in the same place in the chain. The user will pass our wrapper a dictionary mapping methods to functions and we'll return a callable object that will process the incoming request and call the right method.
The Implementation
A few minutes playing around with some possibilities led to this:
from django import http
class MethodDispatcher(object):
def __init__(self, method_map):
self.methods = {}
self.default = None
for key, value in method_map.items():
if key == '__default__':
self.default = value
elif isinstance(key, basestring):
self.methods[key] = value
else:
for elt in key:
self.methods[elt] = value
def __call__(self, request, *args, **kwargs):
view_func = self.methods.get(request.method, self.default)
if view_func:
return view_func(request, *args, **kwargs)
return http.HttpResponseNotAllowed(self.methods.keys())
I've also made this into an entry over at djangosnippets in case anybody comes up with massive improvements or suggestions.
I can use this in my URLConf by writing something like
handlers = {
'DELETE': delete_view,
'PUT': creation_view,
'__default__': normal_view,
}
urlconf = patterns('',
('/my/url/pattern/', MethodDispatcher(handlers), ...),
...
)
Difficult to think how this could be much shorter or more direct. If your views only need to act on different arguments captured from the URL input, just use Python's ability to capture arbitrary arguments (**args and * *kwargs*) to swallow the unneeded parameters.
Some Further Context
I should point out that this particular problem didn't only come up in the thread I referenced earlier. Over the Northern Hemisphere summer, I was mentoring Andreas Stuhlmüller for Google's Summer of Code and his work on creating a REST interface framework for Django. I need to write a lot more about what Andreas did, because his output and work process were very good, plus we learnt some interesting things from the problem. Dispatching based on HTTP verbs was something that came up naturally in that context and although we never got around to writing code to implement it, Andreas and I did have a few IM conversations about an approach just like this. There were some complications in the case of the framework he was building — reverse resolving really was tricky — that made us both feel the design might not be as clean as possible without being able to put our finger on a concrete improvement to make.
Conclusion
Should this find it's way into Django's core? I'm not convinced. It has the advantage of being very short and simple. However, there are lots of code fragments that meet that mark and if we included them all, Django would be a not-quite-svelte 200,000 lines of code within a few months.
My main concern, though, is that not everybody wants precisely the same behaviour. So something starts out simply and then develops more levers and buttons and hooks to allow customisation. For very short code fragments, which can be easily copied around, there's really no penalty to just creating a copy and modifying it to suit your needs, rather than adding more and more hooks and parameters. This keeps both versions, the original and your fit-for-purpose modification, simple and easy to read. Once you have a version that suits your needs, changes to the original are irrelevant to you.
This is a very restricted problem space, in some sense, so you can have confidence that once you've got a solution that works for you, you're unlikely to find a big hole. Usually you reuse code and try not to make local copies so that changes to the original are of some benefit to you and because the original solves a big problem and is expanding to solve more and more or it. In this case, though, what's left to solve that's a genuine extension as opposed to a different approach altogether? By all means shove a copy in your local library if you want to work this way, but the general applicability just doesn't seem to make it worth it.
My goal in writing this explanation and showing some code was more to act as an inspiration for people to write custom solutions and to show it doesn't take a lot of code.
Personally, I'm probably unlikely to use this code much for browser-based stuff, since there is a lot of commonality between the GET and POST paths. For more machine-oriented interfaces that also support PUT and DELETE, it might be useful.
Topics: software/django/tips