mirror of
https://github.com/django/django.git
synced 2025-09-10 11:09:12 +00:00
Fixed #36559 -- Respected verbatim and comment blocks in PartialTemplate.source.
This commit is contained in:
parent
3485599ef0
commit
d82f25d3f0
@ -89,11 +89,6 @@ UNKNOWN_SOURCE = "<unknown source>"
|
|||||||
# than instantiating SimpleLazyObject with _lazy_re_compile().
|
# than instantiating SimpleLazyObject with _lazy_re_compile().
|
||||||
tag_re = re.compile(r"({%.*?%}|{{.*?}}|{#.*?#})")
|
tag_re = re.compile(r"({%.*?%}|{{.*?}}|{#.*?#})")
|
||||||
|
|
||||||
combined_partial_re = re.compile(
|
|
||||||
r"{%\s*partialdef\s+(?P<name>[\w-]+)(?:\s+inline)?\s*%}"
|
|
||||||
r"|{%\s*endpartialdef(?:\s+[\w-]+)?\s*%}"
|
|
||||||
)
|
|
||||||
|
|
||||||
logger = logging.getLogger("django.template")
|
logger = logging.getLogger("django.template")
|
||||||
|
|
||||||
|
|
||||||
@ -301,29 +296,26 @@ class PartialTemplate:
|
|||||||
Wraps nodelist as a partial, in order to be able to bind context.
|
Wraps nodelist as a partial, in order to be able to bind context.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, nodelist, origin, name):
|
def __init__(self, nodelist, origin, name, source_start=None, source_end=None):
|
||||||
self.nodelist = nodelist
|
self.nodelist = nodelist
|
||||||
self.origin = origin
|
self.origin = origin
|
||||||
self.name = name
|
self.name = name
|
||||||
|
# If available (debug mode), the absolute character offsets in the
|
||||||
|
# template.source correspond to the full partial region.
|
||||||
|
self._source_start = source_start
|
||||||
|
self._source_end = source_end
|
||||||
|
|
||||||
def get_exception_info(self, exception, token):
|
def get_exception_info(self, exception, token):
|
||||||
template = self.origin.loader.get_template(self.origin.template_name)
|
template = self.origin.loader.get_template(self.origin.template_name)
|
||||||
return template.get_exception_info(exception, token)
|
return template.get_exception_info(exception, token)
|
||||||
|
|
||||||
def find_partial_source(self, full_source, partial_name):
|
def find_partial_source(self, full_source):
|
||||||
start_match = None
|
if (
|
||||||
nesting = 0
|
self._source_start is not None
|
||||||
|
and self._source_end is not None
|
||||||
for match in combined_partial_re.finditer(full_source):
|
and 0 <= self._source_start <= self._source_end <= len(full_source)
|
||||||
if name := match["name"]: # Opening tag.
|
):
|
||||||
if start_match is None and name == partial_name:
|
return full_source[self._source_start : self._source_end]
|
||||||
start_match = match
|
|
||||||
if start_match is not None:
|
|
||||||
nesting += 1
|
|
||||||
elif start_match is not None:
|
|
||||||
nesting -= 1
|
|
||||||
if nesting == 0:
|
|
||||||
return full_source[start_match.start() : match.end()]
|
|
||||||
|
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@ -337,7 +329,7 @@ class PartialTemplate:
|
|||||||
RuntimeWarning,
|
RuntimeWarning,
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
return self.find_partial_source(template.source, self.name)
|
return self.find_partial_source(template.source)
|
||||||
|
|
||||||
def _render(self, context):
|
def _render(self, context):
|
||||||
return self.nodelist.render(context)
|
return self.nodelist.render(context)
|
||||||
|
@ -1235,11 +1235,18 @@ def partialdef_func(parser, token):
|
|||||||
|
|
||||||
# Parse the content until the end tag.
|
# Parse the content until the end tag.
|
||||||
valid_endpartials = ("endpartialdef", f"endpartialdef {partial_name}")
|
valid_endpartials = ("endpartialdef", f"endpartialdef {partial_name}")
|
||||||
|
|
||||||
|
pos_open = getattr(token, "position", None)
|
||||||
|
source_start = pos_open[0] if isinstance(pos_open, tuple) else None
|
||||||
|
|
||||||
nodelist = parser.parse(valid_endpartials)
|
nodelist = parser.parse(valid_endpartials)
|
||||||
endpartial = parser.next_token()
|
endpartial = parser.next_token()
|
||||||
if endpartial.contents not in valid_endpartials:
|
if endpartial.contents not in valid_endpartials:
|
||||||
parser.invalid_block_tag(endpartial, "endpartialdef", valid_endpartials)
|
parser.invalid_block_tag(endpartial, "endpartialdef", valid_endpartials)
|
||||||
|
|
||||||
|
pos_close = getattr(endpartial, "position", None)
|
||||||
|
source_end = pos_close[1] if isinstance(pos_close, tuple) else None
|
||||||
|
|
||||||
# Store the partial nodelist in the parser.extra_data attribute.
|
# Store the partial nodelist in the parser.extra_data attribute.
|
||||||
partials = parser.extra_data.setdefault("partials", {})
|
partials = parser.extra_data.setdefault("partials", {})
|
||||||
if partial_name in partials:
|
if partial_name in partials:
|
||||||
@ -1247,7 +1254,13 @@ def partialdef_func(parser, token):
|
|||||||
f"Partial '{partial_name}' is already defined in the "
|
f"Partial '{partial_name}' is already defined in the "
|
||||||
f"'{parser.origin.name}' template."
|
f"'{parser.origin.name}' template."
|
||||||
)
|
)
|
||||||
partials[partial_name] = PartialTemplate(nodelist, parser.origin, partial_name)
|
partials[partial_name] = PartialTemplate(
|
||||||
|
nodelist,
|
||||||
|
parser.origin,
|
||||||
|
partial_name,
|
||||||
|
source_start=source_start,
|
||||||
|
source_end=source_end,
|
||||||
|
)
|
||||||
|
|
||||||
return PartialDefNode(partial_name, inline, nodelist)
|
return PartialDefNode(partial_name, inline, nodelist)
|
||||||
|
|
||||||
|
@ -32,33 +32,6 @@ class PartialTagsTests(TestCase):
|
|||||||
):
|
):
|
||||||
engine.get_template(template_name)
|
engine.get_template(template_name)
|
||||||
|
|
||||||
def test_template_source_is_correct(self):
|
|
||||||
partial = engine.get_template("partial_examples.html#test-partial")
|
|
||||||
msg = (
|
|
||||||
"PartialTemplate.source is only available when "
|
|
||||||
"template debugging is enabled."
|
|
||||||
)
|
|
||||||
with self.assertRaisesMessage(RuntimeWarning, msg):
|
|
||||||
self.assertEqual(
|
|
||||||
partial.template.source,
|
|
||||||
"{% partialdef test-partial %}\n"
|
|
||||||
"TEST-PARTIAL-CONTENT\n"
|
|
||||||
"{% endpartialdef %}",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_template_source_inline_is_correct(self):
|
|
||||||
partial = engine.get_template("partial_examples.html#inline-partial")
|
|
||||||
msg = (
|
|
||||||
"PartialTemplate.source is only available when "
|
|
||||||
"template debugging is enabled."
|
|
||||||
)
|
|
||||||
with self.assertRaisesMessage(RuntimeWarning, msg):
|
|
||||||
self.assertEqual(
|
|
||||||
partial.template.source,
|
|
||||||
"{% partialdef inline-partial inline %}\nINLINE-CONTENT\n"
|
|
||||||
"{% endpartialdef %}",
|
|
||||||
)
|
|
||||||
|
|
||||||
def test_full_template_from_loader(self):
|
def test_full_template_from_loader(self):
|
||||||
template = engine.get_template("partial_examples.html")
|
template = engine.get_template("partial_examples.html")
|
||||||
rendered = template.render({})
|
rendered = template.render({})
|
||||||
@ -172,12 +145,7 @@ class PartialTagsTests(TestCase):
|
|||||||
"PartialTemplate.source is only available when template "
|
"PartialTemplate.source is only available when template "
|
||||||
"debugging is enabled.",
|
"debugging is enabled.",
|
||||||
):
|
):
|
||||||
self.assertEqual(
|
self.assertEqual(partial.template.source, "")
|
||||||
partial.template.source,
|
|
||||||
"{% partialdef test-partial %}\n"
|
|
||||||
"TEST-PARTIAL-CONTENT\n"
|
|
||||||
"{% endpartialdef %}",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class RobustPartialHandlingTests(TestCase):
|
class RobustPartialHandlingTests(TestCase):
|
||||||
@ -287,6 +255,20 @@ INLINE-CONTENT
|
|||||||
{% endpartialdef %}"""
|
{% endpartialdef %}"""
|
||||||
self.assertEqual(partial_proxy.source.strip(), expected.strip())
|
self.assertEqual(partial_proxy.source.strip(), expected.strip())
|
||||||
|
|
||||||
|
def test_find_partial_source_fallback_cases(self):
|
||||||
|
cases = {"None offsets": (None, None), "Out of bounds offsets": (10, 20)}
|
||||||
|
for name, (source_start, source_end) in cases.items():
|
||||||
|
with self.subTest(name):
|
||||||
|
partial = PartialTemplate(
|
||||||
|
NodeList(),
|
||||||
|
Origin("test"),
|
||||||
|
"test",
|
||||||
|
source_start=source_start,
|
||||||
|
source_end=source_end,
|
||||||
|
)
|
||||||
|
result = partial.find_partial_source("nonexistent-partial")
|
||||||
|
self.assertEqual(result, "")
|
||||||
|
|
||||||
@setup(
|
@setup(
|
||||||
{
|
{
|
||||||
"empty_partial_template": ("{% partialdef empty %}{% endpartialdef %}"),
|
"empty_partial_template": ("{% partialdef empty %}{% endpartialdef %}"),
|
||||||
@ -297,7 +279,7 @@ INLINE-CONTENT
|
|||||||
template = self.engine.get_template("empty_partial_template")
|
template = self.engine.get_template("empty_partial_template")
|
||||||
partial_proxy = template.extra_data["partials"]["empty"]
|
partial_proxy = template.extra_data["partials"]["empty"]
|
||||||
|
|
||||||
result = partial_proxy.find_partial_source(template.source, "empty")
|
result = partial_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(result, "{% partialdef empty %}{% endpartialdef %}")
|
self.assertEqual(result, "{% partialdef empty %}{% endpartialdef %}")
|
||||||
|
|
||||||
@setup(
|
@setup(
|
||||||
@ -315,10 +297,10 @@ INLINE-CONTENT
|
|||||||
empty_proxy = template.extra_data["partials"]["empty"]
|
empty_proxy = template.extra_data["partials"]["empty"]
|
||||||
other_proxy = template.extra_data["partials"]["other"]
|
other_proxy = template.extra_data["partials"]["other"]
|
||||||
|
|
||||||
empty_result = empty_proxy.find_partial_source(template.source, "empty")
|
empty_result = empty_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(empty_result, "{% partialdef empty %}{% endpartialdef %}")
|
self.assertEqual(empty_result, "{% partialdef empty %}{% endpartialdef %}")
|
||||||
|
|
||||||
other_result = other_proxy.find_partial_source(template.source, "other")
|
other_result = other_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(other_result, "{% partialdef other %}...{% endpartialdef %}")
|
self.assertEqual(other_result, "{% partialdef other %}...{% endpartialdef %}")
|
||||||
|
|
||||||
def test_partials_with_duplicate_names(self):
|
def test_partials_with_duplicate_names(self):
|
||||||
@ -368,7 +350,7 @@ INLINE-CONTENT
|
|||||||
template = self.engine.get_template("named_end_tag_template")
|
template = self.engine.get_template("named_end_tag_template")
|
||||||
partial_proxy = template.extra_data["partials"]["thing"]
|
partial_proxy = template.extra_data["partials"]["thing"]
|
||||||
|
|
||||||
result = partial_proxy.find_partial_source(template.source, "thing")
|
result = partial_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
result, "{% partialdef thing %}CONTENT{% endpartialdef thing %}"
|
result, "{% partialdef thing %}CONTENT{% endpartialdef thing %}"
|
||||||
)
|
)
|
||||||
@ -389,7 +371,7 @@ INLINE-CONTENT
|
|||||||
empty_proxy = template.extra_data["partials"]["outer"]
|
empty_proxy = template.extra_data["partials"]["outer"]
|
||||||
other_proxy = template.extra_data["partials"]["inner"]
|
other_proxy = template.extra_data["partials"]["inner"]
|
||||||
|
|
||||||
outer_result = empty_proxy.find_partial_source(template.source, "outer")
|
outer_result = empty_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
outer_result,
|
outer_result,
|
||||||
(
|
(
|
||||||
@ -398,7 +380,7 @@ INLINE-CONTENT
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
inner_result = other_proxy.find_partial_source(template.source, "inner")
|
inner_result = other_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(inner_result, "{% partialdef inner %}...{% endpartialdef %}")
|
self.assertEqual(inner_result, "{% partialdef inner %}...{% endpartialdef %}")
|
||||||
|
|
||||||
@setup(
|
@setup(
|
||||||
@ -417,7 +399,7 @@ INLINE-CONTENT
|
|||||||
empty_proxy = template.extra_data["partials"]["outer"]
|
empty_proxy = template.extra_data["partials"]["outer"]
|
||||||
other_proxy = template.extra_data["partials"]["inner"]
|
other_proxy = template.extra_data["partials"]["inner"]
|
||||||
|
|
||||||
outer_result = empty_proxy.find_partial_source(template.source, "outer")
|
outer_result = empty_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
outer_result,
|
outer_result,
|
||||||
(
|
(
|
||||||
@ -426,7 +408,7 @@ INLINE-CONTENT
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
inner_result = other_proxy.find_partial_source(template.source, "inner")
|
inner_result = other_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
inner_result, "{% partialdef inner %}...{% endpartialdef inner %}"
|
inner_result, "{% partialdef inner %}...{% endpartialdef inner %}"
|
||||||
)
|
)
|
||||||
@ -447,7 +429,7 @@ INLINE-CONTENT
|
|||||||
empty_proxy = template.extra_data["partials"]["outer"]
|
empty_proxy = template.extra_data["partials"]["outer"]
|
||||||
other_proxy = template.extra_data["partials"]["inner"]
|
other_proxy = template.extra_data["partials"]["inner"]
|
||||||
|
|
||||||
outer_result = empty_proxy.find_partial_source(template.source, "outer")
|
outer_result = empty_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
outer_result,
|
outer_result,
|
||||||
(
|
(
|
||||||
@ -456,7 +438,7 @@ INLINE-CONTENT
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
inner_result = other_proxy.find_partial_source(template.source, "inner")
|
inner_result = other_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(inner_result, "{% partialdef inner %}...{% endpartialdef %}")
|
self.assertEqual(inner_result, "{% partialdef inner %}...{% endpartialdef %}")
|
||||||
|
|
||||||
@setup(
|
@setup(
|
||||||
@ -475,7 +457,7 @@ INLINE-CONTENT
|
|||||||
empty_proxy = template.extra_data["partials"]["outer"]
|
empty_proxy = template.extra_data["partials"]["outer"]
|
||||||
other_proxy = template.extra_data["partials"]["inner"]
|
other_proxy = template.extra_data["partials"]["inner"]
|
||||||
|
|
||||||
outer_result = empty_proxy.find_partial_source(template.source, "outer")
|
outer_result = empty_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
outer_result,
|
outer_result,
|
||||||
(
|
(
|
||||||
@ -484,7 +466,138 @@ INLINE-CONTENT
|
|||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
inner_result = other_proxy.find_partial_source(template.source, "inner")
|
inner_result = other_proxy.find_partial_source(template.source)
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
inner_result, "{% partialdef inner %}...{% endpartialdef inner %}"
|
inner_result, "{% partialdef inner %}...{% endpartialdef inner %}"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{
|
||||||
|
"partial_embedded_in_verbatim": (
|
||||||
|
"{% verbatim %}\n"
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Should be ignored</p>"
|
||||||
|
"{% endpartialdef testing-name %}\n"
|
||||||
|
"{% endverbatim %}\n"
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Content</p>\n"
|
||||||
|
"{% endpartialdef %}\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
debug_only=True,
|
||||||
|
)
|
||||||
|
def test_partial_template_embedded_in_verbatim(self):
|
||||||
|
template = self.engine.get_template("partial_embedded_in_verbatim")
|
||||||
|
partial_template = template.extra_data["partials"]["testing-name"]
|
||||||
|
self.assertEqual(
|
||||||
|
partial_template.source,
|
||||||
|
"{% partialdef testing-name %}\n<p>Content</p>\n{% endpartialdef %}",
|
||||||
|
)
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{
|
||||||
|
"partial_debug_source": (
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Content</p>\n"
|
||||||
|
"{% endpartialdef %}\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
debug_only=True,
|
||||||
|
)
|
||||||
|
def test_partial_source_uses_offsets_in_debug(self):
|
||||||
|
template = self.engine.get_template("partial_debug_source")
|
||||||
|
partial_template = template.extra_data["partials"]["testing-name"]
|
||||||
|
|
||||||
|
self.assertEqual(partial_template._source_start, 0)
|
||||||
|
self.assertEqual(partial_template._source_end, 64)
|
||||||
|
expected = template.source[
|
||||||
|
partial_template._source_start : partial_template._source_end
|
||||||
|
]
|
||||||
|
self.assertEqual(partial_template.source, expected)
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{
|
||||||
|
"partial_embedded_in_named_verbatim": (
|
||||||
|
"{% verbatim block1 %}\n"
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"{% endverbatim block1 %}\n"
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Named Content</p>\n"
|
||||||
|
"{% endpartialdef %}\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
debug_only=True,
|
||||||
|
)
|
||||||
|
def test_partial_template_embedded_in_named_verbatim(self):
|
||||||
|
template = self.engine.get_template("partial_embedded_in_named_verbatim")
|
||||||
|
partial_template = template.extra_data["partials"]["testing-name"]
|
||||||
|
self.assertEqual(
|
||||||
|
"{% partialdef testing-name %}\n<p>Named Content</p>\n{% endpartialdef %}",
|
||||||
|
partial_template.source,
|
||||||
|
)
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{
|
||||||
|
"partial_embedded_in_comment_block": (
|
||||||
|
"{% comment %}\n"
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"{% endcomment %}\n"
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Comment Content</p>\n"
|
||||||
|
"{% endpartialdef %}\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
debug_only=True,
|
||||||
|
)
|
||||||
|
def test_partial_template_embedded_in_comment_block(self):
|
||||||
|
template = self.engine.get_template("partial_embedded_in_comment_block")
|
||||||
|
partial_template = template.extra_data["partials"]["testing-name"]
|
||||||
|
self.assertEqual(
|
||||||
|
partial_template.source,
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Comment Content</p>\n"
|
||||||
|
"{% endpartialdef %}",
|
||||||
|
)
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{
|
||||||
|
"partial_embedded_in_inline_comment": (
|
||||||
|
"{# {% partialdef testing-name %} #}\n"
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Inline Comment Content</p>\n"
|
||||||
|
"{% endpartialdef %}\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
debug_only=True,
|
||||||
|
)
|
||||||
|
def test_partial_template_embedded_in_inline_comment(self):
|
||||||
|
template = self.engine.get_template("partial_embedded_in_inline_comment")
|
||||||
|
partial_template = template.extra_data["partials"]["testing-name"]
|
||||||
|
self.assertEqual(
|
||||||
|
partial_template.source,
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"<p>Inline Comment Content</p>\n"
|
||||||
|
"{% endpartialdef %}",
|
||||||
|
)
|
||||||
|
|
||||||
|
@setup(
|
||||||
|
{
|
||||||
|
"partial_contains_fake_end_inside_verbatim": (
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"{% verbatim %}{% endpartialdef %}{% endverbatim %}\n"
|
||||||
|
"<p>Body</p>\n"
|
||||||
|
"{% endpartialdef %}\n"
|
||||||
|
),
|
||||||
|
},
|
||||||
|
debug_only=True,
|
||||||
|
)
|
||||||
|
def test_partial_template_contains_fake_end_inside_verbatim(self):
|
||||||
|
template = self.engine.get_template("partial_contains_fake_end_inside_verbatim")
|
||||||
|
partial_template = template.extra_data["partials"]["testing-name"]
|
||||||
|
self.assertEqual(
|
||||||
|
partial_template.source,
|
||||||
|
"{% partialdef testing-name %}\n"
|
||||||
|
"{% verbatim %}{% endpartialdef %}{% endverbatim %}\n"
|
||||||
|
"<p>Body</p>\n"
|
||||||
|
"{% endpartialdef %}",
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user