Class Based Template Tags

The problem

In Django, template tags currently are separated between a Node class and a “parsing function”. The parsing function takes the tag, represented as a string, parses the input, and passes the correct arguments to a Node class. The Node class then does whatever rendering it does, or updating of the context, and then renders itself in a form suitable for the template.

This is mainly by convention that there is a separation here between the parsing and the Node. As I see it, there is no particular reason that the Tag can’t be responsible for the parsing and rendering itself. A lot of the time I find the parsing function and the Node separated by hundreds of lines in a file, making it hard to understand.

The proposed solution

We can combine the parsing and rendering of a node in a similar way in something I call Class Based Template Tags. This allows the template tag to be able to parse and render itself.

I have an example in my playground over at github. They are based around a lot of the ideas in django-template-utils. Specifically, this example will be recreating the get_latest_objects tag from that package.

class ClassBasedTag(template.Node):
    Tag that combined parsing and rendering

    Subclasses should define ``render_content()`` and ``parse_content()``.

    def __call__(self, parser, token):
        self.token = token
        self.parser = parser
        return self

    def render(self, context):
        self.context = context
        self.parsed = self.parse_content(self.parser, self.token)
        return self.render_content(context)

    def parse_content(self, parser, token):
        This is called to parse the incoming context.

        It's return value will be set to self.parsed
        raise NotImplementedError

    def render_content(self, context):
        This is called to return a node to the template.

        It should return set things in the context or return
        whatever representation is appropriate for the template.
        raise NotImplementedError

As you can see, this tag combined the concepts of Parsing and Rendering a tag into the same place. The parse_content and render_content are equivalent to the current Django way of doing a parsing function, and Node class render function. Currently the render function depends on self.parsed being there, and not being passed in, this is to keep the function arguments the same as previous render functions. The code isn’t meant to be production quality, more of a proof of concept.

A couple of gains are made from combining things together. First of all is the fact that the code is right next to each other, as mentioned earlier. However, it also allows you to subclass these classes, and provide functionality that makes people’s lives easier. Having the rendering and parsing in the same class also allows for some trickery with passing around data, like mentioned, which may be a good or a bad thing.

Let’s go ahead and show an example of an implementation of this type of tag.

class GetContentTag(ClassBasedTag):

    def parse_content(self, parser, token):
        bits = token.contents.split()
        return (bits[1], 1, bits[3])

    def render_content(self, context):
        model, pk, varname = self.parsed = template.Variable(pk)
        self.varname = varname
        self.model = get_model(*model.split('.'))
        context[self.varname] = self.model._default_manager.get(

register.tag('get_latest_content', GetContentTag())

This tag is used in the following manner:

{% get_latest_content news.story as latest_story %}

As you can see, I think it makes it nice and concise to be able to have the parsing and the rendering of a tag right there in the same place.

This code is a very simplified use case for the idea. It is basically the simplest possible thing that could work. I will expand on the ways that this idea gives us a lot of power and flexibility over our Template Tags in the future, but I think this idea stands well on it’s own.

Hey there! I'm Eric and I work on communities in the world of software documentation. Feel free to email me if you have comments on this post!