Opened 7 months ago

Closed 3 weeks ago

#35493 closed Bug (fixed)

Allow `./` and `../` in paths when recursively including templates

Reported by: Gabriel Nick Pivovarov Owned by: Brock Smickley
Component: Template system Version: 5.0
Severity: Normal Keywords: template
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: yes UI/UX: no

Description (last modified by Gabriel Nick Pivovarov)

Hi. Currently, when trying to recursively include a Django template within itself using the include tag with a path that contains ./ or ../, Django raises a TemplateSyntaxError. However, using a path that does not contain ./ or ../ does not raise the error. When the error is raised, the debug toolbar describes it like this:

TemplateSyntaxError at /

The relative path ‘“./ul.html”’ was translated to template name ‘app/ul.html’, the same template in which the tag appears.

Here is an example of a template in a Django app called app with the path app/templates/app/ul.html that would produce the error given above:

<ul>
    {% for section in sections %}
        <li>
            <p>{{ section.name }}</p>
            {% if section.sections|length != 0 %}
                {% include "./ul.html" with sections=section.sections %}
            {% endif %}
        </li>
    {% endfor %}
</ul>

However, replacing the directory ./ul.html with the equivalent app/ul.html makes the error go away (assuming that the project's settings.py specifies APP_DIRS = True and the views and URLs are configured correctly). The actual paths are translated identically in both cases, and the behavior of the include tag should not depend simply on whether or not the path string uses ./ or ../ (or if it should, this is not documented in the Django documentation). Therefore, it seems that this is a bug. The expected behavior is that an error is only raised when recursively using the extends template, not when recursively using the include template.

Contrapositively, it appears that recursively extending a template using the extends tag with a path that does not contain ./ or ../ raises a TemplateDoesNotExist exception.

One possible fix is to modify the django/template/loader_tags.py file (https://github.com/django/django/blob/main/django/template/loader_tags.py) such that the error is raised when a template attempts to extend itself (not when a template attempts to include itself, which would otherwise be valid). The error handling logic in question starts on line 267 of that file within the construct_relative_path function; perhaps it should only be used when called from the do_extends function.

Here is a relevant discussion in the Django forums: https://forum.djangoproject.com/t/template-recursion-why-does-django-not-allow-and/31689

Change History (14)

comment:1 by Gabriel Nick Pivovarov, 7 months ago

Description: modified (diff)

comment:2 by Gabriel Nick Pivovarov, 7 months ago

Owner: changed from nobody to Gabriel Nick Pivovarov
Status: newassigned

comment:3 by Gabriel Nick Pivovarov, 7 months ago

Has patch: set

comment:4 by Gabriel Nick Pivovarov, 7 months ago

Needs tests: set

comment:5 by Gabriel Nick Pivovarov, 7 months ago

Description: modified (diff)

comment:6 by Sarah Boyce, 7 months ago

Triage Stage: UnreviewedAccepted

Relative path support was added in #26402 and recursive include support was added in #3544.
I think this was missed when working on #26402, replicated. Here is my test:

  • tests/template_tests/syntax_tests/test_include.py

    diff --git a/tests/template_tests/syntax_tests/test_include.py b/tests/template_tests/syntax_tests/test_include.py
    index 3ee99b3798..6dafba5040 100644
    a b class IncludeTests(SimpleTestCase):  
    339339            .replace("\n", " ")
    340340            .strip(),
    341341        )
     342        t = engine.get_template("recursive_relative_include.html")
     343        self.assertEqual(
     344            "Recursion!  A1  Recursion!  B1   B2   B3  Recursion!  C1",
     345            t.render(Context({"comments": comments}))
     346            .replace(" ", "")
     347            .replace("\n", " ")
     348            .strip(),
     349        )
    342350
    343351    def test_include_cache(self):
    344352        """
  • tests/template_tests/templates/recursive_relative_include.html

    diff --git a/tests/template_tests/templates/recursive_relative_include.html b/tests/template_tests/templates/recursive_relative_include.html
    index e69de29bb2..ae49cc0a43 100644
    a b  
     1Recursion!
     2{% for comment in comments %}
     3    {{ comment.comment }}
     4    {% if comment.children %}
     5        {% include "./recursive_relative_include.html" with comments=comment.children %}
     6    {% endif %}
     7{% endfor %}

comment:7 by YashRaj1506, 5 months ago

Owner: changed from Gabriel Nick Pivovarov to YashRaj1506

comment:8 by YashRaj1506, 5 weeks ago

Owner: YashRaj1506 removed
Status: assignednew

comment:9 by Brock Smickley, 4 weeks ago

Owner: set to Brock Smickley
Status: newassigned

comment:10 by Brock Smickley, 4 weeks ago

Needs tests: unset

comment:11 by Brock Smickley, 4 weeks ago

Owner: changed from Brock Smickley to Brock Smickley

comment:13 by Sarah Boyce, 3 weeks ago

Triage Stage: AcceptedReady for checkin

comment:14 by Sarah Boyce <42296566+sarahboyce@…>, 3 weeks ago

Resolution: fixed
Status: assignedclosed

In 55855bc:

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

Co-authored-by: Brock <bsmick97@…>

Note: See TracTickets for help on using tickets.
Back to Top