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.




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):

class MyTemplateTag(TemplateTagNode):
  noun_for = {'by': 'user', 'in': 'object'}

  def execute_query(self, user, object):
    """ the return value of this will be stuffed into a new context var defined by the user """
    return whatever_i_want_to_do(user, object)
register.tag('my_template_tag', MyTemplateTag.process_tag)

Usage then looks like

{% my_template_tag by request.user in foo_object as new_var %}

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:

get_XYZ as XYZ [using ...]

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

Comments support markdown

Comments are closed.

Comments have been close for this post.

About this post

Posted at 6:32 p.m. on November 3, 2009

Comments: 8

Tags: , , , , ,

Search Blog


Recent Posts

A simple Perl IRCBot

2 months Ago (Comments: 0)

Correct way to handle default model fields.

3 months, 3 weeks Ago (Comments: 8)

More Posts...

Projects


Friends


Categories


Tag Cloud

abstract aggregator book classbased community conferences conventions core dash debugging deployment designers django djangocon doctest education eurodjangocon fixtures idea ideas iowa kong largeproblems lawrence mediaphormedia mentor middleware migrations music packaging parsing pdb philosophy politics pony post-a-day postaday09 practical pretty production project projects python ramblings reusable review school screencast setuptools software solutions south sphinx ssh students talk teaching template-tags templates templatetags testing testing-series testmaker tip tips tutorial umw unittest

Archive


I may not have gone where I intended to go, but I think I have ended up where I intended to be.

- Douglas Adams