From 5d911f2d2fecc703be91b2b9b28acc59d34b35f3 Mon Sep 17 00:00:00 2001 From: David Smith Date: Sun, 26 Apr 2026 07:53:13 +0100 Subject: Fixed #35738 -- Deprecated double-dot variable lookups. --- django/template/base.py | 19 +++++++++- docs/internals/deprecation.txt | 3 ++ docs/releases/6.1.txt | 4 ++ tests/template_tests/syntax_tests/test_basic.py | 49 ++++++++++++++++++++++++- 4 files changed, 73 insertions(+), 2 deletions(-) diff --git a/django/template/base.py b/django/template/base.py index 9d75111e42..8c6390de33 100644 --- a/django/template/base.py +++ b/django/template/base.py @@ -57,7 +57,7 @@ import warnings from enum import Enum from django.template.context import BaseContext -from django.utils.deprecation import django_file_prefixes +from django.utils.deprecation import RemovedInDjango70Warning, django_file_prefixes from django.utils.formats import localize from django.utils.html import conditional_escape from django.utils.inspect import lazy_annotations, signature @@ -555,6 +555,23 @@ class Parser: except TemplateSyntaxError as e: raise self.error(token, e) var_node = VariableNode(filter_expression) + if ".." in str(filter_expression.var): + warnings.warn( + "Support for double-dot lookups '..' which maps to a " + "lookup of the empty string is deprecated.\n" + f" Template: {self.origin.name}\n" + f" Line: {token.lineno}", + RemovedInDjango70Warning, + skip_file_prefixes=django_file_prefixes(), + ) + + # RemovedInDjango70Warning + # When deprecation ends elevate the warning to an error. + # raise self.error( + # token, + # ("Variable contains '..' on line %d" % token.lineno), + # ) + self.extend_nodelist(nodelist, var_node, token) elif token_type == 2: # TokenType.BLOCK try: diff --git a/docs/internals/deprecation.txt b/docs/internals/deprecation.txt index c8e467a0ee..3cda71933b 100644 --- a/docs/internals/deprecation.txt +++ b/docs/internals/deprecation.txt @@ -77,6 +77,9 @@ details on these changes. ``django.contrib.postgres.aggregates.BitOr``, and ``django.contrib.postgres.aggregates.BitXor`` classes will be removed. +* Support for double-dot variable lookup, like ``{{ book..title }}``, + is removed. + .. _deprecation-removed-in-6.1: 6.1 diff --git a/docs/releases/6.1.txt b/docs/releases/6.1.txt index 48f8939041..3ec680702c 100644 --- a/docs/releases/6.1.txt +++ b/docs/releases/6.1.txt @@ -573,6 +573,10 @@ Miscellaneous :class:`~django.db.models.BitOr`, and :class:`~django.db.models.BitXor` classes. +* Support for a double-dot variable lookup like ``{{ book..title }}`` which + maps to a lookup of the empty string before the next lookup of the named + attribute is deprecated. + Features removed in 6.1 ======================= diff --git a/tests/template_tests/syntax_tests/test_basic.py b/tests/template_tests/syntax_tests/test_basic.py index 04cf5f4401..47bc949fdc 100644 --- a/tests/template_tests/syntax_tests/test_basic.py +++ b/tests/template_tests/syntax_tests/test_basic.py @@ -1,7 +1,9 @@ +from django.template import Engine from django.template.base import Origin, Template, TemplateSyntaxError from django.template.context import Context from django.template.loader_tags import BlockContext, BlockNode -from django.test import SimpleTestCase +from django.test import SimpleTestCase, ignore_warnings +from django.utils.deprecation import RemovedInDjango70Warning from django.views.debug import ExceptionReporter from ..utils import SilentAttrClass, SilentGetItemClass, SomeClass, setup @@ -393,6 +395,42 @@ class BasicSyntaxTests(SimpleTestCase): output = self.engine.render_to_string("template", {"meals": Meals}) self.assertEqual(output, "soup is yummy.") + def test_double_dot_lookup(self): + loaders = [ + ( + "django.template.loaders.cached.Loader", + [ + ( + "django.template.loaders.locmem.Loader", + {"template": "{{ doubledot..lookup }}"}, + ), + ], + ), + ] + + msg = ( + "Support for double-dot lookups '..' which maps to a lookup of the empty " + "string is deprecated.\n Template: template\n Line: 1" + ) + + for debug in [True, False]: + with self.subTest(debug=debug): + engine = Engine(loaders=loaders, debug=debug) + with self.assertWarnsMessage(RemovedInDjango70Warning, msg): + engine.render_to_string("template", {}) + # Cached loader results in warning only on first access. + engine.render_to_string("template", {}) + + # RemovedInDjango70Warning. + # Replace the above test with the following. + # @setup({"template": "{{ doubledot..lookup }}"}) + # def test_double_dot_lookup(self): + # with self.assertRaisesMessage( + # TemplateSyntaxError, + # "Variable contains '..' on line 1", + # ): + # self.engine.render_to_string("template") + class BlockContextTests(SimpleTestCase): def test_repr(self): @@ -429,3 +467,12 @@ class TemplateNameInExceptionTests(SimpleTestCase): Template("{% endfor %}") except TemplateSyntaxError as e: self.assertEqual(str(e), self.template_error_msg) + + +# RemovedInDjango70Warning +@ignore_warnings(category=RemovedInDjango70Warning) +class DeprecatedTests(SimpleTestCase): + @setup({"template": "{{ doubledot..lookup }}"}) + def test_double_dot_lookup(self): + context = Context({"doubledot": {"": {"lookup": "value"}}}) + self.assertEqual(self.engine.render_to_string("template", context), "value") -- cgit v1.3