Browse Source

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

Alexander Allakhverdiyev 8 years ago
parent
commit
a7c6c705e8

+ 15 - 8
django/template/library.py

@@ -106,7 +106,7 @@ class Library:
             return 'world'
         """
         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__)
 
             @functools.wraps(func)
@@ -118,7 +118,7 @@ class Library:
                     bits = bits[:-2]
                 args, kwargs = parse_bits(
                     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)
             self.tag(function_name, compile_func)
@@ -143,7 +143,7 @@ class Library:
             return {'choices': choices}
         """
         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__)
 
             @functools.wraps(func)
@@ -151,7 +151,7 @@ class Library:
                 bits = token.split_contents()[1:]
                 args, kwargs = parse_bits(
                     parser, bits, params, varargs, varkw, defaults,
-                    takes_context, function_name,
+                    kwonly, kwonly_defaults, takes_context, function_name,
                 )
                 return InclusionNode(
                     func, takes_context, args, kwargs, filename,
@@ -235,7 +235,7 @@ class InclusionNode(TagHelperNode):
 
 
 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
     particular by detecting syntax errors and by extracting positional and
@@ -251,13 +251,17 @@ def parse_bits(parser, bits, params, varargs, varkw, defaults,
     args = []
     kwargs = {}
     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:
         # First we try to extract a potential kwarg from the bit
         kwarg = token_kwargs([bit], parser)
         if kwarg:
             # The kwarg was successfully extracted
             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
                 raise TemplateSyntaxError(
                     "'%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
                     # consume it.
                     unhandled_params.remove(param)
+                elif param in unhandled_kwargs:
+                    # Same for keyword-only arguments
+                    unhandled_kwargs.remove(param)
         else:
             if kwargs:
                 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
         # number of defaults.
         unhandled_params = unhandled_params[:-len(defaults)]
-    if unhandled_params:
+    if unhandled_params or unhandled_kwargs:
         # Some positional arguments were not supplied
         raise TemplateSyntaxError(
             "'%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
 
 

+ 2 - 0
docs/releases/2.0.txt

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

+ 10 - 0
tests/template_tests/templatetags/custom.py

@@ -80,6 +80,16 @@ def simple_two_params(one, two):
 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
 def simple_one_default(one, two='hi'):
     """Expected simple_one_default __doc__"""

+ 6 - 0
tests/template_tests/test_custom.py

@@ -53,6 +53,10 @@ class SimpleTagTests(TagTestCase):
             ('{% load custom %}{% params_and_context 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_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 two="hello" %}',
                 'simple_one_default - Expected result: 37, hello'),
@@ -86,6 +90,8 @@ class SimpleTagTests(TagTestCase):
                 '{% load custom %}{% simple_two_params 37 42 56 %}'),
             ("'simple_one_default' received too many positional arguments",
                 '{% 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)",
                 '{% 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'",