« Large Problems in Django, Mostly Solved: Search | Making Template Tag Parsing Easier »
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.
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.
Comments have been close for this post.
Posted at 6:32 p.m. on November 3, 2009
Comments: 8
Tags: classbased , django , idea , postaday09 , templates , templatetags
Django Inspect: A generic introspection API for Django models
4 weeks Ago (Comments: 4)
The role of designers in the Django community
1 month Ago (Comments: 7)
Large Problems in Django, Mostly Solved: Documentation
1 month, 1 week Ago (Comments: 5)
2 months Ago (Comments: 0)
Correct way to handle default model fields.
3 months, 3 weeks Ago (Comments: 8)
I may not have gone where I intended to go, but I think I have ended up where I intended to be.
- Douglas Adams


Comments
1 Alex Gaynor says...
This cuts down some of the repition in writing template tags, but in my experience the real pain is in writing the same parsing code, over and over again. Consider something like {% get_vote for obj by user as vote %}. This has 3 parts, some of which are optional, some are static text, and others are variables. A way to formalize the arguments a template tag takes would go a long way to reducing the pain in writing template tags.
Posted at 1:16 a.m. on November 4, 2009
2 Eric Holscher says...
@Alex: You sir, may have predicted the future :) Stay tuned.
Posted at 3:37 a.m. on November 4, 2009
3 Chris Dickinson says...
I actually have been working along the same lines since we talked about this a while back -- I've wrapped it into something I'm calling "tag_utils." It provides simple helper functions to link a tag function to a surlex string, plus it actually can coerce the arguments to the correct types (and keywords) based on the surlex macros you use.
http://github.com/chrisdickinson/tag_utils
Posted at 7:36 a.m. on November 4, 2009
4 brisban says...
Yes, I just hope that there will be an easier way to parse all the attributes..
Posted at 1:11 p.m. on November 4, 2009
5 Honza says...
Wouldn't this approach make you unable to raise TemplateSyntaxError in parse time as opposed to render time? Also if you would cache the parsed template you could get into quite some trouble since the instance attribues would be cached as well. Not to mention the need to parse thetag every time you render the template (as opposed to parse).
Btw. for a similar approach, have a look at django.contrib.comments :)
Posted at 3:12 p.m. on November 4, 2009
6 ejucovy says...
@alex,
I wrote a simple base class that I now use for all of my template tags which result in a new context variable. It lets me formalize those static-text prepositions without much repetition, and with nice error reporting. A subclass basically looks like (cutting out a bit of the noisier stuff for clarity):
Usage then looks like
You can see the base class code in
http://github.com/ccnmtl/djangohelpers/blob/master/django... and examples of its use in http://github.com/ccnmtl/djangohelpers/blob/master/django... -- it might be of interest.
For even more convenience, I generate skeletons for all my new templatetags using a simple console script -- since the only thing that varies in the subclass is the choice of prepositions and variables, and the actual query to execute.
http://github.com/ccnmtl/djangohelpers/blob/master/django...
Posted at 3:26 p.m. on November 4, 2009
7 David Krauth says...
About a year ago I undertook a similar approach to simplifying this process, though as a less general approach. I found that I was writing numerous "getter" tags that match a common pattern:
where the using clause was an optional means of passing parameters:
See http://www.dakrauth.com/blog/entry/simplifying-django-template-extensions/
I like the simplicity of your approach here, thanks for sharing.
Posted at 3:35 p.m. on November 4, 2009
8 Marcin Nowak says...
Well, some time ago I wrote my solution similar to django-widgets. Few days ago I created fork and merged my sources. Currently I'm using widgets instead of writing complex template tags.
Look at sources and docs on github: <a href="http://github.com/marcinn/django-widgets">http://github.com/marcinn/django-widgets</a>
Any comments are welcome.
Posted at 7 p.m. on November 5, 2009