#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 (10)
comment:1 by , 8 months ago
comment:2 by , 8 months ago
Triage Stage: | Unreviewed → Accepted |
---|
{{{!#diff
diff --git a/tests/defer_regress/models.py b/tests/defer_regress/models.py
index dd492993b7..38ba4a622f 100644
--- a/tests/defer_regress/models.py
+++ b/tests/defer_regress/models.py
@@ -10,6 +10,12 @@ class Item(models.Model):
text = models.TextField(default="xyzzy")
value = models.IntegerField()
other_value = models.IntegerField(default=0)
+ source = models.OneToOneField(
+ "self",
+ related_name="destination",
+ on_delete=models.CASCADE,
+ null=True,
+ )
class RelatedItem(models.Model):
diff --git a/tests/defer_regress/tests.py b/tests/defer_regress/tests.py
index 10100e348d..d8b2186f0a 100644
--- a/tests/defer_regress/tests.py
+++ b/tests/defer_regress/tests.py
@@ -309,6 +309,21 @@ def test_only_reverse_many_to_many_ignored(self):
with self.assertNumQueries(1):
self.assertEqual(Item.objects.only("request").get(), item)
+ def test_self_referential(self):
+ first = Item.objects.create(name="first", value=1)
+ second = Item.objects.create(name="second", value=2, source=first)
+ with self.assertNumQueries(1):
+ deferred_first, deferred_second = (
+ Item.objects.select_related("source", "destination")
+ .only("name", "sourcename", "destinationvalue")
+ .order_by("pk")
+ )
+ with self.assertNumQueries(0):
+ self.assertEqual(deferred_first.name, first.name)
+ self.assertEqual(deferred_second.name, second.name)
+ self.assertEqual(deferred_second.source.name, first.name)
+ self.assertEqual(deferred_first.destination.name, second.name)
+
class DeferDeletionSignalsTests(TestCase):
senders = [Item, Proxy]
}}}
comment:4 by , 8 months ago
Has patch: | set |
---|
comment:5 by , 8 months 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 , 7 months ago
Owner: | changed from | to
---|---|
Status: | new → assigned |
Triage Stage: | Accepted → Ready for checkin |
comment:9 by , 6 months ago
Will this fix be released on the version 4.2 series? Or should I expect to only see it getting applied to version 5.0+ ?
comment:10 by , 6 months ago
It will not be backported to 4.2 as it's long term support at this point and only receive security and data loss fixing patches at this point.
Likely related to #21204 (b3db6c8dcb5145f7d45eff517bcd96460475c879) which was merged in 4.2.