Extending the Template System

Now that you understand a bit more about the internals of the template system, let’s look at how to extend the system with custom code. Most template customization comes in the form of custom template tags and/or filters. Although the Django template language comes with many built-in tags and filters, you’ll probably assemble your own libraries of tags and filters that fit your own needs. Fortunately, it’s quite easy to define your own functionality.

Creating a Template Library – Whether you’re writing custom tags or filters, the first thing to do is to create a template library — a small bit of infrastructure Django can hook into. Creating a template library is a two-step process:

  • First, decide which Django application should house the template library. If you’ve created an app via py startapp, you can put it in there, or you can create another app solely for the template library. Whichever route you take, make sure to add the app to your INSTALLED_APPS setting. We’ll explain this shortly.
  • Second, create a templatetags directory in the appropriate Django application’s package. It should be on the same level as py, views.py, and so forth. For example:

books/   __init__.py   models.py   templatetags/   views.py

Create two empty files in the templatetags directory: an __init__.py file (to indicate to Python that this is a package containing Python code) and a file that will contain your custom tag/filter definitions. The name of the latter file is what you’ll use to load the tags later. For example, if your custom tags/filters are in a file called poll_extras.py, you’d write the following in a template:

{% load poll_extras %}

The {% load %} tag looks at your INSTALLED_APPS setting and only allows the loading of template libraries within installed Django applications. This is a security feature; it allows you to host Python code for many template libraries on a single computer without enabling access to all of them for every Django installation.

If you write a template library that isn’t tied to any particular models/views, it’s valid and quite normal to have a Django application package that contains only a templatetags package. There’s no limit on how many modules you put in the templatetags package. Just keep in mind that a {% load %} statement will load tags/filters for the given Python module name, not the name of the application. Once you’ve created that Python module, you’ll just have to write a bit of Python code, depending on whether you’re writing filters or tags.

To be a valid tag library, the module must contain a module-level variable named register that is a template.Library instance. This template.Library instance is the data structure in which all the tags and filters are registered. So, near the top of your module, insert the following:

from django import template

register = template.Library()

Note – For a good number of examples, read the source code for Django’s default filters and tags. They’re in django/template/defaultfilters.py and django/template/defaulttags.py, respectively. Some applications in django.contrib also contain template libraries. Once you’ve created this register variable, you’ll use it to create template filters and tags.

Writing Custom Template Filters – Custom filters are just Python functions that take one or two arguments:

  • The value of the variable (input)
  • The value of the argument, which can have a default value or be left out altogether

For example, in the filter {{ var|foo:”bar” }}, the filter foo would be passed the contents of the variable var and the argument “bar”. Filter functions should always return something. They shouldn’t raise exceptions, and they should fail silently. If there’s an error, they should return either the original input or an empty string, whichever makes more sense. Here’s an example filter definition:

def cut(value, arg):   “Removes all values of arg from the given string”   return value.replace(arg, ”)

And here’s an example of how that filter would be used:

{{ somevariable|cut:”0″ }}

Most filters don’t take arguments. In this case, just leave the argument out of your function:

def lower(value): # Only one argument.   “Converts a string into all lowercase”   return value.lower()

When you’ve written your filter definition, you need to register it with your Library instance, to make it available to Django’s template language:

register.filter(‘cut’, cut)register.filter(‘lower’, lower)

The Library.filter() method takes two arguments:

  • The name of the filter (a string)
  • The filter function itself

If you’re using Python 2.4 or above, you can use register.filter() as a decorator instead:

@register.filter(name=’cut’)def cut(value, arg):   return value.replace(arg, ”) @register.filterdef lower(value):   return value.lower()

If you leave off the name argument, as in the second example, Django will use the function’s name as the filter name. Here, then, is a complete template library example, supplying the cut filter:

from django import template register = template.Library()@register.filter(name=’cut’)def cut(value, arg):   return value.replace(arg, ”)

Writing Custom Template Tags – Tags are more complex than filters, because tags can do nearly

