Ticket #13956: 13956.ttag_varargs_in_templates.1.diff

File 13956.ttag_varargs_in_templates.1.diff, 9.4 KB (added by pczerkas, 9 years ago)
  • django/template/base.py

    diff --git a/django/template/base.py b/django/template/base.py
    index ee33dac..96e97bb 100644
    a b from functools import partial  
    55from importlib import import_module
    66from inspect import getargspec, getcallargs
    77import warnings
     8import copy
    89
    910from django.apps import apps
    1011from django.conf import settings
    class Parser(object):  
    273274                self.extend_nodelist(nodelist, var_node, token)
    274275            elif token.token_type == 2:  # TOKEN_BLOCK
    275276                try:
    276                     command = token.contents.split()[0]
     277                    command = token.contents.split(None, 1)[0]
    277278                except IndexError:
    278279                    self.empty_block_tag(token)
    279280                if command in parse_until:
    class Parser(object):  
    288289                    compile_func = self.tags[command]
    289290                except KeyError:
    290291                    self.invalid_block_tag(token, command, parse_until)
     292                if self.has_vararg(command, token):
     293                    # defer compile to the render() phase
     294                    compile_func = VarArgsNode(compile_func)
    291295                try:
    292296                    compiled_result = compile_func(self, token)
    293297                except TemplateSyntaxError as e:
    class Parser(object):  
    379383        else:
    380384            raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name)
    381385
     386    def has_vararg(self, command, token):
     387        args = token.contents[len(command):]
     388        if vararg_prefix in args:
     389            for bit in smart_split(args):
     390                if bit.startswith(vararg_prefix):
     391                    return True
     392        return False
     393
    382394
    383395class TokenParser(object):
    384396    """
    constant_string = r"""  
    512524}
    513525constant_string = constant_string.replace("\n", "")
    514526
     527vararg_prefix = '*'
     528
    515529filter_raw_string = r"""
    516530^(?P<constant>%(constant)s)|
    517 ^(?P<var>[%(var_chars)s]+|%(num)s)|
     531^(?P<var>%(vararg_prefix)s{0,2}[%(var_chars)s]+|%(num)s)|
    518532 (?:\s*%(filter_sep)s\s*
    519533     (?P<filter_name>\w+)
    520534         (?:%(arg_sep)s
    filter_raw_string = r"""  
    529543    'var_chars': "\w\.",
    530544    'filter_sep': re.escape(FILTER_SEPARATOR),
    531545    'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR),
     546    'vararg_prefix': re.escape(vararg_prefix),
    532547}
    533548
    534549filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE)
    class TagHelperNode(Node):  
    10671082        return resolved_args, resolved_kwargs
    10681083
    10691084
     1085class VarArgsNode(Node):
     1086    def __init__(self, compile_func):
     1087        self.compile_func = compile_func
     1088
     1089    def __call__(self, parser, token):
     1090        self.parser = parser
     1091        self.token = copy.copy(token)
     1092        self.contents = token.contents
     1093        return self
     1094
     1095    def get_filters(self, expr):
     1096        if expr.filters:
     1097            return expr.token[len(expr.var.var):]
     1098        return ''
     1099
     1100    def render(self, context):
     1101        contents = []
     1102        for content in smart_split(self.contents):
     1103            if not contents:
     1104                contents.append(content)
     1105            elif content.startswith(vararg_prefix * 2):
     1106                # **kwargs case
     1107                expr = self.parser.compile_filter(content[2:])
     1108                kwargs = expr.var.resolve(context)
     1109                if kwargs:
     1110                    filters = self.get_filters(expr)
     1111                    for name, value in kwargs.items():
     1112                        if isinstance(value, (int, float)) or \
     1113                                getattr(value, 'vararg_from_context_in_templates', False):
     1114                            contents.append('%s=%s%s' % (name, value, filters))
     1115                        elif isinstance(value, basestring):
     1116                            contents.append('%s="%s"%s' % (name, value, filters))
     1117                        else:
     1118                            self.bad_value_type(self.token, value)
     1119            elif content.startswith(vararg_prefix):
     1120                # *args case
     1121                expr = self.parser.compile_filter(content[1:])
     1122                args = expr.var.resolve(context)
     1123                if args:
     1124                    filters = self.get_filters(expr)
     1125                    for value in args:
     1126                        if isinstance(value, (int, float)) or \
     1127                                getattr(value, 'vararg_from_context_in_templates', False):
     1128                            contents.append('%s%s' % (value, filters))
     1129                        elif isinstance(value, basestring):
     1130                            contents.append('"%s"%s' % (value, filters))
     1131                        else:
     1132                            self.bad_value_type(self.token, value)
     1133            else:
     1134                contents.append(content)
     1135
     1136        contents = ' '.join(contents)
     1137        if not (self.token.contents == contents and hasattr(self, 'nodelist')):
     1138            # recompile
     1139            self.token.contents = contents
     1140            parser = self.parser.__class__([self.token])
     1141            parser.tags = self.parser.tags
     1142            self.nodelist = parser.parse()
     1143        return self.nodelist.render(context)
     1144
     1145    def bad_value_type(self, token, value):
     1146        raise TemplateSyntaxError(
     1147            "Only string/numeric types can be unpacked from "
     1148            "templatetag variable argument list: '%s'" % value)
     1149
     1150
    10701151class Library(object):
    10711152    def __init__(self):
    10721153        self.filters = {}
  • tests/template_tests/tests.py

    diff --git a/tests/template_tests/tests.py b/tests/template_tests/tests.py
    index ed3a019..8e83288 100644
    a b class SSITests(TestCase):  
    19561956        with override_settings(ALLOWED_INCLUDE_ROOTS=(self.ssi_dir,)):
    19571957            for path in disallowed_paths:
    19581958                self.assertEqual(self.render_ssi(path), '')
     1959
     1960
     1961class VarAragsTests(TestCase):
     1962    def test_varargs(self):
     1963        c = template.Context({'args': [1], 'kwargs': {'a': 3, 'b': 4}})
     1964        t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs *args **kwargs 5=5 as var %}The result is: {{ var }}')
     1965        self.assertEqual(t.render(c), 'The result is: assignment_unlimited_args_kwargs - Expected result: 1, hi / 5=5, a=3, b=4')
     1966
     1967    def test_varargs_no_params(self):
     1968        c = template.Context({'args': [], 'kwargs': {}})
     1969        t = template.Template('{% load custom %}{% no_params *args **kwargs %}')
     1970        self.assertEqual(t.render(c), 'no_params - Expected result')
     1971
     1972        six.assertRaisesRegex(self, template.TemplateSyntaxError,
     1973            "'no_params' received too many positional arguments",
     1974            template.Template, '{% load custom %}{% no_params "*args" %}')
     1975
     1976    def test_varargs_filter(self):
     1977        c = template.Context({'args': ['3', 4], 'kwargs': {'a': '1', 'b': 2}})
     1978        t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs 1 *args|add:20|add:10 **kwargs|add:"10" 3=3 as var %}{{ var }}')
     1979        self.assertEqual(t.render(c), 'assignment_unlimited_args_kwargs - Expected result: 1, 33, 34 / 3=3, a=11, b=12')
     1980
     1981    def test_varargs_include(self):
     1982        c = template.Context({'args': ['inclusion.html'], 'kwargs': None, 'result': 'OK'})
     1983        t = template.Template('{% include *args **kwargs %}')
     1984        self.assertEqual(t.render(c), 'OK\n')
     1985
     1986    def test_varargs_cycle(self):
     1987        c = template.Context({'ll': [('1', '2', '3',), ('4', '5', '6',), ('a', 'b', 'c')]})
     1988        t = template.Template('{% for l in ll %}{% for i in l %}i:{{ i|safe }}, c:{% cycle *l %}; {% endfor %}{% endfor %}')
     1989        self.assertEqual(t.render(c), 'i:1, c:1; i:2, c:2; i:3, c:3; i:4, c:4; i:5, c:5; i:6, c:6; i:a, c:a; i:b, c:b; i:c, c:c; ')
     1990
     1991    def test_varargs_bad_type(self):
     1992        c = template.Context({'kwargs': {'': lambda: None}})
     1993        t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs **kwargs as var %}')
     1994        six.assertRaisesRegex(self, template.TemplateSyntaxError,
     1995            "Only string/numeric types can be unpacked from templatetag variable argument list:",
     1996            t.render, c)
     1997
     1998    def test_varargs_missing_context(self):
     1999        # The 'context' parameter must be present when takes_context is True
     2000        # exception will be raised in t.render() phase
     2001        c = template.Context({'args': [123]})
     2002        t = template.Template('{% load custom %}{% assignment_tag_without_context_parameter *args as var %}')
     2003        six.assertRaisesRegex(self, template.TemplateSyntaxError,
     2004            "'assignment_tag_without_context_parameter' is decorated with takes_context=True so it must have a first argument of 'context'",
     2005            t.render, c)
     2006
     2007    def test_vararg_from_context(self):
     2008        class MyStr(str):
     2009            vararg_from_context_in_templates = True
     2010 
     2011        my_str = MyStr('*objs')
     2012        c = template.Context({'objs': [1, 2], 'args': [my_str]})
     2013        t = template.Template('{% load testtags %}{% echo *args %}')
     2014        self.assertEqual(t.render(c), '1 2')
     2015
     2016        my_str.vararg_from_context_in_templates = False
     2017        self.assertEqual(t.render(c), '"*objs"')
     2018
     2019    def test_varargs_from_context_dot(self):
     2020        class MyStr(str):
     2021            vararg_from_context_in_templates = True
     2022
     2023        c = template.Context({'args': [MyStr('data.0')], 'kwargs': {'a': MyStr('data.1')}, 'data': ['abc', 'def']})
     2024        t = template.Template('{% load custom %}{% assignment_unlimited_args_kwargs 1 *args **kwargs 3=3 as var %}{{ var }}')
     2025        self.assertEqual(t.render(c), 'assignment_unlimited_args_kwargs - Expected result: 1, abc / 3=3, a=def')
Back to Top