Recurse template tag for Django

 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. ModelChoiceField with choice groups for recursive relationships by estecb 2 months, 4 weeks ago
  2. dict recurse template tag for django by stefanp 4 years ago
  3. Allow template tags in a Flatpage's content by kylefox 5 years, 9 months ago
  4. Radio widget with labels after inputs by avsd 1 year, 9 months ago
  5. Command to dump data as a python script by willhardy 5 years, 10 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

#

(Forgotten your password?)