From 201d29b3719ef15637648be7bd947ef90a66ab55 Mon Sep 17 00:00:00 2001 From: Simon Charette Date: Tue, 16 May 2023 15:11:19 -0400 Subject: [PATCH] [4.2.x] Fixed #34570 -- Silenced noop deferral of many-to-many and GFK. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While deferring many-to-many and GFK has no effect, the previous implementation of QuerySet.defer() ignore them instead of crashing. Regression in b3db6c8dcb5145f7d45eff517bcd96460475c879. Thanks Paco Martínez for the report. Backport of 99e5dff737cd20b12d060e4794e097063b61ec40 from main --- django/db/models/sql/query.py | 10 +++++++++- docs/releases/4.2.2.txt | 4 ++++ tests/defer_regress/tests.py | 6 ++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/django/db/models/sql/query.py b/django/db/models/sql/query.py index b347c6bf3f..b615d06f28 100644 --- a/django/db/models/sql/query.py +++ b/django/db/models/sql/query.py @@ -725,7 +725,15 @@ class Query(BaseExpression): field_select_mask = select_mask.setdefault((field_name, relation), {}) field = relation.field else: - field = opts.get_field(field_name).field + reverse_rel = opts.get_field(field_name) + # While virtual fields such as many-to-many and generic foreign + # keys cannot be effectively deferred we've historically + # allowed them to be passed to QuerySet.defer(). Ignore such + # field references until a layer of validation at mask + # alteration time will be implemented eventually. + if not hasattr(reverse_rel, "field"): + continue + field = reverse_rel.field field_select_mask = select_mask.setdefault(field, {}) related_model = field.model._meta.concrete_model self._get_defer_select_mask( diff --git a/docs/releases/4.2.2.txt b/docs/releases/4.2.2.txt index a92582b7a0..ca21e5f1ec 100644 --- a/docs/releases/4.2.2.txt +++ b/docs/releases/4.2.2.txt @@ -15,3 +15,7 @@ Bugfixes * Restored, following a regression in Django 4.2, ``get_prep_value()`` call in ``JSONField`` subclasses (:ticket:`34539`). + +* Fixed a regression in Django 4.2 that caused a crash of ``QuerySet.defer()`` + when passing a ``ManyToManyField`` or ``GenericForeignKey`` reference. While + doing so is a no-op, it was allowed in older version (:ticket:`34570`). diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py index 9f94d5fac1..ab2be085c6 100644 --- a/tests/defer_regress/tests.py +++ b/tests/defer_regress/tests.py @@ -296,6 +296,12 @@ class DeferRegressionTest(TestCase): with self.assertNumQueries(1): self.assertEqual(leaf.second_child.value, 64) + def test_defer_many_to_many_ignored(self): + location = Location.objects.create() + request = Request.objects.create(location=location) + with self.assertNumQueries(1): + self.assertEqual(Request.objects.defer("items").get(), request) + class DeferDeletionSignalsTests(TestCase): senders = [Item, Proxy]