Writing simple templatetags is only marginally less painful that writing complex tags. There's a good deal of boilerplate code that goes into writing tags:
class SomeObjectsNode(Node):
def __init__(self, queryset, var_name, limit=5):
self.queryset = queryset
self.var_name = var_name
self.limit = int(limit)
def render(self, context):
queryset = template.Variable(self.queryset).resolve(context)
try:
objects = self.queryset[:self.limit]
except:
objects = None
context[self.var_name] = objects
return ''
@register.tag
def get_some_objects(parser, token):
"""
Get me some objects
usage:
{% get_some_objects from queryset as [var_name] (limit [limit]) %}
"""
bits = token.split_contents()
if len(bits) not in (4, 6):
raise TemplateSyntaxError('%s takes 2 or 3 arguments' % (bits[0]))
elif len(bits) == 4:
return LatestEntriesNode(bits[1], bits[3])
elif len(bits) == 6:
return LatestEntriesNode(bits[1], bits[3], bits[5])
When you get down to the core of it, though, this is what you want to express:
def get_latest_entries(context, queryset, var_name, limit=5):
try:
objects = queryset[:limit]
except:
objects = None
context[var_name] = objects
I wrote django-simpletag with the goal of making this work. There are a few quirks, but the simpletag implementation would look like this:
register = template.Library()
@simpletag(register)
def get_latest_entries(context, queryset, __as, _var_name, __limit, _limit=5):
try:
objects = queryset[:_limit]
except:
objects = None
context[_var_name] = objects
So what's up with the underscores.
Yeah they're pretty gross, but rather than doing things in the decorator, I opted to put them right there on the function. Templatetags seem to generally take 3 kinds of arguments: string literals that matter (var_name), throwaways that should probably be validated anyways (as), and template variables that have to be evaluated. I've accomodated for these types by a convention of underscore prefixes. A single underscore, like varname should be evaluated as a string literal. A double-underscore, like __as, is for those throwaways that make the templatetag operations clear - it should be validated that, if present, the user entered the right thing (__as == 'as'). Lastly, no underscores means evaluate as a template variable. Default arguments can be supplied, but are limited to the same rules python uses (so no default args before required args).
How it works
It's really simple. The simpletag decorator registers a templatetag that uses your functions name. The callable passed into the templatetag registry is actually a class that wraps your function in parsing and validation logic. inspect.getargspec is used to introspect the arguments you've defined for your function. This is all of the code:
import inspect
from django import template
def simpletag(register):
def inner(func):
register.tag(func.__name__, SimpleTag(func))
return func
return inner
class SimpleTag(object):
def __init__(self, func):
self.func = func
# introspect the function, reading its arguments
args, _, _, defaults = inspect.getargspec(self.func)
assert args[0] == 'context', 'First argument of a simpletag must be "context"'
# ignore the first arg, which is always 'context'
self.args = args[1:]
if defaults:
self.num_defaults = len(defaults)
else:
self.num_defaults = 0
def __call__(self, parser, token):
bits = token.split_contents()[1:]
if len(bits) > len(self.args):
self._error('expects no more than %d arguments' % (len(self.args)))
for indx, arg in enumerate(self.args):
if arg.startswith('__'):
try:
named_bit = bits[indx]
if named_bit != arg[2:]:
self._error('expected argument %d to be "%s"' % \
(indx + 1, arg[2:]))
except IndexError:
# append an empty string to the bits
bits.append('')
if len(bits) < len(self.args) - self.num_defaults:
self._error('expects at least %d arguments' % \
(len(self.args) - self.num_defaults - 1))
return SimpleNode(self.func, bits, self.args)
def _error(self, message):
raise template.TemplateSyntaxError('%s %s' % \
(self.func.__name__, message))
class SimpleNode(template.Node):
def __init__(self, func, params, args):
self.func = func
self.params = params
self.args = args
def render(self, context):
for indx, bit in enumerate(self.params):
if not self.args[indx].startswith('_'):
self.params[indx] = template.Variable(bit).resolve(context)
return_value = self.func(context, *self.params)
return return_value or ''
django-simpletag is up on github, if you'd like to take a peek.
Note
I recognize it's sort of a rite-of-passage to attempt to make templatetag writing less painful. There are a lot of other ways to do it, and Eric Holscher has written some great posts on the subject. Alex Gaynor has written what I think is probably the neatest solution, specifying arguments in the decorator. As always, I welcome your feedback!
Related links
- django-templatetag-sugar by Alex Gayner - this one's awesome
Comments (5)
Commenting has been disabled for this entry

Carl Meyer
My favorite is django-tagcon (http://github.com/korpios/django-tagcon). It allows a similar level of conciseness, but with a syntax that I find cleaner than either this or templatetag-sugar. Also, tags are classes so that I can reuse code via subclassing.
February 15, 2010 at 9:19 a.m. ( permalink )
Charles Leifer
Carl - thanks for the link, I hadn't seen that library before nor had I considered a declarative approach. Very cool!
February 15, 2010 at 1:45 p.m. ( permalink )
Vinicius Mendes
What do you think about putting something like kwargs in the end of the method?
def tag_name(context, kwargs):
So you allow the use of this:
{% tag_name user=request.user %}
February 21, 2010 at 8:10 a.m. ( permalink )
Charles Leifer
So while reading Pro Django I learned that Django actually has most of this functionality already. D'oh! The relevant bit is a decorator that is actually called simple_tag.
Check it out:
February 27, 2010 at 7:59 p.m. ( permalink )
Sam
I'd like to humbly submit my own contribution to template tag simplification. It's called fancy_tag. It takes Django's simple_tag and adds "as " support, keyword argument support, and *args/kwargs support.
You can get it here: http://github.com/trapeze/fancy_tag/
March 1, 2010 at 1:08 p.m. ( permalink )