Ticket #13956: 13956.ttag_varargs_in_templates.1.diff
File 13956.ttag_varargs_in_templates.1.diff, 9.4 KB (added by , 10 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 5 5 from importlib import import_module 6 6 from inspect import getargspec, getcallargs 7 7 import warnings 8 import copy 8 9 9 10 from django.apps import apps 10 11 from django.conf import settings … … class Parser(object): 273 274 self.extend_nodelist(nodelist, var_node, token) 274 275 elif token.token_type == 2: # TOKEN_BLOCK 275 276 try: 276 command = token.contents.split( )[0]277 command = token.contents.split(None, 1)[0] 277 278 except IndexError: 278 279 self.empty_block_tag(token) 279 280 if command in parse_until: … … class Parser(object): 288 289 compile_func = self.tags[command] 289 290 except KeyError: 290 291 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) 291 295 try: 292 296 compiled_result = compile_func(self, token) 293 297 except TemplateSyntaxError as e: … … class Parser(object): 379 383 else: 380 384 raise TemplateSyntaxError("Invalid filter: '%s'" % filter_name) 381 385 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 382 394 383 395 class TokenParser(object): 384 396 """ … … constant_string = r""" 512 524 } 513 525 constant_string = constant_string.replace("\n", "") 514 526 527 vararg_prefix = '*' 528 515 529 filter_raw_string = r""" 516 530 ^(?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)| 518 532 (?:\s*%(filter_sep)s\s* 519 533 (?P<filter_name>\w+) 520 534 (?:%(arg_sep)s … … filter_raw_string = r""" 529 543 'var_chars': "\w\.", 530 544 'filter_sep': re.escape(FILTER_SEPARATOR), 531 545 'arg_sep': re.escape(FILTER_ARGUMENT_SEPARATOR), 546 'vararg_prefix': re.escape(vararg_prefix), 532 547 } 533 548 534 549 filter_re = re.compile(filter_raw_string, re.UNICODE | re.VERBOSE) … … class TagHelperNode(Node): 1067 1082 return resolved_args, resolved_kwargs 1068 1083 1069 1084 1085 class 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 1070 1151 class Library(object): 1071 1152 def __init__(self): 1072 1153 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): 1956 1956 with override_settings(ALLOWED_INCLUDE_ROOTS=(self.ssi_dir,)): 1957 1957 for path in disallowed_paths: 1958 1958 self.assertEqual(self.render_ssi(path), '') 1959 1960 1961 class 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')