anything.Chapter 4 describes how the template system works in a two-step process: compiling and rendering. To define a custom template tag, you need to tell Django how to manage both steps when it gets to your tag. When Django compiles a template, it splits the raw template text into nodes. Each node is an instance of django.template.Node and has a render() method. Thus, a compiled template is simply a list of Node objects.

When you call render() on a compiled template, the template calls render() on each Node in its node list, with the given context. The results are all concatenated together to form the output of the template. Thus, to define a custom template tag, you specify how the raw template tag is converted into a Node (the compilation function) and what the node’s render() method does.

Writing the Compilation Function – For each template tag it encounters, the template parser calls a Python function with the tag contents and the parser object itself. This function is responsible for returning a Node instance based on the contents of the tag.

For example, let’s write a template tag, {% current_time %}, that displays the current date/time, formatted according to a parameter given in the tag, in strftime syntax (see http://www.djangoproject.com/r/python/strftime/). It’s a good idea to decide the tag syntax before anything else. In our case, let’s say the tag should be used like this:

<p>The time is {% current_time “%Y-%m-%d %I:%M %p” %}.</p>

Note – Yes, this template tag is redundant—Django’s default {% now %} tag does the same task with simpler syntax. This template tag is presented here just for example purposes. The parser for this function should grab the parameter and create a Node object:

from django import template def do_current_time(parser, token):   try:       # split_contents() knows not to split quoted strings.       tag_name, format_string = token.split_contents()   except ValueError:       msg = ‘%r tag requires a single argument’ % token.contents[0]       raise template.TemplateSyntaxError(msg)   return CurrentTimeNode(format_string[1:-1])

There’s actually a lot going here:

  • parser is the template parser object. We don’t need it in this example.
  • contents is a string of the raw contents of the tag. In our example, it’s ‘current_time”%Y-%m-%d%I:%M%p”‘.
  • The split_contents() method separates the arguments on spaces while keeping quoted strings together. Avoid using token.contents.split() (which just uses Python’s standard string-splitting semantics). It’s not as robust, as it naively splits on all spaces, including those within quoted strings.
  • This function is responsible for raising template.TemplateSyntaxError, with helpful messages, for any syntax error.
  • Don’t hard-code the tag’s name in your error messages, because that couples the tag’s name to your function. split_contents()[0] will always be the name of your tag—even when the tag has no arguments.
  • The function returns a CurrentTimeNode (which we’ll create shortly) containing everything the node needs to know about this tag. In this case, it just passes the argument “%Y-%m-%d %I:%M %p”. The leading and trailing quotes from the template tag are removed with format_string[1:-1].
  • Template tag compilation functions must return a Node subclass; any other return value is an error.

Writing the Template Node – The second step in writing custom tags is to define a Node subclass that has a render() method. Continuing the preceding example, we need to define CurrentTimeNode:

import datetime class CurrentTimeNode(template.Node):    def __init__(self, format_string):       self.format_string = format_string    def render(self, context):       now = datetime.datetime.now()       return now.strftime(self.format_string)

These two functions (__init__ and render) map directly to the two steps in template processing (compilation and rendering). Thus, the initialization function only needs to store the format string for later use, and the render() function does the real work.Like template filters, these rendering functions should fail silently instead of raising errors. The only time that template tags are allowed to raise errors is at compilation time.

Registering the Tag – Finally, you need to register the tag with your module’s Library instance. Registering custom tags is very similar to registering custom filters (as explained above). Just instantiate a template.Library instance and call its tag() method. For example:

register.tag(‘current_time’, do_current_time)

The tag() method takes two arguments:

  • The name of the template tag (string). If this is left out, the name of the compilation function will be used.
  • The compilation function.

As with filter registration, it is also possible to use register.tag as a decorator in Python 2.4 and above:

@register.tag(name=”current_time”)def do_current_time(parser, token):   # … @register.tagdef shout(parser, token):   # …

If you leave off the name argument, as in the second example, Django will use the function’s name as the tag name.

Setting a Variable in the Context – The previous section’s example simply returned a value. Often it’s useful to set template variables instead of returning values. That way, template authors can just use the variables that your template tags set. To set a variable in the context, use dictionary assignment on the context object in the render() method. Here’s an updated version of CurrentTimeNode that sets a template variable, current_time, instead of returning it:

class CurrentTimeNode2(template.Node):   def __init__(self, format_string):       self.format_string = format_string    def render(self, context):       now = datetime.datetime.now()       context[‘current_time’] = now.strftime(self.format_string)       return ”

Note that render() returns an empty string. render() should always return a string, so if all the template tag does is set a variable, render() should return an empty string. Here’s how you’d use this new version of the tag:

{% current_time2 “%Y-%M-%d %I:%M %p” %}<p>The time is {{ current_time }}.</p>

But there’s a problem with CurrentTimeNode2: the variable name current_time is hard-coded. This means you’ll need to make sure your template doesn’t use {{ current_time }} anywhere else, because {% current_time2 %} will blindly overwrite that variable’s value. A cleaner solution is to make the template tag specify the name of the variable to be set, like so:

{% get_current_time “%Y-%M-%d %I:%M %p” as my_current_time %}<p>The current time is {{ my_current_time }}.</p>

To do so, you’ll need to refactor both the compilation function and the Node class, as follows:

import re class CurrentTimeNode3(template.Node):    def __init__(self, format_string, var_name):       self.format_string = format_string       self.var_name = var_name    def render(self, context):       now = datetime.datetime.now()       context[self.var_name] = now.strftime(self.format_string)       return ”

def do_current_time(parser, token):   # This version uses a regular expression to parse tag contents.   try:       # Splitting by None == splitting by spaces.       tag_name, arg = token.contents.split(None, 1)   except ValueError:       msg = ‘%r tag requires arguments’ % token.contents[0]       raise template.TemplateSyntaxError(msg)    m = re.search(r'(.*?) as (\w+)’, arg)   if m:       fmt, var_name = m.groups()   else:       msg = ‘%r tag had invalid arguments’ % tag_name       raise template.TemplateSyntaxError(msg)    if not (fmt[0] == fmt[-1] and fmt[0] in (‘”‘, “‘”)):       msg = “%r tag’s argument should be in quotes” % tag_name       raise template.TemplateSyntaxError(msg)    return CurrentTimeNode3(fmt[1:-1], var_name)

Now do_current_time() passes the format string and the variable name to CurrentTimeNode3.

Parsing Until Another Block Tag – Template tags can work as blocks containing other tags (think {% if %}, {% for %}, etc.). To create a template tag like this, use parser.parse() in your compilation function. Here’s how the standard {% comment %} tag is implemented:

def do_comment(parser, token):   nodelist = parser.parse((‘endcomment’,))   parser.delete_first_token()   return CommentNode() class CommentNode(template.Node):   def render(self, context):       return ”

parser.parse() takes a tuple of names of block tags to parse until. It returns an instance of django.template.NodeList, which is a list of all Node objects that the parser encountered before it encountered any of the tags named in the tuple. So in the preceding example, nodelist is a list of all nodes between {% comment %} and {% endcomment %}, not counting {% comment %} and {% endcomment %} themselves.

After parser.parse() is called, the parser hasn’t yet “consumed” the {% endcomment %} tag, so the code needs to explicitly call parser.delete_first_token() to prevent that tag from being processed twice. Then CommentNode.render() simply returns an empty string. Anything between {% comment %} and {% endcomment %} is ignored.

Parsing Until Another Block Tag and Saving Contents – In the previous example, do_comment() discarded everything between {% comment %} and {% endcomment %}. It’s also possible to do something with the code between block tags instead. For example, here’s a custom template tag, {% upper %}, that capitalizes everything between itself and {% endupper %}:

{% upper %}

This will appear in uppercase, {{ your_name }}.

{% endupper %}

As in the previous example, we’ll use parser.parse(). This time, we pass the resulting nodelist to Node:

@register.tag

def do_upper(parser, token):

nodelist = parser.parse((‘endupper’,))

parser.delete_first_token()

return UpperNode(nodelist)

class UpperNode(template.Node):

def __init__(self, nodelist):

self.nodelist = nodelist

def render(self, context):

output = self.nodelist.render(context)

return output.upper()

The only new concept here is self.nodelist.render(context) in UpperNode.render(). This simply calls render() on each Node in the node list. For more examples of complex rendering, see the source code for {% if %}, {% for %}, {% ifequal %}, and {% ifchanged %}. They live in django/template/defaulttags.py.

Shortcut for Simple Tags – Many template tags take a single argument—a string or a template variable reference—and return a string after doing some processing based solely on the input argument and some external information. For example, the current_time tag we wrote earlier is of this variety. We give it a format string, and it returns the time as a string. To ease the creation of these types of tags, Django provides a helper function, simple_tag. This function, which is a method of django.template.Library, takes a function that accepts one argument, wraps it in a render function and the other necessary bits mentioned previously, and registers it with the template system. Our earlier current_time function could thus be written like this:

def current_time(format_string):

return datetime.datetime.now().strftime(format_string)

register.simple_tag(current_time)

In Python 2.4, the decorator syntax also works:

@register.simple_tag

def current_time(token):

A couple of things to notice about the simple_tag helper function are as follows:

  • Only the (single) argument is passed into our function.
  • Checking for the required number of arguments has already been done by the time our function is called, so we don’t need to do that.
  • The quotes around the argument (if any) have already been stripped away, so we receive a plain string.

 Inclusion Tags – Another common template tag is the type that displays some data by rendering another template. For example, Django’s admin interface uses custom template tags to display the buttons along the bottom of the “add/change” form pages. Those buttons always look the same, but the link targets change depending on the object being edited. They’re a perfect case for using a small template that is filled with details from the current object. These sorts of tags are called inclusion tags. Writing inclusion tags is probably best demonstrated by example. Let’s write a tag that produces a list of choices for a simple multiple-choice Poll object. We’ll use the tag like this:

{% show_results poll %}

The result will be something like this:

<ul>

<li>First choice</li>

<li>Second choice</li>

<li>Third choice</li>

</ul>

First, we define the function that takes the argument and produces a dictionary of data for the result. Notice that we need to return only a dictionary, not anything more complex. This will be used as the context for the template fragment:

def show_books_for_author(author):

books = author.book_set.all()

return {‘books’: books}

Next, we create the template used to render the tag’s output. Following our example, the template is very simple:

<ul>

{% for book in books %}

<li> {{ book }} </li>

{% endfor %}

</ul>

Finally, we create and register the inclusion tag by calling the inclusion_tag() method on a Library object. Following our example, if the preceding template is in a file called polls/result_snippet.html, we register the tag like this:

register.inclusion_tag(‘books/books_for_author.html’)(show_books_for_author)

As always, Python 2.4 decorator syntax works as well, so we could have instead written this:

@register.inclusion_tag(‘books/books_for_author.html’)

def show_books_for_author(show_books_for_author):

Sometimes, your inclusion tags need access to values from the parent template’s context. To solve this, Django provides a takes_context option for inclusion tags. If you specify takes_context in creating a template tag, the tag will have no required arguments, and the underlying Python function will have one argument: the template context as of when the tag was called.

For example, say you’re writing an inclusion tag that will always be used in a context that contains home_link and home_title variables that point back to the main page. Here’s what the Python function would look like:

@register.inclusion_tag(‘link.html’, takes_context=True)

def jump_link(context):

return {

‘link’: context[‘home_link’],

‘title’: context[‘home_title’],

}

Note – The first parameter to the function must be called context.

The template link.html might contain the following:

Jump directly to <a href=”{{ link }}”>{{ title }}</a>.

Then, anytime you want to use that custom tag, load its library and call it without any arguments, like so:

{% jump_link %}

Back to Tutorial

Share this post
[social_warfare]
Inside Template Loading
Writing Custom Template Loaders

Get industry recognized certification – Contact us

keyboard_arrow_up