Login

Recurse template tag for Django

Author:
Zarin
Posted:
February 9, 2008
Language:
Python
Version:
.96
Score:
4 (after 4 ratings)

Original and further information available from here.

v1.1 Update (20/04/08): Added the ability to recurse single elements as well as automatic skipping of empty elements.

Most of the tags are self explanatory, the only one that may cause confusion is the main {% recurse %} one. The format for this tag is {% recurse [children] with [parent] as [child] %} where “[children]” is the property that contains the children of the current element, “[parent]” is your starting element and “[child]” is the variable named used in the loop.

Example usage:

{% load recurse %}

... Headers and stuff ...

{% recurse category.category_set.all with categories as category %}
<ul>
    {% loop %}
    <li>
        <h{{ level }}>{{ category.title }}</h{{ level }}>
        {% child %}
    </li>
    {% endloop %}
</ul>
{% endrecurse %}

... The rest of the page ...
 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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
###############################################################################
# Recurse template tag for Django v1.1
# Copyright (C) 2008 Lucas Murray
# http://www.undefinedfire.com
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Lesser General Public License as published
# by the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Lesser General Public License for more details.
###############################################################################

from django import template

register = template.Library()

class RecurseNode(template.Node):
    def __init__(self, var, name, child, nodeList):
        self.var = var
        self.name = name
        self.child = child
        self.nodeList = nodeList

    def __repr__(self):
        return '<RecurseNode>'

    def renderCallback(self, context, vals, level):
        output = []
        try:
            if len(vals):
                pass
        except:
            vals = [vals]
        if len(vals):
            if 'loop' in self.nodeList:
                output.append(self.nodeList['loop'].render(context))
            for val in vals:
                context.push()
                context['level'] = level
                context[self.name] = val
                if 'child' in self.nodeList:
                    output.append(self.nodeList['child'].render(context))
                    child = self.child.resolve(context)
                    if child:
                        output.append(self.renderCallback(context, child, level + 1))
                if 'endloop' in self.nodeList:
                    output.append(self.nodeList['endloop'].render(context))
                else:
                    output.append(self.nodeList['endrecurse'].render(context))
                context.pop()
            if 'endloop' in self.nodeList:
                output.append(self.nodeList['endrecurse'].render(context))
        return ''.join(output)

    def render(self, context):
        vals = self.var.resolve(context)
        output = self.renderCallback(context, vals, 1)
        return output

def do_recurse(parser, token):
    bits = list(token.split_contents())
    if len(bits) != 6 and bits[2] != 'with' and bits[4] != 'as':
        raise template.TemplateSyntaxError, "Invalid tag syxtax expected '{% recurse [childVar] with [parents] as [parent] %}'"
    child = parser.compile_filter(bits[1])
    var = parser.compile_filter(bits[3])
    name = bits[5]

    nodeList = {}
    while len(nodeList) < 4:
        temp = parser.parse(('child','loop','endloop','endrecurse'))
        tag = parser.tokens[0].contents
        nodeList[tag] = temp
        parser.delete_first_token()
        if tag == 'endrecurse':
            break

    return RecurseNode(var, name, child, nodeList)
do_recurse = register.tag('recurse', do_recurse)

More like this

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 2 weeks ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 3 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 7 months ago

Comments

eabinga (on March 28, 2008):

I changed line 33 to read as:

    try: 
        if len(vals): 
            pass 
    except: 
        vals = [vals]

this allows as root list or a root object

#

eabinga (on March 28, 2008):

actually added it above line 33

#

Zarin (on April 20, 2008):

Thanks for that modification, I'll add it to the code.

Just came across an error caused by that bug. =)

#

aluuu (on August 3, 2009):

thanx! i'll give you thousands of internets for this!

#

grahamu (on November 15, 2009):

I believe the conditional on line 71 uses incorrect boolean operators. 'Or' should be used instead of 'and'. The code should raise a template syntax error if any of the conditions is True, not only when all the conditions are True:

if len(bits) != 6 or bits[2] != 'with' or bits[4] != 'as':
    raise ...

#

robbles (on March 7, 2010):

Just got this working, and it's pretty fantastic...

One question though - does the {% child %} tag have to be a direct child of the {% loop %} tag?

When I tried surrounding it with a simple {% if X %} ... {% endif %} I got this template error:

"Invalid block tag: 'child'"

#

geeshock (on December 15, 2010):

awesome

#

Please login first before commenting.