Opened 3 years ago
Closed 3 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 , 3 years ago
| Component: | Uncategorized → Database layer (models, ORM) |
|---|---|
| Resolution: | → needsinfo |
| Status: | new → closed |
follow-up: 5 comment:2 by , 3 years ago
Replying to Mariusz Felisiak:
Why? it doesn't use a
namevalues 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 , 3 years ago
| Type: | Uncategorized → Bug |
|---|
comment:4 by , 3 years ago
| Cc: | added |
|---|---|
| Resolution: | needsinfo |
| Status: | closed → new |
| Triage Stage: | Unreviewed → Accepted |
comment:5 by , 3 years ago
Replying to Mariusz Felisiak:
Replying to Mariusz Felisiak:
Why? it doesn't use a
namevalues 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 , 3 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 , 3 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 , 3 years ago
Thanks, can you prepare PR (with patch and tests) and submit it via GitHub? (docs changes are not required.)
comment:9 by , 3 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 , 3 years ago
| Has patch: | set |
|---|---|
| Owner: | changed from to |
| Status: | new → assigned |
comment:12 by , 3 years ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Replying to zhu:
Why? it doesn't use a
namevalues from the loop. I don't think you've explained the issue.