Django tips: template loading and rendering
I’ve been reminded today by Maura that November is National Blog Posting Month, when — in theory — bloggers the world over try to keep up a pace of one entry every day. I don’t know how well this is going to go, but I’d like to give it a try. And, inspired by Drew McLellan’s excellent 24 ways “advent calendars” of web-design tips, I’m going to give it a theme: one Django tip every day during the month of November. Kicking off the series, I’d like to focus today on the deceptively-simple task of template loading and rendering.
The long way
If you’ve ever read through the Django tutorial, you know that manually loading and rendering a template is a bit of a chore, because it involves multiple separate steps. The canonical example looks like this:
from django.template import loader, Context t = loader.get_template('my_template.html') c = Context({ 'object_list': SomeModel.objects.all() }) rendered = t.render(c)
This shows off all of the steps involved:
-
A template loader is used to locate the actual template to use and parses it into a
Template
object suitale for rendering. -
A
Context
of some sort is instantiated to provide theTemplate
some variables to work with. -
The
Template
is rendered (via itsrender()
method) using theContext
.
Multiple templates
Before moving on to shortcuts, let’s stop for a second and consider a common use case: customizing templates on a per-object basis. For example, at work we have news stories organized into “sections” according to their topics; we have sections for things like local news, sports, politics, etc. Naturally, some of these sections need to get special treatment by having their own templates customized for a particular section’s topical theme.
Now, one way to do this would be to add an optional field on the Section
model for specifying a custom template name, and use that if it’s specified or fall back to a default template if not. The view code might look like this (assuming we look up sections from a slug in the URL):
section = get_object_or_404(Section, slug__exact=slug) if section.custom_template_name: t = loader.get_template(section.custom_template_name) else: t = loader.get_template("news/section.html")
That’s awfully wordy, though, and it requires lots of editing of the Section
objects. A better option — in most cases (sometimes, for example in the case of the FlatPage
model in django.contrib.flatpages
, it’s more convenient to use a field for this) — would be some sort of automatic method of checking for a custom template. For example, if there’s a section with the slug “sports”, it’d be nice to check for the template “news/section_sports.html” and then fall back, because then the only work to be done is creating that template. And we could adapt the above code to do it:
section = get_object_or_404(Section, slug__exact=slug) try: t = loader.get_template("news/section_%s.html" % section.slug) except TemplateDoesNotExist: t = loader.get_template("news/section.html")
This is still pretty nasty, though, and it’d get even worse if we wanted multiple possible levels of template customization. Fortunately, Django provides an easy solution: the select_template()
method of the template loader, which takes a list of template names and returns the first one that actually exists. Here’s how it would work in our example:
section = get_object_or_404(Section, slug__exact=slug) t = loader.select_template(["news/section_%s.html" % section.slug, "news/section.html"])
Under the hood, select_template()
simply runs through the list of template names in order, catching TemplateDoesNotExist
when needed and returning the first template that loads successfully. This makes for a very compact and easy-to-use idiom for per-object template customization (if you’ve ever wondered how the Django admin lets you override templates on a per-model or per-application basis: it’s calling select_template()
with a list of decreasingly-specific names).
Getting a response
Of course, that’s only half the battle; when you’re working in a view, you also need to return an HttpResponse
, usually by doing something like (returning to the first example above, where we loaded a template in the variable t
and created a Context
named c
):
return HttpResponse(t.render(c))
And that makes the whole thing even more tedious, which is why the documentation goes to great pains to point out the render_to_response() shortcut. Using render_to_response()
, everything gets much simpler:
return render_to_response('my_template.html', { 'object_list': SomeModel.objects.all() })
This collapses the whole process of loading a template, creating a Context
, rendering the template and creating an HttpResponse
into one easy step. I’d hope that anyone who spends more than an hour or two playing with Django picks up on this, but it’s worth pointing out in case you’ve never run across it.
What if you don’t want a response?
Of course, there are times when you don’t want to return an HttpResponse
(or aren’t quite ready to do that yet). One example I run across all the time is using templates to programmatically generate email messages; passing some variables in and rendering a template makes for an extremely easy way to do this, but render_to_response
doesn’t help at all here, since you want to feed that into the email-sending machinery, not the HTTP request/response handlers. That’s why there’s another (and, from my experience, less-well-known) function which stops short of creating an HttpResponse
: django.template.loader.render_to_string(). Here’s an example:
from django.template.loader import render_to_string email_body = render_to_string("my_email_template.txt", { 'name': 'Bob', 'message': 'Hello!' })
The argument signature here is pretty much identical, and in fact render_to_response()
is really just a light wrapper around render_to_string()
which stuffs the result into an HttpResponse
before returning (render_to_response()
does take a few extra optional arguments related to HTTP, though). I use this all the time for generating emails and other things which won’t necessarily end up as part of an HttpResponse
(though it’s worth noting that if you use this for the subject of an email, you should take care to forcibly collapse it down to a single line; remember, email is sensitive to newlines).
Using RequestContext
Another common problem in template rendering is setting up some list of variables that need to appear in every (or nearly every) Context
you use for template rendering: you’ll often want to have the current logged-in User
available, the value of the MEDIA_URL
setting, and sometimes project- or application-specific values.
The intuitive solution, from a Python programming point of view, would be to note that Context
is a class, and so could easily be subclassed with the subclass set up to automatically include certain values without needing to explicitly list them every single time. And Django provides a built-in subclass of Context
, called RequestContext
, which does just that: in addition to a dictionary of variables specific to the view you’re writing, a RequestContext
takes an HttpRequest
as an argument and calls a set of functions called “context processors” to let them specify additional variables based on the request.
Although it’s covered in the Django documentation and has been written about in various blog posts (including one by yours truly), I’m always surprised at how many people don’t know about RequestContext
, especially because it’s so easy to use. Returning to the original example we started with, here’s how it’d be rewritten to use RequestContext
:
from django.template import loader, RequestContext t = loader.get_template('my_template.html') c = RequestContext(request, { 'object_list': SomeModel.objects.all() }) rendered = t.render(c)
Any context processors specified in your TEMPLATE_CONTEXT_PROCESSORS
setting would be applied automatically; if you’re using the default set, that’d get things like the current User
, the value of MEDIA_URL
, the currently-active translation (for the internationalization system), etc.
Putting it all together
And the two template-rendering shortcuts — render_to_response()
and render_to_string()
— both allow you to take advantage of the extra features we’ve covered here. For example, they both allow you to pass a list of template names, and (by checking whether the argument was a list or a string) will automatically use select_template()
instead of get_template()
. This means that for the example above of customizable per-section templates, we could do:
section = get_object_or_404(Section, slug__exact=slug) return render_to_response(["news/section_%s.html" % section.slug, "news/section.html"], { 'section': section })
Both functions also support an optional context_instance
argument which lets you drop in any Context
— or, more importantly, any subclass of Context
— you like. For example:
return render_to_response('my_template.html', { 'object_list': SomeModel.objects.all() }, context_instance=RequestContext(request))
Taken together, this means you’ve got access to simple, easy-to-remember shortcuts for quickly rendering to a string or HttpResponse
, using any type of Context
you like, and with the full flexibility of select_template()
. Most of the time, you can get all of that into a single line of code (if you don’t mind it running a bit long to accommodate the arguments). And that’s nothing to sneeze at.