Coverage for /var/srv/projects/api.amasfac.comuna18.com/tmp/venv/lib/python3.9/site-packages/django/template/defaulttags.py: 22%
626 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"""Default tags used by the template system, available to all templates."""
2import re
3import sys
4import warnings
5from collections import namedtuple
6from datetime import datetime
7from itertools import cycle as itertools_cycle
8from itertools import groupby
10from django.conf import settings
11from django.utils import timezone
12from django.utils.html import conditional_escape, escape, format_html
13from django.utils.lorem_ipsum import paragraphs, words
14from django.utils.safestring import mark_safe
16from .base import (
17 BLOCK_TAG_END,
18 BLOCK_TAG_START,
19 COMMENT_TAG_END,
20 COMMENT_TAG_START,
21 FILTER_SEPARATOR,
22 SINGLE_BRACE_END,
23 SINGLE_BRACE_START,
24 VARIABLE_ATTRIBUTE_SEPARATOR,
25 VARIABLE_TAG_END,
26 VARIABLE_TAG_START,
27 Node,
28 NodeList,
29 TemplateSyntaxError,
30 VariableDoesNotExist,
31 kwarg_re,
32 render_value_in_context,
33 token_kwargs,
34)
35from .context import Context
36from .defaultfilters import date
37from .library import Library
38from .smartif import IfParser, Literal
40register = Library()
43class AutoEscapeControlNode(Node):
44 """Implement the actions of the autoescape tag."""
46 def __init__(self, setting, nodelist):
47 self.setting, self.nodelist = setting, nodelist
49 def render(self, context):
50 old_setting = context.autoescape
51 context.autoescape = self.setting
52 output = self.nodelist.render(context)
53 context.autoescape = old_setting
54 if self.setting:
55 return mark_safe(output)
56 else:
57 return output
60class CommentNode(Node):
61 child_nodelists = ()
63 def render(self, context):
64 return ""
67class CsrfTokenNode(Node):
68 child_nodelists = ()
70 def render(self, context):
71 csrf_token = context.get("csrf_token")
72 if csrf_token:
73 if csrf_token == "NOTPROVIDED":
74 return format_html("")
75 else:
76 return format_html(
77 '<input type="hidden" name="csrfmiddlewaretoken" value="{}">',
78 csrf_token,
79 )
80 else:
81 # It's very probable that the token is missing because of
82 # misconfiguration, so we raise a warning
83 if settings.DEBUG:
84 warnings.warn(
85 "A {% csrf_token %} was used in a template, but the context "
86 "did not provide the value. This is usually caused by not "
87 "using RequestContext."
88 )
89 return ""
92class CycleNode(Node):
93 def __init__(self, cyclevars, variable_name=None, silent=False):
94 self.cyclevars = cyclevars
95 self.variable_name = variable_name
96 self.silent = silent
98 def render(self, context):
99 if self not in context.render_context:
100 # First time the node is rendered in template
101 context.render_context[self] = itertools_cycle(self.cyclevars)
102 cycle_iter = context.render_context[self]
103 value = next(cycle_iter).resolve(context)
104 if self.variable_name:
105 context.set_upward(self.variable_name, value)
106 if self.silent:
107 return ""
108 return render_value_in_context(value, context)
110 def reset(self, context):
111 """
112 Reset the cycle iteration back to the beginning.
113 """
114 context.render_context[self] = itertools_cycle(self.cyclevars)
117class DebugNode(Node):
118 def render(self, context):
119 if not settings.DEBUG:
120 return ""
122 from pprint import pformat
124 output = [escape(pformat(val)) for val in context]
125 output.append("\n\n")
126 output.append(escape(pformat(sys.modules)))
127 return "".join(output)
130class FilterNode(Node):
131 def __init__(self, filter_expr, nodelist):
132 self.filter_expr, self.nodelist = filter_expr, nodelist
134 def render(self, context):
135 output = self.nodelist.render(context)
136 # Apply filters.
137 with context.push(var=output):
138 return self.filter_expr.resolve(context)
141class FirstOfNode(Node):
142 def __init__(self, variables, asvar=None):
143 self.vars = variables
144 self.asvar = asvar
146 def render(self, context):
147 first = ""
148 for var in self.vars:
149 value = var.resolve(context, ignore_failures=True)
150 if value:
151 first = render_value_in_context(value, context)
152 break
153 if self.asvar:
154 context[self.asvar] = first
155 return ""
156 return first
159class ForNode(Node):
160 child_nodelists = ("nodelist_loop", "nodelist_empty")
162 def __init__(
163 self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None
164 ):
165 self.loopvars, self.sequence = loopvars, sequence
166 self.is_reversed = is_reversed
167 self.nodelist_loop = nodelist_loop
168 if nodelist_empty is None:
169 self.nodelist_empty = NodeList()
170 else:
171 self.nodelist_empty = nodelist_empty
173 def __repr__(self):
174 reversed_text = " reversed" if self.is_reversed else ""
175 return "<%s: for %s in %s, tail_len: %d%s>" % (
176 self.__class__.__name__,
177 ", ".join(self.loopvars),
178 self.sequence,
179 len(self.nodelist_loop),
180 reversed_text,
181 )
183 def render(self, context):
184 if "forloop" in context:
185 parentloop = context["forloop"]
186 else:
187 parentloop = {}
188 with context.push():
189 values = self.sequence.resolve(context, ignore_failures=True)
190 if values is None:
191 values = []
192 if not hasattr(values, "__len__"):
193 values = list(values)
194 len_values = len(values)
195 if len_values < 1:
196 return self.nodelist_empty.render(context)
197 nodelist = []
198 if self.is_reversed:
199 values = reversed(values)
200 num_loopvars = len(self.loopvars)
201 unpack = num_loopvars > 1
202 # Create a forloop value in the context. We'll update counters on each
203 # iteration just below.
204 loop_dict = context["forloop"] = {"parentloop": parentloop}
205 for i, item in enumerate(values):
206 # Shortcuts for current loop iteration number.
207 loop_dict["counter0"] = i
208 loop_dict["counter"] = i + 1
209 # Reverse counter iteration numbers.
210 loop_dict["revcounter"] = len_values - i
211 loop_dict["revcounter0"] = len_values - i - 1
212 # Boolean values designating first and last times through loop.
213 loop_dict["first"] = i == 0
214 loop_dict["last"] = i == len_values - 1
216 pop_context = False
217 if unpack:
218 # If there are multiple loop variables, unpack the item into
219 # them.
220 try:
221 len_item = len(item)
222 except TypeError: # not an iterable
223 len_item = 1
224 # Check loop variable count before unpacking
225 if num_loopvars != len_item:
226 raise ValueError(
227 "Need {} values to unpack in for loop; got {}. ".format(
228 num_loopvars, len_item
229 ),
230 )
231 unpacked_vars = dict(zip(self.loopvars, item))
232 pop_context = True
233 context.update(unpacked_vars)
234 else:
235 context[self.loopvars[0]] = item
237 for node in self.nodelist_loop:
238 nodelist.append(node.render_annotated(context))
240 if pop_context:
241 # Pop the loop variables pushed on to the context to avoid
242 # the context ending up in an inconsistent state when other
243 # tags (e.g., include and with) push data to context.
244 context.pop()
245 return mark_safe("".join(nodelist))
248class IfChangedNode(Node):
249 child_nodelists = ("nodelist_true", "nodelist_false")
251 def __init__(self, nodelist_true, nodelist_false, *varlist):
252 self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
253 self._varlist = varlist
255 def render(self, context):
256 # Init state storage
257 state_frame = self._get_context_stack_frame(context)
258 state_frame.setdefault(self)
260 nodelist_true_output = None
261 if self._varlist:
262 # Consider multiple parameters. This behaves like an OR evaluation
263 # of the multiple variables.
264 compare_to = [
265 var.resolve(context, ignore_failures=True) for var in self._varlist
266 ]
267 else:
268 # The "{% ifchanged %}" syntax (without any variables) compares
269 # the rendered output.
270 compare_to = nodelist_true_output = self.nodelist_true.render(context)
272 if compare_to != state_frame[self]:
273 state_frame[self] = compare_to
274 # render true block if not already rendered
275 return nodelist_true_output or self.nodelist_true.render(context)
276 elif self.nodelist_false:
277 return self.nodelist_false.render(context)
278 return ""
280 def _get_context_stack_frame(self, context):
281 # The Context object behaves like a stack where each template tag can
282 # create a new scope. Find the place where to store the state to detect
283 # changes.
284 if "forloop" in context:
285 # Ifchanged is bound to the local for loop.
286 # When there is a loop-in-loop, the state is bound to the inner loop,
287 # so it resets when the outer loop continues.
288 return context["forloop"]
289 else:
290 # Using ifchanged outside loops. Effectively this is a no-op
291 # because the state is associated with 'self'.
292 return context.render_context
295class IfNode(Node):
296 def __init__(self, conditions_nodelists):
297 self.conditions_nodelists = conditions_nodelists
299 def __repr__(self):
300 return "<%s>" % self.__class__.__name__
302 def __iter__(self):
303 for _, nodelist in self.conditions_nodelists:
304 yield from nodelist
306 @property
307 def nodelist(self):
308 return NodeList(self)
310 def render(self, context):
311 for condition, nodelist in self.conditions_nodelists:
313 if condition is not None: # if / elif clause
314 try:
315 match = condition.eval(context)
316 except VariableDoesNotExist:
317 match = None
318 else: # else clause
319 match = True
321 if match:
322 return nodelist.render(context)
324 return ""
327class LoremNode(Node):
328 def __init__(self, count, method, common):
329 self.count, self.method, self.common = count, method, common
331 def render(self, context):
332 try:
333 count = int(self.count.resolve(context))
334 except (ValueError, TypeError):
335 count = 1
336 if self.method == "w":
337 return words(count, common=self.common)
338 else:
339 paras = paragraphs(count, common=self.common)
340 if self.method == "p":
341 paras = ["<p>%s</p>" % p for p in paras]
342 return "\n\n".join(paras)
345GroupedResult = namedtuple("GroupedResult", ["grouper", "list"])
348class RegroupNode(Node):
349 def __init__(self, target, expression, var_name):
350 self.target, self.expression = target, expression
351 self.var_name = var_name
353 def resolve_expression(self, obj, context):
354 # This method is called for each object in self.target. See regroup()
355 # for the reason why we temporarily put the object in the context.
356 context[self.var_name] = obj
357 return self.expression.resolve(context, ignore_failures=True)
359 def render(self, context):
360 obj_list = self.target.resolve(context, ignore_failures=True)
361 if obj_list is None:
362 # target variable wasn't found in context; fail silently.
363 context[self.var_name] = []
364 return ""
365 # List of dictionaries in the format:
366 # {'grouper': 'key', 'list': [list of contents]}.
367 context[self.var_name] = [
368 GroupedResult(grouper=key, list=list(val))
369 for key, val in groupby(
370 obj_list, lambda obj: self.resolve_expression(obj, context)
371 )
372 ]
373 return ""
376class LoadNode(Node):
377 child_nodelists = ()
379 def render(self, context):
380 return ""
383class NowNode(Node):
384 def __init__(self, format_string, asvar=None):
385 self.format_string = format_string
386 self.asvar = asvar
388 def render(self, context):
389 tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None
390 formatted = date(datetime.now(tz=tzinfo), self.format_string)
392 if self.asvar:
393 context[self.asvar] = formatted
394 return ""
395 else:
396 return formatted
399class ResetCycleNode(Node):
400 def __init__(self, node):
401 self.node = node
403 def render(self, context):
404 self.node.reset(context)
405 return ""
408class SpacelessNode(Node):
409 def __init__(self, nodelist):
410 self.nodelist = nodelist
412 def render(self, context):
413 from django.utils.html import strip_spaces_between_tags
415 return strip_spaces_between_tags(self.nodelist.render(context).strip())
418class TemplateTagNode(Node):
419 mapping = {
420 "openblock": BLOCK_TAG_START,
421 "closeblock": BLOCK_TAG_END,
422 "openvariable": VARIABLE_TAG_START,
423 "closevariable": VARIABLE_TAG_END,
424 "openbrace": SINGLE_BRACE_START,
425 "closebrace": SINGLE_BRACE_END,
426 "opencomment": COMMENT_TAG_START,
427 "closecomment": COMMENT_TAG_END,
428 }
430 def __init__(self, tagtype):
431 self.tagtype = tagtype
433 def render(self, context):
434 return self.mapping.get(self.tagtype, "")
437class URLNode(Node):
438 child_nodelists = ()
440 def __init__(self, view_name, args, kwargs, asvar):
441 self.view_name = view_name
442 self.args = args
443 self.kwargs = kwargs
444 self.asvar = asvar
446 def __repr__(self):
447 return "<%s view_name='%s' args=%s kwargs=%s as=%s>" % (
448 self.__class__.__qualname__,
449 self.view_name,
450 repr(self.args),
451 repr(self.kwargs),
452 repr(self.asvar),
453 )
455 def render(self, context):
456 from django.urls import NoReverseMatch, reverse
458 args = [arg.resolve(context) for arg in self.args]
459 kwargs = {k: v.resolve(context) for k, v in self.kwargs.items()}
460 view_name = self.view_name.resolve(context)
461 try:
462 current_app = context.request.current_app
463 except AttributeError:
464 try:
465 current_app = context.request.resolver_match.namespace
466 except AttributeError:
467 current_app = None
468 # Try to look up the URL. If it fails, raise NoReverseMatch unless the
469 # {% url ... as var %} construct is used, in which case return nothing.
470 url = ""
471 try:
472 url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app)
473 except NoReverseMatch:
474 if self.asvar is None:
475 raise
477 if self.asvar:
478 context[self.asvar] = url
479 return ""
480 else:
481 if context.autoescape:
482 url = conditional_escape(url)
483 return url
486class VerbatimNode(Node):
487 def __init__(self, content):
488 self.content = content
490 def render(self, context):
491 return self.content
494class WidthRatioNode(Node):
495 def __init__(self, val_expr, max_expr, max_width, asvar=None):
496 self.val_expr = val_expr
497 self.max_expr = max_expr
498 self.max_width = max_width
499 self.asvar = asvar
501 def render(self, context):
502 try:
503 value = self.val_expr.resolve(context)
504 max_value = self.max_expr.resolve(context)
505 max_width = int(self.max_width.resolve(context))
506 except VariableDoesNotExist:
507 return ""
508 except (ValueError, TypeError):
509 raise TemplateSyntaxError("widthratio final argument must be a number")
510 try:
511 value = float(value)
512 max_value = float(max_value)
513 ratio = (value / max_value) * max_width
514 result = str(round(ratio))
515 except ZeroDivisionError:
516 result = "0"
517 except (ValueError, TypeError, OverflowError):
518 result = ""
520 if self.asvar:
521 context[self.asvar] = result
522 return ""
523 else:
524 return result
527class WithNode(Node):
528 def __init__(self, var, name, nodelist, extra_context=None):
529 self.nodelist = nodelist
530 # var and name are legacy attributes, being left in case they are used
531 # by third-party subclasses of this Node.
532 self.extra_context = extra_context or {}
533 if name:
534 self.extra_context[name] = var
536 def __repr__(self):
537 return "<%s>" % self.__class__.__name__
539 def render(self, context):
540 values = {key: val.resolve(context) for key, val in self.extra_context.items()}
541 with context.push(**values):
542 return self.nodelist.render(context)
545@register.tag
546def autoescape(parser, token):
547 """
548 Force autoescape behavior for this block.
549 """
550 # token.split_contents() isn't useful here because this tag doesn't accept
551 # variable as arguments.
552 args = token.contents.split()
553 if len(args) != 2:
554 raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
555 arg = args[1]
556 if arg not in ("on", "off"):
557 raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
558 nodelist = parser.parse(("endautoescape",))
559 parser.delete_first_token()
560 return AutoEscapeControlNode((arg == "on"), nodelist)
563@register.tag
564def comment(parser, token):
565 """
566 Ignore everything between ``{% comment %}`` and ``{% endcomment %}``.
567 """
568 parser.skip_past("endcomment")
569 return CommentNode()
572@register.tag
573def cycle(parser, token):
574 """
575 Cycle among the given strings each time this tag is encountered.
577 Within a loop, cycles among the given strings each time through
578 the loop::
580 {% for o in some_list %}
581 <tr class="{% cycle 'row1' 'row2' %}">
582 ...
583 </tr>
584 {% endfor %}
586 Outside of a loop, give the values a unique name the first time you call
587 it, then use that name each successive time through::
589 <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
590 <tr class="{% cycle rowcolors %}">...</tr>
591 <tr class="{% cycle rowcolors %}">...</tr>
593 You can use any number of values, separated by spaces. Commas can also
594 be used to separate values; if a comma is used, the cycle values are
595 interpreted as literal strings.
597 The optional flag "silent" can be used to prevent the cycle declaration
598 from returning any value::
600 {% for o in some_list %}
601 {% cycle 'row1' 'row2' as rowcolors silent %}
602 <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr>
603 {% endfor %}
604 """
605 # Note: This returns the exact same node on each {% cycle name %} call;
606 # that is, the node object returned from {% cycle a b c as name %} and the
607 # one returned from {% cycle name %} are the exact same object. This
608 # shouldn't cause problems (heh), but if it does, now you know.
609 #
610 # Ugly hack warning: This stuffs the named template dict into parser so
611 # that names are only unique within each template (as opposed to using
612 # a global variable, which would make cycle names have to be unique across
613 # *all* templates.
614 #
615 # It keeps the last node in the parser to be able to reset it with
616 # {% resetcycle %}.
618 args = token.split_contents()
620 if len(args) < 2:
621 raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
623 if len(args) == 2:
624 # {% cycle foo %} case.
625 name = args[1]
626 if not hasattr(parser, "_named_cycle_nodes"):
627 raise TemplateSyntaxError(
628 "No named cycles in template. '%s' is not defined" % name
629 )
630 if name not in parser._named_cycle_nodes:
631 raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
632 return parser._named_cycle_nodes[name]
634 as_form = False
636 if len(args) > 4:
637 # {% cycle ... as foo [silent] %} case.
638 if args[-3] == "as":
639 if args[-1] != "silent":
640 raise TemplateSyntaxError(
641 "Only 'silent' flag is allowed after cycle's name, not '%s'."
642 % args[-1]
643 )
644 as_form = True
645 silent = True
646 args = args[:-1]
647 elif args[-2] == "as":
648 as_form = True
649 silent = False
651 if as_form:
652 name = args[-1]
653 values = [parser.compile_filter(arg) for arg in args[1:-2]]
654 node = CycleNode(values, name, silent=silent)
655 if not hasattr(parser, "_named_cycle_nodes"):
656 parser._named_cycle_nodes = {}
657 parser._named_cycle_nodes[name] = node
658 else:
659 values = [parser.compile_filter(arg) for arg in args[1:]]
660 node = CycleNode(values)
661 parser._last_cycle_node = node
662 return node
665@register.tag
666def csrf_token(parser, token):
667 return CsrfTokenNode()
670@register.tag
671def debug(parser, token):
672 """
673 Output a whole load of debugging information, including the current
674 context and imported modules.
676 Sample usage::
678 <pre>
679 {% debug %}
680 </pre>
681 """
682 return DebugNode()
685@register.tag("filter")
686def do_filter(parser, token):
687 """
688 Filter the contents of the block through variable filters.
690 Filters can also be piped through each other, and they can have
691 arguments -- just like in variable syntax.
693 Sample usage::
695 {% filter force_escape|lower %}
696 This text will be HTML-escaped, and will appear in lowercase.
697 {% endfilter %}
699 Note that the ``escape`` and ``safe`` filters are not acceptable arguments.
700 Instead, use the ``autoescape`` tag to manage autoescaping for blocks of
701 template code.
702 """
703 # token.split_contents() isn't useful here because this tag doesn't accept
704 # variable as arguments.
705 _, rest = token.contents.split(None, 1)
706 filter_expr = parser.compile_filter("var|%s" % (rest))
707 for func, unused in filter_expr.filters:
708 filter_name = getattr(func, "_filter_name", None)
709 if filter_name in ("escape", "safe"):
710 raise TemplateSyntaxError(
711 '"filter %s" is not permitted. Use the "autoescape" tag instead.'
712 % filter_name
713 )
714 nodelist = parser.parse(("endfilter",))
715 parser.delete_first_token()
716 return FilterNode(filter_expr, nodelist)
719@register.tag
720def firstof(parser, token):
721 """
722 Output the first variable passed that is not False.
724 Output nothing if all the passed variables are False.
726 Sample usage::
728 {% firstof var1 var2 var3 as myvar %}
730 This is equivalent to::
732 {% if var1 %}
733 {{ var1 }}
734 {% elif var2 %}
735 {{ var2 }}
736 {% elif var3 %}
737 {{ var3 }}
738 {% endif %}
740 but much cleaner!
742 You can also use a literal string as a fallback value in case all
743 passed variables are False::
745 {% firstof var1 var2 var3 "fallback value" %}
747 If you want to disable auto-escaping of variables you can use::
749 {% autoescape off %}
750 {% firstof var1 var2 var3 "<strong>fallback value</strong>" %}
751 {% autoescape %}
753 Or if only some variables should be escaped, you can use::
755 {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %}
756 """
757 bits = token.split_contents()[1:]
758 asvar = None
759 if not bits:
760 raise TemplateSyntaxError("'firstof' statement requires at least one argument")
762 if len(bits) >= 2 and bits[-2] == "as":
763 asvar = bits[-1]
764 bits = bits[:-2]
765 return FirstOfNode([parser.compile_filter(bit) for bit in bits], asvar)
768@register.tag("for")
769def do_for(parser, token):
770 """
771 Loop over each item in an array.
773 For example, to display a list of athletes given ``athlete_list``::
775 <ul>
776 {% for athlete in athlete_list %}
777 <li>{{ athlete.name }}</li>
778 {% endfor %}
779 </ul>
781 You can loop over a list in reverse by using
782 ``{% for obj in list reversed %}``.
784 You can also unpack multiple values from a two-dimensional array::
786 {% for key,value in dict.items %}
787 {{ key }}: {{ value }}
788 {% endfor %}
790 The ``for`` tag can take an optional ``{% empty %}`` clause that will
791 be displayed if the given array is empty or could not be found::
793 <ul>
794 {% for athlete in athlete_list %}
795 <li>{{ athlete.name }}</li>
796 {% empty %}
797 <li>Sorry, no athletes in this list.</li>
798 {% endfor %}
799 <ul>
801 The above is equivalent to -- but shorter, cleaner, and possibly faster
802 than -- the following::
804 <ul>
805 {% if athlete_list %}
806 {% for athlete in athlete_list %}
807 <li>{{ athlete.name }}</li>
808 {% endfor %}
809 {% else %}
810 <li>Sorry, no athletes in this list.</li>
811 {% endif %}
812 </ul>
814 The for loop sets a number of variables available within the loop:
816 ========================== ================================================
817 Variable Description
818 ========================== ================================================
819 ``forloop.counter`` The current iteration of the loop (1-indexed)
820 ``forloop.counter0`` The current iteration of the loop (0-indexed)
821 ``forloop.revcounter`` The number of iterations from the end of the
822 loop (1-indexed)
823 ``forloop.revcounter0`` The number of iterations from the end of the
824 loop (0-indexed)
825 ``forloop.first`` True if this is the first time through the loop
826 ``forloop.last`` True if this is the last time through the loop
827 ``forloop.parentloop`` For nested loops, this is the loop "above" the
828 current one
829 ========================== ================================================
830 """
831 bits = token.split_contents()
832 if len(bits) < 4:
833 raise TemplateSyntaxError(
834 "'for' statements should have at least four words: %s" % token.contents
835 )
837 is_reversed = bits[-1] == "reversed"
838 in_index = -3 if is_reversed else -2
839 if bits[in_index] != "in":
840 raise TemplateSyntaxError(
841 "'for' statements should use the format"
842 " 'for x in y': %s" % token.contents
843 )
845 invalid_chars = frozenset((" ", '"', "'", FILTER_SEPARATOR))
846 loopvars = re.split(r" *, *", " ".join(bits[1:in_index]))
847 for var in loopvars:
848 if not var or not invalid_chars.isdisjoint(var):
849 raise TemplateSyntaxError(
850 "'for' tag received an invalid argument: %s" % token.contents
851 )
853 sequence = parser.compile_filter(bits[in_index + 1])
854 nodelist_loop = parser.parse(
855 (
856 "empty",
857 "endfor",
858 )
859 )
860 token = parser.next_token()
861 if token.contents == "empty":
862 nodelist_empty = parser.parse(("endfor",))
863 parser.delete_first_token()
864 else:
865 nodelist_empty = None
866 return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)
869class TemplateLiteral(Literal):
870 def __init__(self, value, text):
871 self.value = value
872 self.text = text # for better error messages
874 def display(self):
875 return self.text
877 def eval(self, context):
878 return self.value.resolve(context, ignore_failures=True)
881class TemplateIfParser(IfParser):
882 error_class = TemplateSyntaxError
884 def __init__(self, parser, *args, **kwargs):
885 self.template_parser = parser
886 super().__init__(*args, **kwargs)
888 def create_var(self, value):
889 return TemplateLiteral(self.template_parser.compile_filter(value), value)
892@register.tag("if")
893def do_if(parser, token):
894 """
895 Evaluate a variable, and if that variable is "true" (i.e., exists, is not
896 empty, and is not a false boolean value), output the contents of the block:
898 ::
900 {% if athlete_list %}
901 Number of athletes: {{ athlete_list|count }}
902 {% elif athlete_in_locker_room_list %}
903 Athletes should be out of the locker room soon!
904 {% else %}
905 No athletes.
906 {% endif %}
908 In the above, if ``athlete_list`` is not empty, the number of athletes will
909 be displayed by the ``{{ athlete_list|count }}`` variable.
911 The ``if`` tag may take one or several `` {% elif %}`` clauses, as well as
912 an ``{% else %}`` clause that will be displayed if all previous conditions
913 fail. These clauses are optional.
915 ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
916 variables or to negate a given variable::
918 {% if not athlete_list %}
919 There are no athletes.
920 {% endif %}
922 {% if athlete_list or coach_list %}
923 There are some athletes or some coaches.
924 {% endif %}
926 {% if athlete_list and coach_list %}
927 Both athletes and coaches are available.
928 {% endif %}
930 {% if not athlete_list or coach_list %}
931 There are no athletes, or there are some coaches.
932 {% endif %}
934 {% if athlete_list and not coach_list %}
935 There are some athletes and absolutely no coaches.
936 {% endif %}
938 Comparison operators are also available, and the use of filters is also
939 allowed, for example::
941 {% if articles|length >= 5 %}...{% endif %}
943 Arguments and operators _must_ have a space between them, so
944 ``{% if 1>2 %}`` is not a valid if tag.
946 All supported operators are: ``or``, ``and``, ``in``, ``not in``
947 ``==``, ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
949 Operator precedence follows Python.
950 """
951 # {% if ... %}
952 bits = token.split_contents()[1:]
953 condition = TemplateIfParser(parser, bits).parse()
954 nodelist = parser.parse(("elif", "else", "endif"))
955 conditions_nodelists = [(condition, nodelist)]
956 token = parser.next_token()
958 # {% elif ... %} (repeatable)
959 while token.contents.startswith("elif"):
960 bits = token.split_contents()[1:]
961 condition = TemplateIfParser(parser, bits).parse()
962 nodelist = parser.parse(("elif", "else", "endif"))
963 conditions_nodelists.append((condition, nodelist))
964 token = parser.next_token()
966 # {% else %} (optional)
967 if token.contents == "else":
968 nodelist = parser.parse(("endif",))
969 conditions_nodelists.append((None, nodelist))
970 token = parser.next_token()
972 # {% endif %}
973 if token.contents != "endif":
974 raise TemplateSyntaxError(
975 'Malformed template tag at line {}: "{}"'.format(
976 token.lineno, token.contents
977 )
978 )
980 return IfNode(conditions_nodelists)
983@register.tag
984def ifchanged(parser, token):
985 """
986 Check if a value has changed from the last iteration of a loop.
988 The ``{% ifchanged %}`` block tag is used within a loop. It has two
989 possible uses.
991 1. Check its own rendered contents against its previous state and only
992 displays the content if it has changed. For example, this displays a
993 list of days, only displaying the month if it changes::
995 <h1>Archive for {{ year }}</h1>
997 {% for date in days %}
998 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
999 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
1000 {% endfor %}
1002 2. If given one or more variables, check whether any variable has changed.
1003 For example, the following shows the date every time it changes, while
1004 showing the hour if either the hour or the date has changed::
1006 {% for date in days %}
1007 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
1008 {% ifchanged date.hour date.date %}
1009 {{ date.hour }}
1010 {% endifchanged %}
1011 {% endfor %}
1012 """
1013 bits = token.split_contents()
1014 nodelist_true = parser.parse(("else", "endifchanged"))
1015 token = parser.next_token()
1016 if token.contents == "else":
1017 nodelist_false = parser.parse(("endifchanged",))
1018 parser.delete_first_token()
1019 else:
1020 nodelist_false = NodeList()
1021 values = [parser.compile_filter(bit) for bit in bits[1:]]
1022 return IfChangedNode(nodelist_true, nodelist_false, *values)
1025def find_library(parser, name):
1026 try:
1027 return parser.libraries[name]
1028 except KeyError:
1029 raise TemplateSyntaxError(
1030 "'%s' is not a registered tag library. Must be one of:\n%s"
1031 % (
1032 name,
1033 "\n".join(sorted(parser.libraries)),
1034 ),
1035 )
1038def load_from_library(library, label, names):
1039 """
1040 Return a subset of tags and filters from a library.
1041 """
1042 subset = Library()
1043 for name in names:
1044 found = False
1045 if name in library.tags:
1046 found = True
1047 subset.tags[name] = library.tags[name]
1048 if name in library.filters:
1049 found = True
1050 subset.filters[name] = library.filters[name]
1051 if found is False:
1052 raise TemplateSyntaxError(
1053 "'%s' is not a valid tag or filter in tag library '%s'"
1054 % (
1055 name,
1056 label,
1057 ),
1058 )
1059 return subset
1062@register.tag
1063def load(parser, token):
1064 """
1065 Load a custom template tag library into the parser.
1067 For example, to load the template tags in
1068 ``django/templatetags/news/photos.py``::
1070 {% load news.photos %}
1072 Can also be used to load an individual tag/filter from
1073 a library::
1075 {% load byline from news %}
1076 """
1077 # token.split_contents() isn't useful here because this tag doesn't accept
1078 # variable as arguments.
1079 bits = token.contents.split()
1080 if len(bits) >= 4 and bits[-2] == "from":
1081 # from syntax is used; load individual tags from the library
1082 name = bits[-1]
1083 lib = find_library(parser, name)
1084 subset = load_from_library(lib, name, bits[1:-2])
1085 parser.add_library(subset)
1086 else:
1087 # one or more libraries are specified; load and add them to the parser
1088 for name in bits[1:]:
1089 lib = find_library(parser, name)
1090 parser.add_library(lib)
1091 return LoadNode()
1094@register.tag
1095def lorem(parser, token):
1096 """
1097 Create random Latin text useful for providing test data in templates.
1099 Usage format::
1101 {% lorem [count] [method] [random] %}
1103 ``count`` is a number (or variable) containing the number of paragraphs or
1104 words to generate (default is 1).
1106 ``method`` is either ``w`` for words, ``p`` for HTML paragraphs, ``b`` for
1107 plain-text paragraph blocks (default is ``b``).
1109 ``random`` is the word ``random``, which if given, does not use the common
1110 paragraph (starting "Lorem ipsum dolor sit amet, consectetuer...").
1112 Examples:
1114 * ``{% lorem %}`` outputs the common "lorem ipsum" paragraph
1115 * ``{% lorem 3 p %}`` outputs the common "lorem ipsum" paragraph
1116 and two random paragraphs each wrapped in HTML ``<p>`` tags
1117 * ``{% lorem 2 w random %}`` outputs two random latin words
1118 """
1119 bits = list(token.split_contents())
1120 tagname = bits[0]
1121 # Random bit
1122 common = bits[-1] != "random"
1123 if not common:
1124 bits.pop()
1125 # Method bit
1126 if bits[-1] in ("w", "p", "b"):
1127 method = bits.pop()
1128 else:
1129 method = "b"
1130 # Count bit
1131 if len(bits) > 1:
1132 count = bits.pop()
1133 else:
1134 count = "1"
1135 count = parser.compile_filter(count)
1136 if len(bits) != 1:
1137 raise TemplateSyntaxError("Incorrect format for %r tag" % tagname)
1138 return LoremNode(count, method, common)
1141@register.tag
1142def now(parser, token):
1143 """
1144 Display the date, formatted according to the given string.
1146 Use the same format as PHP's ``date()`` function; see https://php.net/date
1147 for all the possible values.
1149 Sample usage::
1151 It is {% now "jS F Y H:i" %}
1152 """
1153 bits = token.split_contents()
1154 asvar = None
1155 if len(bits) == 4 and bits[-2] == "as":
1156 asvar = bits[-1]
1157 bits = bits[:-2]
1158 if len(bits) != 2:
1159 raise TemplateSyntaxError("'now' statement takes one argument")
1160 format_string = bits[1][1:-1]
1161 return NowNode(format_string, asvar)
1164@register.tag
1165def regroup(parser, token):
1166 """
1167 Regroup a list of alike objects by a common attribute.
1169 This complex tag is best illustrated by use of an example: say that
1170 ``musicians`` is a list of ``Musician`` objects that have ``name`` and
1171 ``instrument`` attributes, and you'd like to display a list that
1172 looks like:
1174 * Guitar:
1175 * Django Reinhardt
1176 * Emily Remler
1177 * Piano:
1178 * Lovie Austin
1179 * Bud Powell
1180 * Trumpet:
1181 * Duke Ellington
1183 The following snippet of template code would accomplish this dubious task::
1185 {% regroup musicians by instrument as grouped %}
1186 <ul>
1187 {% for group in grouped %}
1188 <li>{{ group.grouper }}
1189 <ul>
1190 {% for musician in group.list %}
1191 <li>{{ musician.name }}</li>
1192 {% endfor %}
1193 </ul>
1194 {% endfor %}
1195 </ul>
1197 As you can see, ``{% regroup %}`` populates a variable with a list of
1198 objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the
1199 item that was grouped by; ``list`` contains the list of objects that share
1200 that ``grouper``. In this case, ``grouper`` would be ``Guitar``, ``Piano``
1201 and ``Trumpet``, and ``list`` is the list of musicians who play this
1202 instrument.
1204 Note that ``{% regroup %}`` does not work when the list to be grouped is not
1205 sorted by the key you are grouping by! This means that if your list of
1206 musicians was not sorted by instrument, you'd need to make sure it is sorted
1207 before using it, i.e.::
1209 {% regroup musicians|dictsort:"instrument" by instrument as grouped %}
1210 """
1211 bits = token.split_contents()
1212 if len(bits) != 6:
1213 raise TemplateSyntaxError("'regroup' tag takes five arguments")
1214 target = parser.compile_filter(bits[1])
1215 if bits[2] != "by":
1216 raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
1217 if bits[4] != "as":
1218 raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must be 'as'")
1219 var_name = bits[5]
1220 # RegroupNode will take each item in 'target', put it in the context under
1221 # 'var_name', evaluate 'var_name'.'expression' in the current context, and
1222 # group by the resulting value. After all items are processed, it will
1223 # save the final result in the context under 'var_name', thus clearing the
1224 # temporary values. This hack is necessary because the template engine
1225 # doesn't provide a context-aware equivalent of Python's getattr.
1226 expression = parser.compile_filter(
1227 var_name + VARIABLE_ATTRIBUTE_SEPARATOR + bits[3]
1228 )
1229 return RegroupNode(target, expression, var_name)
1232@register.tag
1233def resetcycle(parser, token):
1234 """
1235 Reset a cycle tag.
1237 If an argument is given, reset the last rendered cycle tag whose name
1238 matches the argument, else reset the last rendered cycle tag (named or
1239 unnamed).
1240 """
1241 args = token.split_contents()
1243 if len(args) > 2:
1244 raise TemplateSyntaxError("%r tag accepts at most one argument." % args[0])
1246 if len(args) == 2:
1247 name = args[1]
1248 try:
1249 return ResetCycleNode(parser._named_cycle_nodes[name])
1250 except (AttributeError, KeyError):
1251 raise TemplateSyntaxError("Named cycle '%s' does not exist." % name)
1252 try:
1253 return ResetCycleNode(parser._last_cycle_node)
1254 except AttributeError:
1255 raise TemplateSyntaxError("No cycles in template.")
1258@register.tag
1259def spaceless(parser, token):
1260 """
1261 Remove whitespace between HTML tags, including tab and newline characters.
1263 Example usage::
1265 {% spaceless %}
1266 <p>
1267 <a href="foo/">Foo</a>
1268 </p>
1269 {% endspaceless %}
1271 This example returns this HTML::
1273 <p><a href="foo/">Foo</a></p>
1275 Only space between *tags* is normalized -- not space between tags and text.
1276 In this example, the space around ``Hello`` isn't stripped::
1278 {% spaceless %}
1279 <strong>
1280 Hello
1281 </strong>
1282 {% endspaceless %}
1283 """
1284 nodelist = parser.parse(("endspaceless",))
1285 parser.delete_first_token()
1286 return SpacelessNode(nodelist)
1289@register.tag
1290def templatetag(parser, token):
1291 """
1292 Output one of the bits used to compose template tags.
1294 Since the template system has no concept of "escaping", to display one of
1295 the bits used in template tags, you must use the ``{% templatetag %}`` tag.
1297 The argument tells which template bit to output:
1299 ================== =======
1300 Argument Outputs
1301 ================== =======
1302 ``openblock`` ``{%``
1303 ``closeblock`` ``%}``
1304 ``openvariable`` ``{{``
1305 ``closevariable`` ``}}``
1306 ``openbrace`` ``{``
1307 ``closebrace`` ``}``
1308 ``opencomment`` ``{#``
1309 ``closecomment`` ``#}``
1310 ================== =======
1311 """
1312 # token.split_contents() isn't useful here because this tag doesn't accept
1313 # variable as arguments.
1314 bits = token.contents.split()
1315 if len(bits) != 2:
1316 raise TemplateSyntaxError("'templatetag' statement takes one argument")
1317 tag = bits[1]
1318 if tag not in TemplateTagNode.mapping:
1319 raise TemplateSyntaxError(
1320 "Invalid templatetag argument: '%s'."
1321 " Must be one of: %s" % (tag, list(TemplateTagNode.mapping))
1322 )
1323 return TemplateTagNode(tag)
1326@register.tag
1327def url(parser, token):
1328 r"""
1329 Return an absolute URL matching the given view with its parameters.
1331 This is a way to define links that aren't tied to a particular URL
1332 configuration::
1334 {% url "url_name" arg1 arg2 %}
1336 or
1338 {% url "url_name" name1=value1 name2=value2 %}
1340 The first argument is a URL pattern name. Other arguments are
1341 space-separated values that will be filled in place of positional and
1342 keyword arguments in the URL. Don't mix positional and keyword arguments.
1343 All arguments for the URL must be present.
1345 For example, if you have a view ``app_name.views.client_details`` taking
1346 the client's id and the corresponding line in a URLconf looks like this::
1348 path('client/<int:id>/', views.client_details, name='client-detail-view')
1350 and this app's URLconf is included into the project's URLconf under some
1351 path::
1353 path('clients/', include('app_name.urls'))
1355 then in a template you can create a link for a certain client like this::
1357 {% url "client-detail-view" client.id %}
1359 The URL will look like ``/clients/client/123/``.
1361 The first argument may also be the name of a template variable that will be
1362 evaluated to obtain the view name or the URL name, e.g.::
1364 {% with url_name="client-detail-view" %}
1365 {% url url_name client.id %}
1366 {% endwith %}
1367 """
1368 bits = token.split_contents()
1369 if len(bits) < 2:
1370 raise TemplateSyntaxError(
1371 "'%s' takes at least one argument, a URL pattern name." % bits[0]
1372 )
1373 viewname = parser.compile_filter(bits[1])
1374 args = []
1375 kwargs = {}
1376 asvar = None
1377 bits = bits[2:]
1378 if len(bits) >= 2 and bits[-2] == "as":
1379 asvar = bits[-1]
1380 bits = bits[:-2]
1382 for bit in bits:
1383 match = kwarg_re.match(bit)
1384 if not match:
1385 raise TemplateSyntaxError("Malformed arguments to url tag")
1386 name, value = match.groups()
1387 if name:
1388 kwargs[name] = parser.compile_filter(value)
1389 else:
1390 args.append(parser.compile_filter(value))
1392 return URLNode(viewname, args, kwargs, asvar)
1395@register.tag
1396def verbatim(parser, token):
1397 """
1398 Stop the template engine from rendering the contents of this block tag.
1400 Usage::
1402 {% verbatim %}
1403 {% don't process this %}
1404 {% endverbatim %}
1406 You can also designate a specific closing tag block (allowing the
1407 unrendered use of ``{% endverbatim %}``)::
1409 {% verbatim myblock %}
1410 ...
1411 {% endverbatim myblock %}
1412 """
1413 nodelist = parser.parse(("endverbatim",))
1414 parser.delete_first_token()
1415 return VerbatimNode(nodelist.render(Context()))
1418@register.tag
1419def widthratio(parser, token):
1420 """
1421 For creating bar charts and such. Calculate the ratio of a given value to a
1422 maximum value, and then apply that ratio to a constant.
1424 For example::
1426 <img src="bar.png" alt="Bar"
1427 height="10" width="{% widthratio this_value max_value max_width %}">
1429 If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100,
1430 the image in the above example will be 88 pixels wide
1431 (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
1433 In some cases you might want to capture the result of widthratio in a
1434 variable. It can be useful for instance in a blocktranslate like this::
1436 {% widthratio this_value max_value max_width as width %}
1437 {% blocktranslate %}The width is: {{ width }}{% endblocktranslate %}
1438 """
1439 bits = token.split_contents()
1440 if len(bits) == 4:
1441 tag, this_value_expr, max_value_expr, max_width = bits
1442 asvar = None
1443 elif len(bits) == 6:
1444 tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits
1445 if as_ != "as":
1446 raise TemplateSyntaxError(
1447 "Invalid syntax in widthratio tag. Expecting 'as' keyword"
1448 )
1449 else:
1450 raise TemplateSyntaxError("widthratio takes at least three arguments")
1452 return WidthRatioNode(
1453 parser.compile_filter(this_value_expr),
1454 parser.compile_filter(max_value_expr),
1455 parser.compile_filter(max_width),
1456 asvar=asvar,
1457 )
1460@register.tag("with")
1461def do_with(parser, token):
1462 """
1463 Add one or more values to the context (inside of this block) for caching
1464 and easy access.
1466 For example::
1468 {% with total=person.some_sql_method %}
1469 {{ total }} object{{ total|pluralize }}
1470 {% endwith %}
1472 Multiple values can be added to the context::
1474 {% with foo=1 bar=2 %}
1475 ...
1476 {% endwith %}
1478 The legacy format of ``{% with person.some_sql_method as total %}`` is
1479 still accepted.
1480 """
1481 bits = token.split_contents()
1482 remaining_bits = bits[1:]
1483 extra_context = token_kwargs(remaining_bits, parser, support_legacy=True)
1484 if not extra_context:
1485 raise TemplateSyntaxError(
1486 "%r expected at least one variable assignment" % bits[0]
1487 )
1488 if remaining_bits:
1489 raise TemplateSyntaxError(
1490 "%r received an invalid token: %r" % (bits[0], remaining_bits[0])
1491 )
1492 nodelist = parser.parse(("endwith",))
1493 parser.delete_first_token()
1494 return WithNode(None, None, nodelist, extra_context=extra_context)