Ticket #20434: patch2.2.patch

File patch2.2.patch, 105.9 KB (added by jonathanslenders, 6 years ago)

Refactoring of the built-in tags to use the TemplateTag and Grammar class. (and thus become introspectable.)

  • django/template/defaulttags.py

    commit c2144eddd05d8b15cbc4abcea8106bb09974ab6a
    Author: Jonathan Slenders <jonathan@slenders.be>
    Date:   Wed Jun 5 22:46:34 2013 +0200
    
        refactoring of the built-in tags to use the TemplateTag class. (and become introspectable.)
    
    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 04e7a37..0d3480d 100644
    a b from django.template.base import (Node, NodeList, Template, Context, Library, 
    1414    SINGLE_BRACE_START, SINGLE_BRACE_END, COMMENT_TAG_START, COMMENT_TAG_END,
    1515    VARIABLE_ATTRIBUTE_SEPARATOR, get_library, token_kwargs, kwarg_re,
    1616    render_value_in_context)
     17from django.template.generic import TemplateTag, Grammar
    1718from django.template.smartif import IfParser, Literal
    1819from django.template.defaultfilters import date
    1920from django.utils.encoding import smart_text
    from django.utils import timezone 
    2425
    2526register = Library()
    2627
    27 class AutoEscapeControlNode(Node):
    28     """Implements the actions of the autoescape tag."""
    29     def __init__(self, setting, nodelist):
    30         self.setting, self.nodelist = setting, nodelist
     28@register.tag
     29class AutoEscapeControlNode(TemplateTag):
     30    """
     31    Force autoescape behavior for this block.
     32    """
     33    # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
     34    grammar = Grammar('autoescape endautoescape', _split_contents=False)
     35
     36    def __init__(self, parser, parse_result):
     37        args = parse_result.arguments.split()
     38
     39        if len(args) != 1:
     40            raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
     41        arg = args[0]
     42        if arg not in ('on', 'off'):
     43            raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
     44
     45        self.setting = (arg == 'on')
    3146
    3247    def render(self, context):
    3348        old_setting = context.autoescape
    class CommentNode(Node): 
    4358    def render(self, context):
    4459        return ''
    4560
    46 class CsrfTokenNode(Node):
     61@register.tag
     62class CsrfTokenNode(TemplateTag):
     63    grammar = Grammar('csrf_token')
     64
    4765    def render(self, context):
    4866        csrf_token = context.get('csrf_token', None)
    4967        if csrf_token:
    class CsrfTokenNode(Node): 
    5977                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.")
    6078            return ''
    6179
    62 class CycleNode(Node):
    63     def __init__(self, cyclevars, variable_name=None, silent=False, escape=False):
    64         self.cyclevars = cyclevars
    65         self.variable_name = variable_name
    66         self.silent = silent
    67         self.escape = escape        # only while the "future" version exists
     80@register.tag
     81class DebugNode(TemplateTag):
     82    """
     83    Outputs a whole load of debugging information, including the current
     84    context and imported modules.
    6885
    69     def render(self, context):
    70         if self not in context.render_context:
    71             # First time the node is rendered in template
    72             context.render_context[self] = itertools_cycle(self.cyclevars)
    73         cycle_iter = context.render_context[self]
    74         value = next(cycle_iter).resolve(context)
    75         if self.variable_name:
    76             context[self.variable_name] = value
    77         if self.silent:
    78             return ''
    79         if not self.escape:
    80             value = mark_safe(value)
    81         return render_value_in_context(value, context)
     86    Sample usage::
     87
     88        <pre>
     89            {% debug %}
     90        </pre>
     91    """
     92    grammar = Grammar('debug')
    8293
    83 class DebugNode(Node):
    8494    def render(self, context):
    8595        from pprint import pformat
    8696        output = [pformat(val) for val in context]
    class DebugNode(Node): 
    8898        output.append(pformat(sys.modules))
    8999        return ''.join(output)
    90100
    91 class FilterNode(Node):
    92     def __init__(self, filter_expr, nodelist):
    93         self.filter_expr, self.nodelist = filter_expr, nodelist
     101@register.tag
     102class FilterNode(TemplateTag):
     103    """
     104    Filters the contents of the block through variable filters.
     105
     106    Filters can also be piped through each other, and they can have
     107    arguments -- just like in variable syntax.
     108
     109    Sample usage::
     110
     111        {% filter force_escape|lower %}
     112            This text will be HTML-escaped, and will appear in lowercase.
     113        {% endfilter %}
     114
     115    Note that the ``escape`` and ``safe`` filters are not acceptable arguments.
     116    Instead, use the ``autoescape`` tag to manage autoescaping for blocks of
     117    template code.
     118    """
     119    # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
     120    grammar = Grammar('filter endfilter', _split_contents=False)
     121
     122    def __init__(self, parser, parse_result):
     123        filter_expr = parser.compile_filter("var|%s" % parse_result.arguments)
     124        for func, unused in filter_expr.filters:
     125            if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
     126                raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
     127
     128        self.filter_expr = filter_expr
    94129
    95130    def render(self, context):
    96131        output = self.nodelist.render(context)
    class FilterNode(Node): 
    100135        context.pop()
    101136        return filtered
    102137
    103 class FirstOfNode(Node):
    104     def __init__(self, variables, escape=False):
    105         self.vars = variables
    106         self.escape = escape        # only while the "future" version exists
     138
     139@register.tag
     140class FirstOfNode(TemplateTag):
     141    """
     142    Outputs the first variable passed that is not False, without escaping.
     143
     144    Outputs nothing if all the passed variables are False.
     145
     146    Sample usage::
     147
     148        {% firstof var1 var2 var3 %}
     149
     150    This is equivalent to::
     151
     152        {% if var1 %}
     153            {{ var1|safe }}
     154        {% elif var2 %}
     155            {{ var2|safe }}
     156        {% elif var3 %}
     157            {{ var3|safe }}
     158        {% endif %}
     159
     160    but obviously much cleaner!
     161
     162    You can also use a literal string as a fallback value in case all
     163    passed variables are False::
     164
     165        {% firstof var1 var2 var3 "fallback value" %}
     166
     167    If you want to escape the output, use a filter tag::
     168
     169        {% filter force_escape %}
     170            {% firstof var1 var2 var3 "fallback value" %}
     171        {% endfilter %}
     172
     173    """
     174    grammar = Grammar('firstof')
     175    escape = False # Only while the "future" version exists
     176
     177    def __init__(self, parser, parse_result):
     178        if not self.escape:
     179            warnings.warn(
     180                "'The `firstof` template tag is changing to escape its arguments; "
     181                "the non-autoescaping version is deprecated. Load it "
     182                "from the `future` tag library to start using the new behavior.",
     183                PendingDeprecationWarning, stacklevel=2)
     184
     185        args = parse_result.arguments
     186        if len(args) < 1:
     187            raise TemplateSyntaxError("'firstof' statement requires at least one argument")
     188        self.vars = [ parser.compile_filter(a) for a in args]
    107189
    108190    def render(self, context):
    109191        for var in self.vars:
    class FirstOfNode(Node): 
    114196                return render_value_in_context(value, context)
    115197        return ''
    116198
    117 class ForNode(Node):
     199@register.tag
     200class ForNode(TemplateTag):
     201    """
     202    Loops over each item in an array.
     203
     204    For example, to display a list of athletes given ``athlete_list``::
     205
     206        <ul>
     207        {% for athlete in athlete_list %}
     208            <li>{{ athlete.name }}</li>
     209        {% endfor %}
     210        </ul>
     211
     212    You can loop over a list in reverse by using
     213    ``{% for obj in list reversed %}``.
     214
     215    You can also unpack multiple values from a two-dimensional array::
     216
     217        {% for key,value in dict.items %}
     218            {{ key }}: {{ value }}
     219        {% endfor %}
     220
     221    The ``for`` tag can take an optional ``{% empty %}`` clause that will
     222    be displayed if the given array is empty or could not be found::
     223
     224        <ul>
     225          {% for athlete in athlete_list %}
     226            <li>{{ athlete.name }}</li>
     227          {% empty %}
     228            <li>Sorry, no athletes in this list.</li>
     229          {% endfor %}
     230        <ul>
     231
     232    The above is equivalent to -- but shorter, cleaner, and possibly faster
     233    than -- the following::
     234
     235        <ul>
     236          {% if althete_list %}
     237            {% for athlete in athlete_list %}
     238              <li>{{ athlete.name }}</li>
     239            {% endfor %}
     240          {% else %}
     241            <li>Sorry, no athletes in this list.</li>
     242          {% endif %}
     243        </ul>
     244
     245    The for loop sets a number of variables available within the loop:
     246
     247        ==========================  ================================================
     248        Variable                    Description
     249        ==========================  ================================================
     250        ``forloop.counter``         The current iteration of the loop (1-indexed)
     251        ``forloop.counter0``        The current iteration of the loop (0-indexed)
     252        ``forloop.revcounter``      The number of iterations from the end of the
     253                                    loop (1-indexed)
     254        ``forloop.revcounter0``     The number of iterations from the end of the
     255                                    loop (0-indexed)
     256        ``forloop.first``           True if this is the first time through the loop
     257        ``forloop.last``            True if this is the last time through the loop
     258        ``forloop.parentloop``      For nested loops, this is the loop "above" the
     259                                    current one
     260        ==========================  ================================================
     261
     262    """
     263    grammar = Grammar('for empty? endfor')
    118264    child_nodelists = ('nodelist_loop', 'nodelist_empty')
    119265
    120     def __init__(self, loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty=None):
    121         self.loopvars, self.sequence = loopvars, sequence
    122         self.is_reversed = is_reversed
    123         self.nodelist_loop = nodelist_loop
    124         if nodelist_empty is None:
    125             self.nodelist_empty = NodeList()
    126         else:
    127             self.nodelist_empty = nodelist_empty
     266    def __init__(self, parser, parse_result):
     267        self.nodelist_empty = NodeList()
     268
     269        for p in parse_result.parts:
     270            if p.name == 'for':
     271                bits = p.arguments
     272                bit_contents = ' '.join(bits)
     273
     274                if len(bits) < 3:
     275                    raise TemplateSyntaxError("'for' statements should have at least four"
     276                                              " words: %s" % bit_contents)
     277
     278                self.is_reversed = bits[-1] == 'reversed'
     279                in_index = -3 if self.is_reversed else -2
     280                if bits[in_index] != 'in':
     281                    raise TemplateSyntaxError("'for' statements should use the format"
     282                                              " 'for x in y': %s" % bit_contents)
     283
     284                self.loopvars = re.split(r' *, *', ' '.join(bits[:in_index]))
     285                for var in self.loopvars:
     286                    if not var or ' ' in var:
     287                        raise TemplateSyntaxError("'for' tag received an invalid argument:"
     288                                                  " %s" % bit_contents)
     289
     290                self.sequence = parser.compile_filter(bits[in_index+1])
     291                self.nodelist_loop = p.nodelist
     292
     293            elif p.name == 'empty':
     294                self.nodelist_empty = p.nodelist
    128295
    129296    def __repr__(self):
    130297        reversed_text = ' reversed' if self.is_reversed else ''
    class ForNode(Node): 
    210377        context.pop()
    211378        return nodelist.render(context)
    212379
    213 class IfChangedNode(Node):
     380@register.tag
     381class IfChangedTag(TemplateTag):
     382    """
     383    Checks if a value has changed from the last iteration of a loop.
     384
     385    The ``{% ifchanged %}`` block tag is used within a loop. It has two
     386    possible uses.
     387
     388    1. Checks its own rendered contents against its previous state and only
     389       displays the content if it has changed. For example, this displays a
     390       list of days, only displaying the month if it changes::
     391
     392            <h1>Archive for {{ year }}</h1>
     393
     394            {% for date in days %}
     395                {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
     396                <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
     397            {% endfor %}
     398
     399    2. If given one or more variables, check whether any variable has changed.
     400       For example, the following shows the date every time it changes, while
     401       showing the hour if either the hour or the date has changed::
     402
     403            {% for date in days %}
     404                {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
     405                {% ifchanged date.hour date.date %}
     406                    {{ date.hour }}
     407                {% endifchanged %}
     408            {% endfor %}
     409    """
     410    grammar = Grammar('ifchanged else? endifchanged')
    214411    child_nodelists = ('nodelist_true', 'nodelist_false')
    215412
    216     def __init__(self, nodelist_true, nodelist_false, *varlist):
    217         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    218         self._varlist = varlist
     413    def __init__(self, parser, parse_result):
     414        self.nodelist_true = NodeList()
     415        self.nodelist_false = NodeList()
     416
     417        for p in parse_result.parts:
     418            if p.name == 'ifchanged':
     419                self.nodelist_true = p.nodelist
     420                self._varlist = [parser.compile_filter(a) for a in p.arguments]
     421            elif p.name == 'else':
     422                self.nodelist_false = p.nodelist
    219423
    220424    def render(self, context):
    221425        # Init state storage
    class IfChangedNode(Node): 
    254458            # Using ifchanged outside loops. Effectively this is a no-op because the state is associated with 'self'.
    255459            return context.render_context
    256460
    257 class IfEqualNode(Node):
     461
     462@register.tag
     463class IfEqualNode(TemplateTag):
     464    """
     465    Outputs the contents of the block if the two arguments equal each other.
     466
     467    Examples::
     468
     469        {% ifequal user.id comment.user_id %}
     470            ...
     471        {% endifequal %}
     472
     473        {% ifnotequal user.id comment.user_id %}
     474            ...
     475        {% else %}
     476            ...
     477        {% endifnotequal %}
     478    """
     479    grammar = Grammar('ifequal else? endifequal')
    258480    child_nodelists = ('nodelist_true', 'nodelist_false')
     481    negate = False
     482
     483    def __init__(self, parser, parse_result):
     484        self.nodelist_true = NodeList()
     485        self.nodelist_false = NodeList()
     486        self.var1 = self.val2 = None
    259487
    260     def __init__(self, var1, var2, nodelist_true, nodelist_false, negate):
    261         self.var1, self.var2 = var1, var2
    262         self.nodelist_true, self.nodelist_false = nodelist_true, nodelist_false
    263         self.negate = negate
     488        for p in parse_result.parts:
     489            if p.name in ('ifequal', 'ifnotequal'):
     490                if len(p.arguments) != 2:
     491                    raise TemplateSyntaxError("%r takes two arguments" % p.name)
     492
     493                self.nodelist_true = p.nodelist
     494                self.var1 = parser.compile_filter(p.arguments[0])
     495                self.var2 = parser.compile_filter(p.arguments[1])
     496            elif p.name == 'else':
     497                self.nodelist_false = p.nodelist
    264498
    265499    def __repr__(self):
    266500        return "<IfEqualNode>"
    class IfEqualNode(Node): 
    272506            return self.nodelist_true.render(context)
    273507        return self.nodelist_false.render(context)
    274508
    275 class IfNode(Node):
     509@register.tag
     510class IfNotEqualNode(IfEqualNode):
     511    """
     512    Outputs the contents of the block if the two arguments are not equal.
     513    See ifequal.
     514    """
     515    grammar = Grammar('ifnotequal else? endifnotequal')
     516    negate = True
    276517
    277     def __init__(self, conditions_nodelists):
    278         self.conditions_nodelists = conditions_nodelists
     518@register.tag
     519class IfNode(TemplateTag):
     520    """
     521    The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
     522    (i.e., exists, is not empty, and is not a false boolean value), the
     523    contents of the block are output:
    279524
    280     def __repr__(self):
    281         return "<IfNode>"
     525    ::
    282526
    283     def __iter__(self):
    284         for _, nodelist in self.conditions_nodelists:
    285             for node in nodelist:
    286                 yield node
     527        {% if athlete_list %}
     528            Number of athletes: {{ athlete_list|count }}
     529        {% elif athlete_in_locker_room_list %}
     530            Athletes should be out of the locker room soon!
     531        {% else %}
     532            No athletes.
     533        {% endif %}
    287534
    288     @property
    289     def nodelist(self):
    290         return NodeList(node for _, nodelist in self.conditions_nodelists for node in nodelist)
     535    In the above, if ``athlete_list`` is not empty, the number of athletes will
     536    be displayed by the ``{{ athlete_list|count }}`` variable.
    291537
    292     def render(self, context):
    293         for condition, nodelist in self.conditions_nodelists:
     538    As you can see, the ``if`` tag may take one or several `` {% elif %}``
     539    clauses, as well as an ``{% else %}`` clause that will be displayed if all
     540    previous conditions fail. These clauses are optional.
    294541
    295             if condition is not None:           # if / elif clause
    296                 try:
    297                     match = condition.eval(context)
    298                 except VariableDoesNotExist:
    299                     match = None
    300             else:                               # else clause
    301                 match = True
     542    ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
     543    variables or to negate a given variable::
    302544
    303             if match:
    304                 return nodelist.render(context)
    305 
    306         return ''
    307 
    308 class RegroupNode(Node):
    309     def __init__(self, target, expression, var_name):
    310         self.target, self.expression = target, expression
    311         self.var_name = var_name
    312 
    313     def resolve_expression(self, obj, context):
    314         # This method is called for each object in self.target. See regroup()
    315         # for the reason why we temporarily put the object in the context.
    316         context[self.var_name] = obj
    317         return self.expression.resolve(context, True)
    318 
    319     def render(self, context):
    320         obj_list = self.target.resolve(context, True)
    321         if obj_list == None:
    322             # target variable wasn't found in context; fail silently.
    323             context[self.var_name] = []
    324             return ''
    325         # List of dictionaries in the format:
    326         # {'grouper': 'key', 'list': [list of contents]}.
    327         context[self.var_name] = [
    328             {'grouper': key, 'list': list(val)}
    329             for key, val in
    330             groupby(obj_list, lambda obj: self.resolve_expression(obj, context))
    331         ]
    332         return ''
    333 
    334 def include_is_allowed(filepath):
    335     for root in settings.ALLOWED_INCLUDE_ROOTS:
    336         if filepath.startswith(root):
    337             return True
    338     return False
    339 
    340 class SsiNode(Node):
    341     def __init__(self, filepath, parsed):
    342         self.filepath = filepath
    343         self.parsed = parsed
    344 
    345     def render(self, context):
    346         filepath = self.filepath.resolve(context)
    347 
    348         if not include_is_allowed(filepath):
    349             if settings.DEBUG:
    350                 return "[Didn't have permission to include file]"
    351             else:
    352                 return '' # Fail silently for invalid includes.
    353         try:
    354             with open(filepath, 'r') as fp:
    355                 output = fp.read()
    356         except IOError:
    357             output = ''
    358         if self.parsed:
    359             try:
    360                 t = Template(output, name=filepath)
    361                 return t.render(context)
    362             except TemplateSyntaxError as e:
    363                 if settings.DEBUG:
    364                     return "[Included template had syntax error: %s]" % e
    365                 else:
    366                     return '' # Fail silently for invalid included templates.
    367         return output
    368 
    369 class LoadNode(Node):
    370     def render(self, context):
    371         return ''
    372 
    373 class NowNode(Node):
    374     def __init__(self, format_string):
    375         self.format_string = format_string
    376 
    377     def render(self, context):
    378         tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None
    379         return date(datetime.now(tz=tzinfo), self.format_string)
    380 
    381 class SpacelessNode(Node):
    382     def __init__(self, nodelist):
    383         self.nodelist = nodelist
    384 
    385     def render(self, context):
    386         from django.utils.html import strip_spaces_between_tags
    387         return strip_spaces_between_tags(self.nodelist.render(context).strip())
    388 
    389 class TemplateTagNode(Node):
    390     mapping = {'openblock': BLOCK_TAG_START,
    391                'closeblock': BLOCK_TAG_END,
    392                'openvariable': VARIABLE_TAG_START,
    393                'closevariable': VARIABLE_TAG_END,
    394                'openbrace': SINGLE_BRACE_START,
    395                'closebrace': SINGLE_BRACE_END,
    396                'opencomment': COMMENT_TAG_START,
    397                'closecomment': COMMENT_TAG_END,
    398                }
    399 
    400     def __init__(self, tagtype):
    401         self.tagtype = tagtype
    402 
    403     def render(self, context):
    404         return self.mapping.get(self.tagtype, '')
    405 
    406 class URLNode(Node):
    407     def __init__(self, view_name, args, kwargs, asvar):
    408         self.view_name = view_name
    409         self.args = args
    410         self.kwargs = kwargs
    411         self.asvar = asvar
    412 
    413     def render(self, context):
    414         from django.core.urlresolvers import reverse, NoReverseMatch
    415         args = [arg.resolve(context) for arg in self.args]
    416         kwargs = dict([(smart_text(k, 'ascii'), v.resolve(context))
    417                        for k, v in self.kwargs.items()])
    418 
    419         view_name = self.view_name.resolve(context)
    420 
    421         if not view_name:
    422             raise NoReverseMatch("'url' requires a non-empty first argument. "
    423                 "The syntax changed in Django 1.5, see the docs.")
    424 
    425         # Try to look up the URL twice: once given the view name, and again
    426         # relative to what we guess is the "main" app. If they both fail,
    427         # re-raise the NoReverseMatch unless we're using the
    428         # {% url ... as var %} construct in which case return nothing.
    429         url = ''
    430         try:
    431             url = reverse(view_name, args=args, kwargs=kwargs, current_app=context.current_app)
    432         except NoReverseMatch:
    433             exc_info = sys.exc_info()
    434             if settings.SETTINGS_MODULE:
    435                 project_name = settings.SETTINGS_MODULE.split('.')[0]
    436                 try:
    437                     url = reverse(project_name + '.' + view_name,
    438                               args=args, kwargs=kwargs,
    439                               current_app=context.current_app)
    440                 except NoReverseMatch:
    441                     if self.asvar is None:
    442                         # Re-raise the original exception, not the one with
    443                         # the path relative to the project. This makes a
    444                         # better error message.
    445                         six.reraise(*exc_info)
    446             else:
    447                 if self.asvar is None:
    448                     raise
    449 
    450         if self.asvar:
    451             context[self.asvar] = url
    452             return ''
    453         else:
    454             return url
    455 
    456 class VerbatimNode(Node):
    457     def __init__(self, content):
    458         self.content = content
    459 
    460     def render(self, context):
    461         return self.content
    462 
    463 class WidthRatioNode(Node):
    464     def __init__(self, val_expr, max_expr, max_width):
    465         self.val_expr = val_expr
    466         self.max_expr = max_expr
    467         self.max_width = max_width
    468 
    469     def render(self, context):
    470         try:
    471             value = self.val_expr.resolve(context)
    472             max_value = self.max_expr.resolve(context)
    473             max_width = int(self.max_width.resolve(context))
    474         except VariableDoesNotExist:
    475             return ''
    476         except (ValueError, TypeError):
    477             raise TemplateSyntaxError("widthratio final argument must be a number")
    478         try:
    479             value = float(value)
    480             max_value = float(max_value)
    481             ratio = (value / max_value) * max_width
    482         except ZeroDivisionError:
    483             return '0'
    484         except (ValueError, TypeError):
    485             return ''
    486         return str(int(round(ratio)))
    487 
    488 class WithNode(Node):
    489     def __init__(self, var, name, nodelist, extra_context=None):
    490         self.nodelist = nodelist
    491         # var and name are legacy attributes, being left in case they are used
    492         # by third-party subclasses of this Node.
    493         self.extra_context = extra_context or {}
    494         if name:
    495             self.extra_context[name] = var
    496 
    497     def __repr__(self):
    498         return "<WithNode>"
    499 
    500     def render(self, context):
    501         values = dict([(key, val.resolve(context)) for key, val in
    502                        six.iteritems(self.extra_context)])
    503         context.update(values)
    504         output = self.nodelist.render(context)
    505         context.pop()
    506         return output
    507 
    508 @register.tag
    509 def autoescape(parser, token):
    510     """
    511     Force autoescape behavior for this block.
    512     """
    513     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    514     args = token.contents.split()
    515     if len(args) != 2:
    516         raise TemplateSyntaxError("'autoescape' tag requires exactly one argument.")
    517     arg = args[1]
    518     if arg not in ('on', 'off'):
    519         raise TemplateSyntaxError("'autoescape' argument should be 'on' or 'off'")
    520     nodelist = parser.parse(('endautoescape',))
    521     parser.delete_first_token()
    522     return AutoEscapeControlNode((arg == 'on'), nodelist)
    523 
    524 @register.tag
    525 def comment(parser, token):
    526     """
    527     Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
    528     """
    529     parser.skip_past('endcomment')
    530     return CommentNode()
    531 
    532 @register.tag
    533 def cycle(parser, token, escape=False):
    534     """
    535     Cycles among the given strings each time this tag is encountered.
    536 
    537     Within a loop, cycles among the given strings each time through
    538     the loop::
    539 
    540         {% for o in some_list %}
    541             <tr class="{% cycle 'row1' 'row2' %}">
    542                 ...
    543             </tr>
    544         {% endfor %}
    545 
    546     Outside of a loop, give the values a unique name the first time you call
    547     it, then use that name each sucessive time through::
    548 
    549             <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
    550             <tr class="{% cycle rowcolors %}">...</tr>
    551             <tr class="{% cycle rowcolors %}">...</tr>
    552 
    553     You can use any number of values, separated by spaces. Commas can also
    554     be used to separate values; if a comma is used, the cycle values are
    555     interpreted as literal strings.
    556 
    557     The optional flag "silent" can be used to prevent the cycle declaration
    558     from returning any value::
    559 
    560         {% for o in some_list %}
    561             {% cycle 'row1' 'row2' as rowcolors silent %}
    562             <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr>
    563         {% endfor %}
    564 
    565     """
    566     if not escape:
    567         warnings.warn(
    568             "'The `cycle` template tag is changing to escape its arguments; "
    569             "the non-autoescaping version is deprecated. Load it "
    570             "from the `future` tag library to start using the new behavior.",
    571             PendingDeprecationWarning, stacklevel=2)
    572 
    573     # Note: This returns the exact same node on each {% cycle name %} call;
    574     # that is, the node object returned from {% cycle a b c as name %} and the
    575     # one returned from {% cycle name %} are the exact same object. This
    576     # shouldn't cause problems (heh), but if it does, now you know.
    577     #
    578     # Ugly hack warning: This stuffs the named template dict into parser so
    579     # that names are only unique within each template (as opposed to using
    580     # a global variable, which would make cycle names have to be unique across
    581     # *all* templates.
    582 
    583     args = token.split_contents()
    584 
    585     if len(args) < 2:
    586         raise TemplateSyntaxError("'cycle' tag requires at least two arguments")
    587 
    588     if ',' in args[1]:
    589         # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
    590         # case.
    591         args[1:2] = ['"%s"' % arg for arg in args[1].split(",")]
    592 
    593     if len(args) == 2:
    594         # {% cycle foo %} case.
    595         name = args[1]
    596         if not hasattr(parser, '_namedCycleNodes'):
    597             raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
    598         if not name in parser._namedCycleNodes:
    599             raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
    600         return parser._namedCycleNodes[name]
    601 
    602     as_form = False
    603 
    604     if len(args) > 4:
    605         # {% cycle ... as foo [silent] %} case.
    606         if args[-3] == "as":
    607             if args[-1] != "silent":
    608                 raise TemplateSyntaxError("Only 'silent' flag is allowed after cycle's name, not '%s'." % args[-1])
    609             as_form = True
    610             silent = True
    611             args = args[:-1]
    612         elif args[-2] == "as":
    613             as_form = True
    614             silent = False
    615 
    616     if as_form:
    617         name = args[-1]
    618         values = [parser.compile_filter(arg) for arg in args[1:-2]]
    619         node = CycleNode(values, name, silent=silent, escape=escape)
    620         if not hasattr(parser, '_namedCycleNodes'):
    621             parser._namedCycleNodes = {}
    622         parser._namedCycleNodes[name] = node
    623     else:
    624         values = [parser.compile_filter(arg) for arg in args[1:]]
    625         node = CycleNode(values, escape=escape)
    626     return node
    627 
    628 @register.tag
    629 def csrf_token(parser, token):
    630     return CsrfTokenNode()
    631 
    632 @register.tag
    633 def debug(parser, token):
    634     """
    635     Outputs a whole load of debugging information, including the current
    636     context and imported modules.
    637 
    638     Sample usage::
    639 
    640         <pre>
    641             {% debug %}
    642         </pre>
    643     """
    644     return DebugNode()
    645 
    646 @register.tag('filter')
    647 def do_filter(parser, token):
    648     """
    649     Filters the contents of the block through variable filters.
    650 
    651     Filters can also be piped through each other, and they can have
    652     arguments -- just like in variable syntax.
    653 
    654     Sample usage::
    655 
    656         {% filter force_escape|lower %}
    657             This text will be HTML-escaped, and will appear in lowercase.
    658         {% endfilter %}
    659 
    660     Note that the ``escape`` and ``safe`` filters are not acceptable arguments.
    661     Instead, use the ``autoescape`` tag to manage autoescaping for blocks of
    662     template code.
    663     """
    664     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    665     _, rest = token.contents.split(None, 1)
    666     filter_expr = parser.compile_filter("var|%s" % (rest))
    667     for func, unused in filter_expr.filters:
    668         if getattr(func, '_decorated_function', func).__name__ in ('escape', 'safe'):
    669             raise TemplateSyntaxError('"filter %s" is not permitted.  Use the "autoescape" tag instead.' % func.__name__)
    670     nodelist = parser.parse(('endfilter',))
    671     parser.delete_first_token()
    672     return FilterNode(filter_expr, nodelist)
    673 
    674 @register.tag
    675 def firstof(parser, token, escape=False):
    676     """
    677     Outputs the first variable passed that is not False, without escaping.
    678 
    679     Outputs nothing if all the passed variables are False.
    680 
    681     Sample usage::
    682 
    683         {% firstof var1 var2 var3 %}
    684 
    685     This is equivalent to::
    686 
    687         {% if var1 %}
    688             {{ var1|safe }}
    689         {% elif var2 %}
    690             {{ var2|safe }}
    691         {% elif var3 %}
    692             {{ var3|safe }}
    693         {% endif %}
    694 
    695     but obviously much cleaner!
    696 
    697     You can also use a literal string as a fallback value in case all
    698     passed variables are False::
    699 
    700         {% firstof var1 var2 var3 "fallback value" %}
    701 
    702     If you want to escape the output, use a filter tag::
    703 
    704         {% filter force_escape %}
    705             {% firstof var1 var2 var3 "fallback value" %}
    706         {% endfilter %}
    707 
    708     """
    709     if not escape:
    710         warnings.warn(
    711             "'The `firstof` template tag is changing to escape its arguments; "
    712             "the non-autoescaping version is deprecated. Load it "
    713             "from the `future` tag library to start using the new behavior.",
    714             PendingDeprecationWarning, stacklevel=2)
    715 
    716     bits = token.split_contents()[1:]
    717     if len(bits) < 1:
    718         raise TemplateSyntaxError("'firstof' statement requires at least one argument")
    719     return FirstOfNode([parser.compile_filter(bit) for bit in bits], escape=escape)
    720 
    721 @register.tag('for')
    722 def do_for(parser, token):
    723     """
    724     Loops over each item in an array.
    725 
    726     For example, to display a list of athletes given ``athlete_list``::
    727 
    728         <ul>
    729         {% for athlete in athlete_list %}
    730             <li>{{ athlete.name }}</li>
    731         {% endfor %}
    732         </ul>
    733 
    734     You can loop over a list in reverse by using
    735     ``{% for obj in list reversed %}``.
    736 
    737     You can also unpack multiple values from a two-dimensional array::
    738 
    739         {% for key,value in dict.items %}
    740             {{ key }}: {{ value }}
    741         {% endfor %}
    742 
    743     The ``for`` tag can take an optional ``{% empty %}`` clause that will
    744     be displayed if the given array is empty or could not be found::
    745 
    746         <ul>
    747           {% for athlete in athlete_list %}
    748             <li>{{ athlete.name }}</li>
    749           {% empty %}
    750             <li>Sorry, no athletes in this list.</li>
    751           {% endfor %}
    752         <ul>
    753 
    754     The above is equivalent to -- but shorter, cleaner, and possibly faster
    755     than -- the following::
    756 
    757         <ul>
    758           {% if althete_list %}
    759             {% for athlete in athlete_list %}
    760               <li>{{ athlete.name }}</li>
    761             {% endfor %}
    762           {% else %}
    763             <li>Sorry, no athletes in this list.</li>
    764           {% endif %}
    765         </ul>
    766 
    767     The for loop sets a number of variables available within the loop:
    768 
    769         ==========================  ================================================
    770         Variable                    Description
    771         ==========================  ================================================
    772         ``forloop.counter``         The current iteration of the loop (1-indexed)
    773         ``forloop.counter0``        The current iteration of the loop (0-indexed)
    774         ``forloop.revcounter``      The number of iterations from the end of the
    775                                     loop (1-indexed)
    776         ``forloop.revcounter0``     The number of iterations from the end of the
    777                                     loop (0-indexed)
    778         ``forloop.first``           True if this is the first time through the loop
    779         ``forloop.last``            True if this is the last time through the loop
    780         ``forloop.parentloop``      For nested loops, this is the loop "above" the
    781                                     current one
    782         ==========================  ================================================
    783 
    784     """
    785     bits = token.split_contents()
    786     if len(bits) < 4:
    787         raise TemplateSyntaxError("'for' statements should have at least four"
    788                                   " words: %s" % token.contents)
    789 
    790     is_reversed = bits[-1] == 'reversed'
    791     in_index = -3 if is_reversed else -2
    792     if bits[in_index] != 'in':
    793         raise TemplateSyntaxError("'for' statements should use the format"
    794                                   " 'for x in y': %s" % token.contents)
    795 
    796     loopvars = re.split(r' *, *', ' '.join(bits[1:in_index]))
    797     for var in loopvars:
    798         if not var or ' ' in var:
    799             raise TemplateSyntaxError("'for' tag received an invalid argument:"
    800                                       " %s" % token.contents)
    801 
    802     sequence = parser.compile_filter(bits[in_index+1])
    803     nodelist_loop = parser.parse(('empty', 'endfor',))
    804     token = parser.next_token()
    805     if token.contents == 'empty':
    806         nodelist_empty = parser.parse(('endfor',))
    807         parser.delete_first_token()
    808     else:
    809         nodelist_empty = None
    810     return ForNode(loopvars, sequence, is_reversed, nodelist_loop, nodelist_empty)
    811 
    812 def do_ifequal(parser, token, negate):
    813     bits = list(token.split_contents())
    814     if len(bits) != 3:
    815         raise TemplateSyntaxError("%r takes two arguments" % bits[0])
    816     end_tag = 'end' + bits[0]
    817     nodelist_true = parser.parse(('else', end_tag))
    818     token = parser.next_token()
    819     if token.contents == 'else':
    820         nodelist_false = parser.parse((end_tag,))
    821         parser.delete_first_token()
    822     else:
    823         nodelist_false = NodeList()
    824     val1 = parser.compile_filter(bits[1])
    825     val2 = parser.compile_filter(bits[2])
    826     return IfEqualNode(val1, val2, nodelist_true, nodelist_false, negate)
    827 
    828 @register.tag
    829 def ifequal(parser, token):
    830     """
    831     Outputs the contents of the block if the two arguments equal each other.
    832 
    833     Examples::
    834 
    835         {% ifequal user.id comment.user_id %}
    836             ...
    837         {% endifequal %}
    838 
    839         {% ifnotequal user.id comment.user_id %}
    840             ...
    841         {% else %}
    842             ...
    843         {% endifnotequal %}
    844     """
    845     return do_ifequal(parser, token, False)
    846 
    847 @register.tag
    848 def ifnotequal(parser, token):
    849     """
    850     Outputs the contents of the block if the two arguments are not equal.
    851     See ifequal.
    852     """
    853     return do_ifequal(parser, token, True)
    854 
    855 class TemplateLiteral(Literal):
    856     def __init__(self, value, text):
    857         self.value = value
    858         self.text = text # for better error messages
    859 
    860     def display(self):
    861         return self.text
    862 
    863     def eval(self, context):
    864         return self.value.resolve(context, ignore_failures=True)
    865 
    866 class TemplateIfParser(IfParser):
    867     error_class = TemplateSyntaxError
    868 
    869     def __init__(self, parser, *args, **kwargs):
    870         self.template_parser = parser
    871         super(TemplateIfParser, self).__init__(*args, **kwargs)
    872 
    873     def create_var(self, value):
    874         return TemplateLiteral(self.template_parser.compile_filter(value), value)
    875 
    876 @register.tag('if')
    877 def do_if(parser, token):
    878     """
    879     The ``{% if %}`` tag evaluates a variable, and if that variable is "true"
    880     (i.e., exists, is not empty, and is not a false boolean value), the
    881     contents of the block are output:
    882 
    883     ::
    884 
    885         {% if athlete_list %}
    886             Number of athletes: {{ athlete_list|count }}
    887         {% elif athlete_in_locker_room_list %}
    888             Athletes should be out of the locker room soon!
    889         {% else %}
    890             No athletes.
    891         {% endif %}
    892 
    893     In the above, if ``athlete_list`` is not empty, the number of athletes will
    894     be displayed by the ``{{ athlete_list|count }}`` variable.
    895 
    896     As you can see, the ``if`` tag may take one or several `` {% elif %}``
    897     clauses, as well as an ``{% else %}`` clause that will be displayed if all
    898     previous conditions fail. These clauses are optional.
    899 
    900     ``if`` tags may use ``or``, ``and`` or ``not`` to test a number of
    901     variables or to negate a given variable::
    902 
    903         {% if not athlete_list %}
    904             There are no athletes.
    905         {% endif %}
     545        {% if not athlete_list %}
     546            There are no athletes.
     547        {% endif %}
    906548
    907549        {% if athlete_list or coach_list %}
    908550            There are some athletes or some coaches.
    def do_if(parser, token): 
    933575
    934576    Operator precedence follows Python.
    935577    """
    936     # {% if ... %}
    937     bits = token.split_contents()[1:]
    938     condition = TemplateIfParser(parser, bits).parse()
    939     nodelist = parser.parse(('elif', 'else', 'endif'))
    940     conditions_nodelists = [(condition, nodelist)]
    941     token = parser.next_token()
     578    grammar = Grammar('if elif* else? endif')
    942579
    943     # {% elif ... %} (repeatable)
    944     while token.contents.startswith('elif'):
    945         bits = token.split_contents()[1:]
    946         condition = TemplateIfParser(parser, bits).parse()
    947         nodelist = parser.parse(('elif', 'else', 'endif'))
    948         conditions_nodelists.append((condition, nodelist))
    949         token = parser.next_token()
     580    def __init__(self, parser, parse_result):
     581        self.conditions_nodelists = []
     582        for p in parse_result.parts:
     583            if p.name in ('if', 'elif'):
     584                condition = TemplateIfParser(parser, p.arguments).parse()
     585                self.conditions_nodelists.append( (condition, p.nodelist) )
     586            elif p.name == 'else':
     587                self.conditions_nodelists.append( (None, p.nodelist) )
     588
     589    def __repr__(self):
     590        return "<IfNode>"
    950591
    951     # {% else %} (optional)
    952     if token.contents == 'else':
    953         nodelist = parser.parse(('endif',))
    954         conditions_nodelists.append((None, nodelist))
    955         token = parser.next_token()
     592    def render(self, context):
     593        for condition, nodelist in self.conditions_nodelists:
    956594
    957     # {% endif %}
    958     assert token.contents == 'endif'
     595            if condition is not None:           # if / elif clause
     596                try:
     597                    match = condition.eval(context)
     598                except VariableDoesNotExist:
     599                    match = None
     600            else:                               # else clause
     601                match = True
    959602
    960     return IfNode(conditions_nodelists)
     603            if match:
     604                return nodelist.render(context)
    961605
     606        return ''
    962607
    963608@register.tag
    964 def ifchanged(parser, token):
     609class RegroupNode(TemplateTag):
    965610    """
    966     Checks if a value has changed from the last iteration of a loop.
     611    Regroups a list of alike objects by a common attribute.
    967612
    968     The ``{% ifchanged %}`` block tag is used within a loop. It has two
    969     possible uses.
     613    This complex tag is best illustrated by use of an example:  say that
     614    ``people`` is a list of ``Person`` objects that have ``first_name``,
     615    ``last_name``, and ``gender`` attributes, and you'd like to display a list
     616    that looks like:
    970617
    971     1. Checks its own rendered contents against its previous state and only
    972        displays the content if it has changed. For example, this displays a
    973        list of days, only displaying the month if it changes::
     618        * Male:
     619            * George Bush
     620            * Bill Clinton
     621        * Female:
     622            * Margaret Thatcher
     623            * Colendeeza Rice
     624        * Unknown:
     625            * Pat Smith
    974626
    975             <h1>Archive for {{ year }}</h1>
     627    The following snippet of template code would accomplish this dubious task::
    976628
    977             {% for date in days %}
    978                 {% ifchanged %}<h3>{{ date|date:"F" }}</h3>{% endifchanged %}
    979                 <a href="{{ date|date:"M/d"|lower }}/">{{ date|date:"j" }}</a>
    980             {% endfor %}
     629        {% regroup people by gender as grouped %}
     630        <ul>
     631        {% for group in grouped %}
     632            <li>{{ group.grouper }}
     633            <ul>
     634                {% for item in group.list %}
     635                <li>{{ item }}</li>
     636                {% endfor %}
     637            </ul>
     638        {% endfor %}
     639        </ul>
    981640
    982     2. If given one or more variables, check whether any variable has changed.
    983        For example, the following shows the date every time it changes, while
    984        showing the hour if either the hour or the date has changed::
     641    As you can see, ``{% regroup %}`` populates a variable with a list of
     642    objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
     643    item that was grouped by; ``list`` contains the list of objects that share
     644    that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
     645    and ``Unknown``, and ``list`` is the list of people with those genders.
     646
     647    Note that ``{% regroup %}`` does not work when the list to be grouped is not
     648    sorted by the key you are grouping by!  This means that if your list of
     649    people was not sorted by gender, you'd need to make sure it is sorted
     650    before using it, i.e.::
     651
     652        {% regroup people|dictsort:"gender" by gender as grouped %}
    985653
    986             {% for date in days %}
    987                 {% ifchanged date.date %} {{ date.date }} {% endifchanged %}
    988                 {% ifchanged date.hour date.date %}
    989                     {{ date.hour }}
    990                 {% endifchanged %}
    991             {% endfor %}
    992654    """
    993     bits = token.split_contents()
    994     nodelist_true = parser.parse(('else', 'endifchanged'))
    995     token = parser.next_token()
    996     if token.contents == 'else':
    997         nodelist_false = parser.parse(('endifchanged',))
    998         parser.delete_first_token()
    999     else:
    1000         nodelist_false = NodeList()
    1001     values = [parser.compile_filter(bit) for bit in bits[1:]]
    1002     return IfChangedNode(nodelist_true, nodelist_false, *values)
     655    grammar = Grammar('regroup')
     656
     657    def __init__(self, parser, parse_result):
     658        bits = parse_result.arguments
     659
     660        if len(bits) != 5:
     661            raise TemplateSyntaxError("'regroup' tag takes five arguments")
     662        self.target = parser.compile_filter(bits[0])
     663        if bits[1] != 'by':
     664            raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
     665        if bits[3] != 'as':
     666            raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
     667                                      " be 'as'")
     668        self.var_name = bits[4]
     669        # RegroupNode will take each item in 'target', put it in the context under
     670        # 'var_name', evaluate 'var_name'.'expression' in the current context, and
     671        # group by the resulting value. After all items are processed, it will
     672        # save the final result in the context under 'var_name', thus clearing the
     673        # temporary values. This hack is necessary because the template engine
     674        # doesn't provide a context-aware equivalent of Python's getattr.
     675        self.expression = parser.compile_filter(self.var_name +
     676                                           VARIABLE_ATTRIBUTE_SEPARATOR +
     677                                           bits[2])
     678
     679    def resolve_expression(self, obj, context):
     680        # This method is called for each object in self.target. See regroup()
     681        # for the reason why we temporarily put the object in the context.
     682        context[self.var_name] = obj
     683        return self.expression.resolve(context, True)
     684
     685    def render(self, context):
     686        obj_list = self.target.resolve(context, True)
     687        if obj_list == None:
     688            # target variable wasn't found in context; fail silently.
     689            context[self.var_name] = []
     690            return ''
     691        # List of dictionaries in the format:
     692        # {'grouper': 'key', 'list': [list of contents]}.
     693        context[self.var_name] = [
     694            {'grouper': key, 'list': list(val)}
     695            for key, val in
     696            groupby(obj_list, lambda obj: self.resolve_expression(obj, context))
     697        ]
     698        return ''
     699
     700def include_is_allowed(filepath):
     701    for root in settings.ALLOWED_INCLUDE_ROOTS:
     702        if filepath.startswith(root):
     703            return True
     704    return False
    1003705
    1004706@register.tag
    1005 def ssi(parser, token):
     707class SsiNode(TemplateTag):
    1006708    """
    1007709    Outputs the contents of a given file into the page.
    1008710
    def ssi(parser, token): 
    1017719
    1018720        {% ssi "/home/html/ljworld.com/includes/right_generic.html" parsed %}
    1019721    """
    1020     bits = token.split_contents()
    1021     parsed = False
    1022     if len(bits) not in (2, 3):
    1023         raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
    1024                                   " the file to be included")
    1025     if len(bits) == 3:
    1026         if bits[2] == 'parsed':
    1027             parsed = True
    1028         else:
    1029             raise TemplateSyntaxError("Second (optional) argument to %s tag"
    1030                                       " must be 'parsed'" % bits[0])
    1031     filepath = parser.compile_filter(bits[1])
    1032     return SsiNode(filepath, parsed)
     722    grammar = Grammar('ssi')
     723
     724    def __init__(self, parser, parse_result):
     725        bits = parse_result.arguments
     726
     727        self.parsed = False
     728        if len(bits) not in (1, 2):
     729            raise TemplateSyntaxError("'ssi' tag takes one argument: the path to"
     730                                      " the file to be included")
     731        if len(bits) == 2:
     732            if bits[1] == 'parsed':
     733                self.parsed = True
     734            else:
     735                raise TemplateSyntaxError("Second (optional) argument to %s tag"
     736                                          " must be 'parsed'" % name)
     737        self.filepath = parser.compile_filter(bits[0])
     738
     739    def render(self, context):
     740        filepath = self.filepath.resolve(context)
     741
     742        if not include_is_allowed(filepath):
     743            if settings.DEBUG:
     744                return "[Didn't have permission to include file]"
     745            else:
     746                return '' # Fail silently for invalid includes.
     747        try:
     748            with open(filepath, 'r') as fp:
     749                output = fp.read()
     750        except IOError:
     751            output = ''
     752        if self.parsed:
     753            try:
     754                t = Template(output, name=filepath)
     755                return t.render(context)
     756            except TemplateSyntaxError as e:
     757                if settings.DEBUG:
     758                    return "[Included template had syntax error: %s]" % e
     759                else:
     760                    return '' # Fail silently for invalid included templates.
     761        return output
    1033762
    1034763@register.tag
    1035 def load(parser, token):
     764class LoadNode(TemplateTag):
    1036765    """
    1037766    Loads a custom template tag set.
    1038767
    def load(parser, token): 
    1048777
    1049778    """
    1050779    # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    1051     bits = token.contents.split()
    1052     if len(bits) >= 4 and bits[-2] == "from":
    1053         try:
    1054             taglib = bits[-1]
    1055             lib = get_library(taglib)
    1056         except InvalidTemplateLibrary as e:
    1057             raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
    1058                                       (taglib, e))
    1059         else:
    1060             temp_lib = Library()
    1061             for name in bits[1:-2]:
    1062                 if name in lib.tags:
    1063                     temp_lib.tags[name] = lib.tags[name]
    1064                     # a name could be a tag *and* a filter, so check for both
    1065                     if name in lib.filters:
    1066                         temp_lib.filters[name] = lib.filters[name]
    1067                 elif name in lib.filters:
    1068                     temp_lib.filters[name] = lib.filters[name]
    1069                 else:
    1070                     raise TemplateSyntaxError("'%s' is not a valid tag or filter in tag library '%s'" %
    1071                                               (name, taglib))
    1072             parser.add_library(temp_lib)
    1073     else:
    1074         for taglib in bits[1:]:
    1075             # add the library to the parser
     780    grammar = Grammar('load', _split_contents=False)
     781
     782    def __init__(self, parser, parse_result):
     783        bits = parse_result.arguments.split()
     784
     785        if len(bits) >= 3 and bits[-2] == "from":
    1076786            try:
     787                taglib = bits[-1]
    1077788                lib = get_library(taglib)
    1078                 parser.add_library(lib)
    1079789            except InvalidTemplateLibrary as e:
    1080790                raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
    1081791                                          (taglib, e))
    1082     return LoadNode()
     792            else:
     793                temp_lib = Library()
     794                for name in bits[0:-2]:
     795                    if name in lib.tags:
     796                        temp_lib.tags[name] = lib.tags[name]
     797                        # a name could be a tag *and* a filter, so check for both
     798                        if name in lib.filters:
     799                            temp_lib.filters[name] = lib.filters[name]
     800                    elif name in lib.filters:
     801                        temp_lib.filters[name] = lib.filters[name]
     802                    else:
     803                        raise TemplateSyntaxError("'%s' is not a valid tag or filter in tag library '%s'" %
     804                                                  (name, taglib))
     805                parser.add_library(temp_lib)
     806        else:
     807            for taglib in bits:
     808                # add the library to the parser
     809                try:
     810                    lib = get_library(taglib)
     811                    parser.add_library(lib)
     812                except InvalidTemplateLibrary as e:
     813                    raise TemplateSyntaxError("'%s' is not a valid tag library: %s" %
     814                                              (taglib, e))
     815
     816    def render(self, context):
     817        return ''
    1083818
    1084819@register.tag
    1085 def now(parser, token):
     820class NowNode(TemplateTag):
    1086821    """
    1087822    Displays the date, formatted according to the given string.
    1088823
    def now(parser, token): 
    1093828
    1094829        It is {% now "jS F Y H:i" %}
    1095830    """
    1096     bits = token.split_contents()
    1097     if len(bits) != 2:
    1098         raise TemplateSyntaxError("'now' statement takes one argument")
    1099     format_string = bits[1][1:-1]
    1100     return NowNode(format_string)
    1101 
    1102 @register.tag
    1103 def regroup(parser, token):
    1104     """
    1105     Regroups a list of alike objects by a common attribute.
    1106 
    1107     This complex tag is best illustrated by use of an example:  say that
    1108     ``people`` is a list of ``Person`` objects that have ``first_name``,
    1109     ``last_name``, and ``gender`` attributes, and you'd like to display a list
    1110     that looks like:
    1111 
    1112         * Male:
    1113             * George Bush
    1114             * Bill Clinton
    1115         * Female:
    1116             * Margaret Thatcher
    1117             * Colendeeza Rice
    1118         * Unknown:
    1119             * Pat Smith
    1120 
    1121     The following snippet of template code would accomplish this dubious task::
    1122 
    1123         {% regroup people by gender as grouped %}
    1124         <ul>
    1125         {% for group in grouped %}
    1126             <li>{{ group.grouper }}
    1127             <ul>
    1128                 {% for item in group.list %}
    1129                 <li>{{ item }}</li>
    1130                 {% endfor %}
    1131             </ul>
    1132         {% endfor %}
    1133         </ul>
    1134 
    1135     As you can see, ``{% regroup %}`` populates a variable with a list of
    1136     objects with ``grouper`` and ``list`` attributes.  ``grouper`` contains the
    1137     item that was grouped by; ``list`` contains the list of objects that share
    1138     that ``grouper``.  In this case, ``grouper`` would be ``Male``, ``Female``
    1139     and ``Unknown``, and ``list`` is the list of people with those genders.
    1140 
    1141     Note that ``{% regroup %}`` does not work when the list to be grouped is not
    1142     sorted by the key you are grouping by!  This means that if your list of
    1143     people was not sorted by gender, you'd need to make sure it is sorted
    1144     before using it, i.e.::
     831    grammar = Grammar('now')
    1145832
    1146         {% regroup people|dictsort:"gender" by gender as grouped %}
     833    def __init__(self, parser, parse_result):
     834        bits = parse_result.arguments
     835        if len(bits) != 1:
     836            raise TemplateSyntaxError("'now' statement takes one argument")
     837        self.format_string = bits[0][1:-1]
    1147838
    1148     """
    1149     bits = token.split_contents()
    1150     if len(bits) != 6:
    1151         raise TemplateSyntaxError("'regroup' tag takes five arguments")
    1152     target = parser.compile_filter(bits[1])
    1153     if bits[2] != 'by':
    1154         raise TemplateSyntaxError("second argument to 'regroup' tag must be 'by'")
    1155     if bits[4] != 'as':
    1156         raise TemplateSyntaxError("next-to-last argument to 'regroup' tag must"
    1157                                   " be 'as'")
    1158     var_name = bits[5]
    1159     # RegroupNode will take each item in 'target', put it in the context under
    1160     # 'var_name', evaluate 'var_name'.'expression' in the current context, and
    1161     # group by the resulting value. After all items are processed, it will
    1162     # save the final result in the context under 'var_name', thus clearing the
    1163     # temporary values. This hack is necessary because the template engine
    1164     # doesn't provide a context-aware equivalent of Python's getattr.
    1165     expression = parser.compile_filter(var_name +
    1166                                        VARIABLE_ATTRIBUTE_SEPARATOR +
    1167                                        bits[3])
    1168     return RegroupNode(target, expression, var_name)
     839    def render(self, context):
     840        tzinfo = timezone.get_current_timezone() if settings.USE_TZ else None
     841        return date(datetime.now(tz=tzinfo), self.format_string)
    1169842
    1170843@register.tag
    1171 def spaceless(parser, token):
     844class SpacelessNode(TemplateTag):
    1172845    """
    1173846    Removes whitespace between HTML tags, including tab and newline characters.
    1174847
    def spaceless(parser, token): 
    1193866            </strong>
    1194867        {% endspaceless %}
    1195868    """
    1196     nodelist = parser.parse(('endspaceless',))
    1197     parser.delete_first_token()
    1198     return SpacelessNode(nodelist)
     869    grammar = Grammar('spaceless endspaceless')
     870
     871    def render(self, context):
     872        from django.utils.html import strip_spaces_between_tags
     873        return strip_spaces_between_tags(self.nodelist.render(context).strip())
     874
    1199875
    1200876@register.tag
    1201 def templatetag(parser, token):
     877class TemplateTagNode(TemplateTag):
    1202878    """
    1203879    Outputs one of the bits used to compose template tags.
    1204880
    def templatetag(parser, token): 
    1220896        ``closecomment``    ``#}``
    1221897        ==================  =======
    1222898    """
    1223     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    1224     bits = token.contents.split()
    1225     if len(bits) != 2:
    1226         raise TemplateSyntaxError("'templatetag' statement takes one argument")
    1227     tag = bits[1]
    1228     if tag not in TemplateTagNode.mapping:
    1229         raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
    1230                                   " Must be one of: %s" %
    1231                                   (tag, list(TemplateTagNode.mapping)))
    1232     return TemplateTagNode(tag)
     899    grammar = Grammar('templatetag')
     900    mapping = {'openblock': BLOCK_TAG_START,
     901               'closeblock': BLOCK_TAG_END,
     902               'openvariable': VARIABLE_TAG_START,
     903               'closevariable': VARIABLE_TAG_END,
     904               'openbrace': SINGLE_BRACE_START,
     905               'closebrace': SINGLE_BRACE_END,
     906               'opencomment': COMMENT_TAG_START,
     907               'closecomment': COMMENT_TAG_END,
     908               }
     909
     910    def __init__(self, parser, parse_result):
     911        bits = parse_result.arguments
     912        if len(bits) != 1:
     913            raise TemplateSyntaxError("'templatetag' statement takes one argument")
     914        self.tagtype = bits[0]
     915        if self.tagtype not in TemplateTagNode.mapping:
     916            raise TemplateSyntaxError("Invalid templatetag argument: '%s'."
     917                                      " Must be one of: %s" %
     918                                      (self.tagtype, list(TemplateTagNode.mapping)))
     919
     920    def render(self, context):
     921        return self.mapping.get(self.tagtype, '')
    1233922
    1234923@register.tag
    1235 def url(parser, token):
     924class URLNode(TemplateTag):
    1236925    """
    1237926    Returns an absolute URL matching given view with its parameters.
    1238927
    def url(parser, token): 
    1295984        {% endwith %}
    1296985
    1297986    """
    1298     bits = token.split_contents()
    1299     if len(bits) < 2:
    1300         raise TemplateSyntaxError("'%s' takes at least one argument"
    1301                                   " (path to a view)" % bits[0])
    1302     try:
    1303         viewname = parser.compile_filter(bits[1])
    1304     except TemplateSyntaxError as exc:
    1305         exc.args = (exc.args[0] + ". "
    1306                 "The syntax of 'url' changed in Django 1.5, see the docs."),
    1307         raise
    1308     args = []
    1309     kwargs = {}
    1310     asvar = None
    1311     bits = bits[2:]
    1312     if len(bits) >= 2 and bits[-2] == 'as':
    1313         asvar = bits[-1]
    1314         bits = bits[:-2]
    1315 
    1316     if len(bits):
    1317         for bit in bits:
    1318             match = kwarg_re.match(bit)
    1319             if not match:
    1320                 raise TemplateSyntaxError("Malformed arguments to url tag")
    1321             name, value = match.groups()
    1322             if name:
    1323                 kwargs[name] = parser.compile_filter(value)
     987    grammar = Grammar('url')
     988
     989    def __init__(self, parser, parse_result):
     990        bits = parse_result.arguments
     991        if len(bits) < 1:
     992            raise TemplateSyntaxError("'%s' takes at least one argument"
     993                                      " (path to a view)" % parse_result.tagname)
     994        try:
     995            view_name = parser.compile_filter(bits[0])
     996        except TemplateSyntaxError as exc:
     997            exc.args = (exc.args[0] + ". "
     998                    "The syntax of 'url' changed in Django 1.5, see the docs."),
     999            raise
     1000        args = []
     1001        kwargs = {}
     1002        asvar = None
     1003        bits = bits[1:]
     1004        if len(bits) >= 2 and bits[-2] == 'as':
     1005            asvar = bits[-1]
     1006            bits = bits[:-2]
     1007
     1008        if len(bits):
     1009            for bit in bits:
     1010                match = kwarg_re.match(bit)
     1011                if not match:
     1012                    raise TemplateSyntaxError("Malformed arguments to url tag")
     1013                name, value = match.groups()
     1014                if name:
     1015                    kwargs[name] = parser.compile_filter(value)
     1016                else:
     1017                    args.append(parser.compile_filter(value))
     1018
     1019        self.view_name = view_name
     1020        self.args, self.kwargs = args, kwargs
     1021        self.asvar = asvar
     1022
     1023    def render(self, context):
     1024        from django.core.urlresolvers import reverse, NoReverseMatch
     1025        args = [arg.resolve(context) for arg in self.args]
     1026        kwargs = dict([(smart_text(k, 'ascii'), v.resolve(context))
     1027                       for k, v in self.kwargs.items()])
     1028
     1029        view_name = self.view_name.resolve(context)
     1030
     1031        if not view_name:
     1032            raise NoReverseMatch("'url' requires a non-empty first argument. "
     1033                "The syntax changed in Django 1.5, see the docs.")
     1034
     1035        # Try to look up the URL twice: once given the view name, and again
     1036        # relative to what we guess is the "main" app. If they both fail,
     1037        # re-raise the NoReverseMatch unless we're using the
     1038        # {% url ... as var %} construct in which case return nothing.
     1039        url = ''
     1040        try:
     1041            url = reverse(view_name, args=args, kwargs=kwargs, current_app=context.current_app)
     1042        except NoReverseMatch:
     1043            exc_info = sys.exc_info()
     1044            if settings.SETTINGS_MODULE:
     1045                project_name = settings.SETTINGS_MODULE.split('.')[0]
     1046                try:
     1047                    url = reverse(project_name + '.' + view_name,
     1048                              args=args, kwargs=kwargs,
     1049                              current_app=context.current_app)
     1050                except NoReverseMatch:
     1051                    if self.asvar is None:
     1052                        # Re-raise the original exception, not the one with
     1053                        # the path relative to the project. This makes a
     1054                        # better error message.
     1055                        six.reraise(*exc_info)
    13241056            else:
    1325                 args.append(parser.compile_filter(value))
     1057                if self.asvar is None:
     1058                    raise
    13261059
    1327     return URLNode(viewname, args, kwargs, asvar)
     1060        if self.asvar:
     1061            context[self.asvar] = url
     1062            return ''
     1063        else:
     1064            return url
    13281065
    13291066@register.tag
    1330 def verbatim(parser, token):
     1067class VerbatimNode(TemplateTag):
    13311068    """
    13321069    Stops the template engine from rendering the contents of this block tag.
    13331070
    def verbatim(parser, token): 
    13441081            ...
    13451082        {% endverbatim myblock %}
    13461083    """
    1347     nodelist = parser.parse(('endverbatim',))
    1348     parser.delete_first_token()
    1349     return VerbatimNode(nodelist.render(Context()))
     1084    grammar = Grammar('verbatim endverbatim')
     1085
     1086    def __init__(self, parser, parse_result):
     1087        self.content = parse_result.nodelist.render(Context())
     1088
     1089    def render(self, context):
     1090        return self.content
    13501091
    13511092@register.tag
    1352 def widthratio(parser, token):
     1093class WidthRatioNode(TemplateTag):
    13531094    """
    13541095    For creating bar charts and such, this tag calculates the ratio of a given
    13551096    value to a maximum value, and then applies that ratio to a constant.
    def widthratio(parser, token): 
    13621103    the image in the above example will be 88 pixels wide
    13631104    (because 175/200 = .875; .875 * 100 = 87.5 which is rounded up to 88).
    13641105    """
    1365     bits = token.split_contents()
    1366     if len(bits) != 4:
    1367         raise TemplateSyntaxError("widthratio takes three arguments")
    1368     tag, this_value_expr, max_value_expr, max_width = bits
     1106    grammar = Grammar('widthratio')
     1107
     1108    def __init__(self, parser, parse_result):
     1109        bits = parse_result.arguments
     1110
     1111        if len(bits) != 3:
     1112            raise TemplateSyntaxError("widthratio takes three arguments")
    13691113
    1370     return WidthRatioNode(parser.compile_filter(this_value_expr),
    1371                           parser.compile_filter(max_value_expr),
    1372                           parser.compile_filter(max_width))
     1114        self.val_expr = parser.compile_filter(bits[0])
     1115        self.max_expr = parser.compile_filter(bits[1])
     1116        self.max_width = parser.compile_filter(bits[2])
     1117
     1118    def render(self, context):
     1119        try:
     1120            value = self.val_expr.resolve(context)
     1121            max_value = self.max_expr.resolve(context)
     1122            max_width = int(self.max_width.resolve(context))
     1123        except VariableDoesNotExist:
     1124            return ''
     1125        except (ValueError, TypeError):
     1126            raise TemplateSyntaxError("widthratio final argument must be a number")
     1127        try:
     1128            value = float(value)
     1129            max_value = float(max_value)
     1130            ratio = (value / max_value) * max_width
     1131        except ZeroDivisionError:
     1132            return '0'
     1133        except (ValueError, TypeError):
     1134            return ''
     1135        return str(int(round(ratio)))
    13731136
    1374 @register.tag('with')
    1375 def do_with(parser, token):
     1137@register.tag
     1138class WithNode(TemplateTag):
    13761139    """
    13771140    Adds one or more values to the context (inside of this block) for caching
    13781141    and easy access.
    def do_with(parser, token): 
    13921155    The legacy format of ``{% with person.some_sql_method as total %}`` is
    13931156    still accepted.
    13941157    """
    1395     bits = token.split_contents()
    1396     remaining_bits = bits[1:]
    1397     extra_context = token_kwargs(remaining_bits, parser, support_legacy=True)
    1398     if not extra_context:
    1399         raise TemplateSyntaxError("%r expected at least one variable "
    1400                                   "assignment" % bits[0])
    1401     if remaining_bits:
    1402         raise TemplateSyntaxError("%r received an invalid token: %r" %
    1403                                   (bits[0], remaining_bits[0]))
    1404     nodelist = parser.parse(('endwith',))
    1405     parser.delete_first_token()
    1406     return WithNode(None, None, nodelist, extra_context=extra_context)
     1158    grammar = Grammar('with endwith')
     1159
     1160    def __init__(self, parser, parse_result):
     1161        bits = parse_result.arguments
     1162
     1163        self.extra_context = token_kwargs(bits, parser, support_legacy=True)
     1164        if not self.extra_context:
     1165            raise TemplateSyntaxError("with expected at least one variable "
     1166                                      "assignment")
     1167        if bits:
     1168            raise TemplateSyntaxError("with received an invalid token: %r" %
     1169                                      (remaining_bits[0])) # XXX: check this. remaining_bits doesn't exist
     1170
     1171    def __repr__(self):
     1172        return "<WithNode>"
     1173
     1174    def render(self, context):
     1175        values = dict([(key, val.resolve(context)) for key, val in
     1176                       six.iteritems(self.extra_context)])
     1177        context.update(values)
     1178        output = self.nodelist.render(context)
     1179        context.pop()
     1180        return output
     1181
     1182@register.tag
     1183def comment(parser, token):
     1184    """
     1185    Ignores everything between ``{% comment %}`` and ``{% endcomment %}``.
     1186    """
     1187    parser.skip_past('endcomment')
     1188    return CommentNode()
     1189comment.grammar = Grammar('comment endcomment')
     1190
     1191@register.tag
     1192class CycleNode(TemplateTag):
     1193    """
     1194    Cycles among the given strings each time this tag is encountered.
     1195
     1196    Within a loop, cycles among the given strings each time through
     1197    the loop::
     1198
     1199        {% for o in some_list %}
     1200            <tr class="{% cycle 'row1' 'row2' %}">
     1201                ...
     1202            </tr>
     1203        {% endfor %}
     1204
     1205    Outside of a loop, give the values a unique name the first time you call
     1206    it, then use that name each sucessive time through::
     1207
     1208            <tr class="{% cycle 'row1' 'row2' 'row3' as rowcolors %}">...</tr>
     1209            <tr class="{% cycle rowcolors %}">...</tr>
     1210            <tr class="{% cycle rowcolors %}">...</tr>
     1211
     1212    You can use any number of values, separated by spaces. Commas can also
     1213    be used to separate values; if a comma is used, the cycle values are
     1214    interpreted as literal strings.
     1215
     1216    The optional flag "silent" can be used to prevent the cycle declaration
     1217    from returning any value::
     1218
     1219        {% for o in some_list %}
     1220            {% cycle 'row1' 'row2' as rowcolors silent %}
     1221            <tr class="{{ rowcolors }}">{% include "subtemplate.html " %}</tr>
     1222        {% endfor %}
     1223
     1224    """
     1225    grammar = Grammar('cycle')
     1226    escape = False # only while the "future" version exists
     1227
     1228    @classmethod
     1229    def handle_parse_result(cls, parser, parse_result):
     1230        if not cls.escape:
     1231            warnings.warn(
     1232                "'The `cycle` template tag is changing to escape its arguments; "
     1233                "the non-autoescaping version is deprecated. Load it "
     1234                "from the `future` tag library to start using the new behavior.",
     1235                PendingDeprecationWarning, stacklevel=2)
     1236
     1237        # Note: This returns the exact same node on each {% cycle name %} call;
     1238        # that is, the node object returned from {% cycle a b c as name %} and the
     1239        # one returned from {% cycle name %} are the exact same object. This
     1240        # shouldn't cause problems (heh), but if it does, now you know.
     1241        #
     1242        # Ugly hack warning: This stuffs the named template dict into parser so
     1243        # that names are only unique within each template (as opposed to using
     1244        # a global variable, which would make cycle names have to be unique across
     1245        # *all* templates.
     1246
     1247        args = parse_result.arguments
     1248
     1249        if len(args) < 1:
     1250            raise TemplateSyntaxError("'cycle' tag requires at least one argument")
     1251
     1252        if ',' in args[0]:
     1253            # Backwards compatibility: {% cycle a,b %} or {% cycle a,b as foo %}
     1254            # case.
     1255            args[0:1] = ['"%s"' % arg for arg in args[0].split(",")]
     1256
     1257        if len(args) == 1:
     1258            # {% cycle foo %} case.
     1259            name = args[0]
     1260            if not hasattr(parser, '_namedCycleNodes'):
     1261                raise TemplateSyntaxError("No named cycles in template. '%s' is not defined" % name)
     1262            if not name in parser._namedCycleNodes:
     1263                raise TemplateSyntaxError("Named cycle '%s' does not exist" % name)
     1264            return parser._namedCycleNodes[name]
     1265
     1266        as_form = False
     1267
     1268        if len(args) > 3:
     1269            # {% cycle ... as foo [silent] %} case.
     1270            if args[-3] == "as":
     1271                if args[-1] != "silent":
     1272                    raise TemplateSyntaxError("Only 'silent' flag is allowed after cycle's name, not '%s'." % args[-1])
     1273                as_form = True
     1274                silent = True
     1275                args = args[:-1]
     1276            elif args[-2] == "as":
     1277                as_form = True
     1278                silent = False
     1279
     1280        if as_form:
     1281            name = args[-1]
     1282            values = [parser.compile_filter(arg) for arg in args[0:-2]]
     1283            node = cls(values, name, silent=silent)
     1284            if not hasattr(parser, '_namedCycleNodes'):
     1285                parser._namedCycleNodes = {}
     1286            parser._namedCycleNodes[name] = node
     1287        else:
     1288            values = [parser.compile_filter(arg) for arg in args]
     1289            node = cls(values)
     1290        return node
     1291
     1292    def __init__(self, cyclevars, variable_name=None, silent=False):
     1293        self.cyclevars = cyclevars
     1294        self.variable_name = variable_name
     1295        self.silent = silent
     1296
     1297    def render(self, context):
     1298        if self not in context.render_context:
     1299            # First time the node is rendered in template
     1300            context.render_context[self] = itertools_cycle(self.cyclevars)
     1301        cycle_iter = context.render_context[self]
     1302        value = next(cycle_iter).resolve(context)
     1303        if self.variable_name:
     1304            context[self.variable_name] = value
     1305        if self.silent:
     1306            return ''
     1307        if not self.escape:
     1308            value = mark_safe(value)
     1309        return render_value_in_context(value, context)
     1310
     1311
     1312class TemplateLiteral(Literal):
     1313    def __init__(self, value, text):
     1314        self.value = value
     1315        self.text = text # for better error messages
     1316
     1317    def display(self):
     1318        return self.text
     1319
     1320    def eval(self, context):
     1321        return self.value.resolve(context, ignore_failures=True)
     1322
     1323class TemplateIfParser(IfParser):
     1324    error_class = TemplateSyntaxError
     1325
     1326    def __init__(self, parser, *args, **kwargs):
     1327        self.template_parser = parser
     1328        super(TemplateIfParser, self).__init__(*args, **kwargs)
     1329
     1330    def create_var(self, value):
     1331        return TemplateLiteral(self.template_parser.compile_filter(value), value)
  • django/template/loader_tags.py

    diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
    index 767f0e5..16b5d60 100644
    a b from django.conf import settings 
    44from django.template.base import TemplateSyntaxError, Library, Node, TextNode,\
    55    token_kwargs, Variable
    66from django.template.loader import get_template
     7from django.template.generic import Grammar
    78from django.utils.safestring import mark_safe
    89from django.utils import six
    910
    def do_block(parser, token): 
    174175    """
    175176    Define a block that can be overridden by child templates.
    176177    """
    177     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    178     bits = token.contents.split()
    179     if len(bits) != 2:
    180         raise TemplateSyntaxError("'%s' tag takes only one argument" % bits[0])
    181     block_name = bits[1]
     178    parse_result = do_block.grammar.parse(parser, token)
     179    bits = parse_result.arguments.split()
     180    tagname = parse_result.tagname
     181
     182    if len(bits) != 1:
     183        raise TemplateSyntaxError("'%s' tag takes only one argument" % tagname)
     184    block_name = bits[0]
    182185    # Keep track of the names of BlockNodes found in this template, so we can
    183186    # check for duplication.
    184187    try:
    185188        if block_name in parser.__loaded_blocks:
    186             raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (bits[0], block_name))
     189            raise TemplateSyntaxError("'%s' tag with name '%s' appears more than once" % (tagname, block_name))
    187190        parser.__loaded_blocks.append(block_name)
    188191    except AttributeError: # parser.__loaded_blocks isn't a list yet
    189192        parser.__loaded_blocks = [block_name]
    190     nodelist = parser.parse(('endblock',))
    191193
    192194    # This check is kept for backwards-compatibility. See #3100.
    193     endblock = parser.next_token()
     195    endblock = parse_result.parts[1].token
    194196    acceptable_endblocks = ('endblock', 'endblock %s' % block_name)
    195197    if endblock.contents not in acceptable_endblocks:
    196198        parser.invalid_block_tag(endblock, 'endblock', acceptable_endblocks)
    197199
    198     return BlockNode(block_name, nodelist)
     200    return BlockNode(block_name, parse_result.nodelist)
     201
     202# token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
     203do_block.grammar = Grammar('block endblock', _split_contents=False)
    199204
    200205@register.tag('extends')
    201206def do_extends(parser, token):
    def do_extends(parser, token): 
    208213    name of the parent template to extend (if it evaluates to a string) or as
    209214    the parent tempate itelf (if it evaluates to a Template object).
    210215    """
    211     bits = token.split_contents()
    212     if len(bits) != 2:
    213         raise TemplateSyntaxError("'%s' takes one argument" % bits[0])
    214     parent_name = parser.compile_filter(bits[1])
     216    parse_result = do_extends.grammar.parse(parser, token)
     217    bits = parse_result.arguments
     218    tagname = parse_result.tagname
     219    if len(bits) != 1:
     220        raise TemplateSyntaxError("'%s' takes one argument" % tagname)
     221    parent_name = parser.compile_filter(bits[0])
    215222    nodelist = parser.parse()
    216223    if nodelist.get_nodes_by_type(ExtendsNode):
    217         raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % bits[0])
     224        raise TemplateSyntaxError("'%s' cannot appear more than once in the same template" % tagname)
    218225    return ExtendsNode(nodelist, parent_name)
    219226
     227do_extends.grammar = Grammar('extends')
     228
     229
    220230@register.tag('include')
    221231def do_include(parser, token):
    222232    """
    def do_include(parser, token): 
    234244        {% include "foo/some_include" only %}
    235245        {% include "foo/some_include" with bar="1" only %}
    236246    """
    237     bits = token.split_contents()
    238     if len(bits) < 2:
    239         raise TemplateSyntaxError("%r tag takes at least one argument: the name of the template to be included." % bits[0])
     247    parse_result = do_include.grammar.parse(parser, token)
     248    bits = parse_result.arguments
     249    tagname = parse_result.tagname
     250
     251    if len(bits) < 1:
     252        raise TemplateSyntaxError("%r tag takes at least one argument: the name of the template to be included." % tagname)
    240253    options = {}
    241     remaining_bits = bits[2:]
     254    remaining_bits = bits[1:]
    242255    while remaining_bits:
    243256        option = remaining_bits.pop(0)
    244257        if option in options:
    def do_include(parser, token): 
    248261            value = token_kwargs(remaining_bits, parser, support_legacy=False)
    249262            if not value:
    250263                raise TemplateSyntaxError('"with" in %r tag needs at least '
    251                                           'one keyword argument.' % bits[0])
     264                                          'one keyword argument.' % tagname)
    252265        elif option == 'only':
    253266            value = True
    254267        else:
    255268            raise TemplateSyntaxError('Unknown argument for %r tag: %r.' %
    256                                       (bits[0], option))
     269                                      (tagname, option))
    257270        options[option] = value
    258271    isolated_context = options.get('only', False)
    259272    namemap = options.get('with', {})
    260     path = bits[1]
     273    path = bits[0]
    261274    if path[0] in ('"', "'") and path[-1] == path[0]:
    262275        return ConstantIncludeNode(path[1:-1], extra_context=namemap,
    263276                                   isolated_context=isolated_context)
    264     return IncludeNode(parser.compile_filter(bits[1]), extra_context=namemap,
     277    return IncludeNode(parser.compile_filter(bits[0]), extra_context=namemap,
    265278                       isolated_context=isolated_context)
     279
     280do_include.grammar = Grammar('include')
  • django/templatetags/cache.py

    diff --git a/django/templatetags/cache.py b/django/templatetags/cache.py
    index 215f117..0871099 100644
    a b  
    11from __future__ import unicode_literals
    22
    33from django.core.cache.utils import make_template_fragment_key
    4 from django.template import Library, Node, TemplateSyntaxError, VariableDoesNotExist
     4from django.template import Library, TemplateSyntaxError, VariableDoesNotExist
     5from django.template.generic import TemplateTag, Grammar
    56from django.core.cache import cache
    67
    78register = Library()
    89
    9 class CacheNode(Node):
    10     def __init__(self, nodelist, expire_time_var, fragment_name, vary_on):
    11         self.nodelist = nodelist
    12         self.expire_time_var = expire_time_var
    13         self.fragment_name = fragment_name
    14         self.vary_on = vary_on
    15 
    16     def render(self, context):
    17         try:
    18             expire_time = self.expire_time_var.resolve(context)
    19         except VariableDoesNotExist:
    20             raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.expire_time_var.var)
    21         try:
    22             expire_time = int(expire_time)
    23         except (ValueError, TypeError):
    24             raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
    25         vary_on = [var.resolve(context) for var in self.vary_on]
    26         cache_key = make_template_fragment_key(self.fragment_name, vary_on)
    27         value = cache.get(cache_key)
    28         if value is None:
    29             value = self.nodelist.render(context)
    30             cache.set(cache_key, value, expire_time)
    31         return value
    32 
    33 @register.tag('cache')
    34 def do_cache(parser, token):
     10@register.tag
     11class CacheNode(TemplateTag):
    3512    """
    3613    This will cache the contents of a template fragment for a given amount
    3714    of time.
    def do_cache(parser, token): 
    5229
    5330    Each unique set of arguments will result in a unique cache entry.
    5431    """
    55     nodelist = parser.parse(('endcache',))
    56     parser.delete_first_token()
    57     tokens = token.split_contents()
    58     if len(tokens) < 3:
    59         raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % tokens[0])
    60     return CacheNode(nodelist,
    61         parser.compile_filter(tokens[1]),
    62         tokens[2], # fragment_name can't be a variable.
    63         [parser.compile_filter(token) for token in tokens[3:]])
     32    grammar = Grammar('cache endcache')
     33
     34    def __init__(self, parser, parse_result):
     35        tokens = parse_result.arguments
     36        if len(tokens) < 2:
     37            raise TemplateSyntaxError("'%r' tag requires at least 2 arguments." % parse_result.tagname)
     38        self.expire_time_var = parser.compile_filter(tokens[0])
     39        self.fragment_name = tokens[1] # fragment_name can't be a variable.
     40        self.vary_on = [parser.compile_filter(token) for token in tokens[2:]]
     41
     42    def render(self, context):
     43        try:
     44            expire_time = self.expire_time_var.resolve(context)
     45        except VariableDoesNotExist:
     46            raise TemplateSyntaxError('"cache" tag got an unknown variable: %r' % self.expire_time_var.var)
     47        try:
     48            expire_time = int(expire_time)
     49        except (ValueError, TypeError):
     50            raise TemplateSyntaxError('"cache" tag got a non-integer timeout value: %r' % expire_time)
     51        vary_on = [var.resolve(context) for var in self.vary_on]
     52        cache_key = make_template_fragment_key(self.fragment_name, vary_on)
     53        value = cache.get(cache_key)
     54        if value is None:
     55            value = self.nodelist.render(context)
     56            cache.set(cache_key, value, expire_time)
     57        return value
  • django/templatetags/future.py

    diff --git a/django/templatetags/future.py b/django/templatetags/future.py
    index 7203f39..4deb120 100644
    a b  
    11from django.template import Library
    2 from django.template import defaulttags
     2from django.template import defaulttags, TemplateTag, Grammar
    33
    44register = Library()
    55
    66
    77@register.tag
    8 def ssi(parser, token):
     8class SsiNode(defaulttags.SsiNode):
    99    # Used for deprecation path during 1.3/1.4, will be removed in 2.0
    10     return defaulttags.ssi(parser, token)
     10    pass
    1111
    1212
    1313@register.tag
    14 def url(parser, token):
     14class URLNode(defaulttags.URLNode):
    1515    # Used for deprecation path during 1.3/1.4, will be removed in 2.0
    16     return defaulttags.url(parser, token)
     16    pass
    1717
    1818
    1919@register.tag
    20 def cycle(parser, token):
     20class CycleNode(defaulttags.CycleNode):
    2121    """
    2222    This is the future version of `cycle` with auto-escaping.
    2323
    def cycle(parser, token): 
    3333
    3434        {% cycle var1 var2|safe var3|safe  as somecycle %}
    3535    """
    36     return defaulttags.cycle(parser, token, escape=True)
     36    escape = True
    3737
    3838
    3939@register.tag
    40 def firstof(parser, token):
     40class FirstOfNode(defaulttags.FirstOfNode):
    4141    """
    4242    This is the future version of `firstof` with auto-escaping.
    4343
    def firstof(parser, token): 
    6262        {% firstof var1 var2|safe var3 "<strong>fallback value</strong>"|safe %}
    6363
    6464    """
    65     return defaulttags.firstof(parser, token, escape=True)
     65    escape = True
  • django/templatetags/i18n.py

    diff --git a/django/templatetags/i18n.py b/django/templatetags/i18n.py
    index 14f0382..82ba4c5 100644
    a b from django.template import (Node, Variable, TemplateSyntaxError, 
    77    TokenParser, Library, TOKEN_TEXT, TOKEN_VAR)
    88from django.template.base import render_value_in_context
    99from django.template.defaulttags import token_kwargs
     10from django.template.generic import TemplateTag, Grammar
    1011from django.utils import six
    1112from django.utils import translation
    1213
    1314
    1415register = Library()
    1516
     17@register.tag
     18class GetLanguageInfoNode(TemplateTag):
     19    """
     20    This will store the language information dictionary for the given language
     21    code in a context variable.
    1622
    17 class GetAvailableLanguagesNode(Node):
    18     def __init__(self, variable):
    19         self.variable = variable
     23    Usage::
    2024
    21     def render(self, context):
    22         context[self.variable] = [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES]
    23         return ''
     25        {% get_language_info for LANGUAGE_CODE as l %}
     26        {{ l.code }}
     27        {{ l.name }}
     28        {{ l.name_local }}
     29        {{ l.bidi|yesno:"bi-directional,uni-directional" }}
     30    """
     31    grammar = Grammar("get_language_info")
    2432
     33    def __init__(self, parser, parse_result):
     34        args = parse_result.arguments
     35        if len(args) != 4 or args[0] != 'for' or args[2] != 'as':
     36            raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (block.name, args))
    2537
    26 class GetLanguageInfoNode(Node):
    27     def __init__(self, lang_code, variable):
    28         self.lang_code = lang_code
    29         self.variable = variable
     38        self.lang_code = parser.compile_filter(args[1])
     39        self.variable = args[3]
    3040
    3141    def render(self, context):
    3242        lang_code = self.lang_code.resolve(context)
    class GetLanguageInfoNode(Node): 
    3444        return ''
    3545
    3646
    37 class GetLanguageInfoListNode(Node):
    38     def __init__(self, languages, variable):
    39         self.languages = languages
    40         self.variable = variable
     47@register.tag
     48class GetLanguageInfoListNode(TemplateTag):
     49    """
     50    This will store a list of language information dictionaries for the given
     51    language codes in a context variable. The language codes can be specified
     52    either as a list of strings or a settings.LANGUAGES style tuple (or any
     53    sequence of sequences whose first items are language codes).
     54
     55    Usage::
     56
     57        {% get_language_info_list for LANGUAGES as langs %}
     58        {% for l in langs %}
     59          {{ l.code }}
     60          {{ l.name }}
     61          {{ l.name_local }}
     62          {{ l.bidi|yesno:"bi-directional,uni-directional" }}
     63        {% endfor %}
     64    """
     65    grammar = Grammar("get_language_info_list")
     66
     67    def __init__(self, parser, parse_result):
     68        args = parse_result.arguments
     69        if len(args) != 4 or args[0] != 'for' or args[2] != 'as':
     70            raise TemplateSyntaxError("'%s' requires 'for sequence as variable' (got %r)" %
     71                            (parse_result.tagname, args))
     72
     73        self.languages = parser.compile_filter(args[1])
     74        self.variable = args[3]
    4175
    4276    def get_language_info(self, language):
    4377        # ``language`` is either a language code string or a sequence
    class GetLanguageInfoListNode(Node): 
    5387        return ''
    5488
    5589
    56 class GetCurrentLanguageNode(Node):
    57     def __init__(self, variable):
    58         self.variable = variable
    59 
    60     def render(self, context):
    61         context[self.variable] = translation.get_language()
    62         return ''
    63 
    64 
    65 class GetCurrentLanguageBidiNode(Node):
    66     def __init__(self, variable):
    67         self.variable = variable
    68 
    69     def render(self, context):
    70         context[self.variable] = translation.get_language_bidi()
    71         return ''
    72 
    73 
    7490class TranslateNode(Node):
    7591    def __init__(self, filter_expression, noop, asvar=None,
    7692                 message_context=None):
    class BlockTranslateNode(Node): 
    160176                result = self.render(context, nested=True)
    161177        return result
    162178
     179@register.tag
     180class LanguageNode(TemplateTag):
     181    """
     182    This will enable the given language just for this block.
     183
     184    Usage::
     185
     186        {% language "de" %}
     187            This is {{ bar }} and {{ boo }}.
     188        {% endlanguage %}
     189
     190    """
     191    grammar = Grammar('language endlanguage')
    163192
    164 class LanguageNode(Node):
    165     def __init__(self, nodelist, language):
    166         self.nodelist = nodelist
    167         self.language = language
     193    def __init__(self, parser, parse_result):
     194        bits = parse_result.arguments
     195        if len(bits) != 1:
     196            raise TemplateSyntaxError("'%s' takes one argument (language)" % parse_result.tagname)
     197        self.language = parser.compile_filter(bits[0])
    168198
    169199    def render(self, context):
    170200        with translation.override(self.language.resolve(context)):
    171201            output = self.nodelist.render(context)
    172202        return output
    173203
    174 
    175 @register.tag("get_available_languages")
    176 def do_get_available_languages(parser, token):
     204@register.assignment_tag
     205def get_available_languages():
    177206    """
    178207    This will store a list of available languages
    179208    in the context.
    def do_get_available_languages(parser, token): 
    189218    your setting file (or the default settings) and
    190219    put it into the named variable.
    191220    """
    192     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    193     args = token.contents.split()
    194     if len(args) != 3 or args[1] != 'as':
    195         raise TemplateSyntaxError("'get_available_languages' requires 'as variable' (got %r)" % args)
    196     return GetAvailableLanguagesNode(args[2])
    197 
    198 @register.tag("get_language_info")
    199 def do_get_language_info(parser, token):
    200     """
    201     This will store the language information dictionary for the given language
    202     code in a context variable.
    203 
    204     Usage::
    205 
    206         {% get_language_info for LANGUAGE_CODE as l %}
    207         {{ l.code }}
    208         {{ l.name }}
    209         {{ l.name_local }}
    210         {{ l.bidi|yesno:"bi-directional,uni-directional" }}
    211     """
    212     args = token.split_contents()
    213     if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
    214         raise TemplateSyntaxError("'%s' requires 'for string as variable' (got %r)" % (args[0], args[1:]))
    215     return GetLanguageInfoNode(parser.compile_filter(args[2]), args[4])
    216 
    217 @register.tag("get_language_info_list")
    218 def do_get_language_info_list(parser, token):
    219     """
    220     This will store a list of language information dictionaries for the given
    221     language codes in a context variable. The language codes can be specified
    222     either as a list of strings or a settings.LANGUAGES style tuple (or any
    223     sequence of sequences whose first items are language codes).
    224 
    225     Usage::
    226 
    227         {% get_language_info_list for LANGUAGES as langs %}
    228         {% for l in langs %}
    229           {{ l.code }}
    230           {{ l.name }}
    231           {{ l.name_local }}
    232           {{ l.bidi|yesno:"bi-directional,uni-directional" }}
    233         {% endfor %}
    234     """
    235     args = token.split_contents()
    236     if len(args) != 5 or args[1] != 'for' or args[3] != 'as':
    237         raise TemplateSyntaxError("'%s' requires 'for sequence as variable' (got %r)" % (args[0], args[1:]))
    238     return GetLanguageInfoListNode(parser.compile_filter(args[2]), args[4])
     221    return [(k, translation.ugettext(v)) for k, v in settings.LANGUAGES]
    239222
    240223@register.filter
    241224def language_name(lang_code):
    def language_name_local(lang_code): 
    249232def language_bidi(lang_code):
    250233    return translation.get_language_info(lang_code)['bidi']
    251234
    252 @register.tag("get_current_language")
    253 def do_get_current_language(parser, token):
     235@register.assignment_tag
     236def get_current_language():
    254237    """
    255238    This will store the current language in the context.
    256239
    def do_get_current_language(parser, token): 
    262245    put it's value into the ``language`` context
    263246    variable.
    264247    """
    265     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    266     args = token.contents.split()
    267     if len(args) != 3 or args[1] != 'as':
    268         raise TemplateSyntaxError("'get_current_language' requires 'as variable' (got %r)" % args)
    269     return GetCurrentLanguageNode(args[2])
    270 
    271 @register.tag("get_current_language_bidi")
    272 def do_get_current_language_bidi(parser, token):
     248    return translation.get_language()
     249
     250@register.assignment_tag
     251def get_current_language_bidi():
    273252    """
    274253    This will store the current language layout in the context.
    275254
    def do_get_current_language_bidi(parser, token): 
    281260    put it's value into the ``bidi`` context variable.
    282261    True indicates right-to-left layout, otherwise left-to-right
    283262    """
    284     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    285     args = token.contents.split()
    286     if len(args) != 3 or args[1] != 'as':
    287         raise TemplateSyntaxError("'get_current_language_bidi' requires 'as variable' (got %r)" % args)
    288     return GetCurrentLanguageBidiNode(args[2])
     263    return translation.get_language_bidi()
    289264
    290265@register.tag("trans")
    291266def do_translate(parser, token):
    def do_translate(parser, token): 
    366341    return TranslateNode(parser.compile_filter(value), noop, asvar,
    367342                         message_context)
    368343
     344do_translate.grammar = Grammar('trans')
     345
    369346@register.tag("blocktrans")
    370347def do_block_translate(parser, token):
    371348    """
    def do_block_translate(parser, token): 
    466443
    467444    return BlockTranslateNode(extra_context, singular, plural, countervar,
    468445            counter, message_context)
    469 
    470 @register.tag
    471 def language(parser, token):
    472     """
    473     This will enable the given language just for this block.
    474 
    475     Usage::
    476 
    477         {% language "de" %}
    478             This is {{ bar }} and {{ boo }}.
    479         {% endlanguage %}
    480 
    481     """
    482     bits = token.split_contents()
    483     if len(bits) != 2:
    484         raise TemplateSyntaxError("'%s' takes one argument (language)" % bits[0])
    485     language = parser.compile_filter(bits[1])
    486     nodelist = parser.parse(('endlanguage',))
    487     parser.delete_first_token()
    488     return LanguageNode(nodelist, language)
     446do_block_translate.grammar = Grammar('blocktrans endblocktrans')
  • django/templatetags/l10n.py

    diff --git a/django/templatetags/l10n.py b/django/templatetags/l10n.py
    index 667de24..6538873 100644
    a b  
    1 from django.template import Node
     1from django.template import TemplateTag, Grammar
    22from django.template import TemplateSyntaxError, Library
    33from django.utils import formats
    44from django.utils.encoding import force_text
    def unlocalize(value): 
    2121    """
    2222    return force_text(value)
    2323
    24 class LocalizeNode(Node):
    25     def __init__(self, nodelist, use_l10n):
    26         self.nodelist = nodelist
    27         self.use_l10n = use_l10n
    28 
    29     def __repr__(self):
    30         return "<LocalizeNode>"
    31 
    32     def render(self, context):
    33         old_setting = context.use_l10n
    34         context.use_l10n = self.use_l10n
    35         output = self.nodelist.render(context)
    36         context.use_l10n = old_setting
    37         return output
    38 
    39 @register.tag('localize')
    40 def localize_tag(parser, token):
     24@register.tag
     25class LocalizeNode(TemplateTag):
    4126    """
    4227    Forces or prevents localization of values, regardless of the value of
    4328    `settings.USE_L10N`.
    def localize_tag(parser, token): 
    4934        {% endlocalize %}
    5035
    5136    """
    52     use_l10n = None
    53     bits = list(token.split_contents())
    54     if len(bits) == 1:
    55         use_l10n = True
    56     elif len(bits) > 2 or bits[1] not in ('on', 'off'):
    57         raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % bits[0])
    58     else:
    59         use_l10n = bits[1] == 'on'
    60     nodelist = parser.parse(('endlocalize',))
    61     parser.delete_first_token()
    62     return LocalizeNode(nodelist, use_l10n)
     37    grammar = Grammar('localize endlocalize')
     38
     39    def __init__(self, parser, parse_result):
     40        self.use_l10n = None
     41        bits = parse_result.arguments
     42        if len(bits) == 0:
     43            self.use_l10n = True
     44        elif len(bits) > 1 or bits[0] not in ('on', 'off'):
     45            raise TemplateSyntaxError("%r argument should be 'on' or 'off'" % self.parse_result.tagname)
     46        else:
     47            self.use_l10n = bits[0] == 'on'
     48
     49    def __repr__(self):
     50        return "<LocalizeNode>"
     51
     52    def render(self, context):
     53        old_setting = context.use_l10n
     54        context.use_l10n = self.use_l10n
     55        output = self.nodelist.render(context)
     56        context.use_l10n = old_setting
     57        return output
     58
  • django/templatetags/static.py

    diff --git a/django/templatetags/static.py b/django/templatetags/static.py
    index 68437e7..320a05b 100644
    a b except ImportError: # Python 2 
    55
    66from django import template
    77from django.template.base import Node
     8from django.template.generic import TemplateTag, Grammar
    89from django.utils.encoding import iri_to_uri
    910
    1011register = template.Library()
    1112
    1213
    13 class PrefixNode(template.Node):
     14class PrefixNode(TemplateTag):
     15    name = None
     16    grammar = None
    1417
    1518    def __repr__(self):
    1619        return "<PrefixNode for %r>" % self.name
    1720
    18     def __init__(self, varname=None, name=None):
    19         if name is None:
    20             raise template.TemplateSyntaxError(
    21                 "Prefix nodes must be given a name to return.")
    22         self.varname = varname
    23         self.name = name
    24 
    25     @classmethod
    26     def handle_token(cls, parser, token, name):
     21    def __init__(self, parser, parse_result):
    2722        """
    2823        Class method to parse prefix node and return a Node.
    2924        """
    30         # token.split_contents() isn't useful here because tags using this method don't accept variable as arguments
    31         tokens = token.contents.split()
    32         if len(tokens) > 1 and tokens[1] != 'as':
     25        tokens = parse_result.arguments.split()
     26        if len(tokens) > 0 and tokens[0] != 'as':
    3327            raise template.TemplateSyntaxError(
    34                 "First argument in '%s' must be 'as'" % tokens[0])
    35         if len(tokens) > 1:
    36             varname = tokens[2]
     28                "First argument in '%s' must be 'as'" % parse_result.tagname)
     29        if len(tokens) > 0:
     30            self.varname = tokens[1]
    3731        else:
    38             varname = None
    39         return cls(varname, name)
     32            self.varname = None
     33
     34        if self.name is None:
     35            raise template.TemplateSyntaxError(
     36                "Prefix nodes must be given a name to return.")
    4037
    4138    @classmethod
    4239    def handle_simple(cls, name):
    class PrefixNode(template.Node): 
    5754
    5855
    5956@register.tag
    60 def get_static_prefix(parser, token):
     57class StaticPrefixNode(PrefixNode):
    6158    """
    6259    Populates a template variable with the static prefix,
    6360    ``settings.STATIC_URL``.
    def get_static_prefix(parser, token): 
    7269        {% get_static_prefix as static_prefix %}
    7370
    7471    """
    75     return PrefixNode.handle_token(parser, token, "STATIC_URL")
     72    # token.split_contents() isn't useful here because tags using this method don't accept variable as arguments
     73    grammar = Grammar('get_static_prefix', _split_contents=False)
     74    name = 'STATIC_URL'
    7675
    7776
    7877@register.tag
    79 def get_media_prefix(parser, token):
     78class MediaPrefixNode(PrefixNode):
    8079    """
    8180    Populates a template variable with the media prefix,
    8281    ``settings.MEDIA_URL``.
    def get_media_prefix(parser, token): 
    9190        {% get_media_prefix as media_prefix %}
    9291
    9392    """
    94     return PrefixNode.handle_token(parser, token, "MEDIA_URL")
    95 
    96 
    97 class StaticNode(Node):
    98     def __init__(self, varname=None, path=None):
    99         if path is None:
    100             raise template.TemplateSyntaxError(
    101                 "Static template nodes must be given a path to return.")
    102         self.path = path
    103         self.varname = varname
    104 
    105     def url(self, context):
    106         path = self.path.resolve(context)
    107         return self.handle_simple(path)
    108 
    109     def render(self, context):
    110         url = self.url(context)
    111         if self.varname is None:
    112             return url
    113         context[self.varname] = url
    114         return ''
    115 
    116     @classmethod
    117     def handle_simple(cls, path):
    118         return urljoin(PrefixNode.handle_simple("STATIC_URL"), path)
    119 
    120     @classmethod
    121     def handle_token(cls, parser, token):
    122         """
    123         Class method to parse prefix node and return a Node.
    124         """
    125         bits = token.split_contents()
     93    grammar = Grammar('get_media_prefix', _split_contents=False)
     94    name = 'MEDIA_URL'
    12695
    127         if len(bits) < 2:
    128             raise template.TemplateSyntaxError(
    129                 "'%s' takes at least one argument (path to file)" % bits[0])
    130 
    131         path = parser.compile_filter(bits[1])
    132 
    133         if len(bits) >= 2 and bits[-2] == 'as':
    134             varname = bits[3]
    135         else:
    136             varname = None
    13796
    138         return cls(varname, path)
    139 
    140 
    141 @register.tag('static')
    142 def do_static(parser, token):
     97@register.tag
     98class StaticNode(TemplateTag):
    14399    """
    144100    Joins the given path with the STATIC_URL setting.
    145101
    def do_static(parser, token): 
    155111        {% static variable_with_path as varname %}
    156112
    157113    """
    158     return StaticNode.handle_token(parser, token)
     114    grammar = Grammar('static')
     115
     116    def __init__(self, parser, parse_result):
     117        """
     118        Class method to parse prefix node and return a Node.
     119        """
     120        bits = parse_result.arguments
     121        if len(bits) < 1:
     122            raise template.TemplateSyntaxError(
     123                "'%s' takes at least one argument (path to file)" % parse_result.tagname)
     124
     125        self.path = parser.compile_filter(bits[0])
     126
     127        if len(bits) >= 2 and bits[1] == 'as':
     128            self.varname = bits[2]
     129        else:
     130            self.varname = None
     131
     132    def render(self, context):
     133        url = self.url(context)
     134        if self.varname is None:
     135            return url
     136        context[self.varname] = url
     137        return ''
     138
     139    def url(self, context):
     140        path = self.path.resolve(context)
     141        return self.handle_simple(path)
     142
     143    @classmethod
     144    def handle_simple(cls, path):
     145        return urljoin(PrefixNode.handle_simple("STATIC_URL"), path)
    159146
    160147
    161148def static(path):
  • django/templatetags/tz.py

    diff --git a/django/templatetags/tz.py b/django/templatetags/tz.py
    index 05088e1..e946f61 100644
    a b except ImportError: 
    77
    88from django.template import Node
    99from django.template import TemplateSyntaxError, Library
     10from django.template.generic import TemplateTag, Grammar
    1011from django.utils import six
    1112from django.utils import timezone
    1213
    def do_timezone(value, arg): 
    8687
    8788# Template tags
    8889
    89 class LocalTimeNode(Node):
    90     """
    91     Template node class used by ``localtime_tag``.
    92     """
    93     def __init__(self, nodelist, use_tz):
    94         self.nodelist = nodelist
    95         self.use_tz = use_tz
    96 
    97     def render(self, context):
    98         old_setting = context.use_tz
    99         context.use_tz = self.use_tz
    100         output = self.nodelist.render(context)
    101         context.use_tz = old_setting
    102         return output
    103 
    104 
    105 class TimezoneNode(Node):
    106     """
    107     Template node class used by ``timezone_tag``.
    108     """
    109     def __init__(self, nodelist, tz):
    110         self.nodelist = nodelist
    111         self.tz = tz
    112 
    113     def render(self, context):
    114         with timezone.override(self.tz.resolve(context)):
    115             output = self.nodelist.render(context)
    116         return output
    117 
    118 
    119 class GetCurrentTimezoneNode(Node):
    120     """
    121     Template node class used by ``get_current_timezone_tag``.
    122     """
    123     def __init__(self, variable):
    124         self.variable = variable
    125 
    126     def render(self, context):
    127         context[self.variable] = timezone.get_current_timezone_name()
    128         return ''
    129 
    130 
    131 @register.tag('localtime')
    132 def localtime_tag(parser, token):
     90@register.tag
     91class LocalTimeNode(TemplateTag):
    13392    """
    13493    Forces or prevents conversion of datetime objects to local time,
    13594    regardless of the value of ``settings.USE_TZ``.
    def localtime_tag(parser, token): 
    13998        {% localtime off %}{{ value_in_utc }}{% endlocaltime %}
    14099
    141100    """
    142     bits = token.split_contents()
    143     if len(bits) == 1:
    144         use_tz = True
    145     elif len(bits) > 2 or bits[1] not in ('on', 'off'):
    146         raise TemplateSyntaxError("%r argument should be 'on' or 'off'" %
    147                                   bits[0])
    148     else:
    149         use_tz = bits[1] == 'on'
    150     nodelist = parser.parse(('endlocaltime',))
    151     parser.delete_first_token()
    152     return LocalTimeNode(nodelist, use_tz)
     101    grammar = Grammar('localtime endlocaltime')
    153102
     103    def __init__(self, parser, parse_result):
     104        bits = parse_result.arguments
     105        tagname = parse_result.tagname
     106        if len(bits) == 0:
     107            self.use_tz = True
     108        elif len(bits) > 1 or bits[0] not in ('on', 'off'):
     109            raise TemplateSyntaxError("%r argument should be 'on' or 'off'" %
     110                                      tagname)
     111        else:
     112            self.use_tz = bits[0] == 'on'
    154113
    155 @register.tag('timezone')
    156 def timezone_tag(parser, token):
     114    def render(self, context):
     115        old_setting = context.use_tz
     116        context.use_tz = self.use_tz
     117        output = self.nodelist.render(context)
     118        context.use_tz = old_setting
     119        return output
     120
     121@register.tag
     122class TimezoneNode(TemplateTag):
    157123    """
    158124    Enables a given time zone just for this block.
    159125
    def timezone_tag(parser, token): 
    168134        {% endtimezone %}
    169135
    170136    """
    171     bits = token.split_contents()
    172     if len(bits) != 2:
    173         raise TemplateSyntaxError("'%s' takes one argument (timezone)" %
    174                                   bits[0])
    175     tz = parser.compile_filter(bits[1])
    176     nodelist = parser.parse(('endtimezone',))
    177     parser.delete_first_token()
    178     return TimezoneNode(nodelist, tz)
    179 
    180 
    181 @register.tag("get_current_timezone")
    182 def get_current_timezone_tag(parser, token):
     137    grammar = Grammar('timezone endtimezone')
     138
     139    def __init__(self, parser, parse_result):
     140        bits = parse_result.arguments
     141        tagname = parse_result.tagname
     142        if len(bits) != 1:
     143            raise TemplateSyntaxError("'%s' takes one argument (timezone)" %
     144                                      tagname)
     145        self.tz = parser.compile_filter(bits[0])
     146
     147    def render(self, context):
     148        with timezone.override(self.tz.resolve(context)):
     149            output = self.nodelist.render(context)
     150        return output
     151
     152
     153@register.assignment_tag
     154def get_current_timezone():
    183155    """
    184156    Stores the name of the current time zone in the context.
    185157
    def get_current_timezone_tag(parser, token): 
    190162    This will fetch the currently active time zone and put its name
    191163    into the ``TIME_ZONE`` context variable.
    192164    """
    193     # token.split_contents() isn't useful here because this tag doesn't accept variable as arguments
    194     args = token.contents.split()
    195     if len(args) != 3 or args[1] != 'as':
    196         raise TemplateSyntaxError("'get_current_timezone' requires "
    197                                   "'as variable' (got %r)" % args)
    198     return GetCurrentTimezoneNode(args[2])
     165    return timezone.get_current_timezone_name()
Back to Top