Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/mptt/templatetags/mptt_tags.py: 21%
119 statements
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
« prev ^ index » next coverage.py v6.4.4, created at 2023-07-17 14:22 -0600
1"""
2Template tags for working with lists of model instances which represent
3trees.
4"""
5from django import template
6from django.apps import apps
7from django.core.exceptions import FieldDoesNotExist
8from django.utils.encoding import force_str
9from django.utils.safestring import mark_safe
10from django.utils.translation import gettext as _
12from mptt.utils import drilldown_tree_for_node, get_cached_trees, tree_item_iterator
15register = template.Library()
18# ## ITERATIVE TAGS
21class FullTreeForModelNode(template.Node):
22 def __init__(self, model, context_var):
23 self.model = model
24 self.context_var = context_var
26 def render(self, context):
27 cls = apps.get_model(*self.model.split("."))
28 if cls is None:
29 raise template.TemplateSyntaxError(
30 _("full_tree_for_model tag was given an invalid model: %s") % self.model
31 )
32 context[self.context_var] = cls._tree_manager.all()
33 return ""
36class DrilldownTreeForNodeNode(template.Node):
37 def __init__(
38 self,
39 node,
40 context_var,
41 foreign_key=None,
42 count_attr=None,
43 cumulative=False,
44 all_descendants=False,
45 ):
46 self.node = template.Variable(node)
47 self.context_var = context_var
48 self.foreign_key = foreign_key
49 self.count_attr = count_attr
50 self.cumulative = cumulative
51 self.all_descendants = all_descendants
53 def render(self, context):
54 # Let any VariableDoesNotExist raised bubble up
55 args = [self.node.resolve(context)]
57 if self.foreign_key is not None:
58 app_label, model_name, fk_attr = self.foreign_key.split(".")
59 cls = apps.get_model(app_label, model_name)
60 if cls is None:
61 raise template.TemplateSyntaxError(
62 _("drilldown_tree_for_node tag was given an invalid model: %s")
63 % ".".join([app_label, model_name])
64 )
65 try:
66 cls._meta.get_field(fk_attr)
67 except FieldDoesNotExist:
68 raise template.TemplateSyntaxError(
69 _(
70 "drilldown_tree_for_node tag was given an invalid model field: %s"
71 )
72 % fk_attr
73 )
74 args.extend([cls, fk_attr, self.count_attr, self.cumulative])
76 context[self.context_var] = drilldown_tree_for_node(
77 *args, all_descendants=self.all_descendants
78 )
79 return ""
82@register.tag
83def full_tree_for_model(parser, token):
84 """
85 Populates a template variable with a ``QuerySet`` containing the
86 full tree for a given model.
88 Usage::
90 {% full_tree_for_model [model] as [varname] %}
92 The model is specified in ``[appname].[modelname]`` format.
94 Example::
96 {% full_tree_for_model tests.Genre as genres %}
98 """
99 bits = token.contents.split()
100 if len(bits) != 4:
101 raise template.TemplateSyntaxError(
102 _("%s tag requires three arguments") % bits[0]
103 )
104 if bits[2] != "as":
105 raise template.TemplateSyntaxError(
106 _("second argument to %s tag must be 'as'") % bits[0]
107 )
108 return FullTreeForModelNode(bits[1], bits[3])
111@register.tag("drilldown_tree_for_node")
112def do_drilldown_tree_for_node(parser, token):
113 """
114 Populates a template variable with the drilldown tree for a given
115 node, optionally counting the number of items associated with its
116 children.
118 A drilldown tree consists of a node's ancestors, itself and its
119 immediate children or all descendants. For example, a drilldown tree
120 for a book category "Personal Finance" might look something like::
122 Books
123 Business, Finance & Law
124 Personal Finance
125 Budgeting (220)
126 Financial Planning (670)
128 Usage::
130 {% drilldown_tree_for_node [node] as [varname] %}
132 Extended usage::
134 {% drilldown_tree_for_node [node] as [varname] all_descendants %}
135 {% drilldown_tree_for_node [node] as [varname] count [foreign_key] in [count_attr] %}
136 {% drilldown_tree_for_node [node] as [varname] cumulative count [foreign_key] in [count_attr] %}
138 The foreign key is specified in ``[appname].[modelname].[fieldname]``
139 format, where ``fieldname`` is the name of a field in the specified
140 model which relates it to the given node's model.
142 When this form is used, a ``count_attr`` attribute on each child of
143 the given node in the drilldown tree will contain a count of the
144 number of items associated with it through the given foreign key.
146 If cumulative is also specified, this count will be for items
147 related to the child node and all of its descendants.
149 Examples::
151 {% drilldown_tree_for_node genre as drilldown %}
152 {% drilldown_tree_for_node genre as drilldown count tests.Game.genre in game_count %}
153 {% drilldown_tree_for_node genre as drilldown cumulative count tests.Game.genre in game_count %}
155 """ # noqa
156 bits = token.contents.split()
157 len_bits = len(bits)
158 if len_bits not in (4, 5, 8, 9, 10):
159 raise template.TemplateSyntaxError(
160 _("%s tag requires either three, four, seven, eight, or nine arguments")
161 % bits[0]
162 )
163 if bits[2] != "as":
164 raise template.TemplateSyntaxError(
165 _("second argument to %s tag must be 'as'") % bits[0]
166 )
168 all_descendants = False
169 if len_bits > 4:
170 if bits[4] == "all_descendants":
171 len_bits -= 1
172 bits.pop(4)
173 all_descendants = True
175 if len_bits == 8:
176 if bits[4] != "count":
177 raise template.TemplateSyntaxError(
178 _(
179 "if seven arguments are given, fourth argument to %s tag must be 'with'"
180 )
181 % bits[0]
182 )
183 if bits[6] != "in":
184 raise template.TemplateSyntaxError(
185 _("if seven arguments are given, sixth argument to %s tag must be 'in'")
186 % bits[0]
187 )
188 return DrilldownTreeForNodeNode(
189 bits[1], bits[3], bits[5], bits[7], all_descendants=all_descendants
190 )
191 elif len_bits == 9:
192 if bits[4] != "cumulative":
193 raise template.TemplateSyntaxError(
194 _(
195 "if eight arguments are given, fourth argument to %s tag must be 'cumulative'"
196 )
197 % bits[0]
198 )
199 if bits[5] != "count":
200 raise template.TemplateSyntaxError(
201 _(
202 "if eight arguments are given, fifth argument to %s tag must be 'count'"
203 )
204 % bits[0]
205 )
206 if bits[7] != "in":
207 raise template.TemplateSyntaxError(
208 _(
209 "if eight arguments are given, seventh argument to %s tag must be 'in'"
210 )
211 % bits[0]
212 )
213 return DrilldownTreeForNodeNode(
214 bits[1],
215 bits[3],
216 bits[6],
217 bits[8],
218 cumulative=True,
219 all_descendants=all_descendants,
220 )
221 else:
222 return DrilldownTreeForNodeNode(
223 bits[1], bits[3], all_descendants=all_descendants
224 )
227@register.filter
228def tree_info(items, features=None):
229 """
230 Given a list of tree items, produces doubles of a tree item and a
231 ``dict`` containing information about the tree structure around the
232 item, with the following contents:
234 new_level
235 ``True`` if the current item is the start of a new level in
236 the tree, ``False`` otherwise.
238 closed_levels
239 A list of levels which end after the current item. This will
240 be an empty list if the next item is at the same level as the
241 current item.
243 Using this filter with unpacking in a ``{% for %}`` tag, you should
244 have enough information about the tree structure to create a
245 hierarchical representation of the tree.
247 Example::
249 {% for genre,structure in genres|tree_info %}
250 {% if structure.new_level %}<ul><li>{% else %}</li><li>{% endif %}
251 {{ genre.name }}
252 {% for level in structure.closed_levels %}</li></ul>{% endfor %}
253 {% endfor %}
255 """
256 kwargs = {}
257 if features:
258 feature_names = features.split(",")
259 if "ancestors" in feature_names:
260 kwargs["ancestors"] = True
261 return tree_item_iterator(items, **kwargs)
264@register.filter
265def tree_path(items, separator=" :: "):
266 """
267 Creates a tree path represented by a list of ``items`` by joining
268 the items with a ``separator``.
270 Each path item will be coerced to unicode, so a list of model
271 instances may be given if required.
273 Example::
275 {{ some_list|tree_path }}
276 {{ some_node.get_ancestors|tree_path:" > " }}
278 """
279 return separator.join(force_str(i) for i in items)
282# ## RECURSIVE TAGS
285@register.filter
286def cache_tree_children(queryset):
287 """
288 Alias to `mptt.utils.get_cached_trees`.
289 """
291 return get_cached_trees(queryset)
294class RecurseTreeNode(template.Node):
295 def __init__(self, template_nodes, queryset_var):
296 self.template_nodes = template_nodes
297 self.queryset_var = queryset_var
299 def _render_node(self, context, node):
300 bits = []
301 context.push()
302 for child in node.get_children():
303 bits.append(self._render_node(context, child))
304 context["node"] = node
305 context["children"] = mark_safe("".join(bits))
306 rendered = self.template_nodes.render(context)
307 context.pop()
308 return rendered
310 def render(self, context):
311 queryset = self.queryset_var.resolve(context)
312 roots = cache_tree_children(queryset)
313 bits = [self._render_node(context, node) for node in roots]
314 return "".join(bits)
317@register.tag
318def recursetree(parser, token):
319 """
320 Iterates over the nodes in the tree, and renders the contained block for each node.
321 This tag will recursively render children into the template variable {{ children }}.
322 Only one database query is required (children are cached for the whole tree)
324 Usage:
325 <ul>
326 {% recursetree nodes %}
327 <li>
328 {{ node.name }}
329 {% if not node.is_leaf_node %}
330 <ul>
331 {{ children }}
332 </ul>
333 {% endif %}
334 </li>
335 {% endrecursetree %}
336 </ul>
337 """
338 bits = token.contents.split()
339 if len(bits) != 2:
340 raise template.TemplateSyntaxError(_("%s tag requires a queryset") % bits[0])
342 queryset_var = template.Variable(bits[1])
344 template_nodes = parser.parse(("endrecursetree",))
345 parser.delete_first_token()
347 return RecurseTreeNode(template_nodes, queryset_var)