Opened 2 years ago
Closed 2 years ago
#34226 closed Bug (fixed)
QuerySet.select_related() with multiple filtered relations to the OneToOneField sets the last one.
Reported by: | zhu | Owned by: | zhu |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 4.1 |
Severity: | Normal | Keywords: | |
Cc: | Simon Charette, Hasan Ramezani | 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
Change History (13)
follow-up: 2 comment:1 by , 2 years ago
Component: | Uncategorized → Database layer (models, ORM) |
---|---|
Resolution: | → needsinfo |
Status: | new → closed |
follow-up: 5 comment:2 by , 2 years ago
Replying to Mariusz Felisiak:
Why? it doesn't use a
name
values from the loop. I don't think you've explained the issue.
Ahh, OK, it uses f
. Can you provide a regression test?
comment:3 by , 2 years ago
Type: | Uncategorized → Bug |
---|
comment:4 by , 2 years ago
Cc: | added |
---|---|
Resolution: | needsinfo |
Status: | closed → new |
Triage Stage: | Unreviewed → Accepted |
comment:5 by , 2 years ago
Replying to Mariusz Felisiak:
Replying to Mariusz Felisiak:
Why? it doesn't use a
name
values from the loop. I don't think you've explained the issue.
Ahh, OK, it uses
f
. Can you provide a regression test?
# models.py from django.db import models class Pool(models.Model): name = models.CharField(max_length=30) class AnotherPoolStyle(models.Model): name = models.CharField(max_length=30) pool1 = models.OneToOneField(Pool, models.CASCADE, related_name='style_from_pool1') pool2 = models.OneToOneField(Pool, models.CASCADE, related_name='style_from_pool2') # tests.py from django.test import TestCase from django.db.models import FilteredRelation class Tests(TestCase): @classmethod def setUpTestData(cls): cls.p1 = Pool.objects.create(name="pool1") cls.p2 = Pool.objects.create(name="pool2") cls.ap = AnotherPoolStyle.objects.create( name="Another Pool Style", pool1=cls.p1, pool2=cls.p2, ) def test_filtered_relation_select_related(self): with self.assertNumQueries(1): ps = list(AnotherPoolStyle.objects.annotate( p1=FilteredRelation('pool1'), p2=FilteredRelation('pool2'), ).select_related('p1', 'p2')) self.assertIs(ps[0], ps[0].p1.style_from_pool1) # failed self.assertIs(ps[0], ps[0].p2.style_from_pool2)
follow-up: 7 comment:6 by , 2 years ago
Summary: | The local_setter may use a wrong f → QuerySet.select_related() with multiple filtered relations to the OneToOneField sets the last one. |
---|
Thanks! Would you like to prepare a patch?
comment:7 by , 2 years ago
Replying to Mariusz Felisiak:
Thanks! Would you like to prepare a patch?
diff --git a/django/db/models/sql/compiler.py b/django/db/models/sql/compiler.py index 2b66ab12b4..831ef79269 100644 --- a/django/db/models/sql/compiler.py +++ b/django/db/models/sql/compiler.py @@ -1261,7 +1261,7 @@ class SQLCompiler: ) get_related_klass_infos(klass_info, next_klass_infos) - def local_setter(obj, from_obj): + def local_setter(f, obj, from_obj): # Set a reverse fk object when relation is non-empty. if from_obj: f.remote_field.set_cached_value(from_obj, obj) @@ -1287,7 +1287,7 @@ class SQLCompiler: "model": model, "field": f, "reverse": True, - "local_setter": local_setter, + "local_setter": partial(local_setter, f), "remote_setter": partial(remote_setter, name), "from_parent": from_parent, }
follow-up: 9 comment:8 by , 2 years ago
Thanks, can you prepare PR (with patch and tests) and submit it via GitHub? (docs changes are not required.)
comment:9 by , 2 years ago
Replying to Mariusz Felisiak:
Thanks, can you prepare PR (with patch and tests) and submit it via GitHub? (docs changes are not required.)
comment:10 by , 2 years ago
Has patch: | set |
---|---|
Owner: | changed from | to
Status: | new → assigned |
comment:12 by , 2 years ago
Triage Stage: | Accepted → Ready for checkin |
---|
Replying to zhu:
Why? it doesn't use a
name
values from the loop. I don't think you've explained the issue.