Closed jet10000 closed 3 months 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.
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.
OMG!
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>
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>
Nice, looks good!
The
tree_info
filter of django-mptt should work almost unchanged. mptt uses._mptt_meta
and.level
while django-tree-queries always usestree_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)
Very nice! Good to know it works.
Would this be worth including in the library? Seems like an important utility.
@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.
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.
https://github.com/triopter/django-tree-query-template exists, and I should probably mention those tools somewhere in the README. Would that help?
Is there an example ?
I wanto display tree in django template, How to do it?