Django view classes and decorators

Todd Reed

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.