Fixed #28129 -- Allowed custom template tags to use keyword-only arguments.
This commit is contained in:
parent
f87256b15e
commit
a7c6c705e8
|
@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
~~~~~
|
~~~~~
|
||||||
|
|
||||||
|
|
|
@ -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__"""
|
||||||
|
|
|
@ -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'",
|
||||||
|
|
Loading…
Reference in New Issue