{% 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 %}



coded by nessus