Fixed #35044 -- Avoided clearing reverse relations and private fields when accessing deferred fields.

Regression in a7b5ad8b19 for reverse
relations and possibly in 123b1d3fcf for
private fields.
This commit is contained in:
Giannis Terzopoulos 2024-02-09 23:47:06 +01:00 committed by Mariusz Felisiak
parent 74f7fe3f3d
commit 73df8b54a2
4 changed files with 75 additions and 2 deletions

View File

@ -740,12 +740,16 @@ class Model(AltersData, metaclass=ModelBase):
# Clear cached relations.
for field in self._meta.related_objects:
if field.is_cached(self):
if (fields is None or field.name in fields) and field.is_cached(self):
field.delete_cached_value(self)
# Clear cached private relations.
for field in self._meta.private_fields:
if field.is_relation and field.is_cached(self):
if (
(fields is None or field.name in fields)
and field.is_relation
and field.is_cached(self)
):
field.delete_cached_value(self)
self._state.db = db_instance._state.db

View File

@ -45,6 +45,18 @@ class GenericForeignKeyTests(TestCase):
new_entity = answer.question
self.assertIsNot(old_entity, new_entity)
def test_clear_cached_generic_relation_explicit_fields(self):
question = Question.objects.create(text="question")
answer = Answer.objects.create(text="answer", question=question)
old_question_obj = answer.question
# The reverse relation is not refreshed if not passed explicitly in
# `fields`.
answer.refresh_from_db(fields=["text"])
self.assertIs(answer.question, old_question_obj)
answer.refresh_from_db(fields=["question"])
self.assertIsNot(answer.question, old_question_obj)
self.assertEqual(answer.question, old_question_obj)
class GenericRelationTests(TestCase):
def test_value_to_string(self):
@ -55,6 +67,29 @@ class GenericRelationTests(TestCase):
self.assertCountEqual(result, [answer1.pk, answer2.pk])
class DeferredGenericRelationTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.question = Question.objects.create(text="question")
cls.answer = Answer.objects.create(text="answer", question=cls.question)
def test_defer_not_clear_cached_private_relations(self):
obj = Answer.objects.defer("text").get(pk=self.answer.pk)
with self.assertNumQueries(1):
obj.question
obj.text # Accessing a deferred field.
with self.assertNumQueries(0):
obj.question
def test_only_not_clear_cached_private_relations(self):
obj = Answer.objects.only("content_type", "object_id").get(pk=self.answer.pk)
with self.assertNumQueries(1):
obj.question
obj.text # Accessing a deferred field.
with self.assertNumQueries(0):
obj.question
class GetPrefetchQuerySetDeprecation(TestCase):
def test_generic_relation_warning(self):
Question.objects.create(text="test")

View File

@ -19,6 +19,14 @@ class Primary(models.Model):
return self.name
class PrimaryOneToOne(models.Model):
name = models.CharField(max_length=50)
value = models.CharField(max_length=50)
related = models.OneToOneField(
Secondary, models.CASCADE, related_name="primary_o2o"
)
class Child(Primary):
pass

View File

@ -6,6 +6,7 @@ from .models import (
Child,
ChildProxy,
Primary,
PrimaryOneToOne,
RefreshPrimaryProxy,
Secondary,
ShadowChild,
@ -326,3 +327,28 @@ class InvalidDeferTests(SimpleTestCase):
)
with self.assertRaisesMessage(FieldError, msg):
Primary.objects.only("name").select_related("related")[0]
class DeferredRelationTests(TestCase):
@classmethod
def setUpTestData(cls):
cls.secondary = Secondary.objects.create(first="a", second="b")
cls.primary = PrimaryOneToOne.objects.create(
name="Bella", value="Baxter", related=cls.secondary
)
def test_defer_not_clear_cached_relations(self):
obj = Secondary.objects.defer("first").get(pk=self.secondary.pk)
with self.assertNumQueries(1):
obj.primary_o2o
obj.first # Accessing a deferred field.
with self.assertNumQueries(0):
obj.primary_o2o
def test_only_not_clear_cached_relations(self):
obj = Secondary.objects.only("first").get(pk=self.secondary.pk)
with self.assertNumQueries(1):
obj.primary_o2o
obj.second # Accessing a deferred field.
with self.assertNumQueries(0):
obj.primary_o2o