Fixed #28129 -- Allowed custom template tags to use keyword-only arguments.

This commit is contained in:
Alexander Allakhverdiyev 2017-05-11 07:09:44 -07:00 committed by Tim Graham
parent f87256b15e
commit a7c6c705e8
4 changed files with 33 additions and 8 deletions

View File

@ -106,7 +106,7 @@ class Library:
return 'world' return 'world'
""" """
def dec(func): def dec(func):
params, varargs, varkw, defaults, _, _, _ = getfullargspec(func) params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(func)
function_name = (name or getattr(func, '_decorated_function', func).__name__) function_name = (name or getattr(func, '_decorated_function', func).__name__)
@functools.wraps(func) @functools.wraps(func)
@ -118,7 +118,7 @@ class Library:
bits = bits[:-2] bits = bits[:-2]
args, kwargs = parse_bits( args, kwargs = parse_bits(
parser, bits, params, varargs, varkw, defaults, parser, bits, params, varargs, varkw, defaults,
takes_context, function_name kwonly, kwonly_defaults, takes_context, function_name,
) )
return SimpleNode(func, takes_context, args, kwargs, target_var) return SimpleNode(func, takes_context, args, kwargs, target_var)
self.tag(function_name, compile_func) self.tag(function_name, compile_func)
@ -143,7 +143,7 @@ class Library:
return {'choices': choices} return {'choices': choices}
""" """
def dec(func): def dec(func):
params, varargs, varkw, defaults, _, _, _ = getfullargspec(func) params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(func)
function_name = (name or getattr(func, '_decorated_function', func).__name__) function_name = (name or getattr(func, '_decorated_function', func).__name__)
@functools.wraps(func) @functools.wraps(func)
@ -151,7 +151,7 @@ class Library:
bits = token.split_contents()[1:] bits = token.split_contents()[1:]
args, kwargs = parse_bits( args, kwargs = parse_bits(
parser, bits, params, varargs, varkw, defaults, parser, bits, params, varargs, varkw, defaults,
takes_context, function_name, kwonly, kwonly_defaults, takes_context, function_name,
) )
return InclusionNode( return InclusionNode(
func, takes_context, args, kwargs, filename, func, takes_context, args, kwargs, filename,
@ -235,7 +235,7 @@ class InclusionNode(TagHelperNode):
def parse_bits(parser, bits, params, varargs, varkw, defaults, def parse_bits(parser, bits, params, varargs, varkw, defaults,
takes_context, name): kwonly, kwonly_defaults, takes_context, name):
""" """
Parse bits for template tag helpers simple_tag and inclusion_tag, in Parse bits for template tag helpers simple_tag and inclusion_tag, in
particular by detecting syntax errors and by extracting positional and particular by detecting syntax errors and by extracting positional and
@ -251,13 +251,17 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults,
args = [] args = []
kwargs = {} kwargs = {}
unhandled_params = list(params) unhandled_params = list(params)
unhandled_kwargs = [
kwarg for kwarg in kwonly
if not kwonly_defaults or kwarg not in kwonly_defaults
]
for bit in bits: for bit in bits:
# First we try to extract a potential kwarg from the bit # First we try to extract a potential kwarg from the bit
kwarg = token_kwargs([bit], parser) kwarg = token_kwargs([bit], parser)
if kwarg: if kwarg:
# The kwarg was successfully extracted # The kwarg was successfully extracted
param, value = kwarg.popitem() param, value = kwarg.popitem()
if param not in params and varkw is None: if param not in params and param not in unhandled_kwargs and varkw is None:
# An unexpected keyword argument was supplied # An unexpected keyword argument was supplied
raise TemplateSyntaxError( raise TemplateSyntaxError(
"'%s' received unexpected keyword argument '%s'" % "'%s' received unexpected keyword argument '%s'" %
@ -274,6 +278,9 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults,
# If using the keyword syntax for a positional arg, then # If using the keyword syntax for a positional arg, then
# consume it. # consume it.
unhandled_params.remove(param) unhandled_params.remove(param)
elif param in unhandled_kwargs:
# Same for keyword-only arguments
unhandled_kwargs.remove(param)
else: else:
if kwargs: if kwargs:
raise TemplateSyntaxError( raise TemplateSyntaxError(
@ -294,11 +301,11 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults,
# Consider the last n params handled, where n is the # Consider the last n params handled, where n is the
# number of defaults. # number of defaults.
unhandled_params = unhandled_params[:-len(defaults)] unhandled_params = unhandled_params[:-len(defaults)]
if unhandled_params: if unhandled_params or unhandled_kwargs:
# Some positional arguments were not supplied # Some positional arguments were not supplied
raise TemplateSyntaxError( raise TemplateSyntaxError(
"'%s' did not receive value(s) for the argument(s): %s" % "'%s' did not receive value(s) for the argument(s): %s" %
(name, ", ".join("'%s'" % p for p in unhandled_params))) (name, ", ".join("'%s'" % p for p in unhandled_params + unhandled_kwargs)))
return args, kwargs return args, kwargs

View File

@ -212,6 +212,8 @@ Templates
apps, it now returns the first engine if multiple ``DjangoTemplates`` engines apps, it now returns the first engine if multiple ``DjangoTemplates`` engines
are configured in ``TEMPLATES`` rather than raising ``ImproperlyConfigured``. are configured in ``TEMPLATES`` rather than raising ``ImproperlyConfigured``.
* Custom template tags may now accept keyword-only arguments.
Tests Tests
~~~~~ ~~~~~

View File

@ -80,6 +80,16 @@ def simple_two_params(one, two):
simple_two_params.anything = "Expected simple_two_params __dict__" simple_two_params.anything = "Expected simple_two_params __dict__"
@register.simple_tag
def simple_keyword_only_param(*, kwarg):
return "simple_keyword_only_param - Expected result: %s" % kwarg
@register.simple_tag
def simple_keyword_only_default(*, kwarg=42):
return "simple_keyword_only_default - Expected result: %s" % kwarg
@register.simple_tag @register.simple_tag
def simple_one_default(one, two='hi'): def simple_one_default(one, two='hi'):
"""Expected simple_one_default __doc__""" """Expected simple_one_default __doc__"""

View File

@ -53,6 +53,10 @@ class SimpleTagTests(TagTestCase):
('{% load custom %}{% params_and_context 37 %}', ('{% load custom %}{% params_and_context 37 %}',
'params_and_context - Expected result (context value: 42): 37'), 'params_and_context - Expected result (context value: 42): 37'),
('{% load custom %}{% simple_two_params 37 42 %}', 'simple_two_params - Expected result: 37, 42'), ('{% load custom %}{% simple_two_params 37 42 %}', 'simple_two_params - Expected result: 37, 42'),
('{% load custom %}{% simple_keyword_only_param kwarg=37 %}',
'simple_keyword_only_param - Expected result: 37'),
('{% load custom %}{% simple_keyword_only_default %}',
'simple_keyword_only_default - Expected result: 42'),
('{% load custom %}{% simple_one_default 37 %}', 'simple_one_default - Expected result: 37, hi'), ('{% load custom %}{% simple_one_default 37 %}', 'simple_one_default - Expected result: 37, hi'),
('{% load custom %}{% simple_one_default 37 two="hello" %}', ('{% load custom %}{% simple_one_default 37 two="hello" %}',
'simple_one_default - Expected result: 37, hello'), 'simple_one_default - Expected result: 37, hello'),
@ -86,6 +90,8 @@ class SimpleTagTests(TagTestCase):
'{% load custom %}{% simple_two_params 37 42 56 %}'), '{% load custom %}{% simple_two_params 37 42 56 %}'),
("'simple_one_default' received too many positional arguments", ("'simple_one_default' received too many positional arguments",
'{% load custom %}{% simple_one_default 37 42 56 %}'), '{% load custom %}{% simple_one_default 37 42 56 %}'),
("'simple_keyword_only_param' did not receive value(s) for the argument(s): 'kwarg'",
'{% load custom %}{% simple_keyword_only_param %}'),
("'simple_unlimited_args_kwargs' received some positional argument(s) after some keyword argument(s)", ("'simple_unlimited_args_kwargs' received some positional argument(s) after some keyword argument(s)",
'{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}'), '{% load custom %}{% simple_unlimited_args_kwargs 37 40|add:2 eggs="scrambled" 56 four=1|add:3 %}'),
("'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'", ("'simple_unlimited_args_kwargs' received multiple values for keyword argument 'eggs'",