diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index 1874d8c528..36703b4782 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -242,7 +242,11 @@ def do_block(parser, token): 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 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 " "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( "The relative path '%s' was translated to template name '%s', the " "same template in which the tag appears." @@ -346,7 +350,11 @@ def do_include(parser, token): options[option] = value isolated_context = options.get("only", False) 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( parser.compile_filter(bits[1]), extra_context=namemap, diff --git a/tests/template_tests/syntax_tests/test_include.py b/tests/template_tests/syntax_tests/test_include.py index 3ee99b3798..be0deee926 100644 --- a/tests/template_tests/syntax_tests/test_include.py +++ b/tests/template_tests/syntax_tests/test_include.py @@ -330,15 +330,43 @@ class IncludeTests(SimpleTestCase): ], } ] - engine = Engine(app_dirs=True) - t = engine.get_template("recursive_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="recursive_include.html"): + engine = Engine(app_dirs=True) + t = engine.get_template("recursive_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="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): """ diff --git a/tests/template_tests/templates/recursive_relative_include.html b/tests/template_tests/templates/recursive_relative_include.html new file mode 100644 index 0000000000..ae49cc0a43 --- /dev/null +++ b/tests/template_tests/templates/recursive_relative_include.html @@ -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 %}