app/django/template/defaulttags.py
changeset 54 03e267d67478
child 323 ff1a9aa48cfd
equal deleted inserted replaced
53:57b4279d8c4e 54:03e267d67478
       
     1 """Default tags used by the template system, available to all templates."""
       
     2 
       
     3 import sys
       
     4 import re
       
     5 from itertools import cycle as itertools_cycle
       
     6 try:
       
     7     reversed
       
     8 except NameError:
       
     9     from django.utils.itercompat import reversed     # Python 2.3 fallback
       
    10 
       
    11 from django.template import Node, NodeList, Template, Context, Variable
       
    12 from django.template 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
       
    13 from django.template import get_library, Library, InvalidTemplateLibrary
       
    14 from django.conf import settings
       
    15 from django.utils.encoding import smart_str, smart_unicode
       
    16 from django.utils.itercompat import groupby
       
    17 from django.utils.safestring import mark_safe
       
    18 
       
    19 register = Library()
       
    20 
       
    21 class AutoEscapeControlNode(Node):
       
    22     """Implements the actions of the autoescape tag."""
       
    23     def __init__(self, setting, nodelist):
       
    24         self.setting, self.nodelist = setting, nodelist
       
    25 
       
    26     def render(self, context):
       
    27         old_setting = context.autoescape
       
    28         context.autoescape = self.setting
       
    29         output = self.nodelist.render(context)
       
    30         context.autoescape = old_setting
       
    31         if self.setting:
       
    32             return mark_safe(output)
       
    33         else:
       
    34             return output
       
    35 
       
    36 class CommentNode(Node):
       
    37     def render(self, context):
       
    38         return ''
       
    39 
       
    40 class CycleNode(Node):
       
    41     def __init__(self, cyclevars, variable_name=None):
       
    42         self.cycle_iter = itertools_cycle(cyclevars)
       
    43         self.variable_name = variable_name
       
    44 
       
    45     def render(self, context):
       
    46         value = self.cycle_iter.next()
       
    47         value = Variable(value).resolve(context)
       
    48         if self.variable_name:
       
    49             context[self.variable_name] = value
       
    50         return value
       
    51 
       
    52 class DebugNode(Node):
       
    53     def render(self, context):
       
    54         from pprint import pformat
       
    55         output = [pformat(val) for val in context]
       
    56         output.append('\n\n')
       
    57         output.append(pformat(sys.modules))
       
    58         return ''.join(output)
       
    59 
       
    60 class FilterNode(Node):
       
    61     def __init__(self, filter_expr, nodelist):
       
    62         self.filter_expr, self.nodelist = filter_expr, nodelist
       
    63 
       
    64     def render(self, context):
       
    65         output = self.nodelist.render(context)
       
    66         # Apply filters.
       
    67         context.update({'var': output})
       
    68         filtered = self.filter_expr.resolve(context)
       
    69         context.pop()
       
    70         return filtered
       
    71 
       
    72 class FirstOfNode(Node):
       
    73     def __init__(self, vars):
       
    74         self.vars = map(Variable, vars)
       
    75 
       
    76     def render(self, context):
       
    77         for var in self.vars:
       
    78             try:
       
    79                 value = var.resolve(context)
       
    80             except VariableDoesNotExist:
       
    81                 continue
       
    82             if value:
       
    83                 return smart_unicode(value)
       
    84         return u''
       
    85 
       
    86 class ForNode(Node):
       
    87     def __init__(self, loopvars, sequence, is_reversed, nodelist_loop):
       
    88         self.loopvars, self.sequence = loopvars, sequence
       
    89         self.is_reversed = is_reversed
       
    90         self.nodelist_loop = nodelist_loop
       
    91 
       
    92     def __repr__(self):
       
    93         reversed_text = self.is_reversed and ' reversed' or ''
       
    94         return "<For Node: for %s in %s, tail_len: %d%s>" % \
       
    95             (', '.join(self.loopvars), self.sequence, len(self.nodelist_loop),
       
    96              reversed_text)
       
    97 
       
    98     def __iter__(self):
       
    99         for node in self.nodelist_loop:
       
   100             yield node
       
   101 
       
   102     def get_nodes_by_type(self, nodetype):
       
   103         nodes = []
       
   104         if isinstance(self, nodetype):
       
   105             nodes.append(self)
       
   106         nodes.extend(self.nodelist_loop.get_nodes_by_type(nodetype))
       
   107         return nodes
       
   108 
       
   109     def render(self, context):
       
   110         nodelist = NodeList()
       
   111         if 'forloop' in context:
       
   112             parentloop = context['forloop']
       
   113         else:
       
   114             parentloop = {}
       
   115         context.push()
       
   116         try:
       
   117             values = self.sequence.resolve(context, True)
       
   118         except VariableDoesNotExist:
       
   119             values = []
       
   120         if values is None:
       
   121             values = []
       
   122         if not hasattr(values, '__len__'):
       
   123             values = list(values)
       
   124         len_values = len(values)
       
   125         if self.is_reversed:
       
   126             values = reversed(values)
       
   127         unpack = len(self.loopvars) > 1
       
   128         # Create a forloop value in the context.  We'll update counters on each
       
   129         # iteration just below.
       
   130         loop_dict = context['forloop'] = {'parentloop': parentloop}
       
   131         for i, item in enumerate(values):
       
   132             # Shortcuts for current loop iteration number.
       
   133             loop_dict['counter0'] = i
       
   134             loop_dict['counter'] = i+1
       
   135             # Reverse counter iteration numbers.
       
   136             loop_dict['revcounter'] = len_values - i
       
   137             loop_dict['revcounter0'] = len_values - i - 1
       
   138             # Boolean values designating first and last times through loop.
       
   139             loop_dict['first'] = (i == 0)
       
   140             loop_dict['last'] = (i == len_values - 1)
       
   141 
       
   142             if unpack:
       
   143                 # If there are multiple loop variables, unpack the item into
       
   144                 # them.
       
   145                 context.update(dict(zip(self.loopvars, item)))
       
   146             else:
       
   147                 context[self.loopvars[0]] = item
       
   148             for node in self.nodelist_loop:
       
   149                 nodelist.append(node.render(context))
       
   150             if unpack:
       
   151                 # The loop variables were pushed on to the context so pop them
       
   152                 # off again. This is necessary because the tag lets the length
       
   153                 # of loopvars differ to the length of each set of items and we
       
   154                 # don't want to leave any vars from the previous loop on the
       
   155                 # context.
       
   156                 context.pop()
       
   157         context.pop()
       
   158         return nodelist.render(context)
       
   159 
       
   160 class IfChangedNode(Node):
       
   161     def __init__(self, nodelist, *varlist):
       
   162         self.nodelist = nodelist
       
   163         self._last_seen = None
       
   164         self._varlist = map(Variable, varlist)
       
   165 
       
   166     def render(self, context):
       
   167         if 'forloop' in context and context['forloop']['first']:
       
   168             self._last_seen = None
       
   169         try:
       
   170             if self._varlist:
       
   171                 # Consider multiple parameters.  This automatically behaves
       
   172                 # like an OR evaluation of the multiple variables.
       
   173                 compare_to = [var.resolve(context) for var in self._varlist]
       
   174             else:
       
   175                 compare_to = self.nodelist.render(context)
       
   176         except VariableDoesNotExist:
       
   177             compare_to = None
       
   178 
       
   179         if  compare_to != self._last_seen:
       
   180             firstloop = (self._last_seen == None)
       
   181             self._last_seen = compare_to
       
   182             context.push()
       
   183             context['ifchanged'] = {'firstloop': firstloop}
       
   184             content = self.nodelist.render(context)
       
   185             context.pop()
       
   186             return content
       
   187         else:
       
   188             return ''
       
   189 
       
   190 class IfEqualNode(Node):
       
   191     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
       
   192         self.var1, self.var2 = Variable(var1), Variable(var2)
       
   193         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
       
   194         self.negate = negate
       
   195 
       
   196     def __repr__(self):
       
   197         return "<IfEqualNode>"
       
   198 
       
   199     def render(self, context):
       
   200         try:
       
   201             val1 = self.var1.resolve(context)
       
   202         except VariableDoesNotExist:
       
   203             val1 = None
       
   204         try:
       
   205             val2 = self.var2.resolve(context)
       
   206         except VariableDoesNotExist:
       
   207             val2 = None
       
   208         if (self.negate and val1 != val2) or (not self.negate and val1 == val2):
       
   209             return self.nodelist_true.render(context)
       
   210         return self.nodelist_false.render(context)
       
   211 
       
   212 class IfNode(Node):
       
   213     def __init__(self, bool_exprs, nodelist_true, nodelist_false, link_type):
       
   214         self.bool_exprs = bool_exprs
       
   215         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
       
   216         self.link_type = link_type
       
   217 
       
   218     def __repr__(self):
       
   219         return "<If node>"
       
   220 
       
   221     def __iter__(self):
       
   222         for node in self.nodelist_true:
       
   223             yield node
       
   224         for node in self.nodelist_false:
       
   225             yield node
       
   226 
       
   227     def get_nodes_by_type(self, nodetype):
       
   228         nodes = []
       
   229         if isinstance(self, nodetype):
       
   230             nodes.append(self)
       
   231         nodes.extend(self.nodelist_true.get_nodes_by_type(nodetype))
       
   232         nodes.extend(self.nodelist_false.get_nodes_by_type(nodetype))
       
   233         return nodes
       
   234 
       
   235     def render(self, context):
       
   236         if self.link_type == IfNode.LinkTypes.or_:
       
   237             for ifnot, bool_expr in self.bool_exprs:
       
   238                 try:
       
   239                     value = bool_expr.resolve(context, True)
       
   240                 except VariableDoesNotExist:
       
   241                     value = None
       
   242                 if (value and not ifnot) or (ifnot and not value):
       
   243                     return self.nodelist_true.render(context)
       
   244             return self.nodelist_false.render(context)
       
   245         else:
       
   246             for ifnot, bool_expr in self.bool_exprs:
       
   247                 try:
       
   248                     value = bool_expr.resolve(context, True)
       
   249                 except VariableDoesNotExist:
       
   250                     value = None
       
   251                 if not ((value and not ifnot) or (ifnot and not value)):
       
   252                     return self.nodelist_false.render(context)
       
   253             return self.nodelist_true.render(context)
       
   254 
       
   255     class LinkTypes:
       
   256         and_ = 0,
       
   257         or_ = 1
       
   258 
       
   259 class RegroupNode(Node):
       
   260     def __init__(self, target, expression, var_name):
       
   261         self.target, self.expression = target, expression
       
   262         self.var_name = var_name
       
   263 
       
   264     def render(self, context):
       
   265         obj_list = self.target.resolve(context, True)
       
   266         if obj_list == None:
       
   267             # target variable wasn't found in context; fail silently.
       
   268             context[self.var_name] = []
       
   269             return ''
       
   270         # List of dictionaries in the format:
       
   271         # {'grouper': 'key', 'list': [list of contents]}.
       
   272         context[self.var_name] = [
       
   273             {'grouper': key, 'list': list(val)}
       
   274             for key, val in
       
   275             groupby(obj_list, lambda v, f=self.expression.resolve: f(v, True))
       
   276         ]
       
   277         return ''
       
   278 
       
   279 def include_is_allowed(filepath):
       
   280     for root in settings.ALLOWED_INCLUDE_ROOTS:
       
   281         if filepath.startswith(root):
       
   282             return True
       
   283     return False
       
   284 
       
   285 class SsiNode(Node):
       
   286     def __init__(self, filepath, parsed):
       
   287         self.filepath, self.parsed = filepath, parsed
       
   288 
       
   289     def render(self, context):
       
   290         if not include_is_allowed(self.filepath):
       
   291             if settings.DEBUG:
       
   292                 return "[Didn't have permission to include file]"
       
   293             else:
       
   294                 return '' # Fail silently for invalid includes.
       
   295         try:
       
   296             fp = open(self.filepath, 'r')
       
   297             output = fp.read()
       
   298             fp.close()
       
   299         except IOError:
       
   300             output = ''
       
   301         if self.parsed:
       
   302             try:
       
   303                 t = Template(output, name=self.filepath)
       
   304                 return t.render(context)
       
   305             except TemplateSyntaxError, e:
       
   306                 if settings.DEBUG:
       
   307                     return "[Included template had syntax error: %s]" % e
       
   308                 else:
       
   309                     return '' # Fail silently for invalid included templates.
       
   310         return output
       
   311 
       
   312 class LoadNode(Node):
       
   313     def render(self, context):
       
   314         return ''
       
   315 
       
   316 class NowNode(Node):
       
   317     def __init__(self, format_string):
       
   318         self.format_string = format_string
       
   319 
       
   320     def render(self, context):
       
   321         from datetime import datetime
       
   322         from django.utils.dateformat import DateFormat
       
   323         df = DateFormat(datetime.now())
       
   324         return df.format(self.format_string)
       
   325 
       
   326 class SpacelessNode(Node):
       
   327     def __init__(self, nodelist):
       
   328         self.nodelist = nodelist
       
   329 
       
   330     def render(self, context):
       
   331         from django.utils.html import strip_spaces_between_tags
       
   332         return strip_spaces_between_tags(self.nodelist.render(context).strip())
       
   333 
       
   334 class TemplateTagNode(Node):
       
   335     mapping = {'openblock': BLOCK_TAG_START,
       
   336                'closeblock': BLOCK_TAG_END,
       
   337                'openvariable': VARIABLE_TAG_START,
       
   338                'closevariable': VARIABLE_TAG_END,
       
   339                'openbrace': SINGLE_BRACE_START,
       
   340                'closebrace': SINGLE_BRACE_END,
       
   341                'opencomment': COMMENT_TAG_START,
       
   342                'closecomment': COMMENT_TAG_END,
       
   343                }
       
   344 
       
   345     def __init__(self, tagtype):
       
   346         self.tagtype = tagtype
       
   347 
       
   348     def render(self, context):
       
   349         return self.mapping.get(self.tagtype, '')
       
   350 
       
   351 class URLNode(Node):
       
   352     def __init__(self, view_name, args, kwargs):
       
   353         self.view_name = view_name
       
   354         self.args = args
       
   355         self.kwargs = kwargs
       
   356 
       
   357     def render(self, context):
       
   358         from django.core.urlresolvers import reverse, NoReverseMatch
       
   359         args = [arg.resolve(context) for arg in self.args]
       
   360         kwargs = dict([(smart_str(k,'ascii'), v.resolve(context))
       
   361                        for k, v in self.kwargs.items()])
       
   362         try:
       
   363             return reverse(self.view_name, args=args, kwargs=kwargs)
       
   364         except NoReverseMatch:
       
   365             try:
       
   366                 project_name = settings.SETTINGS_MODULE.split('.')[0]
       
   367                 return reverse(project_name + '.' + self.view_name,
       
   368                                args=args, kwargs=kwargs)
       
   369             except NoReverseMatch:
       
   370                 return ''
       
   371 
       
   372 class WidthRatioNode(Node):
       
   373     def __init__(self, val_expr, max_expr, max_width):
       
   374         self.val_expr = val_expr
       
   375         self.max_expr = max_expr
       
   376         self.max_width = max_width
       
   377 
       
   378     def render(self, context):
       
   379         try:
       
   380             value = self.val_expr.resolve(context)
       
   381             maxvalue = self.max_expr.resolve(context)
       
   382         except VariableDoesNotExist:
       
   383             return ''
       
   384         try:
       
   385             value = float(value)
       
   386             maxvalue = float(maxvalue)
       
   387             ratio = (value / maxvalue) * int(self.max_width)
       
   388         except (ValueError, ZeroDivisionError):
       
   389             return ''
       
   390         return str(int(round(ratio)))
       
   391 
       
   392 class WithNode(Node):
       
   393     def __init__(self, var, name, nodelist):
       
   394         self.var = var
       
   395         self.name = name
       
   396         self.nodelist = nodelist
       
   397 
       
   398     def __repr__(self):
       
   399         return "<WithNode>"
       
   400 
       
   401     def render(self, context):
       
   402         val = self.var.resolve(context)
       
   403         context.push()
       
   404         context[self.name] = val
       
   405         output = self.nodelist.render(context)
       
   406         context.pop()
       
   407         return output
       
   408 
       
   409 #@register.tag
       
   410 def autoescape(parser, token):
       
   411     """
       
   412     Force autoescape behaviour for this block.
       
   413     """
       
   414     args = token.contents.split()
       
   415     if len(args) != 2:
       
   416         raise TemplateSyntaxError("'Autoescape' tag requires exactly one argument.")
       
   417     arg = args[1]
       
   418     if arg not in (u'on', u'off'):
       
   419         raise TemplateSyntaxError("'Autoescape' argument should be 'on' or 'off'")
       
   420     nodelist = parser.parse(('endautoescape',))
       
   421     parser.delete_first_token()
       
   422     return AutoEscapeControlNode((arg == 'on'), nodelist)
       
   423 autoescape = register.tag(autoescape)
       
   424 
       
   425 #@register.tag
       
   426 def comment(parser, token):
       
   427     """
       
   428     Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
       
   429     """
       
   430     parser.skip_past('endcomment')
       
   431     return CommentNode()
       
   432 comment = register.tag(comment)
       
   433 
       
   434 #@register.tag
       
   435 def cycle(parser, token):
       
   436     """
       
   437     Cycles among the given strings each time this tag is encountered.
       
   438 
       
   439     Within a loop, cycles among the given strings each time through
       
   440     the loop::
       
   441 
       
   442         {% for o in some_list %}
       
   443             <tr class="{% cycle 'row1' 'row2' %}">
       
   444                 ...
       
   445             </tr>
       
   446         {% endfor %}
       
   447 
       
   448     Outside of a loop, give the values a unique name the first time you call
       
   449     it, then use that name each sucessive time through::
       
   450 
       
   451             <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
       
   452             <tr class="{% cycle rowcolors %}">...</tr>
       
   453             <tr class="{% cycle rowcolors %}">...</tr>
       
   454 
       
   455     You can use any number of values, seperated by spaces. Commas can also
       
   456     be used to separate values; if a comma is used, the cycle values are
       
   457     interpreted as literal strings.
       
   458     """
       
   459 
       
   460     # Note: This returns the exact same node on each {% cycle name %} call;
       
   461     # that is, the node object returned from {% cycle a b c as name %} and the
       
   462     # one returned from {% cycle name %} are the exact same object.  This
       
   463     # shouldn't cause problems (heh), but if it does, now you know.
       
   464     #
       
   465     # Ugly hack warning: this stuffs the named template dict into parser so
       
   466     # that names are only unique within each template (as opposed to using
       
   467     # a global variable, which would make cycle names have to be unique across
       
   468     # *all* templates.
       
   469 
       
   470     args = token.split_contents()
       
   471 
       
   472     if len(args) < 2:
       
   473         raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
       
   474 
       
   475     if ',' in args[1]:
       
   476         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
       
   477         # case.
       
   478         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
       
   479 
       
   480     if len(args) == 2:
       
   481         # {% cycle foo %} case.
       
   482         name = args[1]
       
   483         if not hasattr(parser, '_namedCycleNodes'):
       
   484             raise TemplateSyntaxError("No named cycles in template."
       
   485                                       " '%s' is not defined" % name)
       
   486         if not name in parser._namedCycleNodes:
       
   487             raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
       
   488         return parser._namedCycleNodes[name]
       
   489 
       
   490     if len(args) > 4 and args[-2] == 'as':
       
   491         name = args[-1]
       
   492         node = CycleNode(args[1:-2], name)
       
   493         if not hasattr(parser, '_namedCycleNodes'):
       
   494             parser._namedCycleNodes = {}
       
   495         parser._namedCycleNodes[name] = node
       
   496     else:
       
   497         node = CycleNode(args[1:])
       
   498     return node
       
   499 cycle = register.tag(cycle)
       
   500 
       
   501 def debug(parser, token):
       
   502     """
       
   503     Outputs a whole load of debugging information, including the current
       
   504     context and imported modules.
       
   505 
       
   506     Sample usage::
       
   507 
       
   508         <pre>
       
   509             {% debug %}
       
   510         </pre>
       
   511     """
       
   512     return DebugNode()
       
   513 debug = register.tag(debug)
       
   514 
       
   515 #@register.tag(name="filter")
       
   516 def do_filter(parser, token):
       
   517     """
       
   518     Filters the contents of the block through variable filters.
       
   519 
       
   520     Filters can also be piped through each other, and they can have
       
   521     arguments -- just like in variable syntax.
       
   522 
       
   523     Sample usage::
       
   524 
       
   525         {% filter force_escape|lower %}
       
   526             This text will be HTML-escaped, and will appear in lowercase.
       
   527         {% endfilter %}
       
   528     """
       
   529     _, rest = token.contents.split(None, 1)
       
   530     filter_expr = parser.compile_filter("var|%s" % (rest))
       
   531     for func, unused in filter_expr.filters:
       
   532         if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
       
   533             raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
       
   534     nodelist = parser.parse(('endfilter',))
       
   535     parser.delete_first_token()
       
   536     return FilterNode(filter_expr, nodelist)
       
   537 do_filter = register.tag("filter", do_filter)
       
   538 
       
   539 #@register.tag
       
   540 def firstof(parser, token):
       
   541     """
       
   542     Outputs the first variable passed that is not False.
       
   543 
       
   544     Outputs nothing if all the passed variables are False.
       
   545 
       
   546     Sample usage::
       
   547 
       
   548         {% firstof var1 var2 var3 %}
       
   549 
       
   550     This is equivalent to::
       
   551 
       
   552         {% if var1 %}
       
   553             {{ var1 }}
       
   554         {% else %}{% if var2 %}
       
   555             {{ var2 }}
       
   556         {% else %}{% if var3 %}
       
   557             {{ var3 }}
       
   558         {% endif %}{% endif %}{% endif %}
       
   559 
       
   560     but obviously much cleaner!
       
   561 
       
   562     You can also use a literal string as a fallback value in case all
       
   563     passed variables are False::
       
   564 
       
   565         {% firstof var1 var2 var3 "fallback value" %}
       
   566 
       
   567     """
       
   568     bits = token.split_contents()[1:]
       
   569     if len(bits) < 1:
       
   570         raise TemplateSyntaxError("'firstof' statement requires at least one"
       
   571                                   " argument")
       
   572     return FirstOfNode(bits)
       
   573 firstof = register.tag(firstof)
       
   574 
       
   575 #@register.tag(name="for")
       
   576 def do_for(parser, token):
       
   577     """
       
   578     Loops over each item in an array.
       
   579 
       
   580     For example, to display a list of athletes given ``athlete_list``::
       
   581 
       
   582         <ul>
       
   583         {% for athlete in athlete_list %}
       
   584             <li>{{ athlete.name }}</li>
       
   585         {% endfor %}
       
   586         </ul>
       
   587 
       
   588     You can loop over a list in reverse by using
       
   589     ``{% for obj in list reversed %}``.
       
   590 
       
   591     You can also unpack multiple values from a two-dimensional array::
       
   592 
       
   593         {% for key,value in dict.items %}
       
   594             {{ key }}: {{ value }}
       
   595         {% endfor %}
       
   596 
       
   597     The for loop sets a number of variables available within the loop:
       
   598 
       
   599         ==========================  ================================================
       
   600         Variable                    Description
       
   601         ==========================  ================================================
       
   602         ``forloop.counter``         The current iteration of the loop (1-indexed)
       
   603         ``forloop.counter0``        The current iteration of the loop (0-indexed)
       
   604         ``forloop.revcounter``      The number of iterations from the end of the
       
   605                                     loop (1-indexed)
       
   606         ``forloop.revcounter0``     The number of iterations from the end of the
       
   607                                     loop (0-indexed)
       
   608         ``forloop.first``           True if this is the first time through the loop
       
   609         ``forloop.last``            True if this is the last time through the loop
       
   610         ``forloop.parentloop``      For nested loops, this is the loop "above" the
       
   611                                     current one
       
   612         ==========================  ================================================
       
   613 
       
   614     """
       
   615     bits = token.contents.split()
       
   616     if len(bits) < 4:
       
   617         raise TemplateSyntaxError("'for' statements should have at least four"
       
   618                                   " words: %s" % token.contents)
       
   619 
       
   620     is_reversed = bits[-1] == 'reversed'
       
   621     in_index = is_reversed and -3 or -2
       
   622     if bits[in_index] != 'in':
       
   623         raise TemplateSyntaxError("'for' statements should use the format"
       
   624                                   " 'for x in y': %s" % token.contents)
       
   625 
       
   626     loopvars = re.sub(r' *, *', ',', ' '.join(bits[1:in_index])).split(',')
       
   627     for var in loopvars:
       
   628         if not var or ' ' in var:
       
   629             raise TemplateSyntaxError("'for' tag received an invalid argument:"
       
   630                                       " %s" % token.contents)
       
   631 
       
   632     sequence = parser.compile_filter(bits[in_index+1])
       
   633     nodelist_loop = parser.parse(('endfor',))
       
   634     parser.delete_first_token()
       
   635     return ForNode(loopvars, sequence, is_reversed, nodelist_loop)
       
   636 do_for = register.tag("for", do_for)
       
   637 
       
   638 def do_ifequal(parser, token, negate):
       
   639     bits = list(token.split_contents())
       
   640     if len(bits) != 3:
       
   641         raise TemplateSyntaxError, "%r takes two arguments" % bits[0]
       
   642     end_tag = 'end' + bits[0]
       
   643     nodelist_true = parser.parse(('else', end_tag))
       
   644     token = parser.next_token()
       
   645     if token.contents == 'else':
       
   646         nodelist_false = parser.parse((end_tag,))
       
   647         parser.delete_first_token()
       
   648     else:
       
   649         nodelist_false = NodeList()
       
   650     return IfEqualNode(bits[1], bits[2], nodelist_true, nodelist_false, negate)
       
   651 
       
   652 #@register.tag
       
   653 def ifequal(parser, token):
       
   654     """
       
   655     Outputs the contents of the block if the two arguments equal each other.
       
   656 
       
   657     Examples::
       
   658 
       
   659         {% ifequal user.id comment.user_id %}
       
   660             ...
       
   661         {% endifequal %}
       
   662 
       
   663         {% ifnotequal user.id comment.user_id %}
       
   664             ...
       
   665         {% else %}
       
   666             ...
       
   667         {% endifnotequal %}
       
   668     """
       
   669     return do_ifequal(parser, token, False)
       
   670 ifequal = register.tag(ifequal)
       
   671 
       
   672 #@register.tag
       
   673 def ifnotequal(parser, token):
       
   674     """
       
   675     Outputs the contents of the block if the two arguments are not equal.
       
   676     See ifequal.
       
   677     """
       
   678     return do_ifequal(parser, token, True)
       
   679 ifnotequal = register.tag(ifnotequal)
       
   680 
       
   681 #@register.tag(name="if")
       
   682 def do_if(parser, token):
       
   683     """
       
   684     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
       
   685     (i.e. exists, is not empty, and is not a false boolean value) the contents
       
   686     of the block are output::
       
   687 
       
   688         {% if athlete_list %}
       
   689             Number of athletes: {{ athlete_list|count }}
       
   690         {% else %}
       
   691             No athletes.
       
   692         {% endif %}
       
   693 
       
   694     In the above, if ``athlete_list`` is not empty, the number of athletes will
       
   695     be displayed by the ``{{ athlete_list|count }}`` variable.
       
   696 
       
   697     As you can see, the ``if`` tag can take an option ``{% else %}`` clause
       
   698     that will be displayed if the test fails.
       
   699 
       
   700     ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
       
   701     variables or to negate a given variable::
       
   702 
       
   703         {% if not athlete_list %}
       
   704             There are no athletes.
       
   705         {% endif %}
       
   706 
       
   707         {% if athlete_list or coach_list %}
       
   708             There are some athletes or some coaches.
       
   709         {% endif %}
       
   710 
       
   711         {% if athlete_list and coach_list %}
       
   712             Both atheletes and coaches are available.
       
   713         {% endif %}
       
   714 
       
   715         {% if not athlete_list or coach_list %}
       
   716             There are no athletes, or there are some coaches.
       
   717         {% endif %}
       
   718 
       
   719         {% if athlete_list and not coach_list %}
       
   720             There are some athletes and absolutely no coaches.
       
   721         {% endif %}
       
   722 
       
   723     ``if`` tags do not allow ``and`` and ``or`` clauses with the same tag,
       
   724     because the order of logic would be ambigous. For example, this is
       
   725     invalid::
       
   726 
       
   727         {% if athlete_list and coach_list or cheerleader_list %}
       
   728 
       
   729     If you need to combine ``and`` and ``or`` to do advanced logic, just use
       
   730     nested if tags. For example::
       
   731 
       
   732         {% if athlete_list %}
       
   733             {% if coach_list or cheerleader_list %}
       
   734                 We have athletes, and either coaches or cheerleaders!
       
   735             {% endif %}
       
   736         {% endif %}
       
   737     """
       
   738     bits = token.contents.split()
       
   739     del bits[0]
       
   740     if not bits:
       
   741         raise TemplateSyntaxError("'if' statement requires at least one argument")
       
   742     # Bits now looks something like this: ['a', 'or', 'not', 'b', 'or', 'c.d']
       
   743     bitstr = ' '.join(bits)
       
   744     boolpairs = bitstr.split(' and ')
       
   745     boolvars = []
       
   746     if len(boolpairs) == 1:
       
   747         link_type = IfNode.LinkTypes.or_
       
   748         boolpairs = bitstr.split(' or ')
       
   749     else:
       
   750         link_type = IfNode.LinkTypes.and_
       
   751         if ' or ' in bitstr:
       
   752             raise TemplateSyntaxError, "'if' tags can't mix 'and' and 'or'"
       
   753     for boolpair in boolpairs:
       
   754         if ' ' in boolpair:
       
   755             try:
       
   756                 not_, boolvar = boolpair.split()
       
   757             except ValueError:
       
   758                 raise TemplateSyntaxError, "'if' statement improperly formatted"
       
   759             if not_ != 'not':
       
   760                 raise TemplateSyntaxError, "Expected 'not' in if statement"
       
   761             boolvars.append((True, parser.compile_filter(boolvar)))
       
   762         else:
       
   763             boolvars.append((False, parser.compile_filter(boolpair)))
       
   764     nodelist_true = parser.parse(('else', 'endif'))
       
   765     token = parser.next_token()
       
   766     if token.contents == 'else':
       
   767         nodelist_false = parser.parse(('endif',))
       
   768         parser.delete_first_token()
       
   769     else:
       
   770         nodelist_false = NodeList()
       
   771     return IfNode(boolvars, nodelist_true, nodelist_false, link_type)
       
   772 do_if = register.tag("if", do_if)
       
   773 
       
   774 #@register.tag
       
   775 def ifchanged(parser, token):
       
   776     """
       
   777     Checks if a value has changed from the last iteration of a loop.
       
   778 
       
   779     The 'ifchanged' block tag is used within a loop. It has two possible uses.
       
   780 
       
   781     1. Checks its own rendered contents against its previous state and only
       
   782        displays the content if it has changed. For example, this displays a
       
   783        list of days, only displaying the month if it changes::
       
   784 
       
   785             <h1>Archive for {{ year }}</h1>
       
   786 
       
   787             {% for date in days %}
       
   788                 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
       
   789                 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
       
   790             {% endfor %}
       
   791 
       
   792     2. If given a variable, check whether that variable has changed.
       
   793        For example, the following shows the date every time it changes, but
       
   794        only shows the hour if both the hour and the date have changed::
       
   795 
       
   796             {% for date in days %}
       
   797                 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
       
   798                 {% ifchanged date.hour date.date %}
       
   799                     {{ date.hour }}
       
   800                 {% endifchanged %}
       
   801             {% endfor %}
       
   802     """
       
   803     bits = token.contents.split()
       
   804     nodelist = parser.parse(('endifchanged',))
       
   805     parser.delete_first_token()
       
   806     return IfChangedNode(nodelist, *bits[1:])
       
   807 ifchanged = register.tag(ifchanged)
       
   808 
       
   809 #@register.tag
       
   810 def ssi(parser, token):
       
   811     """
       
   812     Outputs the contents of a given file into the page.
       
   813 
       
   814     Like a simple "include" tag, the ``ssi`` tag includes the contents
       
   815     of another file -- which must be specified using an absolute path --
       
   816     in the current page::
       
   817 
       
   818         {% ssi /home/html/ljworld.com/includes/right_generic.html %}
       
   819 
       
   820     If the optional "parsed" parameter is given, the contents of the included
       
   821     file are evaluated as template code, with the current context::
       
   822 
       
   823         {% ssi /home/html/ljworld.com/includes/right_generic.html parsed %}
       
   824     """
       
   825     bits = token.contents.split()
       
   826     parsed = False
       
   827     if len(bits) not in (2, 3):
       
   828         raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
       
   829                                   " the file to be included")
       
   830     if len(bits) == 3:
       
   831         if bits[2] == 'parsed':
       
   832             parsed = True
       
   833         else:
       
   834             raise TemplateSyntaxError("Second (optional) argument to %s tag"
       
   835                                       " must be 'parsed'" % bits[0])
       
   836     return SsiNode(bits[1], parsed)
       
   837 ssi = register.tag(ssi)
       
   838 
       
   839 #@register.tag
       
   840 def load(parser, token):
       
   841     """
       
   842     Loads a custom template tag set.
       
   843 
       
   844     For example, to load the template tags in
       
   845     ``django/templatetags/news/photos.py``::
       
   846 
       
   847         {% load news.photos %}
       
   848     """
       
   849     bits = token.contents.split()
       
   850     for taglib in bits[1:]:
       
   851         # add the library to the parser
       
   852         try:
       
   853             lib = get_library("django.templatetags.%s" % taglib)
       
   854             parser.add_library(lib)
       
   855         except InvalidTemplateLibrary, e:
       
   856             raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
       
   857                                       (taglib, e))
       
   858     return LoadNode()
       
   859 load = register.tag(load)
       
   860 
       
   861 #@register.tag
       
   862 def now(parser, token):
       
   863     """
       
   864     Displays the date, formatted according to the given string.
       
   865 
       
   866     Uses the same format as PHP's ``date()`` function; see http://php.net/date
       
   867     for all the possible values.
       
   868 
       
   869     Sample usage::
       
   870 
       
   871         It is {% now "jS F Y H:i" %}
       
   872     """
       
   873     bits = token.contents.split('"')
       
   874     if len(bits) != 3:
       
   875         raise TemplateSyntaxError, "'now' statement takes one argument"
       
   876     format_string = bits[1]
       
   877     return NowNode(format_string)
       
   878 now = register.tag(now)
       
   879 
       
   880 #@register.tag
       
   881 def regroup(parser, token):
       
   882     """
       
   883     Regroups a list of alike objects by a common attribute.
       
   884 
       
   885     This complex tag is best illustrated by use of an example:  say that
       
   886     ``people`` is a list of ``Person`` objects that have ``first_name``,
       
   887     ``last_name``, and ``gender`` attributes, and you'd like to display a list
       
   888     that looks like:
       
   889 
       
   890         * Male:
       
   891             * George Bush
       
   892             * Bill Clinton
       
   893         * Female:
       
   894             * Margaret Thatcher
       
   895             * Colendeeza Rice
       
   896         * Unknown:
       
   897             * Pat Smith
       
   898 
       
   899     The following snippet of template code would accomplish this dubious task::
       
   900 
       
   901         {% regroup people by gender as grouped %}
       
   902         <ul>
       
   903         {% for group in grouped %}
       
   904             <li>{{ group.grouper }}
       
   905             <ul>
       
   906                 {% for item in group.list %}
       
   907                 <li>{{ item }}</li>
       
   908                 {% endfor %}
       
   909             </ul>
       
   910         {% endfor %}
       
   911         </ul>
       
   912 
       
   913     As you can see, ``{% regroup %}`` populates a variable with a list of
       
   914     objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
       
   915     item that was grouped by; ``list`` contains the list of objects that share
       
   916     that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
       
   917     and ``Unknown``, and ``list`` is the list of people with those genders.
       
   918 
       
   919     Note that `{% regroup %}`` does not work when the list to be grouped is not
       
   920     sorted by the key you are grouping by!  This means that if your list of
       
   921     people was not sorted by gender, you'd need to make sure it is sorted
       
   922     before using it, i.e.::
       
   923 
       
   924         {% regroup people|dictsort:"gender" by gender as grouped %}
       
   925 
       
   926     """
       
   927     firstbits = token.contents.split(None, 3)
       
   928     if len(firstbits) != 4:
       
   929         raise TemplateSyntaxError, "'regroup' tag takes five arguments"
       
   930     target = parser.compile_filter(firstbits[1])
       
   931     if firstbits[2] != 'by':
       
   932         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
       
   933     lastbits_reversed = firstbits[3][::-1].split(None, 2)
       
   934     if lastbits_reversed[1][::-1] != 'as':
       
   935         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
       
   936                                   " be 'as'")
       
   937 
       
   938     expression = parser.compile_filter(lastbits_reversed[2][::-1])
       
   939 
       
   940     var_name = lastbits_reversed[0][::-1]
       
   941     return RegroupNode(target, expression, var_name)
       
   942 regroup = register.tag(regroup)
       
   943 
       
   944 def spaceless(parser, token):
       
   945     """
       
   946     Removes whitespace between HTML tags, including tab and newline characters.
       
   947 
       
   948     Example usage::
       
   949 
       
   950         {% spaceless %}
       
   951             <p>
       
   952                 <a href="foo/">Foo</a>
       
   953             </p>
       
   954         {% endspaceless %}
       
   955 
       
   956     This example would return this HTML::
       
   957 
       
   958         <p><a href="foo/">Foo</a></p>
       
   959 
       
   960     Only space between *tags* is normalized -- not space between tags and text.
       
   961     In this example, the space around ``Hello`` won't be stripped::
       
   962 
       
   963         {% spaceless %}
       
   964             <strong>
       
   965                 Hello
       
   966             </strong>
       
   967         {% endspaceless %}
       
   968     """
       
   969     nodelist = parser.parse(('endspaceless',))
       
   970     parser.delete_first_token()
       
   971     return SpacelessNode(nodelist)
       
   972 spaceless = register.tag(spaceless)
       
   973 
       
   974 #@register.tag
       
   975 def templatetag(parser, token):
       
   976     """
       
   977     Outputs one of the bits used to compose template tags.
       
   978 
       
   979     Since the template system has no concept of "escaping", to display one of
       
   980     the bits used in template tags, you must use the ``{% templatetag %}`` tag.
       
   981 
       
   982     The argument tells which template bit to output:
       
   983 
       
   984         ==================  =======
       
   985         Argument            Outputs
       
   986         ==================  =======
       
   987         ``openblock``       ``{%``
       
   988         ``closeblock``      ``%}``
       
   989         ``openvariable``    ``{{``
       
   990         ``closevariable``   ``}}``
       
   991         ``openbrace``       ``{``
       
   992         ``closebrace``      ``}``
       
   993         ``opencomment``     ``{#``
       
   994         ``closecomment``    ``#}``
       
   995         ==================  =======
       
   996     """
       
   997     bits = token.contents.split()
       
   998     if len(bits) != 2:
       
   999         raise TemplateSyntaxError, "'templatetag' statement takes one argument"
       
  1000     tag = bits[1]
       
  1001     if tag not in TemplateTagNode.mapping:
       
  1002         raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
       
  1003                                   " Must be one of: %s" %
       
  1004                                   (tag, TemplateTagNode.mapping.keys()))
       
  1005     return TemplateTagNode(tag)
       
  1006 templatetag = register.tag(templatetag)
       
  1007 
       
  1008 def url(parser, token):
       
  1009     """
       
  1010     Returns an absolute URL matching given view with its parameters.
       
  1011 
       
  1012     This is a way to define links that aren't tied to a particular URL
       
  1013     configuration::
       
  1014 
       
  1015         {% url path.to.some_view arg1,arg2,name1=value1 %}
       
  1016 
       
  1017     The first argument is a path to a view. It can be an absolute python path
       
  1018     or just ``app_name.view_name`` without the project name if the view is
       
  1019     located inside the project.  Other arguments are comma-separated values
       
  1020     that will be filled in place of positional and keyword arguments in the
       
  1021     URL. All arguments for the URL should be present.
       
  1022 
       
  1023     For example if you have a view ``app_name.client`` taking client's id and
       
  1024     the corresponding line in a URLconf looks like this::
       
  1025 
       
  1026         ('^client/(\d+)/$', 'app_name.client')
       
  1027 
       
  1028     and this app's URLconf is included into the project's URLconf under some
       
  1029     path::
       
  1030 
       
  1031         ('^clients/', include('project_name.app_name.urls'))
       
  1032 
       
  1033     then in a template you can create a link for a certain client like this::
       
  1034 
       
  1035         {% url app_name.client client.id %}
       
  1036 
       
  1037     The URL will look like ``/clients/client/123/``.
       
  1038     """
       
  1039     bits = token.contents.split(' ', 2)
       
  1040     if len(bits) < 2:
       
  1041         raise TemplateSyntaxError("'%s' takes at least one argument"
       
  1042                                   " (path to a view)" % bits[0])
       
  1043     args = []
       
  1044     kwargs = {}
       
  1045     if len(bits) > 2:
       
  1046         for arg in bits[2].split(','):
       
  1047             if '=' in arg:
       
  1048                 k, v = arg.split('=', 1)
       
  1049                 k = k.strip()
       
  1050                 kwargs[k] = parser.compile_filter(v)
       
  1051             else:
       
  1052                 args.append(parser.compile_filter(arg))
       
  1053     return URLNode(bits[1], args, kwargs)
       
  1054 url = register.tag(url)
       
  1055 
       
  1056 #@register.tag
       
  1057 def widthratio(parser, token):
       
  1058     """
       
  1059     For creating bar charts and such, this tag calculates the ratio of a given
       
  1060     value to a maximum value, and then applies that ratio to a constant.
       
  1061 
       
  1062     For example::
       
  1063 
       
  1064         <img src='bar.gif' height='10' width='{% widthratio this_value max_value 100 %}' />
       
  1065 
       
  1066     Above, if ``this_value`` is 175 and ``max_value`` is 200, the the image in
       
  1067     the above example will be 88 pixels wide (because 175/200 = .875;
       
  1068     .875 * 100 = 87.5 which is rounded up to 88).
       
  1069     """
       
  1070     bits = token.contents.split()
       
  1071     if len(bits) != 4:
       
  1072         raise TemplateSyntaxError("widthratio takes three arguments")
       
  1073     tag, this_value_expr, max_value_expr, max_width = bits
       
  1074     try:
       
  1075         max_width = int(max_width)
       
  1076     except ValueError:
       
  1077         raise TemplateSyntaxError("widthratio final argument must be an integer")
       
  1078     return WidthRatioNode(parser.compile_filter(this_value_expr),
       
  1079                           parser.compile_filter(max_value_expr), max_width)
       
  1080 widthratio = register.tag(widthratio)
       
  1081 
       
  1082 #@register.tag
       
  1083 def do_with(parser, token):
       
  1084     """
       
  1085     Adds a value to the context (inside of this block) for caching and easy
       
  1086     access.
       
  1087 
       
  1088     For example::
       
  1089 
       
  1090         {% with person.some_sql_method as total %}
       
  1091             {{ total }} object{{ total|pluralize }}
       
  1092         {% endwith %}
       
  1093     """
       
  1094     bits = list(token.split_contents())
       
  1095     if len(bits) != 4 or bits[2] != "as":
       
  1096         raise TemplateSyntaxError("%r expected format is 'value as name'" %
       
  1097                                   bits[0])
       
  1098     var = parser.compile_filter(bits[1])
       
  1099     name = bits[3]
       
  1100     nodelist = parser.parse(('endwith',))
       
  1101     parser.delete_first_token()
       
  1102     return WithNode(var, name, nodelist)
       
  1103 do_with = register.tag('with', do_with)