< / >

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.