One thing that really bugs me about the django template system is the inability to repeat a block more than once. This has been discussed and marked as "wontfix", which I find silly.

On this very page, I use the same navigation bar at the top of the story and at the bottom of the story. Different pages need to use different navigation bars. Some are date-based. Some are page-based. Some are entry-based. If I can't declare a block once and repeat it later, then every time I change the navigation bar somewhere I have to change it twice. This is stupid and definitely not DRY.

This is one of the reasons that I preferred the Jinja template engine. It gives you this power trivially. You just say {{ self.block_name }} at any place to repeat that block.

However, Jinja gives you enough power to shoot yourself in the foot pretty hard. For a large project I'd use Jinja, but for a small project like this site I would prefer that the templates be kept clean. Also, using the django template language with django makes life a lot easier.

So what I did to get around this issue was create a few template tags that allow me to declare a block once and then repeat it later. I toyed with the idea of hacking the template language to allow me to repeat a block more than once, but that would probably have gotten pretty messy. So instead, I just banged out some template tags really quickly. Here's the code for anyone interested:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
from django import template

class RecordNode(template.Node):
    def __init__(self, name, nodelist):
        self.name, self.nodelist = name, nodelist

    def render(self, context):
        content = self.nodelist.render(context)
        context.dicts[-1].setdefault('recordings', {})[self.name] = content
        return ''


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

    def render(self, context):
        return context.get('recordings', {}).get(self.name, '')


def do_record(parser, token):
    try:
        tag, name = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError('%r tag should have only one argument' % token.contents.split()[0])
    nodelist = parser.parse(('endrecord',))
    parser.delete_first_token()
    return RecordNode(name, nodelist)


def do_recall(parser, token):
    try:
        tag, name = token.split_contents()
    except ValueError:
        raise template.TemplateSyntaxError('%r tag should have only one argument' % token.contents.split()[0])
    return RecallNode(name)

You record a section like this:

1
2
3
4
{% record navigation %}
<!-- my nav bar -->
...
{% endrecord %}

And then, as many times as you want, you can recall it like this:

1
2
3
4
5
6
7
<div id="top-navigation">
{% recall navigation %}
</div>
...
<div id="bottom-navigation">
{% recall navigation %}
</div>

record parses the text and saves it for later. recall just outputs the saved text. As an added bonus, it saves the text in the context in a "recordings" dictionary, so you can actually get to it through "recordings.recording_name" if you really want to. This sort of abuses the context by throwing the recording in the highest level of the context stack, which is probably a very bad idea, but it works for my intents and purposes.

I'd still prefer something like Jinja's {{ self.block_name }}. It would make repeating parts of your page (such as navigation bars) trivial. Maybe I'll head over to the dev discussion and see if I can convince them to reverse their decision.

Published on May 04, 2009. Last updated on May 17, 2009.
2 comments so far. Add your own
Software companies UK
November 11, 2009

That was inspiring,

This is a great way to repeat a block in djdango...

Keep up the good work...

Thanks for bringing this up

Sergey
August 16, 2010

Thanks a lot!

I have many surprises when try django templates after XSLT, but your code works as i want!