From 3485599ef08811e2fd066458aa083b2567f3cb84 Mon Sep 17 00:00:00 2001 From: farhan Date: Tue, 2 Sep 2025 14:59:30 -0400 Subject: [PATCH] Refs #36559 -- Ran template partial source tests in debug mode only. Added a warning for accessing PartialTemplate.source when debugging is disabled. Thanks Sarah Boyce for the idea. --- django/template/base.py | 8 + tests/template_tests/test_partials.py | 205 ++++++++++++++++++-------- tests/template_tests/utils.py | 5 +- 3 files changed, 153 insertions(+), 65 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 74b3987410..d7bc59d668 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -53,6 +53,7 @@ times with multiple contexts) import inspect import logging import re +import warnings from enum import Enum from django.template.context import BaseContext @@ -329,6 +330,13 @@ class PartialTemplate: @property def source(self): template = self.origin.loader.get_template(self.origin.template_name) + if not template.engine.debug: + warnings.warn( + "PartialTemplate.source is only available when template " + "debugging is enabled.", + RuntimeWarning, + stacklevel=2, + ) return self.find_partial_source(template.source, self.name) def _render(self, context): diff --git a/tests/template_tests/test_partials.py b/tests/template_tests/test_partials.py index 8f1a74b2ec..cc3ef7fb25 100644 --- a/tests/template_tests/test_partials.py +++ b/tests/template_tests/test_partials.py @@ -4,7 +4,9 @@ from unittest import mock from django.http import HttpResponse from django.template import ( Context, + NodeList, Origin, + PartialTemplate, Template, TemplateDoesNotExist, TemplateSyntaxError, @@ -15,6 +17,8 @@ from django.template.loader import render_to_string from django.test import TestCase, override_settings from django.urls import path, reverse +from .utils import setup + engine = engines["django"] @@ -30,18 +34,30 @@ class PartialTagsTests(TestCase): def test_template_source_is_correct(self): partial = engine.get_template("partial_examples.html#test-partial") - self.assertEqual( - partial.template.source, - "{% partialdef test-partial %}\nTEST-PARTIAL-CONTENT\n{% endpartialdef %}", + 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") - self.assertEqual( - partial.template.source, - "{% partialdef inline-partial inline %}\nINLINE-CONTENT\n" - "{% endpartialdef %}", + 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): template = engine.get_template("partial_examples.html") @@ -149,6 +165,20 @@ class PartialTagsTests(TestCase): rendered_content = template_with_partial.render({}) self.assertEqual("TEST-PARTIAL-CONTENT", rendered_content.strip()) + def test_template_source_warning(self): + partial = engine.get_template("partial_examples.html#test-partial") + with self.assertWarnsMessage( + RuntimeWarning, + "PartialTemplate.source is only available when template " + "debugging is enabled.", + ): + self.assertEqual( + partial.template.source, + "{% partialdef test-partial %}\n" + "TEST-PARTIAL-CONTENT\n" + "{% endpartialdef %}", + ) + class RobustPartialHandlingTests(TestCase): @@ -219,8 +249,18 @@ class RobustPartialHandlingTests(TestCase): class FindPartialSourceTests(TestCase): + @setup( + { + "partial_source_success_template": ( + "{% partialdef test-partial %}\n" + "TEST-PARTIAL-CONTENT\n" + "{% endpartialdef %}\n" + ), + }, + debug_only=True, + ) def test_find_partial_source_success(self): - template = engine.get_template("partial_examples.html").template + template = self.engine.get_template("partial_source_success_template") partial_proxy = template.extra_data["partials"]["test-partial"] expected = """{% partialdef test-partial %} @@ -228,8 +268,18 @@ TEST-PARTIAL-CONTENT {% endpartialdef %}""" self.assertEqual(partial_proxy.source.strip(), expected.strip()) + @setup( + { + "partial_source_with_inline_template": ( + "{% partialdef inline-partial inline %}\n" + "INLINE-CONTENT\n" + "{% endpartialdef %}\n" + ), + }, + debug_only=True, + ) def test_find_partial_source_with_inline(self): - template = engine.get_template("partial_examples.html").template + template = self.engine.get_template("partial_source_with_inline_template") partial_proxy = template.extra_data["partials"]["inline-partial"] expected = """{% partialdef inline-partial inline %} @@ -237,38 +287,38 @@ INLINE-CONTENT {% endpartialdef %}""" self.assertEqual(partial_proxy.source.strip(), expected.strip()) - def test_find_partial_source_nonexistent_partial(self): - template = engine.get_template("partial_examples.html").template - partial_proxy = template.extra_data["partials"]["test-partial"] - - result = partial_proxy.find_partial_source( - template.source, "nonexistent-partial" - ) - self.assertEqual(result, "") - + @setup( + { + "empty_partial_template": ("{% partialdef empty %}{% endpartialdef %}"), + }, + debug_only=True, + ) def test_find_partial_source_empty_partial(self): - template_source = "{% partialdef empty %}{% endpartialdef %}" - template = Template(template_source) + template = self.engine.get_template("empty_partial_template") partial_proxy = template.extra_data["partials"]["empty"] - result = partial_proxy.find_partial_source(template_source, "empty") + result = partial_proxy.find_partial_source(template.source, "empty") self.assertEqual(result, "{% partialdef empty %}{% endpartialdef %}") + @setup( + { + "consecutive_partials_template": ( + "{% partialdef empty %}{% endpartialdef %}" + "{% partialdef other %}...{% endpartialdef %}" + ), + }, + debug_only=True, + ) def test_find_partial_source_multiple_consecutive_partials(self): - - template_source = ( - "{% partialdef empty %}{% endpartialdef %}" - "{% partialdef other %}...{% endpartialdef %}" - ) - template = Template(template_source) + template = self.engine.get_template("consecutive_partials_template") empty_proxy = template.extra_data["partials"]["empty"] 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, "empty") 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, "other") self.assertEqual(other_result, "{% partialdef other %}...{% endpartialdef %}") def test_partials_with_duplicate_names(self): @@ -306,28 +356,40 @@ INLINE-CONTENT ): Template(template_source, origin=Origin(name="template.html")) + @setup( + { + "named_end_tag_template": ( + "{% partialdef thing %}CONTENT{% endpartialdef thing %}" + ), + }, + debug_only=True, + ) def test_find_partial_source_supports_named_end_tag(self): - template_source = "{% partialdef thing %}CONTENT{% endpartialdef thing %}" - template = Template(template_source) + template = self.engine.get_template("named_end_tag_template") partial_proxy = template.extra_data["partials"]["thing"] - result = partial_proxy.find_partial_source(template_source, "thing") + result = partial_proxy.find_partial_source(template.source, "thing") self.assertEqual( result, "{% partialdef thing %}CONTENT{% endpartialdef thing %}" ) + @setup( + { + "nested_partials_basic_template": ( + "{% partialdef outer %}" + "{% partialdef inner %}...{% endpartialdef %}" + "{% endpartialdef %}" + ), + }, + debug_only=True, + ) def test_find_partial_source_supports_nested_partials(self): - template_source = ( - "{% partialdef outer %}" - "{% partialdef inner %}...{% endpartialdef %}" - "{% endpartialdef %}" - ) - template = Template(template_source) + template = self.engine.get_template("nested_partials_basic_template") empty_proxy = template.extra_data["partials"]["outer"] 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, "outer") self.assertEqual( outer_result, ( @@ -336,21 +398,26 @@ INLINE-CONTENT ), ) - inner_result = other_proxy.find_partial_source(template_source, "inner") + inner_result = other_proxy.find_partial_source(template.source, "inner") self.assertEqual(inner_result, "{% partialdef inner %}...{% endpartialdef %}") + @setup( + { + "nested_partials_named_end_template": ( + "{% partialdef outer %}" + "{% partialdef inner %}...{% endpartialdef inner %}" + "{% endpartialdef outer %}" + ), + }, + debug_only=True, + ) def test_find_partial_source_supports_nested_partials_and_named_end_tags(self): - template_source = ( - "{% partialdef outer %}" - "{% partialdef inner %}...{% endpartialdef inner %}" - "{% endpartialdef outer %}" - ) - template = Template(template_source) + template = self.engine.get_template("nested_partials_named_end_template") empty_proxy = template.extra_data["partials"]["outer"] 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, "outer") self.assertEqual( outer_result, ( @@ -359,23 +426,28 @@ INLINE-CONTENT ), ) - inner_result = other_proxy.find_partial_source(template_source, "inner") + inner_result = other_proxy.find_partial_source(template.source, "inner") self.assertEqual( inner_result, "{% partialdef inner %}...{% endpartialdef inner %}" ) + @setup( + { + "nested_partials_mixed_end_1_template": ( + "{% partialdef outer %}" + "{% partialdef inner %}...{% endpartialdef %}" + "{% endpartialdef outer %}" + ), + }, + debug_only=True, + ) def test_find_partial_source_supports_nested_partials_and_mixed_end_tags_1(self): - template_source = ( - "{% partialdef outer %}" - "{% partialdef inner %}...{% endpartialdef %}" - "{% endpartialdef outer %}" - ) - template = Template(template_source) + template = self.engine.get_template("nested_partials_mixed_end_1_template") empty_proxy = template.extra_data["partials"]["outer"] 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, "outer") self.assertEqual( outer_result, ( @@ -384,21 +456,26 @@ INLINE-CONTENT ), ) - inner_result = other_proxy.find_partial_source(template_source, "inner") + inner_result = other_proxy.find_partial_source(template.source, "inner") self.assertEqual(inner_result, "{% partialdef inner %}...{% endpartialdef %}") + @setup( + { + "nested_partials_mixed_end_2_template": ( + "{% partialdef outer %}" + "{% partialdef inner %}...{% endpartialdef inner %}" + "{% endpartialdef %}" + ), + }, + debug_only=True, + ) def test_find_partial_source_supports_nested_partials_and_mixed_end_tags_2(self): - template_source = ( - "{% partialdef outer %}" - "{% partialdef inner %}...{% endpartialdef inner %}" - "{% endpartialdef %}" - ) - template = Template(template_source) + template = self.engine.get_template("nested_partials_mixed_end_2_template") empty_proxy = template.extra_data["partials"]["outer"] 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, "outer") self.assertEqual( outer_result, ( @@ -407,7 +484,7 @@ INLINE-CONTENT ), ) - inner_result = other_proxy.find_partial_source(template_source, "inner") + inner_result = other_proxy.find_partial_source(template.source, "inner") self.assertEqual( inner_result, "{% partialdef inner %}...{% endpartialdef inner %}" ) diff --git a/tests/template_tests/utils.py b/tests/template_tests/utils.py index 9bab583e79..da5da123f1 100644 --- a/tests/template_tests/utils.py +++ b/tests/template_tests/utils.py @@ -9,7 +9,7 @@ ROOT = os.path.dirname(os.path.abspath(__file__)) TEMPLATE_DIR = os.path.join(ROOT, "templates") -def setup(templates, *args, test_once=False): +def setup(templates, *args, test_once=False, debug_only=False): """ Runs test method multiple times in the following order: @@ -54,11 +54,14 @@ def setup(templates, *args, test_once=False): self.engine = Engine( libraries=libraries, loaders=loaders, + debug=debug_only, ) func(self) if test_once: return func(self) + if debug_only: + return self.engine = Engine( libraries=libraries,