diff --git a/django/template/loader_tags.py b/django/template/loader_tags.py index 0f70a39655..69e7ce471d 100644 --- a/django/template/loader_tags.py +++ b/django/template/loader_tags.py @@ -176,7 +176,7 @@ class IncludeNode(Node): if not callable(getattr(template, 'render', None)): # If not, we'll try our cache, and get_template() template_name = template - cache = context.render_context.setdefault(self.context_key, {}) + cache = context.render_context.dicts[0].setdefault(self, {}) template = cache.get(template_name) if template is None: template = context.template.engine.get_template(template_name) diff --git a/tests/template_tests/syntax_tests/test_if_changed.py b/tests/template_tests/syntax_tests/test_if_changed.py index 889a24fcd8..2e6b2c9684 100644 --- a/tests/template_tests/syntax_tests/test_if_changed.py +++ b/tests/template_tests/syntax_tests/test_if_changed.py @@ -212,3 +212,14 @@ class IfChangedTests(SimpleTestCase): ]) output = engine.render_to_string('template', dict(vars=[1, 1, 2, 2, 3, 3])) self.assertEqual(output, "123") + + def test_include_state(self): + """Tests the node state for different IncludeNodes (#27974).""" + engine = Engine(loaders=[ + ('django.template.loaders.locmem.Loader', { + 'template': '{% for x in vars %}{% include "include" %}{% include "include" %}{% endfor %}', + 'include': '{% ifchanged %}{{ x }}{% endifchanged %}', + }), + ]) + output = engine.render_to_string('template', dict(vars=[1, 1, 2, 2, 3, 3])) + self.assertEqual(output, '112233') diff --git a/tests/template_tests/syntax_tests/test_include.py b/tests/template_tests/syntax_tests/test_include.py index 904209ea8b..5f92efc05a 100644 --- a/tests/template_tests/syntax_tests/test_include.py +++ b/tests/template_tests/syntax_tests/test_include.py @@ -308,3 +308,23 @@ class IncludeTests(SimpleTestCase): "Recursion! A1 Recursion! B1 B2 B3 Recursion! C1", t.render(Context({'comments': comments})).replace(' ', '').replace('\n', ' ').strip(), ) + + def test_include_cache(self): + """ + {% include %} keeps resolved templates constant (#27974). The + CounterNode object in the {% counter %} template tag is created once + if caching works properly. Each iteration increases the counter instead + of restarting it. + + This works as a regression test only if the cached loader + isn't used, so the @setup decorator isn't used. + """ + engine = Engine(loaders=[ + ('django.template.loaders.locmem.Loader', { + 'template': '{% for x in vars %}{% include "include" %}{% endfor %}', + 'include': '{% include "next" %}', + 'next': '{% load custom %}{% counter %}' + }), + ], libraries={'custom': 'template_tests.templatetags.custom'}) + output = engine.render_to_string('template', dict(vars=range(9))) + self.assertEqual(output, '012345678') diff --git a/tests/template_tests/templatetags/custom.py b/tests/template_tests/templatetags/custom.py index 485f49a82d..7c40bc9cea 100644 --- a/tests/template_tests/templatetags/custom.py +++ b/tests/template_tests/templatetags/custom.py @@ -166,3 +166,18 @@ def minustwo_overridden_name(value): register.simple_tag(lambda x: x - 1, name='minusone') + + +@register.tag('counter') +def counter(parser, token): + return CounterNode() + + +class CounterNode(template.Node): + def __init__(self): + self.count = 0 + + def render(self, context): + count = self.count + self.count = count + 1 + return count