Django view classes and decorators
Note
Since I originally wrote this, Django 1.3 has implemented support for class-based views, and the function method_decorator from the django.utils.decorators module offers the same solution presented here. So, unless you’re tied to Django 1.2 or earlier, you may just want to check the Django 1.3 docs.
There have been several proposals (for example,
here and
here) for
using view classes instead of view functions for rendering views in
Django. One of the problems I encountered when first attempting to use
this approach was that (some of) Django’s view decorators don’t work on methods
because the self
method argument gets interpreted as the request
argument that is always passed to views. None of the proposals I found
online provided a suitable workaround, and I was ready to give up and
revert to using regular view functions. (One proposed workaround was to use
class methods instead of instance methods, but this negates some of
the benefits of using a class approach in the first place.)
Here’s a simplified view class that’s typical of what others have proposed and what I’m using:
class View(object): __metaclass__ = metaclass.SingletonMetaClass METHODS = ('OPTIONS', 'GET', 'HEAD', 'POST', 'PUT', 'DELETE', 'TRACE', 'CONNECT') def __init__(self): self.supported_methods = [method for method in View.METHODS if hasattr(self, method.lower())] def __call__(self, request, *args, **kwargs): try: method = getattr(self, request.method.lower()) except AttributeError: return HttpResponseNotAllowed(self.supported_methods) else: return method(request, *args, **kwargs)
Note
Others have also suggested subclassing HttpResponse
and using
the __init__()
initializer to render the response, but I prefer
the __call__()
approach. See
http://www.djangosnippets.org/snippets/1072/ for an example of
subclassing HttpResponse
.
The solution I ended up using was to create a new decorator that
wraps the Django decorators. Expending all my intellectual power on
the guts of a workaround, I failed to find an appropriate name for
this decorator wrapper and opted for the meaningless name on_method
. This is what is looks like to use:
class ContentView(View): @on_method(cache_page(60*5)) @on_method(cache_control(max_age=60*5)) def get(self, request, slug): pass
And here’s the definition of on_method
:
def on_method(function_decorator): def decorate_method(unbound_method): def method_proxy(self, *args, **kwargs): def f(*a, **kw): return unbound_method(self, *a, **kw) return function_decorator(f)(*args, **kwargs) return method_proxy return decorate_method
This decorator wraps an instance method in a regular function, and then invokes the Django decorator on this regular function.