/python/lib/Lib/site-packages/django/template/defaulttags.py
Python | 1365 lines | 1314 code | 15 blank | 36 comment | 43 complexity | 394703b6a00af8e6ae7f146b58e3866b MD5 | raw file
Possible License(s): BSD-3-Clause, Apache-2.0, MPL-2.0-no-copyleft-exception, MIT, EPL-1.0, AGPL-1.0
- """Default tags used by the template system, available to all templates."""
- import sys
- import re
- from itertools import groupby, cycle as itertools_cycle
- from django.template.base import Node, NodeList, Template, Context, Variable
- from django.template.base import TemplateSyntaxError, VariableDoesNotExist, BLOCK_TAG_START, BLOCK_TAG_END, VARIABLE_TAG_START, VARIABLE_TAG_END, SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END
- from django.template.base import get_library, Library, InvalidTemplateLibrary
- from django.template.smartif import IfParser, Literal
- from django.conf import settings
- from django.utils.encoding import smart_str, smart_unicode
- from django.utils.safestring import mark_safe
- register = Library()
- # Regex for token keyword arguments
- kwarg_re = re.compile(r"(?:(\w+)=)?(.+)")
- def token_kwargs(bits, parser, support_legacy=False):
- """
- A utility method for parsing token keyword arguments.
- :param bits: A list containing remainder of the token (split by spaces)
- that is to be checked for arguments. Valid arguments will be removed
- from this list.
- :param support_legacy: If set to true ``True``, the legacy format
- ``1 as foo`` will be accepted. Otherwise, only the standard ``foo=1``
- format is allowed.
- :returns: A dictionary of the arguments retrieved from the ``bits`` token
- list.
- There is no requirement for all remaining token ``bits`` to be keyword
- arguments, so the dictionary will be returned as soon as an invalid
- argument format is reached.
- """
- if not bits:
- return {}
- match = kwarg_re.match(bits[0])
- kwarg_format = match and match.group(1)
- if not kwarg_format:
- if not support_legacy:
- return {}
- if len(bits) < 3 or bits[1] != 'as':
- return {}
- kwargs = {}
- while bits:
- if kwarg_format:
- match = kwarg_re.match(bits[0])
- if not match or not match.group(1):
- return kwargs
- key, value = match.groups()
- del bits[:1]
- else:
- if len(bits) < 3 or bits[1] != 'as':
- return kwargs
- key, value = bits[2], bits[0]
- del bits[:3]
- kwargs[key] = parser.compile_filter(value)
- if bits and not kwarg_format:
- if bits[0] != 'and':
- return kwargs
- del bits[:1]
- return kwargs
- 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', None)
- if csrf_token:
- if csrf_token == 'NOTPROVIDED':
- return mark_safe(u"")
- else:
- return mark_safe(u"<div style='display:none'><input type='hidden' name='csrfmiddlewaretoken' value='%s' /></div>" % csrf_token)
- else:
- # It's very probable that the token is missing because of
- # misconfiguration, so we raise a warning
- from django.conf import settings
- if settings.DEBUG:
- import warnings
- 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 u''
- 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)
- if self.silent:
- return ''
- cycle_iter = context.render_context[self]
- value = cycle_iter.next().resolve(context)
- if self.variable_name:
- context[self.variable_name] = value
- return value
- class DebugNode(Node):
- def render(self, context):
- from pprint import pformat
- output = [pformat(val) for val in context]
- output.append('\n\n')
- output.append(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.
- context.update({'var': output})
- filtered = self.filter_expr.resolve(context)
- context.pop()
- return filtered
- class FirstOfNode(Node):
- def __init__(self, vars):
- self.vars = vars
- def render(self, context):
- for var in self.vars:
- value = var.resolve(context, True)
- if value:
- return smart_unicode(value)
- return u''
- 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 = self.is_reversed and ' reversed' or ''
- 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 = {}
- 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:
- context.pop()
- return self.nodelist_empty.render(context)
- nodelist = NodeList()
- if self.is_reversed:
- values = reversed(values)
- unpack = len(self.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:
- unpacked_vars = dict(zip(self.loopvars, item))
- except TypeError:
- pass
- else:
- pop_context = True
- context.update(unpacked_vars)
- else:
- context[self.loopvars[0]] = item
- for node in self.nodelist_loop:
- nodelist.append(node.render(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()
- context.pop()
- return nodelist.render(context)
- 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._last_seen = None
- self._varlist = varlist
- self._id = str(id(self))
- def render(self, context):
- if 'forloop' in context and self._id not in context['forloop']:
- self._last_seen = None
- context['forloop'][self._id] = 1
- 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:
- compare_to = self.nodelist_true.render(context)
- except VariableDoesNotExist:
- compare_to = None
- if compare_to != self._last_seen:
- firstloop = (self._last_seen == None)
- self._last_seen = compare_to
- content = self.nodelist_true.render(context)
- return content
- elif self.nodelist_false:
- return self.nodelist_false.render(context)
- return ''
- 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):
- child_nodelists = ('nodelist_true', 'nodelist_false')
- def __init__(self, var, nodelist_true, nodelist_false=None):
- self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
- self.var = var
- def __repr__(self):
- return "<If node>"
- def __iter__(self):
- for node in self.nodelist_true:
- yield node
- for node in self.nodelist_false:
- yield node
- def render(self, context):
- try:
- var = self.var.eval(context)
- except VariableDoesNotExist:
- var = None
- if var:
- return self.nodelist_true.render(context)
- else:
- return self.nodelist_false.render(context)
- class RegroupNode(Node):
- def __init__(self, target, expression, var_name):
- self.target, self.expression = target, expression
- self.var_name = var_name
- def render(self, context):
- obj_list = self.target.resolve(context, True)
- if obj_list == 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] = [
- {'grouper': key, 'list': list(val)}
- for key, val in
- groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
- ]
- return ''
- def include_is_allowed(filepath):
- for root in settings.ALLOWED_INCLUDE_ROOTS:
- if filepath.startswith(root):
- return True
- return False
- class SsiNode(Node):
- def __init__(self, filepath, parsed, legacy_filepath=True):
- self.filepath = filepath
- self.parsed = parsed
- self.legacy_filepath = legacy_filepath
- def render(self, context):
- filepath = self.filepath
- if not self.legacy_filepath:
- filepath = filepath.resolve(context)
- if not include_is_allowed(filepath):
- if settings.DEBUG:
- return "[Didn't have permission to include file]"
- else:
- return '' # Fail silently for invalid includes.
- try:
- fp = open(filepath, 'r')
- output = fp.read()
- fp.close()
- except IOError:
- output = ''
- if self.parsed:
- try:
- t = Template(output, name=filepath)
- return t.render(context)
- except TemplateSyntaxError, e:
- if settings.DEBUG:
- return "[Included template had syntax error: %s]" % e
- else:
- return '' # Fail silently for invalid included templates.
- return output
- class LoadNode(Node):
- def render(self, context):
- return ''
- class NowNode(Node):
- def __init__(self, format_string):
- self.format_string = format_string
- def render(self, context):
- from datetime import datetime
- from django.utils.dateformat import DateFormat
- df = DateFormat(datetime.now())
- return df.format(self.format_string)
- 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, legacy_view_name=True):
- self.view_name = view_name
- self.legacy_view_name = legacy_view_name
- self.args = args
- self.kwargs = kwargs
- self.asvar = asvar
- def render(self, context):
- from django.core.urlresolvers import reverse, NoReverseMatch
- args = [arg.resolve(context) for arg in self.args]
- kwargs = dict([(smart_str(k, 'ascii'), v.resolve(context))
- for k, v in self.kwargs.items()])
- view_name = self.view_name
- if not self.legacy_view_name:
- view_name = view_name.resolve(context)
- # Try to look up the URL twice: once given the view name, and again
- # relative to what we guess is the "main" app. If they both fail,
- # re-raise the NoReverseMatch unless we're using the
- # {% url ... as var %} construct in which cause return nothing.
- url = ''
- try:
- url = reverse(view_name, args=args, kwargs=kwargs, current_app=context.current_app)
- except NoReverseMatch, e:
- if settings.SETTINGS_MODULE:
- project_name = settings.SETTINGS_MODULE.split('.')[0]
- try:
- url = reverse(project_name + '.' + view_name,
- args=args, kwargs=kwargs,
- current_app=context.current_app)
- except NoReverseMatch:
- if self.asvar is None:
- # Re-raise the original exception, not the one with
- # the path relative to the project. This makes a
- # better error message.
- raise e
- else:
- if self.asvar is None:
- raise e
- if self.asvar:
- context[self.asvar] = url
- return ''
- else:
- return url
- class WidthRatioNode(Node):
- def __init__(self, val_expr, max_expr, max_width):
- self.val_expr = val_expr
- self.max_expr = max_expr
- self.max_width = max_width
- def render(self, context):
- try:
- value = self.val_expr.resolve(context)
- maxvalue = self.max_expr.resolve(context)
- max_width = int(self.max_width.resolve(context))
- except VariableDoesNotExist:
- return ''
- except ValueError:
- raise TemplateSyntaxError("widthratio final argument must be an number")
- try:
- value = float(value)
- maxvalue = float(maxvalue)
- ratio = (value / maxvalue) * max_width
- except (ValueError, ZeroDivisionError):
- return ''
- return str(int(round(ratio)))
- class WithNode(Node):
- def __init__(self, var, name, nodelist, extra_context=None,
- isolated_context=False):
- 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
- self.isolated_context = isolated_context
- def __repr__(self):
- return "<WithNode>"
- def render(self, context):
- values = dict([(key, val.resolve(context)) for key, val in
- self.extra_context.iteritems()])
- if self.isolated_context:
- return self.nodelist.render(Context(values))
- context.update(values)
- output = self.nodelist.render(context)
- context.pop()
- return output
- #@register.tag
- def autoescape(parser, token):
- """
- Force autoescape behaviour for this block.
- """
- args = token.contents.split()
- if len(args) != 2:
- raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
- arg = args[1]
- if arg not in (u'on', u'off'):
- raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
- nodelist = parser.parse(('endautoescape',))
- parser.delete_first_token()
- return AutoEscapeControlNode((arg == 'on'), nodelist)
- autoescape = register.tag(autoescape)
- #@register.tag
- def comment(parser, token):
- """
- Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
- """
- parser.skip_past('endcomment')
- return CommentNode()
- comment = register.tag(comment)
- #@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 sucessive 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::
- {% cycle 'row1' 'row2' as rowcolors silent %}{# no value here #}
- {% for o in some_list %}
- <tr class="{% cycle rowcolors %}">{# first value will be "row1" #}
- ...
- </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.
- args = token.split_contents()
- if len(args) < 2:
- raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
- if ',' in args[1]:
- # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
- # case.
- args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
- if len(args) == 2:
- # {% cycle foo %} case.
- name = args[1]
- if not hasattr(parser, '_namedCycleNodes'):
- raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
- if not name in parser._namedCycleNodes:
- raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
- return parser._namedCycleNodes[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, '_namedCycleNodes'):
- parser._namedCycleNodes = {}
- parser._namedCycleNodes[name] = node
- else:
- values = [parser.compile_filter(arg) for arg in args[1:]]
- node = CycleNode(values)
- return node
- cycle = register.tag(cycle)
- def csrf_token(parser, token):
- return CsrfTokenNode()
- register.tag(csrf_token)
- 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()
- debug = register.tag(debug)
- #@register.tag(name="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 %}
- """
- _, rest = token.contents.split(None, 1)
- filter_expr = parser.compile_filter("var|%s" % (rest))
- for func, unused in filter_expr.filters:
- if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
- raise TemplateSyntaxError('"filter %s" is not permitted. Use the "autoescape" tag instead.' % func.__name__)
- nodelist = parser.parse(('endfilter',))
- parser.delete_first_token()
- return FilterNode(filter_expr, nodelist)
- do_filter = register.tag("filter", do_filter)
- #@register.tag
- def firstof(parser, token):
- """
- Outputs the first variable passed that is not False, without escaping.
- Outputs nothing if all the passed variables are False.
- Sample usage::
- {% firstof var1 var2 var3 %}
- This is equivalent to::
- {% if var1 %}
- {{ var1|safe }}
- {% else %}{% if var2 %}
- {{ var2|safe }}
- {% else %}{% if var3 %}
- {{ var3|safe }}
- {% endif %}{% endif %}{% 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 escape the output, use a filter tag::
- {% filter force_escape %}
- {% firstof var1 var2 var3 "fallback value" %}
- {% endfilter %}
- """
- bits = token.split_contents()[1:]
- if len(bits) < 1:
- raise TemplateSyntaxError("'firstof' statement requires at least one argument")
- return FirstOfNode([parser.compile_filter(bit) for bit in bits])
- firstof = register.tag(firstof)
- #@register.tag(name="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 althete_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.contents.split()
- if len(bits) < 4:
- raise TemplateSyntaxError("'for' statements should have at least four"
- " words: %s" % token.contents)
- is_reversed = bits[-1] == 'reversed'
- in_index = is_reversed and -3 or -2
- if bits[in_index] != 'in':
- raise TemplateSyntaxError("'for' statements should use the format"
- " 'for x in y': %s" % token.contents)
- loopvars = re.split(r' *, *', ' '.join(bits[1:in_index]))
- for var in loopvars:
- if not var or ' ' in 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)
- do_for = register.tag("for", do_for)
- 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)
- ifequal = register.tag(ifequal)
- #@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)
- ifnotequal = register.tag(ifnotequal)
- 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
- return super(TemplateIfParser, self).__init__(*args, **kwargs)
- def create_var(self, value):
- return TemplateLiteral(self.template_parser.compile_filter(value), value)
- #@register.tag(name="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 }}
- {% 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 can take an option ``{% else %}`` clause
- that will be displayed if the test fails.
- ``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 atheletes 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``
- ``==`` (or ``=``), ``!=``, ``>``, ``>=``, ``<`` and ``<=``.
- Operator precedence follows Python.
- """
- bits = token.split_contents()[1:]
- var = TemplateIfParser(parser, bits).parse()
- nodelist_true = parser.parse(('else', 'endif'))
- token = parser.next_token()
- if token.contents == 'else':
- nodelist_false = parser.parse(('endif',))
- parser.delete_first_token()
- else:
- nodelist_false = NodeList()
- return IfNode(var, nodelist_true, nodelist_false)
- do_if = register.tag("if", do_if)
- #@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 a variable, check whether that variable has changed.
- For example, the following shows the date every time it changes, but
- only shows the hour if both the hour and the date have changed::
- {% for date in days %}
- {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
- {% ifchanged date.hour date.date %}
- {{ date.hour }}
- {% endifchanged %}
- {% endfor %}
- """
- bits = token.contents.split()
- 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)
- ifchanged = register.tag(ifchanged)
- #@register.tag
- def ssi(parser, token):
- """
- Outputs the contents of a given file into the page.
- Like a simple "include" tag, the ``ssi`` tag includes the contents
- of another file -- which must be specified using an absolute path --
- in the current page::
- {% ssi /home/html/ljworld.com/includes/right_generic.html %}
- If the optional "parsed" parameter is given, the contents of the included
- file are evaluated as template code, with the current context::
- {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
- """
- import warnings
- warnings.warn('The syntax for the ssi template tag is changing. Load the `ssi` tag from the `future` tag library to start using the new behavior.',
- category=PendingDeprecationWarning)
- bits = token.contents.split()
- parsed = False
- if len(bits) not in (2, 3):
- raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
- " the file to be included")
- if len(bits) == 3:
- if bits[2] == 'parsed':
- parsed = True
- else:
- raise TemplateSyntaxError("Second (optional) argument to %s tag"
- " must be 'parsed'" % bits[0])
- return SsiNode(bits[1], parsed, legacy_filepath=True)
- ssi = register.tag(ssi)
- #@register.tag
- def load(parser, token):
- """
- Loads a custom template tag set.
- 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 %}
- """
- bits = token.contents.split()
- if len(bits) >= 4 and bits[-2] == "from":
- try:
- taglib = bits[-1]
- lib = get_library(taglib)
- except InvalidTemplateLibrary, e:
- raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
- (taglib, e))
- else:
- temp_lib = Library()
- for name in bits[1:-2]:
- if name in lib.tags:
- temp_lib.tags[name] = lib.tags[name]
- # a name could be a tag *and* a filter, so check for both
- if name in lib.filters:
- temp_lib.filters[name] = lib.filters[name]
- elif name in lib.filters:
- temp_lib.filters[name] = lib.filters[name]
- else:
- raise TemplateSyntaxError("'%s' is not a valid tag or filter in tag library '%s'" %
- (name, taglib))
- parser.add_library(temp_lib)
- else:
- for taglib in bits[1:]:
- # add the library to the parser
- try:
- lib = get_library(taglib)
- parser.add_library(lib)
- except InvalidTemplateLibrary, e:
- raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
- (taglib, e))
- return LoadNode()
- load = register.tag(load)
- #@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.contents.split('"')
- if len(bits) != 3:
- raise TemplateSyntaxError("'now' statement takes one argument")
- format_string = bits[1]
- return NowNode(format_string)
- now = register.tag(now)
- #@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
- ``people`` is a list of ``Person`` objects that have ``first_name``,
- ``last_name``, and ``gender`` attributes, and you'd like to display a list
- that looks like:
- * Male:
- * George Bush
- * Bill Clinton
- * Female:
- * Margaret Thatcher
- * Colendeeza Rice
- * Unknown:
- * Pat Smith
- The following snippet of template code would accomplish this dubious task::
- {% regroup people by gender as grouped %}
- <ul>
- {% for group in grouped %}
- <li>{{ group.grouper }}
- <ul>
- {% for item in group.list %}
- <li>{{ item }}</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 ``Male``, ``Female``
- and ``Unknown``, and ``list`` is the list of people with those genders.
- 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
- people was not sorted by gender, you'd need to make sure it is sorted
- before using it, i.e.::
- {% regroup people|dictsort:"gender" by gender as grouped %}
- """
- firstbits = token.contents.split(None, 3)
- if len(firstbits) != 4:
- raise TemplateSyntaxError("'regroup' tag takes five arguments")
- target = parser.compile_filter(firstbits[1])
- if firstbits[2] != 'by':
- raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
- lastbits_reversed = firstbits[3][::-1].split(None, 2)
- if lastbits_reversed[1][::-1] != 'as':
- raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
- " be 'as'")
- expression = parser.compile_filter(lastbits_reversed[2][::-1])
- var_name = lastbits_reversed[0][::-1]
- return RegroupNode(target, expression, var_name)
- regroup = register.tag(regroup)
- 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)
- spaceless = register.tag(spaceless)
- #@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`` ``#}``
- ================== =======
- """
- 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, TemplateTagNode.mapping.keys()))
- return TemplateTagNode(tag)
- templatetag = register.tag(templatetag)
- def url(parser, token):
- """
- Returns an absolute URL matching given view with its parameters.
- This is a way to define links that aren't tied to a particular URL
- configuration::
- {% url path.to.some_view arg1 arg2 %}
- or
- {% url path.to.some_view name1=value1 name2=value2 %}
- The first argument is a path to a view. It can be an absolute python path
- or just ``app_name.view_name`` without the project name if the view is
- located inside the project. Other arguments are comma-separated values
- that will be filled in place of positional and keyword arguments in the
- URL. All arguments for the URL should be present.
- For example if you have a view ``app_name.client`` taking client's id and
- the corresponding line in a URLconf looks like this::
- ('^client/(\d+)/$', 'app_name.client')
- and this app's URLconf is included into the project's URLconf under some
- path::
- ('^clients/', include('project_name.app_name.urls'))
- then in a template you can create a link for a certain client like this::
- {% url app_name.client client.id %}
- The URL will look like ``/clients/client/123/``.
- """
- import warnings
- warnings.warn('The syntax for the url template tag is changing. Load the `url` tag from the `future` tag library to start using the new behavior.',
- category=PendingDeprecationWarning)
- bits = token.split_contents()
- if len(bits) < 2:
- raise TemplateSyntaxError("'%s' takes at least one argument"
- " (path to a view)" % bits[0])
- viewname = bits[1]
- args = []
- kwargs = {}
- asvar = None
- bits = bits[2:]
- if len(bits) >= 2 and bits[-2] == 'as':
- asvar = bits[-1]
- bits = bits[:-2]
- # Backwards compatibility: check for the old comma separated format
- # {% url urlname arg1,arg2 %}
- # Initial check - that the first space separated bit has a comma in it
- if bits and ',' in bits[0]:
- check_old_format = True
- # In order to *really* be old format, there must be a comma
- # in *every* space separated bit, except the last.
- for bit in bits[1:-1]:
- if ',' not in bit:
- # No comma in this bit. Either the comma we found
- # in bit 1 was a false positive (e.g., comma in a string),
- # or there is a syntax problem with missing commas
- check_old_format = False
- break
- else:
- # No comma found - must be new format.
- check_old_format = False
- if check_old_format:
- # Confirm that this is old format by trying to parse the first
- # argument. An exception will be raised if the comma is
- # unexpected (i.e. outside of a static string).
- match = kwarg_re.match(bits[0])
- if match:
- value = match.groups()[1]
- try:
- parser.compile_filter(value)
- except TemplateSyntaxError:
- bits = ''.join(bits).split(',')
- # Now all the bits are parsed into new format,
- # process them as template vars
- 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, legacy_view_name=True)
- url = register.tag(url)
- #@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.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
- Above, if ``this_value`` is 175 and ``max_value`` is 200, 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).
- """
- bits = token.contents.split()
- if len(bits) != 4:
- raise TemplateSyntaxError("widthratio takes three arguments")
- tag, this_value_expr, max_value_expr, max_width = bits
- return WidthRatioNode(parser.compile_filter(this_value_expr),
- parser.compile_filter(max_value_expr),
- parser.compile_filter(max_width))
- widthratio = register.tag(widthratio)
- #@register.tag
- 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)
- do_with = register.tag('with', do_with)