feincms / django-tree-queries

Adjacency-list trees for Django using recursive common table expressions. Supports PostgreSQL, sqlite, MySQL and MariaDB.
https://django-tree-queries.readthedocs.io/
BSD 3-Clause "New" or "Revised" License
432 stars 27 forks source link

How to trasvase tree in django template? #29

Closed jet10000 closed 3 months ago

jet10000 commented 2 years ago

Is there an example ?

I wanto display tree in django template, How to do it?

matthiask commented 2 years ago

Hi @jet10000

Sorry, unfortunately there is no example for how to do this.

The tree_info filter of django-mptt should work almost unchanged. mptt uses ._mptt_meta and .level while django-tree-queries always uses tree_depth but apart from this minor change you should be good.

https://github.com/django-mptt/django-mptt/blob/7a6a54c6d2572a45ea63bd639c25507108fff3e6/mptt/templatetags/mptt_tags.py#L228

django-mptt also ships a recursetree tag but I always liked the tree_info filter better.

Or maybe you could build the (partial) tree in Python code and find other ways to display it in the template.

jet10000 commented 2 years ago

OMG!

jet10000 commented 2 years ago

I refer to this thread https://github.com/matthiask/django-tree-queries/issues/27

it looks worked

class HomeView(TemplateView):
    template_name = 'index.html'

    def get_context_data(self, **kwargs):
        context = super(HomeView, self).get_context_data(**kwargs)
        context['tree_obj'] = BzFolder.objects.with_tree_fields().annotate(Count("children"))
        children = defaultdict(list)
        for node in BzFolder.objects.with_tree_fields(False).select_related("parent"):
            children[node.parent].append(node)
        roots = [root for root in children[None]]
        print(children)
        print(roots)
        context['roots'] = roots
        return context
        <ul>
            {% for p in roots %}
                <li>{{ p.name }}
                    <ul>
                        {% for pp in p.children.all %}
                            <li>{{ pp.name }}
                                <ul>
                                    {% for ppp in pp.children.all %}
                                        <li>{{ ppp.name }}</li>
                                    {% endfor %}
                                </ul>
                            </li>
                        {% endfor %}
                    </ul>
                </li>
            {% endfor %}
        </ul>
jet10000 commented 2 years ago

Optimized!

views.py

class HomeView(TemplateView):
    template_name = 'index.html'

    def get_context_data(self, **kwargs):
        context = super(HomeView, self).get_context_data(**kwargs)

        children = defaultdict(list)
        for node in BzFolder.objects.with_tree_fields(False).select_related("parent"):
            children[node.parent].append(node)

        roots1 = [self.serialize(root, children) for root in children[None]]
        print(roots1)
        context['roots1'] = roots1
        return context

    def serialize(self, parent, children):
        return {"id": parent.id, "name": parent.name,
                "children": [self.serialize(child, children) for child in children[parent]]}

index.html

        <ul>
            {% for p in roots1 %}
                {% include "item.html" %}
            {% endfor %}
        </ul>

item.html

<li> {{p.name}}
    {%if p.children|length != 0 %}
        <ul>
         {%for pp in p.children %}
              {%with p=pp template_name="item.html" %}
                   {%include template_name%}
              {%endwith%}
         {%endfor%}
         </ul>
    {%endif%}
</li>

image image

matthiask commented 2 years ago

Nice, looks good!

LazerRaptor commented 1 year ago

The tree_info filter of django-mptt should work almost unchanged. mptt uses ._mptt_meta and .level while django-tree-queries always uses tree_depth but apart from this minor change you should be good.

Indeed it works nicely:

import copy
import itertools

from django import template

register = template.Library()

def previous_current_next(items):
    """
    From http://www.wordaligned.org/articles/zippy-triples-served-with-python
    Creates an iterator which returns (previous, current, next) triples,
    with ``None`` filling in when there is no previous or next
    available.
    """
    extend = itertools.chain([None], items, [None])
    prev, cur, nex = itertools.tee(extend, 3)
    # Advancing an iterator twice when we know there are two items (the
    # two Nones at the start and at the end) will never fail except if
    # `items` is some funny StopIteration-raising generator. There's no point
    # in swallowing this exception.
    next(cur)
    next(nex)
    next(nex)
    return zip(prev, cur, nex)

