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.
That was inspiring,
This is a great way to repeat a block in djdango...
Keep up the good work...
Thanks for bringing this up
Thanks a lot!
I have many surprises when try django templates after XSLT, but your code works as i want!