Django Recipe: A template for any blog post

Posted Saturday, February 13, 2010 at 8:08 p.m. by Chris Amico in Projects about Django, Python, recipes and templates

One more quick code recipe before I jump back into the Journalism to Django series. In my last post, I mentioned that I set up permalinked paragraphs on a couple recent entries, using a different technique on each one. You might be wondering how I did that.

This is documented, but it took me a while before I realized how simple it is. The key is Django's get_template and select_template functions, which are part of the template system. Get template takes a string and gets a template. Select template chooses the first one that matches from a list or tuple.

Django's documentation suggests this pattern:

You can use select_template() for super-flexible "templatability." For example, if you've written a news story and want some stories to have custom templates, use something like select_template(['story_%s_detail.html' % story.id, 'story_detail.html']). That'll allow you to use a custom template for an individual story, with a fallback template for stories that don't have custom templates.

So, when I needed something like this in the past, I'd write a view like this:

from django.http import HttpResponse
from django.template import RequestContext
from django.template.loader import select_template

def object_detail(request, object_id):
    # some code to get an object
    c = RequestContext(request, {'object': object})
    t = select_template(['object_%s_detail.html' % object.id, 'object_detail.html'])
    return HttpResponse(t.render(c))

I wanted template flexibility, and I wanted to use RequestContext, which lets me use all my context processors. I thought that meant I couldn't use the ubiquitous render_to_response shortcut.

The part I didn't notice until I spent some time nosing around in the source code is that render_to_string will call get_template if the first argument is a string, but it will call select_template if you give it a list or tuple instead.

And since render_to_response calls render_to_string, you can simply pass a list or tuple at the end of your view:

from django.shortcuts import render_to_response
from django.template import RequestContext

def object_detail(request, object_id):
    # get that object
    return render_to_response(
        ['object_%s_detail.html' % object.id, 'object_detail.html'],
        {'object': object},
        context_instance=RequestContext(request)
    )

I'm being a bit more verbose there, but the point should be clear. That second way is a lot closer to the way I'd normally write a view, using render_to_response instead of going through building a context, selecting a template and returning an HTTP response.

That and a little template inheritance makes for a lot of flexibility without much work at all:

# object_42_detail.html

{% extends "object_detail.html" %}

{% block js %}
<script src="{{ MEDIA_URL }}/js/some-script.js"></script>
{% endblock %}

And that's pretty much it.



Comments:

feb 17, 2010 at 5:37 p.m. // Adam Skory said:

Good trick, I'll remember that one.

Comments are closed for this post. If you still have something to say, please email me.