def tree_item_iterator(items, ancestors=False, callback=str):
    """
    Given a list of tree items, iterates over the list, generating
    two-tuples of the current tree item and a ``dict`` containing
    information about the tree structure around the item, with the
    following keys:
       ``'new_level'``
          ``True`` if the current item is the start of a new level in
          the tree, ``False`` otherwise.
       ``'closed_levels'``
          A list of levels which end after the current item. This will
          be an empty list if the next item is at the same level as the
          current item.
    If ``ancestors`` is ``True``, the following key will also be
    available:
       ``'ancestors'``
          A list of representations of the ancestors of the current
          node, in descending order (root node first, immediate parent
          last).
          For example: given the sample tree below, the contents of the
          list which would be available under the ``'ancestors'`` key
          are given on the right::
             Books                    ->  []
                Sci-fi                ->  ['Books']
                   Dystopian Futures  ->  ['Books', 'Sci-fi']
          You can overload the default representation by providing an
          optional ``callback`` function which takes a single argument
          and performs coersion as required.
    """
    structure = {}
    first_item_level = 0
    for previous, current, next_ in previous_current_next(items):
        current_level = current.tree_depth
        if previous:
            structure["new_level"] = previous.tree_depth < current_level
            if ancestors:
                # If the previous node was the end of any number of
                # levels, remove the appropriate number of ancestors
                # from the list.
                if structure["closed_levels"]:
                    structure["ancestors"] = structure["ancestors"][
                        : -len(structure["closed_levels"])
                    ]
                # If the current node is the start of a new level, add its
                # parent to the ancestors list.
                if structure["new_level"]:
                    structure["ancestors"].append(callback(previous))
        else:
            structure["new_level"] = True
            if ancestors:
                # Set up the ancestors list on the first item
                structure["ancestors"] = []

            first_item_level = current_level
        if next_:
            structure["closed_levels"] = list(
                range(current_level, next_.tree_depth, -1)
            )
        else:
            # All remaining levels need to be closed
            structure["closed_levels"] = list(
                range(current_level, first_item_level - 1, -1)
            )

        # Return a deep copy of the structure dict so this function can
        # be used in situations where the iterator is consumed
        # immediately.
        yield current, copy.deepcopy(structure)

@register.filter
def tree_info(items, features=None):
    """
    Given a list of tree items, produces doubles of a tree item and a
    ``dict`` containing information about the tree structure around the
    item, with the following contents:
       new_level
          ``True`` if the current item is the start of a new level in
          the tree, ``False`` otherwise.
       closed_levels
          A list of levels which end after the current item. This will
          be an empty list if the next item is at the same level as the
          current item.
    Using this filter with unpacking in a ``{% for %}`` tag, you should
    have enough information about the tree structure to create a
    hierarchical representation of the tree.
    Example::
       {% for genre,structure in genres|tree_info %}
       {% if structure.new_level %}<ul><li>{% else %}</li><li>{% endif %}
       {{ genre.name }}
       {% for level in structure.closed_levels %}</li></ul>{% endfor %}
       {% endfor %}
    """
    kwargs = {}
    if features:
        feature_names = features.split(",")
        if "ancestors" in feature_names:
            kwargs["ancestors"] = True
    return tree_item_iterator(items, **kwargs)
matthiask commented 1 year ago

Very nice! Good to know it works.

triopter commented 10 months ago

Would this be worth including in the library? Seems like an important utility.

matthiask commented 10 months ago

@triopter Short answer: I'm going to say maybe?

Longer version: A few years back I wrote a blogpost outlining the reasons for django-tree-queries' existence: https://406.ch/writing/django-tree-queries/ , and mentioned the advantages of small API surfaces: They keep maintenance low. I myself haven't used something like tree_info for a long time now. The preferred way to render navigation structure is something like this these days: https://github.com/feincms/feincms3-example/blob/main/app/templatetags/menus.py – because I mostly do not have a need for deep hierarchies. And even if I do, the levels of the tree mostly aren't the same, so just using tree_info (or recursetree) isn't all that helpful either.

That being said: If someone were to contribute code with tests and docs and if they also were willing to help out with maintaining this down the road I can definitely see me helping out as well. I know there are no guarantees anyone can give, but it's important to me that it's not just a big code dump which offloads the maintenance work to me.

benjaminLevinson commented 6 months ago

Would this be worth including in the library? Seems like an important utility.

Just my 2 cents, I think this would be a very helpful addition to the library. I spent many hours trying to build my own solution to display some trees in a template as well as optimizing that solution. I was lucky to have noticed this thread tonight and tried the solutions listed here. It turned out that, even with my "optimizations", my solution was an order of magnitude slower (and not to mention less elegant) compared to the snippet @LazerRaptor provided.

matthiask commented 6 months ago

https://github.com/triopter/django-tree-query-template exists, and I should probably mention those tools somewhere in the README. Would that help?