views:

209

answers:

1

I'm trying to write a very simple, tree-walking template in jinja2, using some custom objects with overloaded special methods (getattr, getitem, etc) It seems straightforward, and the equivalent python walk of the tree works fine, but there's something about the way that Jinja's recursion works that I don't understand. The code is shown below:

from jinja2 import Template

class Category(object):

 def __init__(self, name):
  self.name = name
  self.items = {}
  self.children = True

 def __iter__(self):
  return iter(self.items)

 def add(self, key, item):
  self.items[key] = item
  return item

 def __getitem__(self, item):
  return self.items[item]

 def __getattr__(self, attr):
  try:
   return self.items[attr]
  except KeyError:
   raise AttributeError(attr)

 def __str__(self):
  return "<Category '%s'>" % self.name

template = '''
<saved_data>
{% for key in category recursive %}
 {% set item = category[key] %}
 {% if item.children %}
  <category name="{{key}}">
   {{ loop(item) }}
  </category>
 {% else %}
  <item name="{{ key }}" value="{{ item }}" />
 {% endif %}
{% endfor %}
</saved_data>
'''

b = Category('root')
c = b.add("numbers", Category('numbers'))
c.add("one", 1)
c.add("two", 2)
c.add("three", 3)
d = b.add("letters", Category('letters'))
d.add('ay','a')
d.add('bee','b')
d.add('cee','c')
e = d.add("bools", Category('bools'))
e.add('tru', True)
e.add('fals', False)

def walk(c, depth=0):
 for key in c:
  item = c[key]
  print (' '*depth) + str(item)
  if hasattr(item, 'children'):
   walk(item, depth+3)
print "Python walking the tree:"
walk(b)

print ""
print "Jinja2 Walking the tree:"
t = Template(template)
print t.render(category = b)

The template is raising an exception as if the recursion didn't actually take place. The inner call is made, but somehow the reference to 'category' still refers to the parent. What gives here? There must be something very fundamental I'm missing about how these recursive templates are supposed to work. (Or something very fundamentally silly that I'm doing that I just can't see.

+2  A: 

As I see from your code you understand recursive correctly, except one thing: it does replace iterable in the for statement, but doesn't update variable (category in your code) originally used in it. Thus, you nested loop iterates through children, but set tag lookups in original category, not one passed to the loop().

I suggest changing __iter__() method to return self.items.iteritems() and template to:

<saved_data>
{% for key, item in category recursive %}
        {% if item.children %}
                <category name="{{key}}">
                        {{ loop(item) }}
                </category>
        {% else %}
                <item name="{{ key }}" value="{{ item }}" />
        {% endif %}
{% endfor %}
</saved_data>
Denis Otkidach