#35356 closed Bug (fixed)
Issue with OneToOneField and recursive relationships in select_related() and only().
Reported by: | Joshua van Besouw | Owned by: | Simon Charette |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 4.2 |
Severity: | Normal | Keywords: | |
Cc: | Joshua van Besouw | Triage Stage: | Ready for checkin |
Has patch: | yes | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
In Django a 4.2 project, if you have a model with a recursive relationship OneToOneField
like this:
class Example(models.Model): name = models.CharField(max_length=32) source = models.OneToOneField( 'self', related_name='destination', on_delete=models.CASCADE )
And then query this model using:
Example.objects.select_related( "source", "destination", ).only( "name", "source__name", "destination__name", ).all()
It throws the following error:
django.core.exceptions.FieldError: Field Example.source cannot be both deferred and traversed using select_related at the same time.
Expected behavior:
The queryset should apply the select_related()
and only()
without an exception occurring as the only()
is specifying sub fields of the fields in the select_related()
. Or at least this is how it used to behave.
Interestingly, if you change the queryset to the following, it works without any issues:
Example.objects.select_related("source").only("name", "source__name").all()
And vice versa also works:
Example.objects.select_related("destination").only("name", "destination__name").all()
Effected versions:
This error occurs in version 4.2+ of Django.
This worked as expected in all versions of Django 4.1.
As far as I can tell, this is a regression as a brief search doesn't indicate this functionality was explicitly changed at any point. I have not tested whether this is only an issue with recursive relationships or a general issues with reverse relationships.
Change History (8)
comment:1 by , 4 weeks ago
comment:2 by , 4 weeks ago
Triage Stage: | Unreviewed → Accepted |
---|
-
tests/defer_regress/models.py
diff --git a/tests/defer_regress/models.py b/tests/defer_regress/models.py index dd492993b7..38ba4a622f 100644
a b class Item(models.Model): 10 10 text = models.TextField(default="xyzzy") 11 11 value = models.IntegerField() 12 12 other_value = models.IntegerField(default=0) 13 source = models.OneToOneField( 14 "self", 15 related_name="destination", 16 on_delete=models.CASCADE, 17 null=True, 18 ) 13 19 14 20 15 21 class RelatedItem(models.Model): -
tests/defer_regress/tests.py
diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py index 10100e348d..98473e0c4f 100644
a b def test_only_reverse_many_to_many_ignored(self): 309 309 with self.assertNumQueries(1): 310 310 self.assertEqual(Item.objects.only("request").get(), item) 311 311 312 def test_self_referential(self): 313 first = Item.objects.create(name="first", value=1) 314 second = Item.objects.create(name="second", value=2, source=first) 315 with self.assertNumQueries(1): 316 deferred_first, deferred_second = ( 317 Item.objects.select_related("source", "destination") 318 .only("name", "source__name", "destination__value") 319 .order_by("pk") 320 ) 321 with self.assertNumQueries(0): 322 self.assertEqual(deferred_first.name, first.name) 323 self.assertEqual(deferred_second.name, second.name) 324 self.assertEqual(deferred_second.source.name, first.name) 325 self.assertEqual(deferred_first.destination.value, second.value) 326 312 327 313 328 class DeferDeletionSignalsTests(TestCase): 314 329 senders = [Item, Proxy]
comment:4 by , 4 weeks ago
Has patch: | set |
---|
comment:5 by , 4 weeks ago
Thanks for the quick patch on this!
I can confirm, form my end, that this patch fixes the issue I was encountering. The FieldError
was not raised and the following SQL was used in the resulting query:
SELECT "test_project_example"."id", "test_project_example"."name", "test_project_example"."source_id", T2."id", T2."name", T3."id", T3."name" FROM "test_project_example" LEFT OUTER JOIN "test_project_example" T2 ON ( "test_project_example"."source_id" = T2."id" ) LEFT OUTER JOIN "test_project_example" T3 ON ( "test_project_example"."id" = T3."source_id" ) ORDER BY "test_project_example"."id" ASC
comment:6 by , 3 weeks ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
Triage Stage: | Accepted → Ready for checkin |
Likely related to #21204 (b3db6c8dcb5145f7d45eff517bcd96460475c879) which was merged in 4.2.