Skip to content

Commit

Permalink
Fixed #35044 -- Avoided clearing reverse relations and private fields…
Browse files Browse the repository at this point in the history
… when accessing deferred fields.

Regression in a7b5ad8 for reverse
relations and possibly in 123b1d3 for
private fields.
  • Loading branch information
g-nie authored and felixxm committed Mar 8, 2024
1 parent 74f7fe3 commit 73df8b5
Show file tree
Hide file tree
Showing 4 changed files with 75 additions and 2 deletions.
8 changes: 6 additions & 2 deletions django/db/models/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -740,12 +740,16 @@ def refresh_from_db(self, using=None, fields=None, from_queryset=None):

# 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
Expand Down
35 changes: 35 additions & 0 deletions tests/contenttypes_tests/test_fields.py
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,18 @@ def test_clear_cached_generic_relation(self):
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):
Expand All @@ -55,6 +67,29 @@ def test_value_to_string(self):
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")
Expand Down
8 changes: 8 additions & 0 deletions tests/defer/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,14 @@ def __str__(self):
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

Expand Down
26 changes: 26 additions & 0 deletions tests/defer/tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
Child,
ChildProxy,
Primary,
PrimaryOneToOne,
RefreshPrimaryProxy,
Secondary,
ShadowChild,
Expand Down Expand Up @@ -326,3 +327,28 @@ def test_only_select_related_raises_invalid_query(self):
)
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

0 comments on commit 73df8b5

Please sign in to comment.