{% TREE %}: Дерева в django-шаблонах
ТЕГ {% TREE %}
Насправді, в Джанго вже є вбудований засіб для виведення деревовидних структур в список: фільтр “unordered_list”. Але він не підійшов, тому що вміє виводити тільки рядки, а хотілось щоб в шаблоні можна було якось впливати на те, що лежить в окремих <li>: посилання зробити, дописати щось..:
{% tree some_list %}
<a href="{{ item.get_absolute_url }}">{{ item }}</a>
{% endtree %}
Змінна “some_list” – структуроване дерево в тому ж форматі, що приймає і “unordererd_list” (кому ліньки було ходити по посиланню, це ось таке: [ ‘item 1’, [ ‘subitem 1’, ‘subitem 2’], ‘item 2 ‘,’ item 3]).
Тим що відкриває і закриває тегами знаходиться мікрошаблон, який використовується для одиночного елементу списку. У нього передаються дві змінні:
- “item”: елемент списку
- “level”: рівень вкладеності (0, 1, 2 …)
class TreeNode(template.Node):
def __init__(self, tree, node_list):
self.tree = tree
self.node_list = node_list
def render(self, context):
tree = self.tree.resolve(context)
# ітератор з вхідного списку, який видає пари виду
# (елемент списку, його подспісок), причому
# одного з елементу пари може не бути
def pairs(items):
# внутрішній "брудний" генератор, який видає пари,
# де можуть бути непотрібні: з обома порожніми head і tail
def dirty(items):
items = iter(items)
head = None
try:
while True:
item = items.next()
if isinstance(item, (list, tuple)):
yield head, item
head = None
else:
yield head, None
head = item
except StopIteration:
yield head, None
# фільтр над брудним генератором, що видаляє
# непотрібні пари
return ((h, t) for h, t in dirty(items) if h or t)
# виводить елемент списку з підсписки
# для підсписки рекурсивно викликається render_items
def render_item(item, sub_items, level):
def render_item(item, sub_items, level):
return ''.join([
'<li>',
item and self.node_list.render(
template.Context({'item': item, 'level': level}))
or '',
sub_items and '<ul>%s</ul>' %
''.join(render_items(sub_items, level + 1)) or '',
'</li>'
])
# вивід списку елементів
def render_items(items, level):
return ''.join(render_item(
h, t, level) for h, t in pairs(items))
return render_items(tree, 0)
@register.tag
def tree(parser, token):
bits = token.split_contents()
if len(bits) != 2:
raise template.TemplateSyntaxError(
'"%s" takes one argument: tree-structured list' % bits[0])
node_list = parser.parse('end' + bits[0])
parser.delete_first_token()
return TreeNode(parser.compile_filter(bits[1]), node_list)
ФІЛЬТР ASTREE
Ще одна невелика проблема – це складання списку, який приймає (% tree%). Спочатку такого списку у мене немає, а є моделька, яка посилається на себе ж у полі “parent”, і таким чином визначає дерево. Відповідно, фільтр, який з queryset’а цієї моделі робить такий згрупований список:
@register.filter
def astree(items, attribute):
# переробка списку в dict: parent -> список дітей
parent_map = defaultdict(list)
for item in items:
parent_map[getattr(item, attribute)].append(item)
# рекурсивний вивід дітей одного parent'а
def tree_level(parent):
for item in parent_map[parent]:
yield item
sub_items = list(tree_level(item))
if sub_items:
yield sub_items
return list(tree_level(None))
Відповідно, в шаблон передається звичайний queryset, і все виглядає так:
{% tree object_list|astree:"parent" %}...{% endtree %}
Читайте також:
- Django yandex and google maps integration
- How to expire session on browser close in django
- Fix Django 1.4 admin_site ForeignKeyRawIdWidget issue
- Django admin filter by ranges of dates with calendar
- django custom admin
- Django defer, only
- django postgresql та SCHEMA для таблиць
- Проксі моделі Django
- Django mongoengine cache backend
- MemcachedKeyLengthError: Key length is > 250