Como a tag "recursiva" Jinja2 realmente funciona?
Pergunta
Estou tentando escrever um modelo muito simples de caminhada em árvore em Jinja2, usando alguns objetos personalizados com métodos especiais sobrecarregados (getattr, getItem, etc.), parece direto, e a caminhada equivalente a python da árvore funciona bem, mas há algo Sobre a maneira como a recursão de Jinja funciona que eu não entendo. O código é mostrado abaixo:
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)
O modelo está levantando uma exceção como se a recursão não fosse realmente. A chamada interna é feita, mas de alguma forma a referência à 'categoria' ainda se refere aos pais. O que dá aqui? Deve haver algo muito fundamental sobre como esses modelos recursivos devem funcionar. (Ou algo muito fundamentalmente bobo que estou fazendo que simplesmente não consigo ver.
Solução
Como vejo no seu código, você entende recursivo corretamente, exceto uma coisa: ele substitui o iterable na declaração for, mas não atualiza a variável (category
no seu código) originalmente usado nele. Assim, você aninhou loop itera através das crianças, mas set
Pesquisas de tags no original category
, nenhum passou para o loop()
.
Eu sugiro mudar __iter__()
método para retornar self.items.iteritems()
e modelo para:
<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>