Index: venv/Lib/site-packages/django/template/defaulttags.py |
=================================================================== |
new file mode 100644 |
--- /dev/null |
+++ b/venv/Lib/site-packages/django/template/defaulttags.py |
@@ -0,0 +1,1492 @@ |
+"""Default tags used by the template system, available to all templates.""" |
+from __future__ import unicode_literals |
+ |
+import re |
+import sys |
+import warnings |
+from collections import namedtuple |
+from datetime import datetime |
+from itertools import cycle as itertools_cycle, groupby |
+ |
+from django.conf import settings |
+from django.utils import six, timezone |
+from django.utils.encoding import force_text |
+from django.utils.html import conditional_escape, format_html |
+from django.utils.lorem_ipsum import paragraphs, words |
+from django.utils.safestring import mark_safe |
+ |
+from .base import ( |
+ BLOCK_TAG_END, BLOCK_TAG_START, COMMENT_TAG_END, COMMENT_TAG_START, |
+ FILTER_SEPARATOR, SINGLE_BRACE_END, SINGLE_BRACE_START, |
+ VARIABLE_ATTRIBUTE_SEPARATOR, VARIABLE_TAG_END, VARIABLE_TAG_START, |
+ Context, Node, NodeList, TemplateSyntaxError, VariableDoesNotExist, |
+ kwarg_re, render_value_in_context, token_kwargs, |
+) |
+from .defaultfilters import date |
+from .library import Library |
+from .smartif import IfParser, Literal |
+ |
+register = Library() |
+ |
+ |
+class AutoEscapeControlNode(Node): |
+ """Implements the actions of the autoescape tag.""" |
+ def __init__(self, setting, nodelist): |
+ self.setting, self.nodelist = setting, nodelist |
+ |
+ def render(self, context): |
+ old_setting = context.autoescape |
+ context.autoescape = self.setting |
+ output = self.nodelist.render(context) |
+ context.autoescape = old_setting |
+ if self.setting: |
+ return mark_safe(output) |
+ else: |
+ return output |
+ |
+ |
+class CommentNode(Node): |
+ def render(self, context): |
+ return '' |
+ |
+ |
+class CsrfTokenNode(Node): |
+ def render(self, context): |
+ csrf_token = context.get('csrf_token') |
+ if csrf_token: |
+ if csrf_token == 'NOTPROVIDED': |
+ return format_html("") |
+ else: |
+ return format_html("<input type='hidden' name='csrfmiddlewaretoken' value='{}' />", csrf_token) |
+ else: |
+ # It's very probable that the token is missing because of |
+ # misconfiguration, so we raise a warning |
+ if settings.DEBUG: |
+ warnings.warn( |
+ "A {% csrf_token %} was used in a template, but the context " |
+ "did not provide the value. This is usually caused by not " |
+ "using RequestContext." |
+ ) |
+ return '' |
+ |
+ |
+class CycleNode(Node): |
+ def __init__(self, cyclevars, variable_name=None, silent=False): |
+ self.cyclevars = cyclevars |
+ self.variable_name = variable_name |
+ self.silent = silent |
+ |
+ def render(self, context): |
+ if self not in context.render_context: |
+ # First time the node is rendered in template |
+ context.render_context[self] = itertools_cycle(self.cyclevars) |
+ cycle_iter = context.render_context[self] |
+ value = next(cycle_iter).resolve(context) |
+ if self.variable_name: |
+ context.set_upward(self.variable_name, value) |
+ if self.silent: |
+ return '' |
+ return render_value_in_context(value, context) |
+ |
+ def reset(self, context): |
+ """ |
+ Reset the cycle iteration back to the beginning. |
+ """ |
+ context.render_context[self] = itertools_cycle(self.cyclevars) |
+ |
+ |
+class DebugNode(Node): |
+ def render(self, context): |
+ from pprint import pformat |
+ output = [force_text(pformat(val)) for val in context] |
+ output.append('\n\n') |
+ output.append(force_text(pformat(sys.modules))) |
+ return ''.join(output) |
+ |
+ |
+class FilterNode(Node): |
+ def __init__(self, filter_expr, nodelist): |
+ self.filter_expr, self.nodelist = filter_expr, nodelist |
+ |
+ def render(self, context): |
+ output = self.nodelist.render(context) |
+ # Apply filters. |
+ with context.push(var=output): |
+ return self.filter_expr.resolve(context) |
+ |
+ |
+class FirstOfNode(Node): |
+ def __init__(self, variables, asvar=None): |
+ self.vars = variables |
+ self.asvar = asvar |
+ |
+ def render(self, context): |
+ for var in self.vars: |
+ value = var.resolve(context, True) |
+ if value: |
+ first = render_value_in_context(value, context) |
+ if self.asvar: |
+ context[self.asvar] = first |
+ return '' |
+ return first |
+ return '' |
+ |
+ |
+class ForNode(Node): |
+ child_nodelists = ('nodelist_loop', 'nodelist_empty') |
+ |
+ def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None): |
+ self.loopvars, self.sequence = loopvars, sequence |
+ self.is_reversed = is_reversed |
+ self.nodelist_loop = nodelist_loop |
+ if nodelist_empty is None: |
+ self.nodelist_empty = NodeList() |
+ else: |
+ self.nodelist_empty = nodelist_empty |
+ |
+ def __repr__(self): |
+ reversed_text = ' reversed' if self.is_reversed else '' |
+ return "<For Node: for %s in %s, tail_len: %d%s>" % \ |
+ (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop), |
+ reversed_text) |
+ |
+ def __iter__(self): |
+ for node in self.nodelist_loop: |
+ yield node |
+ for node in self.nodelist_empty: |
+ yield node |
+ |
+ def render(self, context): |
+ if 'forloop' in context: |
+ parentloop = context['forloop'] |
+ else: |
+ parentloop = {} |
+ with context.push(): |
+ try: |
+ values = self.sequence.resolve(context, True) |
+ except VariableDoesNotExist: |
+ values = [] |
+ if values is None: |
+ values = [] |
+ if not hasattr(values, '__len__'): |
+ values = list(values) |
+ len_values = len(values) |
+ if len_values < 1: |
+ return self.nodelist_empty.render(context) |
+ nodelist = [] |
+ if self.is_reversed: |
+ values = reversed(values) |
+ num_loopvars = len(self.loopvars) |
+ unpack = num_loopvars > 1 |
+ # Create a forloop value in the context. We'll update counters on each |
+ # iteration just below. |
+ loop_dict = context['forloop'] = {'parentloop': parentloop} |
+ for i, item in enumerate(values): |
+ # Shortcuts for current loop iteration number. |
+ loop_dict['counter0'] = i |
+ loop_dict['counter'] = i + 1 |
+ # Reverse counter iteration numbers. |
+ loop_dict['revcounter'] = len_values - i |
+ loop_dict['revcounter0'] = len_values - i - 1 |
+ # Boolean values designating first and last times through loop. |
+ loop_dict['first'] = (i == 0) |
+ loop_dict['last'] = (i == len_values - 1) |
+ |
+ pop_context = False |
+ if unpack: |
+ # If there are multiple loop variables, unpack the item into |
+ # them. |
+ try: |
+ len_item = len(item) |
+ except TypeError: # not an iterable |
+ len_item = 1 |
+ # Check loop variable count before unpacking |
+ if num_loopvars != len_item: |
+ raise ValueError( |
+ "Need {} values to unpack in for loop; got {}. " |
+ .format(num_loopvars, len_item), |
+ ) |
+ unpacked_vars = dict(zip(self.loopvars, item)) |
+ pop_context = True |
+ context.update(unpacked_vars) |
+ else: |
+ context[self.loopvars[0]] = item |
+ |
+ for node in self.nodelist_loop: |
+ nodelist.append(node.render_annotated(context)) |
+ |
+ if pop_context: |
+ # The loop variables were pushed on to the context so pop them |
+ # off again. This is necessary because the tag lets the length |
+ # of loopvars differ to the length of each set of items and we |
+ # don't want to leave any vars from the previous loop on the |
+ # context. |
+ context.pop() |
+ return mark_safe(''.join(force_text(n) for n in nodelist)) |
+ |
+ |
+class IfChangedNode(Node): |
+ child_nodelists = ('nodelist_true', 'nodelist_false') |
+ |
+ def __init__(self, nodelist_true, nodelist_false, *varlist): |
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false |
+ self._varlist = varlist |
+ |
+ def render(self, context): |
+ # Init state storage |
+ state_frame = self._get_context_stack_frame(context) |
+ if self not in state_frame: |
+ state_frame[self] = None |
+ |
+ nodelist_true_output = None |
+ try: |
+ if self._varlist: |
+ # Consider multiple parameters. This automatically behaves |
+ # like an OR evaluation of the multiple variables. |
+ compare_to = [var.resolve(context, True) for var in self._varlist] |
+ else: |
+ # The "{% ifchanged %}" syntax (without any variables) compares the rendered output. |
+ compare_to = nodelist_true_output = self.nodelist_true.render(context) |
+ except VariableDoesNotExist: |
+ compare_to = None |
+ |
+ if compare_to != state_frame[self]: |
+ state_frame[self] = compare_to |
+ # render true block if not already rendered |
+ return nodelist_true_output or self.nodelist_true.render(context) |
+ elif self.nodelist_false: |
+ return self.nodelist_false.render(context) |
+ return '' |
+ |
+ def _get_context_stack_frame(self, context): |
+ # The Context object behaves like a stack where each template tag can create a new scope. |
+ # Find the place where to store the state to detect changes. |
+ if 'forloop' in context: |
+ # Ifchanged is bound to the local for loop. |
+ # When there is a loop-in-loop, the state is bound to the inner loop, |
+ # so it resets when the outer loop continues. |
+ return context['forloop'] |
+ else: |
+ # Using ifchanged outside loops. Effectively this is a no-op because the state is associated with 'self'. |
+ return context.render_context |
+ |
+ |
+class IfEqualNode(Node): |
+ child_nodelists = ('nodelist_true', 'nodelist_false') |
+ |
+ def __init__(self, var1, var2, nodelist_true, nodelist_false, negate): |
+ self.var1, self.var2 = var1, var2 |
+ self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false |
+ self.negate = negate |
+ |
+ def __repr__(self): |
+ return "<IfEqualNode>" |
+ |
+ def render(self, context): |
+ val1 = self.var1.resolve(context, True) |
+ val2 = self.var2.resolve(context, True) |
+ if (self.negate and val1 != val2) or (not self.negate and val1 == val2): |
+ return self.nodelist_true.render(context) |
+ return self.nodelist_false.render(context) |
+ |
+ |
+class IfNode(Node): |
+ |
+ def __init__(self, conditions_nodelists): |
+ self.conditions_nodelists = conditions_nodelists |
+ |
+ def __repr__(self): |
+ return "<IfNode>" |
+ |
+ def __iter__(self): |
+ for _, nodelist in self.conditions_nodelists: |
+ for node in nodelist: |
+ yield node |
+ |
+ @property |
+ def nodelist(self): |
+ return NodeList(node for _, nodelist in self.conditions_nodelists for node in nodelist) |
+ |
+ def render(self, context): |
+ for condition, nodelist in self.conditions_nodelists: |
+ |
+ if condition is not None: # if / elif clause |
+ try: |
+ match = condition.eval(context) |
+ except VariableDoesNotExist: |
+ match = None |
+ else: # else clause |
+ match = True |
+ |
+ if match: |
+ return nodelist.render(context) |
+ |
+ return '' |
+ |
+ |
+class LoremNode(Node): |
+ def __init__(self, count, method, common): |
+ self.count, self.method, self.common = count, method, common |
+ |
+ def render(self, context): |
+ try: |
+ count = int(self.count.resolve(context)) |
+ except (ValueError, TypeError): |
+ count = 1 |
+ if self.method == 'w': |
+ return words(count, common=self.common) |
+ else: |
+ paras = paragraphs(count, common=self.common) |
+ if self.method == 'p': |
+ paras = ['<p>%s</p>' % p for p in paras] |
+ return '\n\n'.join(paras) |
+ |
+ |
+GroupedResult = namedtuple('GroupedResult', ['grouper', 'list']) |
+ |
+ |
+class RegroupNode(Node): |
+ def __init__(self, target, expression, var_name): |
+ self.target, self.expression = target, expression |
+ self.var_name = var_name |
+ |
+ def resolve_expression(self, obj, context): |
+ # This method is called for each object in self.target. See regroup() |
+ # for the reason why we temporarily put the object in the context. |
+ context[self.var_name] = obj |
+ return self.expression.resolve(context, True) |
+ |
+ def render(self, context): |
+ obj_list = self.target.resolve(context, True) |
+ if obj_list is None: |
+ # target variable wasn't found in context; fail silently. |
+ context[self.var_name] = [] |
+ return '' |
+ # List of dictionaries in the format: |
+ # {'grouper': 'key', 'list': [list of contents]}. |
+ context[self.var_name] = [ |
+ GroupedResult(grouper=key, list=list(val)) |
+ for key, val in |
+ groupby(obj_list, lambda obj: self.resolve_expression(obj, context)) |
+ ] |
+ return '' |
+ |
+ |
+class LoadNode(Node): |
+ def render(self, context): |
+ return '' |
+ |
+ |
+class NowNode(Node): |
+ def __init__(self, format_string, asvar=None): |
+ self.format_string = format_string |
+ self.asvar = asvar |
+ |
+ def render(self, context): |
+ tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None |
+ formatted = date(datetime.now(tz=tzinfo), self.format_string) |
+ |
+ if self.asvar: |
+ context[self.asvar] = formatted |
+ return '' |
+ else: |
+ return formatted |
+ |
+ |
+class ResetCycleNode(Node): |
+ def __init__(self, node): |
+ self.node = node |
+ |
+ def render(self, context): |
+ self.node.reset(context) |
+ return '' |
+ |
+ |
+class SpacelessNode(Node): |
+ def __init__(self, nodelist): |
+ self.nodelist = nodelist |
+ |
+ def render(self, context): |
+ from django.utils.html import strip_spaces_between_tags |
+ return strip_spaces_between_tags(self.nodelist.render(context).strip()) |
+ |
+ |
+class TemplateTagNode(Node): |
+ mapping = {'openblock': BLOCK_TAG_START, |
+ 'closeblock': BLOCK_TAG_END, |
+ 'openvariable': VARIABLE_TAG_START, |
+ 'closevariable': VARIABLE_TAG_END, |
+ 'openbrace': SINGLE_BRACE_START, |
+ 'closebrace': SINGLE_BRACE_END, |
+ 'opencomment': COMMENT_TAG_START, |
+ 'closecomment': COMMENT_TAG_END, |
+ } |
+ |
+ def __init__(self, tagtype): |
+ self.tagtype = tagtype |
+ |
+ def render(self, context): |
+ return self.mapping.get(self.tagtype, '') |
+ |
+ |
+class URLNode(Node): |
+ def __init__(self, view_name, args, kwargs, asvar): |
+ self.view_name = view_name |
+ self.args = args |
+ self.kwargs = kwargs |
+ self.asvar = asvar |
+ |
+ def render(self, context): |
+ from django.urls import reverse, NoReverseMatch |
+ args = [arg.resolve(context) for arg in self.args] |
+ kwargs = { |
+ force_text(k, 'ascii'): v.resolve(context) |
+ for k, v in self.kwargs.items() |
+ } |
+ view_name = self.view_name.resolve(context) |
+ try: |
+ current_app = context.request.current_app |
+ except AttributeError: |
+ try: |
+ current_app = context.request.resolver_match.namespace |
+ except AttributeError: |
+ current_app = None |
+ # Try to look up the URL. If it fails, raise NoReverseMatch unless the |
+ # {% url ... as var %} construct is used, in which case return nothing. |
+ url = '' |
+ try: |
+ url = reverse(view_name, args=args, kwargs=kwargs, current_app=current_app) |
+ except NoReverseMatch: |
+ if self.asvar is None: |
+ raise |
+ |
+ if self.asvar: |
+ context[self.asvar] = url |
+ return '' |
+ else: |
+ if context.autoescape: |
+ url = conditional_escape(url) |
+ return url |
+ |
+ |
+class VerbatimNode(Node): |
+ def __init__(self, content): |
+ self.content = content |
+ |
+ def render(self, context): |
+ return self.content |
+ |
+ |
+class WidthRatioNode(Node): |
+ def __init__(self, val_expr, max_expr, max_width, asvar=None): |
+ self.val_expr = val_expr |
+ self.max_expr = max_expr |
+ self.max_width = max_width |
+ self.asvar = asvar |
+ |
+ def render(self, context): |
+ try: |
+ value = self.val_expr.resolve(context) |
+ max_value = self.max_expr.resolve(context) |
+ max_width = int(self.max_width.resolve(context)) |
+ except VariableDoesNotExist: |
+ return '' |
+ except (ValueError, TypeError): |
+ raise TemplateSyntaxError("widthratio final argument must be a number") |
+ try: |
+ value = float(value) |
+ max_value = float(max_value) |
+ ratio = (value / max_value) * max_width |
+ result = str(int(round(ratio))) |
+ except ZeroDivisionError: |
+ return '0' |
+ except (ValueError, TypeError, OverflowError): |
+ return '' |
+ |
+ if self.asvar: |
+ context[self.asvar] = result |
+ return '' |
+ else: |
+ return result |
+ |
+ |
+class WithNode(Node): |
+ def __init__(self, var, name, nodelist, extra_context=None): |
+ self.nodelist = nodelist |
+ # var and name are legacy attributes, being left in case they are used |
+ # by third-party subclasses of this Node. |
+ self.extra_context = extra_context or {} |
+ if name: |
+ self.extra_context[name] = var |
+ |
+ def __repr__(self): |
+ return "<WithNode>" |
+ |
+ def render(self, context): |
+ values = {key: val.resolve(context) for key, val in |
+ six.iteritems(self.extra_context)} |
+ with context.push(**values): |
+ return self.nodelist.render(context) |
+ |
+ |
+@register.tag |
+def autoescape(parser, token): |
+ """ |
+ Force autoescape behavior for this block. |
+ """ |
+ # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments |
+ args = token.contents.split() |
+ if len(args) != 2: |
+ raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.") |
+ arg = args[1] |
+ if arg not in ('on', 'off'): |
+ raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'") |
+ nodelist = parser.parse(('endautoescape',)) |
+ parser.delete_first_token() |
+ return AutoEscapeControlNode((arg == 'on'), nodelist) |
+ |
+ |
+@register.tag |
+def comment(parser, token): |
+ """ |
+ Ignores everything between ``{% comment %}`` and ``{% endcomment %}``. |
+ """ |
+ parser.skip_past('endcomment') |
+ return CommentNode() |
+ |
+ |
+@register.tag |
+def cycle(parser, token): |
+ """ |
+ Cycles among the given strings each time this tag is encountered. |
+ |
+ Within a loop, cycles among the given strings each time through |
+ the loop:: |
+ |
+ {% for o in some_list %} |
+ <tr class="{% cycle 'row1' 'row2' %}"> |
+ ... |
+ </tr> |
+ {% endfor %} |
+ |
+ Outside of a loop, give the values a unique name the first time you call |
+ it, then use that name each successive time through:: |
+ |
+ <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr> |
+ <tr class="{% cycle rowcolors %}">...</tr> |
+ <tr class="{% cycle rowcolors %}">...</tr> |
+ |
+ You can use any number of values, separated by spaces. Commas can also |
+ be used to separate values; if a comma is used, the cycle values are |
+ interpreted as literal strings. |
+ |
+ The optional flag "silent" can be used to prevent the cycle declaration |
+ from returning any value:: |
+ |
+ {% for o in some_list %} |
+ {% cycle 'row1' 'row2' as rowcolors silent %} |
+ <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr> |
+ {% endfor %} |
+ """ |
+ # Note: This returns the exact same node on each {% cycle name %} call; |
+ # that is, the node object returned from {% cycle a b c as name %} and the |
+ # one returned from {% cycle name %} are the exact same object. This |
+ # shouldn't cause problems (heh), but if it does, now you know. |
+ # |
+ # Ugly hack warning: This stuffs the named template dict into parser so |
+ # that names are only unique within each template (as opposed to using |
+ # a global variable, which would make cycle names have to be unique across |
+ # *all* templates. |
+ # |
+ # It keeps the last node in the parser to be able to reset it with |
+ # {% resetcycle %}. |
+ |
+ args = token.split_contents() |
+ |
+ if len(args) < 2: |
+ raise TemplateSyntaxError("'cycle' tag requires at least two arguments") |
+ |
+ if len(args) == 2: |
+ # {% cycle foo %} case. |
+ name = args[1] |
+ if not hasattr(parser, '_named_cycle_nodes'): |
+ raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name) |
+ if name not in parser._named_cycle_nodes: |
+ raise TemplateSyntaxError("Named cycle '%s' does not exist" % name) |
+ return parser._named_cycle_nodes[name] |
+ |
+ as_form = False |
+ |
+ if len(args) > 4: |
+ # {% cycle ... as foo [silent] %} case. |
+ if args[-3] == "as": |
+ if args[-1] != "silent": |
+ raise TemplateSyntaxError("Only 'silent' flag is allowed after cycle's name, not '%s'." % args[-1]) |
+ as_form = True |
+ silent = True |
+ args = args[:-1] |
+ elif args[-2] == "as": |
+ as_form = True |
+ silent = False |
+ |
+ if as_form: |
+ name = args[-1] |
+ values = [parser.compile_filter(arg) for arg in args[1:-2]] |
+ node = CycleNode(values, name, silent=silent) |
+ if not hasattr(parser, '_named_cycle_nodes'): |
+ parser._named_cycle_nodes = {} |
+ parser._named_cycle_nodes[name] = node |
+ else: |
+ values = [parser.compile_filter(arg) for arg in args[1:]] |
+ node = CycleNode(values) |
+ parser._last_cycle_node = node |
+ return node |
+ |
+ |
+@register.tag |
+def csrf_token(parser, token): |
+ return CsrfTokenNode() |
+ |
+ |
+@register.tag |
+def debug(parser, token): |
+ """ |
+ Outputs a whole load of debugging information, including the current |
+ context and imported modules. |
+ |
+ Sample usage:: |
+ |
+ <pre> |
+ {% debug %} |
+ </pre> |
+ """ |
+ return DebugNode() |
+ |
+ |
+@register.tag('filter') |
+def do_filter(parser, token): |
+ """ |
+ Filters the contents of the block through variable filters. |
+ |
+ Filters can also be piped through each other, and they can have |
+ arguments -- just like in variable syntax. |
+ |
+ Sample usage:: |
+ |
+ {% filter force_escape|lower %} |
+ This text will be HTML-escaped, and will appear in lowercase. |
+ {% endfilter %} |
+ |
+ Note that the ``escape`` and ``safe`` filters are not acceptable arguments. |
+ Instead, use the ``autoescape`` tag to manage autoescaping for blocks of |
+ template code. |
+ """ |
+ # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments |
+ _, rest = token.contents.split(None, 1) |
+ filter_expr = parser.compile_filter("var|%s" % (rest)) |
+ for func, unused in filter_expr.filters: |
+ filter_name = getattr(func, '_filter_name', None) |
+ if filter_name in ('escape', 'safe'): |
+ raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % filter_name) |
+ nodelist = parser.parse(('endfilter',)) |
+ parser.delete_first_token() |
+ return FilterNode(filter_expr, nodelist) |
+ |
+ |
+@register.tag |
+def firstof(parser, token): |
+ """ |
+ Outputs the first variable passed that is not False. |
+ |
+ Outputs nothing if all the passed variables are False. |
+ |
+ Sample usage:: |
+ |
+ {% firstof var1 var2 var3 as myvar %} |
+ |
+ This is equivalent to:: |
+ |
+ {% if var1 %} |
+ {{ var1 }} |
+ {% elif var2 %} |
+ {{ var2 }} |
+ {% elif var3 %} |
+ {{ var3 }} |
+ {% endif %} |
+ |
+ but obviously much cleaner! |
+ |
+ You can also use a literal string as a fallback value in case all |
+ passed variables are False:: |
+ |
+ {% firstof var1 var2 var3 "fallback value" %} |
+ |
+ If you want to disable auto-escaping of variables you can use:: |
+ |
+ {% autoescape off %} |
+ {% firstof var1 var2 var3 "<strong>fallback value</strong>" %} |
+ {% autoescape %} |
+ |
+ Or if only some variables should be escaped, you can use:: |
+ |
+ {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %} |
+ """ |
+ bits = token.split_contents()[1:] |
+ asvar = None |
+ if len(bits) < 1: |
+ raise TemplateSyntaxError("'firstof' statement requires at least one argument") |
+ |
+ if len(bits) >= 2 and bits[-2] == 'as': |
+ asvar = bits[-1] |
+ bits = bits[:-2] |
+ return FirstOfNode([parser.compile_filter(bit) for bit in bits], asvar) |
+ |
+ |
+@register.tag('for') |
+def do_for(parser, token): |
+ """ |
+ Loops over each item in an array. |
+ |
+ For example, to display a list of athletes given ``athlete_list``:: |
+ |
+ <ul> |
+ {% for athlete in athlete_list %} |
+ <li>{{ athlete.name }}</li> |
+ {% endfor %} |
+ </ul> |
+ |
+ You can loop over a list in reverse by using |
+ ``{% for obj in list reversed %}``. |
+ |
+ You can also unpack multiple values from a two-dimensional array:: |
+ |
+ {% for key,value in dict.items %} |
+ {{ key }}: {{ value }} |
+ {% endfor %} |
+ |
+ The ``for`` tag can take an optional ``{% empty %}`` clause that will |
+ be displayed if the given array is empty or could not be found:: |
+ |
+ <ul> |
+ {% for athlete in athlete_list %} |
+ <li>{{ athlete.name }}</li> |
+ {% empty %} |
+ <li>Sorry, no athletes in this list.</li> |
+ {% endfor %} |
+ <ul> |
+ |
+ The above is equivalent to -- but shorter, cleaner, and possibly faster |
+ than -- the following:: |
+ |
+ <ul> |
+ {% if athlete_list %} |
+ {% for athlete in athlete_list %} |
+ <li>{{ athlete.name }}</li> |
+ {% endfor %} |
+ {% else %} |
+ <li>Sorry, no athletes in this list.</li> |
+ {% endif %} |
+ </ul> |
+ |
+ The for loop sets a number of variables available within the loop: |
+ |
+ ========================== ================================================ |
+ Variable Description |
+ ========================== ================================================ |
+ ``forloop.counter`` The current iteration of the loop (1-indexed) |
+ ``forloop.counter0`` The current iteration of the loop (0-indexed) |
+ ``forloop.revcounter`` The number of iterations from the end of the |
+ loop (1-indexed) |
+ ``forloop.revcounter0`` The number of iterations from the end of the |
+ loop (0-indexed) |
+ ``forloop.first`` True if this is the first time through the loop |
+ ``forloop.last`` True if this is the last time through the loop |
+ ``forloop.parentloop`` For nested loops, this is the loop "above" the |
+ current one |
+ ========================== ================================================ |
+ """ |
+ bits = token.split_contents() |
+ if len(bits) < 4: |
+ raise TemplateSyntaxError("'for' statements should have at least four" |
+ " words: %s" % token.contents) |
+ |
+ is_reversed = bits[-1] == 'reversed' |
+ in_index = -3 if is_reversed else -2 |
+ if bits[in_index] != 'in': |
+ raise TemplateSyntaxError("'for' statements should use the format" |
+ " 'for x in y': %s" % token.contents) |
+ |
+ invalid_chars = frozenset((' ', '"', "'", FILTER_SEPARATOR)) |
+ loopvars = re.split(r' *, *', ' '.join(bits[1:in_index])) |
+ for var in loopvars: |
+ if not var or not invalid_chars.isdisjoint(var): |
+ raise TemplateSyntaxError("'for' tag received an invalid argument:" |
+ " %s" % token.contents) |
+ |
+ sequence = parser.compile_filter(bits[in_index + 1]) |
+ nodelist_loop = parser.parse(('empty', 'endfor',)) |
+ token = parser.next_token() |
+ if token.contents == 'empty': |
+ nodelist_empty = parser.parse(('endfor',)) |
+ parser.delete_first_token() |
+ else: |
+ nodelist_empty = None |
+ return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty) |
+ |
+ |
+def do_ifequal(parser, token, negate): |
+ bits = list(token.split_contents()) |
+ if len(bits) != 3: |
+ raise TemplateSyntaxError("%r takes two arguments" % bits[0]) |
+ end_tag = 'end' + bits[0] |
+ nodelist_true = parser.parse(('else', end_tag)) |
+ token = parser.next_token() |
+ if token.contents == 'else': |
+ nodelist_false = parser.parse((end_tag,)) |
+ parser.delete_first_token() |
+ else: |
+ nodelist_false = NodeList() |
+ val1 = parser.compile_filter(bits[1]) |
+ val2 = parser.compile_filter(bits[2]) |
+ return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate) |
+ |
+ |
+@register.tag |
+def ifequal(parser, token): |
+ """ |
+ Outputs the contents of the block if the two arguments equal each other. |
+ |
+ Examples:: |
+ |
+ {% ifequal user.id comment.user_id %} |
+ ... |
+ {% endifequal %} |
+ |
+ {% ifnotequal user.id comment.user_id %} |
+ ... |
+ {% else %} |
+ ... |
+ {% endifnotequal %} |
+ """ |
+ return do_ifequal(parser, token, False) |
+ |
+ |
+@register.tag |
+def ifnotequal(parser, token): |
+ """ |
+ Outputs the contents of the block if the two arguments are not equal. |
+ See ifequal. |
+ """ |
+ return do_ifequal(parser, token, True) |
+ |
+ |
+class TemplateLiteral(Literal): |
+ def __init__(self, value, text): |
+ self.value = value |
+ self.text = text # for better error messages |
+ |
+ def display(self): |
+ return self.text |
+ |
+ def eval(self, context): |
+ return self.value.resolve(context, ignore_failures=True) |
+ |
+ |
+class TemplateIfParser(IfParser): |
+ error_class = TemplateSyntaxError |
+ |
+ def __init__(self, parser, *args, **kwargs): |
+ self.template_parser = parser |
+ super(TemplateIfParser, self).__init__(*args, **kwargs) |
+ |
+ def create_var(self, value): |
+ return TemplateLiteral(self.template_parser.compile_filter(value), value) |
+ |
+ |
+@register.tag('if') |
+def do_if(parser, token): |
+ """ |
+ The ``{% if %}`` tag evaluates a variable, and if that variable is "true" |
+ (i.e., exists, is not empty, and is not a false boolean value), the |
+ contents of the block are output: |
+ |
+ :: |
+ |
+ {% if athlete_list %} |
+ Number of athletes: {{ athlete_list|count }} |
+ {% elif athlete_in_locker_room_list %} |
+ Athletes should be out of the locker room soon! |
+ {% else %} |
+ No athletes. |
+ {% endif %} |
+ |
+ In the above, if ``athlete_list`` is not empty, the number of athletes will |
+ be displayed by the ``{{ athlete_list|count }}`` variable. |
+ |
+ As you can see, the ``if`` tag may take one or several `` {% elif %}`` |
+ clauses, as well as an ``{% else %}`` clause that will be displayed if all |
+ previous conditions fail. These clauses are optional. |
+ |
+ ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of |
+ variables or to negate a given variable:: |
+ |
+ {% if not athlete_list %} |
+ There are no athletes. |
+ {% endif %} |
+ |
+ {% if athlete_list or coach_list %} |
+ There are some athletes or some coaches. |
+ {% endif %} |
+ |
+ {% if athlete_list and coach_list %} |
+ Both athletes and coaches are available. |
+ {% endif %} |
+ |
+ {% if not athlete_list or coach_list %} |
+ There are no athletes, or there are some coaches. |
+ {% endif %} |
+ |
+ {% if athlete_list and not coach_list %} |
+ There are some athletes and absolutely no coaches. |
+ {% endif %} |
+ |
+ Comparison operators are also available, and the use of filters is also |
+ allowed, for example:: |
+ |
+ {% if articles|length >= 5 %}...{% endif %} |
+ |
+ Arguments and operators _must_ have a space between them, so |
+ ``{% if 1>2 %}`` is not a valid if tag. |
+ |
+ All supported operators are: ``or``, ``and``, ``in``, ``not in`` |
+ ``==``, ``!=``, ``>``, ``>=``, ``<`` and ``<=``. |
+ |
+ Operator precedence follows Python. |
+ """ |
+ # {% if ... %} |
+ bits = token.split_contents()[1:] |
+ condition = TemplateIfParser(parser, bits).parse() |
+ nodelist = parser.parse(('elif', 'else', 'endif')) |
+ conditions_nodelists = [(condition, nodelist)] |
+ token = parser.next_token() |
+ |
+ # {% elif ... %} (repeatable) |
+ while token.contents.startswith('elif'): |
+ bits = token.split_contents()[1:] |
+ condition = TemplateIfParser(parser, bits).parse() |
+ nodelist = parser.parse(('elif', 'else', 'endif')) |
+ conditions_nodelists.append((condition, nodelist)) |
+ token = parser.next_token() |
+ |
+ # {% else %} (optional) |
+ if token.contents == 'else': |
+ nodelist = parser.parse(('endif',)) |
+ conditions_nodelists.append((None, nodelist)) |
+ token = parser.next_token() |
+ |
+ # {% endif %} |
+ if token.contents != 'endif': |
+ raise TemplateSyntaxError('Malformed template tag at line {0}: "{1}"'.format(token.lineno, token.contents)) |
+ |
+ return IfNode(conditions_nodelists) |
+ |
+ |
+@register.tag |
+def ifchanged(parser, token): |
+ """ |
+ Checks if a value has changed from the last iteration of a loop. |
+ |
+ The ``{% ifchanged %}`` block tag is used within a loop. It has two |
+ possible uses. |
+ |
+ 1. Checks its own rendered contents against its previous state and only |
+ displays the content if it has changed. For example, this displays a |
+ list of days, only displaying the month if it changes:: |
+ |
+ <h1>Archive for {{ year }}</h1> |
+ |
+ {% for date in days %} |
+ {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %} |
+ <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a> |
+ {% endfor %} |
+ |
+ 2. If given one or more variables, check whether any variable has changed. |
+ For example, the following shows the date every time it changes, while |
+ showing the hour if either the hour or the date has changed:: |
+ |
+ {% for date in days %} |
+ {% ifchanged date.date %} {{ date.date }} {% endifchanged %} |
+ {% ifchanged date.hour date.date %} |
+ {{ date.hour }} |
+ {% endifchanged %} |
+ {% endfor %} |
+ """ |
+ bits = token.split_contents() |
+ nodelist_true = parser.parse(('else', 'endifchanged')) |
+ token = parser.next_token() |
+ if token.contents == 'else': |
+ nodelist_false = parser.parse(('endifchanged',)) |
+ parser.delete_first_token() |
+ else: |
+ nodelist_false = NodeList() |
+ values = [parser.compile_filter(bit) for bit in bits[1:]] |
+ return IfChangedNode(nodelist_true, nodelist_false, *values) |
+ |
+ |
+def find_library(parser, name): |
+ try: |
+ return parser.libraries[name] |
+ except KeyError: |
+ raise TemplateSyntaxError( |
+ "'%s' is not a registered tag library. Must be one of:\n%s" % ( |
+ name, "\n".join(sorted(parser.libraries.keys())), |
+ ), |
+ ) |
+ |
+ |
+def load_from_library(library, label, names): |
+ """ |
+ Return a subset of tags and filters from a library. |
+ """ |
+ subset = Library() |
+ for name in names: |
+ found = False |
+ if name in library.tags: |
+ found = True |
+ subset.tags[name] = library.tags[name] |
+ if name in library.filters: |
+ found = True |
+ subset.filters[name] = library.filters[name] |
+ if found is False: |
+ raise TemplateSyntaxError( |
+ "'%s' is not a valid tag or filter in tag library '%s'" % ( |
+ name, label, |
+ ), |
+ ) |
+ return subset |
+ |
+ |
+@register.tag |
+def load(parser, token): |
+ """ |
+ Loads a custom template tag library into the parser. |
+ |
+ For example, to load the template tags in |
+ ``django/templatetags/news/photos.py``:: |
+ |
+ {% load news.photos %} |
+ |
+ Can also be used to load an individual tag/filter from |
+ a library:: |
+ |
+ {% load byline from news %} |
+ """ |
+ # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments |
+ bits = token.contents.split() |
+ if len(bits) >= 4 and bits[-2] == "from": |
+ # from syntax is used; load individual tags from the library |
+ name = bits[-1] |
+ lib = find_library(parser, name) |
+ subset = load_from_library(lib, name, bits[1:-2]) |
+ parser.add_library(subset) |
+ else: |
+ # one or more libraries are specified; load and add them to the parser |
+ for name in bits[1:]: |
+ lib = find_library(parser, name) |
+ parser.add_library(lib) |
+ return LoadNode() |
+ |
+ |
+@register.tag |
+def lorem(parser, token): |
+ """ |
+ Creates random Latin text useful for providing test data in templates. |
+ |
+ Usage format:: |
+ |
+ {% lorem [count] [method] [random] %} |
+ |
+ ``count`` is a number (or variable) containing the number of paragraphs or |
+ words to generate (default is 1). |
+ |
+ ``method`` is either ``w`` for words, ``p`` for HTML paragraphs, ``b`` for |
+ plain-text paragraph blocks (default is ``b``). |
+ |
+ ``random`` is the word ``random``, which if given, does not use the common |
+ paragraph (starting "Lorem ipsum dolor sit amet, consectetuer..."). |
+ |
+ Examples: |
+ |
+ * ``{% lorem %}`` will output the common "lorem ipsum" paragraph |
+ * ``{% lorem 3 p %}`` will output the common "lorem ipsum" paragraph |
+ and two random paragraphs each wrapped in HTML ``<p>`` tags |
+ * ``{% lorem 2 w random %}`` will output two random latin words |
+ """ |
+ bits = list(token.split_contents()) |
+ tagname = bits[0] |
+ # Random bit |
+ common = bits[-1] != 'random' |
+ if not common: |
+ bits.pop() |
+ # Method bit |
+ if bits[-1] in ('w', 'p', 'b'): |
+ method = bits.pop() |
+ else: |
+ method = 'b' |
+ # Count bit |
+ if len(bits) > 1: |
+ count = bits.pop() |
+ else: |
+ count = '1' |
+ count = parser.compile_filter(count) |
+ if len(bits) != 1: |
+ raise TemplateSyntaxError("Incorrect format for %r tag" % tagname) |
+ return LoremNode(count, method, common) |
+ |
+ |
+@register.tag |
+def now(parser, token): |
+ """ |
+ Displays the date, formatted according to the given string. |
+ |
+ Uses the same format as PHP's ``date()`` function; see http://php.net/date |
+ for all the possible values. |
+ |
+ Sample usage:: |
+ |
+ It is {% now "jS F Y H:i" %} |
+ """ |
+ bits = token.split_contents() |
+ asvar = None |
+ if len(bits) == 4 and bits[-2] == 'as': |
+ asvar = bits[-1] |
+ bits = bits[:-2] |
+ if len(bits) != 2: |
+ raise TemplateSyntaxError("'now' statement takes one argument") |
+ format_string = bits[1][1:-1] |
+ return NowNode(format_string, asvar) |
+ |
+ |
+@register.tag |
+def regroup(parser, token): |
+ """ |
+ Regroups a list of alike objects by a common attribute. |
+ |
+ This complex tag is best illustrated by use of an example: say that |
+ ``musicians`` is a list of ``Musician`` objects that have ``name`` and |
+ ``instrument`` attributes, and you'd like to display a list that |
+ looks like: |
+ |
+ * Guitar: |
+ * Django Reinhardt |
+ * Emily Remler |
+ * Piano: |
+ * Lovie Austin |
+ * Bud Powell |
+ * Trumpet: |
+ * Duke Ellington |
+ |
+ The following snippet of template code would accomplish this dubious task:: |
+ |
+ {% regroup musicians by instrument as grouped %} |
+ <ul> |
+ {% for group in grouped %} |
+ <li>{{ group.grouper }} |
+ <ul> |
+ {% for musician in group.list %} |
+ <li>{{ musician.name }}</li> |
+ {% endfor %} |
+ </ul> |
+ {% endfor %} |
+ </ul> |
+ |
+ As you can see, ``{% regroup %}`` populates a variable with a list of |
+ objects with ``grouper`` and ``list`` attributes. ``grouper`` contains the |
+ item that was grouped by; ``list`` contains the list of objects that share |
+ that ``grouper``. In this case, ``grouper`` would be ``Guitar``, ``Piano`` |
+ and ``Trumpet``, and ``list`` is the list of musicians who play this |
+ instrument. |
+ |
+ Note that ``{% regroup %}`` does not work when the list to be grouped is not |
+ sorted by the key you are grouping by! This means that if your list of |
+ musicians was not sorted by instrument, you'd need to make sure it is sorted |
+ before using it, i.e.:: |
+ |
+ {% regroup musicians|dictsort:"instrument" by instrument as grouped %} |
+ """ |
+ bits = token.split_contents() |
+ if len(bits) != 6: |
+ raise TemplateSyntaxError("'regroup' tag takes five arguments") |
+ target = parser.compile_filter(bits[1]) |
+ if bits[2] != 'by': |
+ raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'") |
+ if bits[4] != 'as': |
+ raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must" |
+ " be 'as'") |
+ var_name = bits[5] |
+ # RegroupNode will take each item in 'target', put it in the context under |
+ # 'var_name', evaluate 'var_name'.'expression' in the current context, and |
+ # group by the resulting value. After all items are processed, it will |
+ # save the final result in the context under 'var_name', thus clearing the |
+ # temporary values. This hack is necessary because the template engine |
+ # doesn't provide a context-aware equivalent of Python's getattr. |
+ expression = parser.compile_filter(var_name + |
+ VARIABLE_ATTRIBUTE_SEPARATOR + |
+ bits[3]) |
+ return RegroupNode(target, expression, var_name) |
+ |
+ |
+@register.tag |
+def resetcycle(parser, token): |
+ """ |
+ Resets a cycle tag. |
+ |
+ If an argument is given, resets the last rendered cycle tag whose name |
+ matches the argument, else resets the last rendered cycle tag (named or |
+ unnamed). |
+ """ |
+ args = token.split_contents() |
+ |
+ if len(args) > 2: |
+ raise TemplateSyntaxError("%r tag accepts at most one argument." % args[0]) |
+ |
+ if len(args) == 2: |
+ name = args[1] |
+ try: |
+ return ResetCycleNode(parser._named_cycle_nodes[name]) |
+ except (AttributeError, KeyError): |
+ raise TemplateSyntaxError("Named cycle '%s' does not exist." % name) |
+ try: |
+ return ResetCycleNode(parser._last_cycle_node) |
+ except AttributeError: |
+ raise TemplateSyntaxError("No cycles in template.") |
+ |
+ |
+@register.tag |
+def spaceless(parser, token): |
+ """ |
+ Removes whitespace between HTML tags, including tab and newline characters. |
+ |
+ Example usage:: |
+ |
+ {% spaceless %} |
+ <p> |
+ <a href="foo/">Foo</a> |
+ </p> |
+ {% endspaceless %} |
+ |
+ This example would return this HTML:: |
+ |
+ <p><a href="foo/">Foo</a></p> |
+ |
+ Only space between *tags* is normalized -- not space between tags and text. |
+ In this example, the space around ``Hello`` won't be stripped:: |
+ |
+ {% spaceless %} |
+ <strong> |
+ Hello |
+ </strong> |
+ {% endspaceless %} |
+ """ |
+ nodelist = parser.parse(('endspaceless',)) |
+ parser.delete_first_token() |
+ return SpacelessNode(nodelist) |
+ |
+ |
+@register.tag |
+def templatetag(parser, token): |
+ """ |
+ Outputs one of the bits used to compose template tags. |
+ |
+ Since the template system has no concept of "escaping", to display one of |
+ the bits used in template tags, you must use the ``{% templatetag %}`` tag. |
+ |
+ The argument tells which template bit to output: |
+ |
+ ================== ======= |
+ Argument Outputs |
+ ================== ======= |
+ ``openblock`` ``{%`` |
+ ``closeblock`` ``%}`` |
+ ``openvariable`` ``{{`` |
+ ``closevariable`` ``}}`` |
+ ``openbrace`` ``{`` |
+ ``closebrace`` ``}`` |
+ ``opencomment`` ``{#`` |
+ ``closecomment`` ``#}`` |
+ ================== ======= |
+ """ |
+ # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments |
+ bits = token.contents.split() |
+ if len(bits) != 2: |
+ raise TemplateSyntaxError("'templatetag' statement takes one argument") |
+ tag = bits[1] |
+ if tag not in TemplateTagNode.mapping: |
+ raise TemplateSyntaxError("Invalid templatetag argument: '%s'." |
+ " Must be one of: %s" % |
+ (tag, list(TemplateTagNode.mapping))) |
+ return TemplateTagNode(tag) |
+ |
+ |
+@register.tag |
+def url(parser, token): |
+ r""" |
+ Return an absolute URL matching the given view with its parameters. |
+ |
+ This is a way to define links that aren't tied to a particular URL |
+ configuration:: |
+ |
+ {% url "url_name" arg1 arg2 %} |
+ |
+ or |
+ |
+ {% url "url_name" name1=value1 name2=value2 %} |
+ |
+ The first argument is a django.conf.urls.url() name. Other arguments are |
+ space-separated values that will be filled in place of positional and |
+ keyword arguments in the URL. Don't mix positional and keyword arguments. |
+ All arguments for the URL must be present. |
+ |
+ For example, if you have a view ``app_name.views.client_details`` taking |
+ the client's id and the corresponding line in a URLconf looks like this:: |
+ |
+ url('^client/(\d+)/$', views.client_details, name='client-detail-view') |
+ |
+ and this app's URLconf is included into the project's URLconf under some |
+ path:: |
+ |
+ url('^clients/', include('app_name.urls')) |
+ |
+ then in a template you can create a link for a certain client like this:: |
+ |
+ {% url "client-detail-view" client.id %} |
+ |
+ The URL will look like ``/clients/client/123/``. |
+ |
+ The first argument may also be the name of a template variable that will be |
+ evaluated to obtain the view name or the URL name, e.g.:: |
+ |
+ {% with url_name="client-detail-view" %} |
+ {% url url_name client.id %} |
+ {% endwith %} |
+ """ |
+ bits = token.split_contents() |
+ if len(bits) < 2: |
+ raise TemplateSyntaxError("'%s' takes at least one argument, the name of a url()." % bits[0]) |
+ viewname = parser.compile_filter(bits[1]) |
+ args = [] |
+ kwargs = {} |
+ asvar = None |
+ bits = bits[2:] |
+ if len(bits) >= 2 and bits[-2] == 'as': |
+ asvar = bits[-1] |
+ bits = bits[:-2] |
+ |
+ if len(bits): |
+ for bit in bits: |
+ match = kwarg_re.match(bit) |
+ if not match: |
+ raise TemplateSyntaxError("Malformed arguments to url tag") |
+ name, value = match.groups() |
+ if name: |
+ kwargs[name] = parser.compile_filter(value) |
+ else: |
+ args.append(parser.compile_filter(value)) |
+ |
+ return URLNode(viewname, args, kwargs, asvar) |
+ |
+ |
+@register.tag |
+def verbatim(parser, token): |
+ """ |
+ Stops the template engine from rendering the contents of this block tag. |
+ |
+ Usage:: |
+ |
+ {% verbatim %} |
+ {% don't process this %} |
+ {% endverbatim %} |
+ |
+ You can also designate a specific closing tag block (allowing the |
+ unrendered use of ``{% endverbatim %}``):: |
+ |
+ {% verbatim myblock %} |
+ ... |
+ {% endverbatim myblock %} |
+ """ |
+ nodelist = parser.parse(('endverbatim',)) |
+ parser.delete_first_token() |
+ return VerbatimNode(nodelist.render(Context())) |
+ |
+ |
+@register.tag |
+def widthratio(parser, token): |
+ """ |
+ For creating bar charts and such, this tag calculates the ratio of a given |
+ value to a maximum value, and then applies that ratio to a constant. |
+ |
+ For example:: |
+ |
+ <img src="bar.png" alt="Bar" |
+ height="10" width="{% widthratio this_value max_value max_width %}" /> |
+ |
+ If ``this_value`` is 175, ``max_value`` is 200, and ``max_width`` is 100, |
+ the image in the above example will be 88 pixels wide |
+ (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88). |
+ |
+ In some cases you might want to capture the result of widthratio in a |
+ variable. It can be useful for instance in a blocktrans like this:: |
+ |
+ {% widthratio this_value max_value max_width as width %} |
+ {% blocktrans %}The width is: {{ width }}{% endblocktrans %} |
+ """ |
+ bits = token.split_contents() |
+ if len(bits) == 4: |
+ tag, this_value_expr, max_value_expr, max_width = bits |
+ asvar = None |
+ elif len(bits) == 6: |
+ tag, this_value_expr, max_value_expr, max_width, as_, asvar = bits |
+ if as_ != 'as': |
+ raise TemplateSyntaxError("Invalid syntax in widthratio tag. Expecting 'as' keyword") |
+ else: |
+ raise TemplateSyntaxError("widthratio takes at least three arguments") |
+ |
+ return WidthRatioNode(parser.compile_filter(this_value_expr), |
+ parser.compile_filter(max_value_expr), |
+ parser.compile_filter(max_width), |
+ asvar=asvar) |
+ |
+ |
+@register.tag('with') |
+def do_with(parser, token): |
+ """ |
+ Adds one or more values to the context (inside of this block) for caching |
+ and easy access. |
+ |
+ For example:: |
+ |
+ {% with total=person.some_sql_method %} |
+ {{ total }} object{{ total|pluralize }} |
+ {% endwith %} |
+ |
+ Multiple values can be added to the context:: |
+ |
+ {% with foo=1 bar=2 %} |
+ ... |
+ {% endwith %} |
+ |
+ The legacy format of ``{% with person.some_sql_method as total %}`` is |
+ still accepted. |
+ """ |
+ bits = token.split_contents() |
+ remaining_bits = bits[1:] |
+ extra_context = token_kwargs(remaining_bits, parser, support_legacy=True) |
+ if not extra_context: |
+ raise TemplateSyntaxError("%r expected at least one variable " |
+ "assignment" % bits[0]) |
+ if remaining_bits: |
+ raise TemplateSyntaxError("%r received an invalid token: %r" % |
+ (bits[0], remaining_bits[0])) |
+ nodelist = parser.parse(('endwith',)) |
+ parser.delete_first_token() |
+ return WithNode(None, None, nodelist, extra_context=extra_context) |