.. post:: 2009-11-03 15:32:07
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
self.pk = template.Variable(pk)
self.varname = varname
self.model = get_model(*model.split('.'))
context[self.varname] = self.model._default_manager.get(pk=self.pk.resolve(context))
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.