Sunday, January 31, 2010

Helpful Django View Decorator Pattern

One of Python's (2.5+) most useful shorthands is the concept of decorators. Here is an example of using Decorators with Django to make view functions a little more "DRY". I probably found the original version of this on www.djangosnippets.org, but I've made a couple of little "enhancements". First of all, the decorator takes two argument, the first the main template to render, but the second optional argument is the name of the template to render in logged-in mode. This is a useful pattern if the same view has two different templates depending on whether the user is logged-in or not. I've also added an automatic "body_class" context variable to the rendered template that can be used to specify a custom class for the HTML body:

<body class="{{ body_class }}>
This might not be the cleanest way to do things, but I find it very useful to automatically have a CSS class added to my body tag. Here is how you apply it in your views:

@render_with('mytemplates/template.html')
def my_view(request):
    return {'context_var': value}

def render_with(template, logged_in_template=None):
    """
    Decorator for Django views that sends returned dict to render_to_response function
    with given template and RequestContext as context instance.
    """
    def renderer(func):
        def wrapper(request, *args, **kw):
            template_to_render = logged_in_template if logged_in_template and \
                request.user.is_authenticated() else template      
            output = func(request, *args, **kw)
            if request.META['PATH_INFO'] == '/':
                body_class = 'home'
            else:
                body_class = ' '.join('body-'+slugify(p) for p in request.META['PATH_INFO'].split('/') if p)      
            if isinstance(output, dict):
                if 'body_class' not in output:
                    output.update({'body_class': body_class})        
                return render_to_response(
                    template_to_render, output,
                    CustomRequestContext(request, output)
                )      
        return output
    return wrapper
return renderer

2 comments:

  1. It's a pattern that is becoming more common but I'm not sure if I like it. While I understand the benefits of re-usability I like to maintain the contract where a view accepts a http request and returns an http response.

    I think a better approach may be to make it easier for us to change/modify the http response objects.

    Your method for providing a body class seems a little odd and I'm not sure its the best approach. I imagine a ID would be better than a class to start with but otherwise it seems like an odd place to change this...

    Why not get the body_class through a context processor? or perhaps a template tag.

    ReplyDelete
  2. I've been wanting to do this, but attaching the original function to the decorated one, so that the original's returned context can be tested separately from rendering.

    ReplyDelete