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.