This commit is contained in:
Jake Howard 2025-11-17 13:45:10 +01:00 committed by GitHub
commit b533676f2d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 94 additions and 160 deletions

View file

@ -76,6 +76,7 @@ def pagination(cl):
@register.tag(name="pagination") @register.tag(name="pagination")
def pagination_tag(parser, token): def pagination_tag(parser, token):
return InclusionAdminNode( return InclusionAdminNode(
"pagination",
parser, parser,
token, token,
func=pagination, func=pagination,
@ -361,6 +362,7 @@ def result_list(cl):
@register.tag(name="result_list") @register.tag(name="result_list")
def result_list_tag(parser, token): def result_list_tag(parser, token):
return InclusionAdminNode( return InclusionAdminNode(
"result_list",
parser, parser,
token, token,
func=result_list, func=result_list,
@ -476,6 +478,7 @@ def date_hierarchy(cl):
@register.tag(name="date_hierarchy") @register.tag(name="date_hierarchy")
def date_hierarchy_tag(parser, token): def date_hierarchy_tag(parser, token):
return InclusionAdminNode( return InclusionAdminNode(
"date_hierarchy",
parser, parser,
token, token,
func=date_hierarchy, func=date_hierarchy,
@ -500,6 +503,7 @@ def search_form(cl):
@register.tag(name="search_form") @register.tag(name="search_form")
def search_form_tag(parser, token): def search_form_tag(parser, token):
return InclusionAdminNode( return InclusionAdminNode(
"search_form",
parser, parser,
token, token,
func=search_form, func=search_form,
@ -532,7 +536,7 @@ def admin_actions(context):
@register.tag(name="admin_actions") @register.tag(name="admin_actions")
def admin_actions_tag(parser, token): def admin_actions_tag(parser, token):
return InclusionAdminNode( return InclusionAdminNode(
parser, token, func=admin_actions, template_name="actions.html" "admin_actions", parser, token, func=admin_actions, template_name="actions.html"
) )
@ -540,6 +544,7 @@ def admin_actions_tag(parser, token):
def change_list_object_tools_tag(parser, token): def change_list_object_tools_tag(parser, token):
"""Display the row of change list object tools.""" """Display the row of change list object tools."""
return InclusionAdminNode( return InclusionAdminNode(
"change_list_object_tools",
parser, parser,
token, token,
func=lambda context: context, func=lambda context: context,

View file

@ -51,6 +51,7 @@ def prepopulated_fields_js(context):
@register.tag(name="prepopulated_fields_js") @register.tag(name="prepopulated_fields_js")
def prepopulated_fields_js_tag(parser, token): def prepopulated_fields_js_tag(parser, token):
return InclusionAdminNode( return InclusionAdminNode(
"prepopulated_fields_js",
parser, parser,
token, token,
func=prepopulated_fields_js, func=prepopulated_fields_js,
@ -115,7 +116,7 @@ def submit_row(context):
@register.tag(name="submit_row") @register.tag(name="submit_row")
def submit_row_tag(parser, token): def submit_row_tag(parser, token):
return InclusionAdminNode( 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): def change_form_object_tools_tag(parser, token):
"""Display the row of change form object tools.""" """Display the row of change form object tools."""
return InclusionAdminNode( return InclusionAdminNode(
"change_form_object_tools",
parser, parser,
token, token,
func=lambda context: context, func=lambda context: context,

View file

@ -1,5 +1,6 @@
from inspect import getfullargspec from inspect import getfullargspec
from django.template.exceptions import TemplateSyntaxError
from django.template.library import InclusionNode, parse_bits from django.template.library import InclusionNode, parse_bits
@ -9,11 +10,20 @@ class InclusionAdminNode(InclusionNode):
or globally. 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 self.template_name = template_name
params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec( params, varargs, varkw, defaults, kwonly, kwonly_defaults, _ = getfullargspec(
func 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() bits = token.split_contents()
args, kwargs = parse_bits( args, kwargs = parse_bits(
parser, parser,
@ -24,7 +34,6 @@ class InclusionAdminNode(InclusionNode):
defaults, defaults,
kwonly, kwonly,
kwonly_defaults, kwonly_defaults,
takes_context,
bits[0], bits[0],
) )
super().__init__(func, takes_context, args, kwargs, filename=None) super().__init__(func, takes_context, args, kwargs, filename=None)

View file

@ -121,6 +121,15 @@ class Library:
) = getfullargspec(unwrap(func)) ) = getfullargspec(unwrap(func))
function_name = name or func.__name__ 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) @wraps(func)
def compile_func(parser, token): def compile_func(parser, token):
bits = token.split_contents()[1:] bits = token.split_contents()[1:]
@ -137,7 +146,6 @@ class Library:
defaults, defaults,
kwonly, kwonly,
kwonly_defaults, kwonly_defaults,
takes_context,
function_name, function_name,
) )
return SimpleNode(func, takes_context, args, kwargs, target_var) return SimpleNode(func, takes_context, args, kwargs, target_var)
@ -180,26 +188,32 @@ class Library:
if end_name is None: if end_name is None:
end_name = f"end{function_name}" end_name = f"end{function_name}"
@wraps(func) if takes_context:
def compile_func(parser, token): if len(params) >= 2 and params[1] == "content":
tag_params = params.copy() del params[1]
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]
else: else:
raise TemplateSyntaxError( 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:] bits = token.split_contents()[1:]
target_var = None target_var = None
if len(bits) >= 2 and bits[-2] == "as": if len(bits) >= 2 and bits[-2] == "as":
@ -212,13 +226,12 @@ class Library:
args, kwargs = parse_bits( args, kwargs = parse_bits(
parser, parser,
bits, bits,
tag_params, params,
varargs, varargs,
varkw, varkw,
defaults, defaults,
kwonly, kwonly,
kwonly_defaults, kwonly_defaults,
takes_context,
function_name, function_name,
) )
@ -260,6 +273,15 @@ class Library:
) = getfullargspec(unwrap(func)) ) = getfullargspec(unwrap(func))
function_name = name or func.__name__ 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) @wraps(func)
def compile_func(parser, token): def compile_func(parser, token):
bits = token.split_contents()[1:] bits = token.split_contents()[1:]
@ -272,7 +294,6 @@ class Library:
defaults, defaults,
kwonly, kwonly,
kwonly_defaults, kwonly_defaults,
takes_context,
function_name, function_name,
) )
return InclusionNode( return InclusionNode(
@ -391,7 +412,6 @@ def parse_bits(
defaults, defaults,
kwonly, kwonly,
kwonly_defaults, kwonly_defaults,
takes_context,
name, name,
): ):
""" """
@ -399,14 +419,6 @@ def parse_bits(
particular by detecting syntax errors and by extracting positional and particular by detecting syntax errors and by extracting positional and
keyword arguments. 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 = [] args = []
kwargs = {} kwargs = {}
unhandled_params = list(params) unhandled_params = list(params)

View file

@ -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) @register.simple_tag(takes_context=True)
def escape_naive(context): def escape_naive(context):
"""A tag that doesn't even think about escaping issues""" """A tag that doesn't even think about escaping issues"""

View file

@ -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") @register.inclusion_tag("inclusion_extends1.html")
def inclusion_extends1(): def inclusion_extends1():
return {} return {}

View file

@ -2,7 +2,7 @@ import os
from django.template import Context, Engine, TemplateSyntaxError from django.template import Context, Engine, TemplateSyntaxError
from django.template.base import Node 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 import SimpleTestCase
from django.test.utils import extend_sys_path from django.test.utils import extend_sys_path
@ -216,10 +216,6 @@ class SimpleTagTests(TagTestCase):
self.verify_tag( self.verify_tag(
custom.simple_unlimited_args_kwargs, "simple_unlimited_args_kwargs" 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): def test_simple_tag_missing_context(self):
# The 'context' parameter must be present when takes_context is True # 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'" "takes_context=True so it must have a first argument of 'context'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): 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): def test_simple_tag_missing_context_no_params(self):
msg = ( msg = (
@ -238,9 +235,10 @@ class SimpleTagTests(TagTestCase):
"takes_context=True so it must have a first argument of 'context'" "takes_context=True so it must have a first argument of 'context'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): 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): class SimpleBlockTagTests(TagTestCase):
@ -423,18 +421,6 @@ class SimpleBlockTagTests(TagTestCase):
"of: endsimple_one_default_block.", "of: endsimple_one_default_block.",
"{% load custom %}{% simple_one_default_block %}Some content", "{% 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: for entry in errors:
@ -485,10 +471,10 @@ class SimpleBlockTagTests(TagTestCase):
"takes_context=True so it must have a first argument of 'context'" "takes_context=True so it must have a first argument of 'context'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.from_string(
"{% load custom %}{% simple_block_tag_without_context_parameter 123 %}" @Library().simple_block_tag(takes_context=True)
"{% endsimple_block_tag_without_context_parameter %}" def simple_block_tag_without_context_parameter(arg):
) return "Expected result"
def test_simple_block_tag_missing_context_no_params(self): def test_simple_block_tag_missing_context_no_params(self):
msg = ( msg = (
@ -496,10 +482,10 @@ class SimpleBlockTagTests(TagTestCase):
"takes_context=True so it must have a first argument of 'context'" "takes_context=True so it must have a first argument of 'context'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.from_string(
"{% load custom %}{% simple_tag_takes_context_without_params_block %}" @Library().simple_block_tag(takes_context=True)
"{% endsimple_tag_takes_context_without_params_block %}" def simple_tag_takes_context_without_params_block():
) return "Expected result"
def test_simple_block_tag_missing_content(self): def test_simple_block_tag_missing_content(self):
# The 'content' parameter must be present when takes_context is True # 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'" "'simple_block_tag_without_content' must have a first argument of 'content'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.from_string(
"{% load custom %}{% simple_block_tag_without_content %}" @Library().simple_block_tag
"{% endsimple_block_tag_without_content %}" def simple_block_tag_without_content():
) return "Expected result"
def test_simple_block_tag_with_context_missing_content(self): def test_simple_block_tag_with_context_missing_content(self):
# The 'content' parameter must be present when takes_context is True # The 'content' parameter must be present when takes_context is True
@ -520,10 +506,10 @@ class SimpleBlockTagTests(TagTestCase):
"second argument of 'content'" "second argument of 'content'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): with self.assertRaisesMessage(TemplateSyntaxError, msg):
self.engine.from_string(
"{% load custom %}{% simple_block_tag_with_context_without_content %}" @Library().simple_block_tag(takes_context=True)
"{% endsimple_block_tag_with_context_without_content %}" def simple_block_tag_with_context_without_content():
) return "Expected result"
def test_simple_block_gets_context(self): def test_simple_block_gets_context(self):
c = Context({"name": "Jack & Jill"}) c = Context({"name": "Jack & Jill"})
@ -720,9 +706,10 @@ class InclusionTagTests(TagTestCase):
"takes_context=True so it must have a first argument of 'context'" "takes_context=True so it must have a first argument of 'context'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): 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): def test_include_tag_missing_context_no_params(self):
msg = ( msg = (
@ -730,9 +717,10 @@ class InclusionTagTests(TagTestCase):
"takes_context=True so it must have a first argument of 'context'" "takes_context=True so it must have a first argument of 'context'"
) )
with self.assertRaisesMessage(TemplateSyntaxError, msg): 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): def test_inclusion_tags_from_template(self):
c = Context({"value": 42}) c = Context({"value": 42})
@ -822,10 +810,6 @@ class InclusionTagTests(TagTestCase):
self.verify_tag( self.verify_tag(
inclusion.inclusion_only_unlimited_args, "inclusion_only_unlimited_args" 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_tag_use_l10n, "inclusion_tag_use_l10n")
self.verify_tag( self.verify_tag(
inclusion.inclusion_unlimited_args_kwargs, "inclusion_unlimited_args_kwargs" inclusion.inclusion_unlimited_args_kwargs, "inclusion_unlimited_args_kwargs"