1
0
mirror of https://github.com/django/django.git synced 2024-12-22 17:16:24 +00:00

Fixed #35493 -- Allowed template self-inclusion with relative paths.

Co-authored-by: Brock <bsmick97@gmail.com>
This commit is contained in:
Gabriel Nick Pivovarov 2024-12-05 16:24:47 +01:00 committed by Sarah Boyce
parent ded4854642
commit 55855bc6d0
3 changed files with 55 additions and 12 deletions

View File

@ -242,7 +242,11 @@ def do_block(parser, token):
return BlockNode(block_name, nodelist) return BlockNode(block_name, nodelist)
def construct_relative_path(current_template_name, relative_name): def construct_relative_path(
current_template_name,
relative_name,
allow_recursion=False,
):
""" """
Convert a relative path (starting with './' or '../') to the full template Convert a relative path (starting with './' or '../') to the full template
name based on the current_template_name. name based on the current_template_name.
@ -264,7 +268,7 @@ def construct_relative_path(current_template_name, relative_name):
"The relative path '%s' points outside the file hierarchy that " "The relative path '%s' points outside the file hierarchy that "
"template '%s' is in." % (relative_name, current_template_name) "template '%s' is in." % (relative_name, current_template_name)
) )
if current_template_name.lstrip("/") == new_name: if not allow_recursion and current_template_name.lstrip("/") == new_name:
raise TemplateSyntaxError( raise TemplateSyntaxError(
"The relative path '%s' was translated to template name '%s', the " "The relative path '%s' was translated to template name '%s', the "
"same template in which the tag appears." "same template in which the tag appears."
@ -346,7 +350,11 @@ def do_include(parser, token):
options[option] = value options[option] = value
isolated_context = options.get("only", False) isolated_context = options.get("only", False)
namemap = options.get("with", {}) namemap = options.get("with", {})
bits[1] = construct_relative_path(parser.origin.template_name, bits[1]) bits[1] = construct_relative_path(
parser.origin.template_name,
bits[1],
allow_recursion=True,
)
return IncludeNode( return IncludeNode(
parser.compile_filter(bits[1]), parser.compile_filter(bits[1]),
extra_context=namemap, extra_context=namemap,

View File

@ -330,15 +330,43 @@ class IncludeTests(SimpleTestCase):
], ],
} }
] ]
engine = Engine(app_dirs=True) with self.subTest(template="recursive_include.html"):
t = engine.get_template("recursive_include.html") engine = Engine(app_dirs=True)
self.assertEqual( t = engine.get_template("recursive_include.html")
"Recursion! A1 Recursion! B1 B2 B3 Recursion! C1", self.assertEqual(
t.render(Context({"comments": comments})) "Recursion! A1 Recursion! B1 B2 B3 Recursion! C1",
.replace(" ", "") t.render(Context({"comments": comments}))
.replace("\n", " ") .replace(" ", "")
.strip(), .replace("\n", " ")
) .strip(),
)
with self.subTest(template="recursive_relative_include.html"):
engine = Engine(app_dirs=True)
t = engine.get_template("recursive_relative_include.html")
self.assertEqual(
"Recursion! A1 Recursion! B1 B2 B3 Recursion! C1",
t.render(Context({"comments": comments}))
.replace(" ", "")
.replace("\n", " ")
.strip(),
)
with self.subTest(template="tmpl"):
engine = Engine()
template = """
Recursion!
{% for c in comments %}
{{ c.comment }}
{% if c.children %}{% include tmpl with comments=c.children %}{% endif %}
{% endfor %}
"""
outer_tmpl = engine.from_string("{% include tmpl %}")
output = outer_tmpl.render(
Context({"tmpl": engine.from_string(template), "comments": comments})
)
self.assertEqual(
"Recursion! A1 Recursion! B1 B2 B3 Recursion! C1",
output.replace(" ", "").replace("\n", " ").strip(),
)
def test_include_cache(self): def test_include_cache(self):
""" """

View File

@ -0,0 +1,7 @@
Recursion!
{% for comment in comments %}
{{ comment.comment }}
{% if comment.children %}
{% include "./recursive_relative_include.html" with comments=comment.children %}
{% endif %}
{% endfor %}