L

Template Tag Advice

July 22nd 2008 01:41:14

You might have noticed that the site has seen a few minor changes. This reflects a large-scale refactoring of the entire blog application (in order to fix a queryset caching issue), and the simplification and consolidation of a lot of crufty templates. You can see my progress via the hg repo.

The Django Template language is not my favorite; there are other faster, more expressive templating languages out there available for python. That being said, the DTL was the first to get me to see the folly of including source full force in the template, it's what I'm stuck with at work, and what I'm resigning myself with for this site.

Compared with Mako, Django Templates enforce simplicity and more clean abstraction. Still, there are a few things that are "missing" out of the box that do not necessarily harm the purity of the philosophy of the DTL that I feel are necessary for my sanity. Although there are already macro tags available, I wanted simply to be able to "set" names to be equal to strings or variables, similar to how you use a preprocessor. One of the things I really wanted to do replace long winded textual representations of something visually small like:

    <img src="/media/icons/arrow_up.png" alt="up" />    

with something like this:

    {{arrow_up}}

Django's templating system doesn't really allow for messing with the context on the fly, and a lot of macro systems would ruin the spirit of the separation of logic and presentation. My solution was a set tag that takes two simple arguments: a name, and an expression. This is similar to what lots of macro languages will allow; the interesting part comes when you get to the expression. What I came up with is roughly outlined below:

    from django import template

    # usage {% set [name] [value] %}
    @register.tag(name="set")
    def set_tag(parser, token):
        try:
            tag_name, name, value = token.split_contents()
        except ValueError:
            raise template.TemplateSyntaxError, "%r tag requires two arguments." % (token.contents[0])
        return SetTagNode(name, value)

    class SetTagNode(template.Node):
        def __init__(self, name, value):
            self.name = name
            self.value = value

        def render(self, context):
            expr = template.FilterExpression(self.value, template.Parser(''))
            value = expr.resolve(context)
            if getattr(settings, 'DEBUG_SET_TAG_INFO', False):
                print "setting %s to be '%s'" % (self.name, value)
            context[self.name] = value
            return ''

The main juice of this is the template.FilterExpression part, which creates an expression that I then resolve within the current context. This allows me to make shortcuts to complex filter applications like date, so I can build up a small set of different constituent pieces of the date and use them consistently throughout the template without mucking about what is essentially display-oriented logic in the view; ie, providing "date", "month", "day_ord", etc.

What it does not allow is for the embedding of other template variables within a string; for instance, you would not be able to do:

    {% set urlfoo "http://urlfoo.com/{{page}}" %}

If you wanted to take this further, you would have to tread lightly. In order to handle the example above, You'd have to treat the argument as a small template each time and resolve it within the current context. If you really want something like this, a macro block tag the likes of which are already available is probably best; eat a block of text and render it in the current context. The simplified set tag keeps me honest while allowing me to keep my templates looking clean.

comments

+ leave a comment on "Template Tag Advice"