Ticket #7817: 7817_extended_with_and_include.diff

File 7817_extended_with_and_include.diff, 8.7 KB (added by lrekucki, 5 years ago)

Improved patch against r13716.

  • django/template/defaulttags.py

    diff --git a/django/template/defaulttags.py b/django/template/defaulttags.py
    index 318ae5f..b595a54 100644
    a b class WidthRatioNode(Node): 
    410410        return str(int(round(ratio)))
    411411
    412412class WithNode(Node):
    413     def __init__(self, var, name, nodelist):
    414         self.var = var
    415         self.name = name
     413    def __init__(self, names, vars, nodelist):
     414        self.names = names
     415        self.vars = vars
    416416        self.nodelist = nodelist
    417417
    418418    def __repr__(self):
    419419        return "<WithNode>"
    420420
    421421    def render(self, context):
    422         val = self.var.resolve(context)
     422        values = [var.resolve(context) for var in self.vars]
    423423        context.push()
    424         context[self.name] = val
     424        for name, value in zip(self.names, values):
     425            context[name] = value
    425426        output = self.nodelist.render(context)
    426427        context.pop()
    427428        return output
    widthratio = register.tag(widthratio) 
    11911192def do_with(parser, token):
    11921193    """
    11931194    Adds a value to the context (inside of this block) for caching and easy
    1194     access.
     1195    access. You bind a value to a new name using ``as``. You can chain multiple
     1196    bindings with ``and``. This works simillar to Python assignment.
    11951197
    11961198    For example::
    11971199
    11981200        {% with person.some_sql_method as total %}
    11991201            {{ total }} object{{ total|pluralize }}
    12001202        {% endwith %}
     1203
     1204        {% with 0 as z and x|add:5 as y and y as x %}
     1205            ({{ x }}, {{ y }}, {{ z }})
     1206        {% endwith %}
    12011207    """
    12021208    bits = list(token.split_contents())
    1203     if len(bits) != 4 or bits[2] != "as":
    1204         raise TemplateSyntaxError("%r expected format is 'value as name'" %
    1205                                   bits[0])
    1206     var = parser.compile_filter(bits[1])
    1207     name = bits[3]
     1209    try:
     1210        if len(bits) % 4:
     1211            raise TemplateSyntaxError()
     1212        for b in bits[2::4]:
     1213            if b != 'as':
     1214                raise TemplateSyntaxError()
     1215        for b in bits[4::4]:
     1216            if b != 'and':
     1217                raise TemplateSyntaxError()
     1218    except TemplateSyntaxError:
     1219        raise TemplateSyntaxError("'%s' statements should use the format "
     1220                                  "'with value as name [and value as name]...'"
     1221                                  ": %s" % (bits[0], token.contents))
     1222    varlist = [parser.compile_filter(b) for b in bits[1::4]]
     1223    namelist = bits[3::4]
    12081224    nodelist = parser.parse(('endwith',))
    12091225    parser.delete_first_token()
    1210     return WithNode(var, name, nodelist)
     1226    return WithNode(namelist, varlist, nodelist)
    12111227do_with = register.tag('with', do_with)
  • django/template/loader_tags.py

    diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py
    index b8bc741..8a4edd9 100644
    a b  
    11from django.template import TemplateSyntaxError, TemplateDoesNotExist, Variable
    22from django.template import Library, Node, TextNode
    33from django.template.loader import get_template
     4from django.template.defaulttags import WithNode, kwarg_re
    45from django.conf import settings
    56from django.utils.safestring import mark_safe
    67
    def do_extends(parser, token): 
    201202
    202203def do_include(parser, token):
    203204    """
    204     Loads a template and renders it with the current context.
     205    Loads a template and renders it with the current context. You can pass
     206    additional context using keyword arguments.
    205207
    206208    Example::
    207209
    208210        {% include "foo/some_include" %}
     211        {% include "foo/some_include" with bar="BAZZ!" %}
     212
    209213    """
    210214    bits = token.split_contents()
    211     if len(bits) != 2:
    212         raise TemplateSyntaxError("%r tag takes one argument: the name of the template to be included" % bits[0])
     215    if len(bits) < 2:
     216        raise TemplateSyntaxError("%r tag takes at least one argument: the name of the template to be included." % bits[0])
     217    namelist, varlist = [], []
     218    if len(bits) > 2:
     219        if bits[2] == 'with':
     220            for bit in bits[3:]:
     221                match = kwarg_re.match(bit)
     222                if not match:
     223                    raise TemplateSyntaxError("Malformed arguments to %r tag." % bits[0])
     224                name, value = match.groups()
     225                if not name:
     226                    raise TemplateSyntaxError('''"with" part of %r tag accepts only keyword arguments.''' % bits[0])
     227                namelist.append(name)
     228                varlist.append(parser.compile_filter(value))
     229            if not namelist:
     230                raise TemplateSyntaxError('''"with" in %r tag needs at least one keyword argument.''' % bits[0])
     231        else:
     232            raise TemplateSyntaxError('''Invalid option %r for %r tag.''' % (bits[2], bits[0]))
    213233    path = bits[1]
    214234    if path[0] in ('"', "'") and path[-1] == path[0]:
    215         return ConstantIncludeNode(path[1:-1])
    216     return IncludeNode(bits[1])
     235        node = ConstantIncludeNode(path[1:-1])
     236    else:
     237        node = IncludeNode(bits[1])
     238    if namelist:
     239        nodelist = parser.create_nodelist()
     240        parser.extend_nodelist(nodelist, node, token)
     241        node = WithNode(namelist, varlist, nodelist)
     242    return node
    217243
    218244register.tag('block', do_block)
    219245register.tag('extends', do_extends)
  • docs/ref/templates/builtins.txt

    diff --git a/docs/ref/templates/builtins.txt b/docs/ref/templates/builtins.txt
    index 002aa3f..7a08994 100644
    a b For example:: 
    10361036The populated variable (in the example above, ``total``) is only available
    10371037between the ``{% with %}`` and ``{% endwith %}`` tags.
    10381038
     1039Multiple variables can be cached by using the word ``"and"`` as a delimiter::
     1040
     1041    {% with items.0 as first and items|slice:"1:" as rest %}
     1042        {{ first }} comes first and {{ rest|join:", " }} follow.
     1043    {% endwith %}
     1044
    10391045.. _ref-templates-builtins-filters:
    10401046
    10411047Built-in filter reference
  • tests/regressiontests/templates/tests.py

    diff --git a/tests/regressiontests/templates/tests.py b/tests/regressiontests/templates/tests.py
    index 5902e8d..1290c86 100644
    a b class Templates(unittest.TestCase): 
    911911            'include 05': ('template with a space', {}, 'template with a space'),
    912912            'include06': ('{% include "include 05"%}', {}, 'template with a space'),
    913913
     914            # extra inline context
     915            'include07': ('{% include "basic-syntax02" with headline="Inline" %}', {'headline': 'Included'}, 'Inline'),
     916            'include08': ('{{ first }}--{% include "basic-syntax03" with first=second|lower|upper second=first|upper %}--{{ second }}', {'first': 'Ul', 'second': 'lU'}, 'Ul--LU --- UL--lU'),
     917
     918            'include-error01': ('{% include "basic-syntax01" with %}', {}, template.TemplateSyntaxError),
     919            'include-error02': ('{% include "basic-syntax01" with "no key" %}', {}, template.TemplateSyntaxError),
     920            'include-error03': ('{% include "basic-syntax01" with dotted.arg="error" %}', {}, template.TemplateSyntaxError),
     921            'include-error04': ('{% include "basic-syntax01" something_random %}', {}, template.TemplateSyntaxError),
     922
    914923            ### NAMED ENDBLOCKS #######################################################
    915924
    916925            # Basic test
    class Templates(unittest.TestCase): 
    11971206            ### WITH TAG ########################################################
    11981207            'with01': ('{% with dict.key as key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, '50'),
    11991208            'with02': ('{{ key }}{% with dict.key as key %}{{ key }}-{{ dict.key }}-{{ key }}{% endwith %}{{ key }}', {'dict': {'key':50}}, ('50-50-50', 'INVALID50-50-50INVALID')),
     1209            'with03': ('{% with dict.k1 as k1 and dict.k2 as k2 %}{{ k1 }}+{{ k2 }}{% endwith %}', {'dict': {'k1':3, 'k2':4}}, '3+4'),
     1210            'with04': ('{% with 42 as z and x|add:5 as y and y as x %}({{ x }},{{ y }},{{ z }}){% endwith %}', {'x': 13, 'y': -5}, '(-5,18,42)'),
    12001211
    12011212            'with-error01': ('{% with dict.key xx key %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
    12021213            'with-error02': ('{% with dict.key as %}{{ key }}{% endwith %}', {'dict': {'key':50}}, template.TemplateSyntaxError),
     1214            'with-error03': ('{% with dict.k1 as k1 and dict.k2 %}{{ key }}{% endwith %}', {'dict': {'k1':3, 'k2':4}}, template.TemplateSyntaxError),
     1215            'with-error04': ('{% with dict.k1 equals k1 %}{{ key }}{% endwith %}', {'dict': {'k1':3, 'k2':4}}, template.TemplateSyntaxError),
     1216            'with-error05': ('{% with dict.k1 as k1, dict.k2 as k2 %}{{ key }}{% endwith %}', {'dict': {'k1':3, 'k2':4}}, template.TemplateSyntaxError),
    12031217
    12041218            ### NOW TAG ########################################################
    12051219            # Simple case
Back to Top