diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 7c433b3f061..c07076d54a1 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1274,6 +1274,9 @@ class SQLCompiler: if from_obj: final_field.remote_field.set_cached_value(from_obj, obj) + def local_setter_noop(obj, from_obj): + pass + def remote_setter(name, obj, from_obj): setattr(from_obj, name, obj) @@ -1295,7 +1298,11 @@ class SQLCompiler: "model": model, "field": final_field, "reverse": True, - "local_setter": partial(local_setter, final_field), + "local_setter": ( + partial(local_setter, final_field) + if len(joins) <= 2 + else local_setter_noop + ), "remote_setter": partial(remote_setter, name), "from_parent": from_parent, } diff --git a/tests/known_related_objects/tests.py b/tests/known_related_objects/tests.py index 6080da3838e..371c73d3955 100644 --- a/tests/known_related_objects/tests.py +++ b/tests/known_related_objects/tests.py @@ -164,3 +164,23 @@ class ExistingRelatedInstancesTests(TestCase): ) self.assertIs(ps[0], ps[0].pool_1.poolstyle) self.assertIs(ps[0], ps[0].pool_2.another_style) + + def test_multilevel_reverse_fk_cyclic_select_related(self): + with self.assertNumQueries(3): + p = list( + PoolStyle.objects.annotate( + tournament_pool=FilteredRelation("pool__tournament__pool"), + ).select_related("tournament_pool", "tournament_pool__tournament") + ) + self.assertEqual(p[0].tournament_pool.tournament, p[0].pool.tournament) + + def test_multilevel_reverse_fk_select_related(self): + with self.assertNumQueries(2): + p = list( + Tournament.objects.filter(id=self.t2.id) + .annotate( + style=FilteredRelation("pool__another_style"), + ) + .select_related("style") + ) + self.assertEqual(p[0].style.another_pool, self.p3)