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")
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,
@ -476,6 +478,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,
@ -500,6 +503,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,
@ -532,7 +536,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"
)
@ -540,6 +544,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,

View file

@ -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,

View file

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

View file

@ -121,6 +121,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:]
@ -137,7 +146,6 @@ class Library:
defaults,
kwonly,
kwonly_defaults,
takes_context,
function_name,
)
return SimpleNode(func, takes_context, args, kwargs, target_var)
@ -180,26 +188,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":
@ -212,13 +226,12 @@ class Library:
args, kwargs = parse_bits(
parser,
bits,
tag_params,
params,
varargs,
varkw,
defaults,
kwonly,
kwonly_defaults,
takes_context,
function_name,
)
@ -260,6 +273,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:]
@ -272,7 +294,6 @@ class Library:
defaults,
kwonly,
kwonly_defaults,
takes_context,
function_name,
)
return InclusionNode(
@ -391,7 +412,6 @@ def parse_bits(
defaults,
kwonly,
kwonly_defaults,
takes_context,
name,
):
"""
@ -399,14 +419,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)

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)
def escape_naive(context):
"""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")
def inclusion_extends1():
return {}

View file

@ -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"