Custom Django Template Tags
The template system within Django is, in my opinion, one of the crown jewels of the web framework. There are a lot of opinions about what is the “correct” implementation of a template system—and most will never be convinced differently. For me, the template system in Django provides the right balance of flexibility and constraints.
On a recent project I needed to be able to provide Next and Previous functionality in a website (similar to the implementation of Polls at LJWorld). Out of the box I couldn’t find this functionality, but luckily the developers of Django made it stupidly easy to add your own tag and filter libraries.
If you’re interested in adding your own tags or filters, I highly recommend downloading the source to any of the following projects:
Each of these provide excellent examples. In addition, you should definitely look at the Django Documentation on Django Templates for Python Programmers.
For my project, I started with the some code from django-template-utils and then modified to fit my needs. I began with the parser token part of the the tag and ended up with the following.
def do_next_object(parser, token):
"""
Retrieves the next object from a given model, in that model's
default ordering, and stores it in a context variable.
Syntax::
{% get_next_object [object] using [field] as [varname] %}
Example::
{% get_next_object object using pub_date as next_object %}
"""
bits = token.contents.split()
if len(bits) != 6:
raise template.TemplateSyntaxError("'%s' tag takes five arguments" % bits[0])
if bits[2] != 'using':
raise template.TemplateSyntaxError("second argument to '%s' tag must be 'using'" % bits[0])
if bits[4] != 'as':
raise template.TemplateSyntaxError("fourth argument to '%s' tag must be 'as'" % bits[0])
return NextObjectNode(bits[1], bits[3], bits[5])
There’s nothing too complex going on here. Mainly this part of the code is just validating that the user has sent in all the right bits and pieces. How you structure the language of your template tag is up to you. For me I thought the above read quite nicely. Remember, you’re creating these for designers not developers, so try to refrain from using developer based terms.
The real work occurs in the overridden render method in the template node. Mine ended up looking like the following:
class NextObjectNode(template.Node):
def __init__(self, obj, field, varname):
self.obj, self.field, self.varname = obj, field, varname
def render(self, context):
obj = resolve_variable(self.obj, context)
try:
context[self.varname] = getattr(obj, 'get_next_by_%s' % self.field)()
except:
pass
return ''
There’s really only two interesting lines of code in here. Tokens received by the tag are received as strings. Well obviously I needed the actual instance object of model that I was working with, so I could use the get_next_by_FOO convenience method that Django automatically adds to each DateField or DateTimeField in my model. Luckily there’s a helper method called resolve_variable that does this heavy lifting for us. It takes the string and the current context and converts it back into its object form. Once I had the actual object all I needed to do was call the get_next_by_FOO method for the specified DateField or DateTimeField and stuff the results into the context.
Using my new tag ends up looking like this in my page template:
{% load taglib %}
{% get_next_object object using pub_date as next_object %}
{% if next_object %}
<a href="{{ next_object.get_absolute_url }}">Next Question</a>
{% endif %}
The full source for these tags is available here. Enjoy!
