< / >

This is a blog by about coding and web development.

DRY your views with middleware

Posted on in

When I have Django URL patterns like:

urlpatterns = patterns('',
    (r'^(?P<foo_id>\w+)/$', 'myproject.myapp.views.show'),
    (r'^(?P<foo_id>\w+)/edit/$', 'myproject.myapp.views.edit'),
    (r'^(?P<foo_id>\w+)/delete/$', 'myproject.myapp.views.delete'),
)

I always end up with views like:

def show(self, foo_id):
    foo = get_object_or_404(Foo, id=foo_id)
    ...

def edit(self, foo_id):
    foo = get_object_or_404(Foo, id=foo_id)
    ...

def delete(self, foo_id):
    foo = get_object_or_404(Foo, id=foo_id)
    ...

Having the same few lines at the top of every function in makes me feel dirty. You can clean this up with a Middleware class, replacing foo_id with the actual object before calling the view.

myproject/myapp/middleware.py looks like this:

from django.shortcuts import get_object_or_404
from someapp.models import Foo

class FindObjects:
    def process_view(self, request, view_func, view_args, view_kwargs):
        if 'foo_id' in view_kwargs:
            foo_id = view_kwargs['foo_id']
            view_kwargs['foo'] = get_object_or_404(Foo, id=foo_id)
            del view_kwargs['foo_id']

I include it in settings.py:

MIDDLEWARE_CLASSES = (
    ...
    'myproject.myapp.middleware.FindObjects',
)

And now my views look like this:

def show(request, foo):
    ... yay, do stuff with foo ...

Very useful for views with many parameters, or views with multiple optional parameters. In one project, I have views which process data for three different situations: data in a metropolitan area, data in a city, or data in a city within a metro. My middleware for that looks like this:

from django.shortcuts import get_object_or_404
from phuce.metros.models import City, Metro

class FindObjects:
    def process_view(self, request, view_func, view_args, view_kwargs):
        city = metro = None
        if 'metro_link' in view_kwargs:
            metro_link = view_kwargs['metro_link']
            metro = get_object_or_404(Metro, link=metro_link)
            del view_kwargs['metro_link']
            view_kwargs['metro'] = metro
        if 'city_link' in view_kwargs:
            city_link = view_kwargs['city_link']
            if metro:
                city = get_object_or_404(City, metro=metro,
                                         metro_link=city_link)
            else:
                city = get_object_or_404(City, link=city_link)
            del view_kwargs['city_link']
            view_kwargs['city'] = city

It’s much tidier without that monster in my views.