mirror of
https://github.com/django/django.git
synced 2026-02-09 02:49:25 +08:00
Fixed #36728 -- Validated template tag arguments at definition time.
Before, `context` and `content` were validated at compile time.
This commit is contained in:
@@ -76,6 +76,7 @@ def pagination(cl):
|
||||
@register.tag(name="pagination")
|
||||
def pagination_tag(parser, token):
|
||||
return InclusionAdminNode(
|
||||
"pagination",
|
||||
parser,
|
||||
token,
|
||||
func=pagination,
|
||||
@@ -361,6 +362,7 @@ def result_list(cl):
|
||||
@register.tag(name="result_list")
|
||||
def result_list_tag(parser, token):
|
||||
return InclusionAdminNode(
|
||||
"result_list",
|
||||
parser,
|
||||
token,
|
||||
func=result_list,
|
||||
@@ -481,6 +483,7 @@ def date_hierarchy(cl):
|
||||
@register.tag(name="date_hierarchy")
|
||||
def date_hierarchy_tag(parser, token):
|
||||
return InclusionAdminNode(
|
||||
"date_hierarchy",
|
||||
parser,
|
||||
token,
|
||||
func=date_hierarchy,
|
||||
@@ -505,6 +508,7 @@ def search_form(cl):
|
||||
@register.tag(name="search_form")
|
||||
def search_form_tag(parser, token):
|
||||
return InclusionAdminNode(
|
||||
"search_form",
|
||||
parser,
|
||||
token,
|
||||
func=search_form,
|
||||
@@ -537,7 +541,7 @@ def admin_actions(context):
|
||||
@register.tag(name="admin_actions")
|
||||
def admin_actions_tag(parser, token):
|
||||
return InclusionAdminNode(
|
||||
parser, token, func=admin_actions, template_name="actions.html"
|
||||
"admin_actions", parser, token, func=admin_actions, template_name="actions.html"
|
||||
)
|
||||
|
||||
|
||||
@@ -545,6 +549,7 @@ def admin_actions_tag(parser, token):
|
||||
def change_list_object_tools_tag(parser, token):
|
||||
"""Display the row of change list object tools."""
|
||||
return InclusionAdminNode(
|
||||
"change_list_object_tools",
|
||||
parser,
|
||||
token,
|
||||
func=lambda context: context,
|
||||
|
||||
@@ -51,6 +51,7 @@ def prepopulated_fields_js(context):
|
||||
@register.tag(name="prepopulated_fields_js")
|
||||
def prepopulated_fields_js_tag(parser, token):
|
||||
return InclusionAdminNode(
|
||||
"prepopulated_fields_js",
|
||||
parser,
|
||||
token,
|
||||
func=prepopulated_fields_js,
|
||||
@@ -115,7 +116,7 @@ def submit_row(context):
|
||||
@register.tag(name="submit_row")
|
||||
def submit_row_tag(parser, token):
|
||||
return InclusionAdminNode(
|
||||
parser, token, func=submit_row, template_name="submit_line.html"
|
||||
"submit_row", parser, token, func=submit_row, template_name="submit_line.html"
|
||||
)
|
||||
|
||||
|
||||
@@ -123,6 +124,7 @@ def submit_row_tag(parser, token):
|
||||
def change_form_object_tools_tag(parser, token):
|
||||
"""Display the row of change form object tools."""
|
||||
return InclusionAdminNode(
|
||||
"change_form_object_tools",
|
||||
parser,
|
||||
token,
|
||||
func=lambda context: context,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from inspect import getfullargspec
|
||||
|
||||
from django.template.exceptions import TemplateSyntaxError
|
||||
from django.template.library import InclusionNode, parse_bits
|
||||
from django.utils.inspect import lazy_annotations
|
||||
|
||||
@@ -10,12 +11,21 @@ class InclusionAdminNode(InclusionNode):
|
||||
or globally.
|
||||
"""
|
||||
|
||||
def __init__(self, parser, token, func, template_name, takes_context=True):
|
||||
def __init__(self, name, parser, token, func, template_name, takes_context=True):
|
||||
self.template_name = template_name
|
||||
with lazy_annotations():
|
||||
params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = (
|
||||
getfullargspec(func)
|
||||
)
|
||||
if takes_context:
|
||||
if params and params[0] == "context":
|
||||
del params[0]
|
||||
else:
|
||||
function_name = func.__name__
|
||||
raise TemplateSyntaxError(
|
||||
f"{name!r} sets takes_context=True so {function_name!r} "
|
||||
"must have a first argument of 'context'"
|
||||
)
|
||||
bits = token.split_contents()
|
||||
args, kwargs = parse_bits(
|
||||
parser,
|
||||
@@ -26,7 +36,6 @@ class InclusionAdminNode(InclusionNode):
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
takes_context,
|
||||
bits[0],
|
||||
)
|
||||
super().__init__(func, takes_context, args, kwargs, filename=None)
|
||||
|
||||
@@ -123,6 +123,15 @@ class Library:
|
||||
) = getfullargspec(unwrap(func))
|
||||
function_name = name or func.__name__
|
||||
|
||||
if takes_context:
|
||||
if params and params[0] == "context":
|
||||
del params[0]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"{function_name!r} is decorated with takes_context=True so it "
|
||||
"must have a first argument of 'context'"
|
||||
)
|
||||
|
||||
@wraps(func)
|
||||
def compile_func(parser, token):
|
||||
bits = token.split_contents()[1:]
|
||||
@@ -139,7 +148,6 @@ class Library:
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
takes_context,
|
||||
function_name,
|
||||
)
|
||||
return SimpleNode(func, takes_context, args, kwargs, target_var)
|
||||
@@ -182,26 +190,32 @@ class Library:
|
||||
if end_name is None:
|
||||
end_name = f"end{function_name}"
|
||||
|
||||
@wraps(func)
|
||||
def compile_func(parser, token):
|
||||
tag_params = params.copy()
|
||||
|
||||
if takes_context:
|
||||
if len(tag_params) >= 2 and tag_params[1] == "content":
|
||||
del tag_params[1]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"{function_name!r} is decorated with takes_context=True so"
|
||||
" it must have a first argument of 'context' and a second "
|
||||
"argument of 'content'"
|
||||
)
|
||||
elif tag_params and tag_params[0] == "content":
|
||||
del tag_params[0]
|
||||
if takes_context:
|
||||
if len(params) >= 2 and params[1] == "content":
|
||||
del params[1]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"'{function_name}' must have a first argument of 'content'"
|
||||
f"{function_name!r} is decorated with takes_context=True so"
|
||||
" it must have a first argument of 'context' and a second "
|
||||
"argument of 'content'"
|
||||
)
|
||||
|
||||
if params and params[0] == "context":
|
||||
del params[0]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"{function_name!r} is decorated with takes_context=True so it "
|
||||
"must have a first argument of 'context'"
|
||||
)
|
||||
elif params and params[0] == "content":
|
||||
del params[0]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"{function_name!r} must have a first argument of 'content'"
|
||||
)
|
||||
|
||||
@wraps(func)
|
||||
def compile_func(parser, token):
|
||||
bits = token.split_contents()[1:]
|
||||
target_var = None
|
||||
if len(bits) >= 2 and bits[-2] == "as":
|
||||
@@ -214,13 +228,12 @@ class Library:
|
||||
args, kwargs = parse_bits(
|
||||
parser,
|
||||
bits,
|
||||
tag_params,
|
||||
params,
|
||||
varargs,
|
||||
varkw,
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
takes_context,
|
||||
function_name,
|
||||
)
|
||||
|
||||
@@ -263,6 +276,15 @@ class Library:
|
||||
) = getfullargspec(unwrap(func))
|
||||
function_name = name or func.__name__
|
||||
|
||||
if takes_context:
|
||||
if params and params[0] == "context":
|
||||
params = params[1:]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
f"{function_name!r} is decorated with takes_context=True so it "
|
||||
"must have a first argument of 'context'"
|
||||
)
|
||||
|
||||
@wraps(func)
|
||||
def compile_func(parser, token):
|
||||
bits = token.split_contents()[1:]
|
||||
@@ -275,7 +297,6 @@ class Library:
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
takes_context,
|
||||
function_name,
|
||||
)
|
||||
return InclusionNode(
|
||||
@@ -394,7 +415,6 @@ def parse_bits(
|
||||
defaults,
|
||||
kwonly,
|
||||
kwonly_defaults,
|
||||
takes_context,
|
||||
name,
|
||||
):
|
||||
"""
|
||||
@@ -402,14 +422,6 @@ def parse_bits(
|
||||
particular by detecting syntax errors and by extracting positional and
|
||||
keyword arguments.
|
||||
"""
|
||||
if takes_context:
|
||||
if params and params[0] == "context":
|
||||
params = params[1:]
|
||||
else:
|
||||
raise TemplateSyntaxError(
|
||||
"'%s' is decorated with takes_context=True so it must "
|
||||
"have a first argument of 'context'" % name
|
||||
)
|
||||
args = []
|
||||
kwargs = {}
|
||||
unhandled_params = list(params)
|
||||
|
||||
@@ -144,6 +144,7 @@ class AdminTemplateTagsTest(AdminViewBasicTestCase):
|
||||
# inspect.getfullargspec(), which is not ready for deferred
|
||||
# evaluation of annotations.
|
||||
InclusionAdminNode(
|
||||
"test",
|
||||
parser=object(),
|
||||
token=Token(token_type=TokenType.TEXT, contents="a"),
|
||||
func=action,
|
||||
|
||||
@@ -268,62 +268,6 @@ def simple_unlimited_args_kwargs_block(content, one, two="hi", *args, **kwargs):
|
||||
)
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_block_tag_without_context_parameter(arg):
|
||||
"""Expected simple_block_tag_without_context_parameter __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_tag_without_content_parameter(arg):
|
||||
"""Expected simple_tag_without_content_parameter __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_tag_with_context_without_content_parameter(context, arg):
|
||||
"""Expected simple_tag_with_context_without_content_parameter __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def simple_tag_without_context_parameter(arg):
|
||||
"""Expected simple_tag_without_context_parameter __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
simple_tag_without_context_parameter.anything = (
|
||||
"Expected simple_tag_without_context_parameter __dict__"
|
||||
)
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_tag_takes_context_without_params_block():
|
||||
"""Expected simple_tag_takes_context_without_params_block __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def simple_tag_takes_context_without_params():
|
||||
"""Expected simple_tag_takes_context_without_params __doc__"""
|
||||
return "Expected result"
|
||||
|
||||
|
||||
simple_tag_takes_context_without_params.anything = (
|
||||
"Expected simple_tag_takes_context_without_params __dict__"
|
||||
)
|
||||
|
||||
|
||||
@register.simple_block_tag
|
||||
def simple_block_tag_without_content():
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_block_tag(takes_context=True)
|
||||
def simple_block_tag_with_context_without_content():
|
||||
return "Expected result"
|
||||
|
||||
|
||||
@register.simple_tag(takes_context=True)
|
||||
def escape_naive(context):
|
||||
"""A tag that doesn't even think about escaping issues"""
|
||||
|
||||
@@ -269,28 +269,6 @@ inclusion_unlimited_args_kwargs.anything = (
|
||||
)
|
||||
|
||||
|
||||
@register.inclusion_tag("inclusion.html", takes_context=True)
|
||||
def inclusion_tag_without_context_parameter(arg):
|
||||
"""Expected inclusion_tag_without_context_parameter __doc__"""
|
||||
return {}
|
||||
|
||||
|
||||
inclusion_tag_without_context_parameter.anything = (
|
||||
"Expected inclusion_tag_without_context_parameter __dict__"
|
||||
)
|
||||
|
||||
|
||||
@register.inclusion_tag("inclusion.html", takes_context=True)
|
||||
def inclusion_tag_takes_context_without_params():
|
||||
"""Expected inclusion_tag_takes_context_without_params __doc__"""
|
||||
return {}
|
||||
|
||||
|
||||
inclusion_tag_takes_context_without_params.anything = (
|
||||
"Expected inclusion_tag_takes_context_without_params __dict__"
|
||||
)
|
||||
|
||||
|
||||
@register.inclusion_tag("inclusion_extends1.html")
|
||||
def inclusion_extends1():
|
||||
return {}
|
||||
|
||||
@@ -2,7 +2,7 @@ import os
|
||||
|
||||
from django.template import Context, Engine, TemplateSyntaxError
|
||||
from django.template.base import Node
|
||||
from django.template.library import InvalidTemplateLibrary
|
||||
from django.template.library import InvalidTemplateLibrary, Library
|
||||
from django.test import SimpleTestCase
|
||||
from django.test.utils import extend_sys_path
|
||||
|
||||
@@ -216,10 +216,6 @@ class SimpleTagTests(TagTestCase):
|
||||
self.verify_tag(
|
||||
custom.simple_unlimited_args_kwargs, "simple_unlimited_args_kwargs"
|
||||
)
|
||||
self.verify_tag(
|
||||
custom.simple_tag_without_context_parameter,
|
||||
"simple_tag_without_context_parameter",
|
||||
)
|
||||
|
||||
def test_simple_tag_missing_context(self):
|
||||
# The 'context' parameter must be present when takes_context is True
|
||||
@@ -228,9 +224,10 @@ class SimpleTagTests(TagTestCase):
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_tag_without_context_parameter 123 %}"
|
||||
)
|
||||
|
||||
@Library().simple_tag(takes_context=True)
|
||||
def simple_tag_without_context_parameter(arg):
|
||||
return "Expected result"
|
||||
|
||||
def test_simple_tag_missing_context_no_params(self):
|
||||
msg = (
|
||||
@@ -238,9 +235,10 @@ class SimpleTagTests(TagTestCase):
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_tag_takes_context_without_params %}"
|
||||
)
|
||||
|
||||
@Library().simple_tag(takes_context=True)
|
||||
def simple_tag_takes_context_without_params():
|
||||
return "Expected result"
|
||||
|
||||
|
||||
class SimpleBlockTagTests(TagTestCase):
|
||||
@@ -423,18 +421,6 @@ class SimpleBlockTagTests(TagTestCase):
|
||||
"of: endsimple_one_default_block.",
|
||||
"{% load custom %}{% simple_one_default_block %}Some content",
|
||||
),
|
||||
(
|
||||
"'simple_tag_without_content_parameter' must have a first argument "
|
||||
"of 'content'",
|
||||
"{% load custom %}{% simple_tag_without_content_parameter %}",
|
||||
),
|
||||
(
|
||||
"'simple_tag_with_context_without_content_parameter' is decorated with "
|
||||
"takes_context=True so it must have a first argument of 'context' and "
|
||||
"a second argument of 'content'",
|
||||
"{% load custom %}"
|
||||
"{% simple_tag_with_context_without_content_parameter %}",
|
||||
),
|
||||
]
|
||||
|
||||
for entry in errors:
|
||||
@@ -485,10 +471,10 @@ class SimpleBlockTagTests(TagTestCase):
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_block_tag_without_context_parameter 123 %}"
|
||||
"{% endsimple_block_tag_without_context_parameter %}"
|
||||
)
|
||||
|
||||
@Library().simple_block_tag(takes_context=True)
|
||||
def simple_block_tag_without_context_parameter(arg):
|
||||
return "Expected result"
|
||||
|
||||
def test_simple_block_tag_missing_context_no_params(self):
|
||||
msg = (
|
||||
@@ -496,10 +482,10 @@ class SimpleBlockTagTests(TagTestCase):
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_tag_takes_context_without_params_block %}"
|
||||
"{% endsimple_tag_takes_context_without_params_block %}"
|
||||
)
|
||||
|
||||
@Library().simple_block_tag(takes_context=True)
|
||||
def simple_tag_takes_context_without_params_block():
|
||||
return "Expected result"
|
||||
|
||||
def test_simple_block_tag_missing_content(self):
|
||||
# The 'content' parameter must be present when takes_context is True
|
||||
@@ -507,10 +493,10 @@ class SimpleBlockTagTests(TagTestCase):
|
||||
"'simple_block_tag_without_content' must have a first argument of 'content'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_block_tag_without_content %}"
|
||||
"{% endsimple_block_tag_without_content %}"
|
||||
)
|
||||
|
||||
@Library().simple_block_tag
|
||||
def simple_block_tag_without_content():
|
||||
return "Expected result"
|
||||
|
||||
def test_simple_block_tag_with_context_missing_content(self):
|
||||
# The 'content' parameter must be present when takes_context is True
|
||||
@@ -520,10 +506,10 @@ class SimpleBlockTagTests(TagTestCase):
|
||||
"second argument of 'content'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load custom %}{% simple_block_tag_with_context_without_content %}"
|
||||
"{% endsimple_block_tag_with_context_without_content %}"
|
||||
)
|
||||
|
||||
@Library().simple_block_tag(takes_context=True)
|
||||
def simple_block_tag_with_context_without_content():
|
||||
return "Expected result"
|
||||
|
||||
def test_simple_block_gets_context(self):
|
||||
c = Context({"name": "Jack & Jill"})
|
||||
@@ -720,9 +706,10 @@ class InclusionTagTests(TagTestCase):
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load inclusion %}{% inclusion_tag_without_context_parameter 123 %}"
|
||||
)
|
||||
|
||||
@Library().inclusion_tag("inclusion.html", takes_context=True)
|
||||
def inclusion_tag_without_context_parameter(arg):
|
||||
return {}
|
||||
|
||||
def test_include_tag_missing_context_no_params(self):
|
||||
msg = (
|
||||
@@ -730,9 +717,10 @@ class InclusionTagTests(TagTestCase):
|
||||
"takes_context=True so it must have a first argument of 'context'"
|
||||
)
|
||||
with self.assertRaisesMessage(TemplateSyntaxError, msg):
|
||||
self.engine.from_string(
|
||||
"{% load inclusion %}{% inclusion_tag_takes_context_without_params %}"
|
||||
)
|
||||
|
||||
@Library().inclusion_tag("inclusion.html", takes_context=True)
|
||||
def inclusion_tag_takes_context_without_params():
|
||||
return {}
|
||||
|
||||
def test_inclusion_tags_from_template(self):
|
||||
c = Context({"value": 42})
|
||||
@@ -822,10 +810,6 @@ class InclusionTagTests(TagTestCase):
|
||||
self.verify_tag(
|
||||
inclusion.inclusion_only_unlimited_args, "inclusion_only_unlimited_args"
|
||||
)
|
||||
self.verify_tag(
|
||||
inclusion.inclusion_tag_without_context_parameter,
|
||||
"inclusion_tag_without_context_parameter",
|
||||
)
|
||||
self.verify_tag(inclusion.inclusion_tag_use_l10n, "inclusion_tag_use_l10n")
|
||||
self.verify_tag(
|
||||
inclusion.inclusion_unlimited_args_kwargs, "inclusion_unlimited_args_kwargs"
|
||||
|
||||
Reference in New Issue
Block a user