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.
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>
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With