XOR Media

Python Django render_with_template Decorator

Python decorators are extremely useful when used with care, and using them is really straightforward. Coding them up on the other hand can be complex and requires, reading along with trial-and-error. There are some helpful modules out there worth taking a look at, but for this use-case we won’t be making use of them.

Django is pretty DRY, but there are still a few common lines of ~boilerplate that commonly show up. One of these fragments involves a call to render_to_response. It usually looks something like the following.

def song_detail(request, song_id):
    song = get_object_or_404(Song, pk=song_id)
    if song.private:
        return HttpResponseForbidden()
    context = {'song': song}
    return render_to_response('template.html', context,
                              context_instance=RequestContext(request))

Get an object from the database, if it’s private deny access, otherwise construct the context for our template, and then make the call to render_to_response. There’s nothing too objectionable to the snippet, but “context_instance=RequestContext(request)” isn’t very relevant to what we’re trying to accomplish, it’s effectively boilerplate. What’s more is that in many situations it’s important to use RequestContext, and it’s not that obvious from reading the docs that you should and when it matters. Enter render_with_template.

@render_with_template('template.html')
def song_detail(request, song_id):
    song = get_object_or_404(Song, pk=song_id)
    if song.private:
        return HttpResponseForbidden()
    return {'song': song}

That’s better. We use a decorator to indicate the template our view will use, then grab our object, and instead of making a call to render_to_response we just return the context we’d like to send in to the template. The difference is subtle, but for someone who doesn’t know django’s render_to_response call well it’s much more readable.

So let’s take a look under the hook of render_with_template.

class render_with_template:

    def __init__(self, template_name):
        self.template_name = template_name

    def __call__(self, func):

        # our decorated function
        def _render_with_template(request, *args, **kwargs):
            context_or_response = func(request, *args, **kwargs)
            if isinstance(context_or_response, HttpResponse):
                # it's already a response, just return it
                return context_or_response
            # it's a context
            return render_to_response(self.template_name, context_or_response,
                                      context_instance=RequestContext(request))

        _render_with_template.__doc__ = func.__doc__
        _render_with_template.__name__ = func.__name__
        _render_with_template.__module__ = func.__module__

        return _render_with_template

I’ll gloss over the mechanics of the decorator, for explanations check out the decorators documentation. We’ll focus on the behavior we’re creating beginning with the call to func, which is the original decorated view function. We catch it’s return value in context_or_response and then immediately look to see which of the two we have. This is accomplished by using isinstance to see if we have a descendant of HttpResponse. This handles the case where the view function returns a response it’s created rather than context for its template (the song.private case.) If we have context rather than a response, we provide it to the render_to_response call along with its other requirements and the rendering process continues normally.

And there we have it, a very common add-on decorator for Django. You can find numerous snippets, some of which are pretty complicated, that serve the same purpose. This is the one I use in all of my Django projects.