#36373 closed Bug (fixed)
select_related() doesn't work when targeting composite primary keys
| Reported by: | Jacob Walls | Owned by: | Simon Charette |
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 5.2 |
| Severity: | Release blocker | Keywords: | |
| Cc: | 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
The suggested workaround until #35956 implements foreign keys to models with composite primary keys is to use ForeignObject, but it causes failures in select_related(). (Of note: prefetch_related() works fine.)
-
tests/composite_pk/tests.py
diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py index 5dea23c9f2..068030bd18 100644
a b class CompositePKTests(TestCase): 184 184 with self.assertNumQueries(1): 185 185 self.assertEqual(user.email, self.user.email) 186 186 187 def test_select_related(self): 188 with self.assertNumQueries(1): 189 for comment in Comment.objects.select_related(): 190 comment.user 191 187 192 def test_model_forms(self): 188 193 fields = ["tenant", "id", "user_id", "text", "integer"] 189 194 self.assertEqual(list(CommentForm.base_fields), fields)
======================================================================
ERROR: test_select_related (composite_pk.tests.CompositePKTests.test_select_related)
----------------------------------------------------------------------
Traceback (most recent call last):
File "/Users/.../django/tests/composite_pk/tests.py", line 189, in test_select_related
for comment in Comment.objects.select_related():
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^
File "/Users/.../django/django/db/models/query.py", line 403, in __iter__
self._fetch_all()
~~~~~~~~~~~~~~~^^
File "/Users/.../django/django/db/models/query.py", line 1966, in _fetch_all
self._result_cache = list(self._iterable_class(self))
~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/.../django/django/db/models/query.py", line 107, in __iter__
related_populators = get_related_populators(klass_info, select, db, fetch_mode)
File "/Users/.../django/django/db/models/query.py", line 2742, in get_related_populators
rel_cls = RelatedPopulator(rel_klass_info, select, db, fetch_mode)
File "/Users/.../django/django/db/models/query.py", line 2710, in __init__
self.pk_idx = self.init_list.index(self.model_cls._meta.pk.attname)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
ValueError: 'pk' is not in list
Change History (9)
comment:1 by , 6 months ago
| Severity: | Normal → Release blocker |
|---|---|
| Triage Stage: | Unreviewed → Accepted |
comment:2 by , 6 months ago
Looks like something like the following should do
-
django/db/models/query.py
diff --git a/django/db/models/query.py b/django/db/models/query.py index 4f4aad91ef..0472e99144 100644
a b def __init__(self, klass_info, select, db): 2680 2680 ) 2681 2681 2682 2682 self.model_cls = klass_info["model"] 2683 self.pk_idx = self.init_list.index(self.model_cls._meta.pk.attname) 2683 pk_fields = self.model_cls._meta.pk_fields 2684 pk_idx = self.init_list.index(pk_fields[0].attname) 2685 if (pk_fields_len := len(pk_fields)) > 1: 2686 self.pk_idx = slice(pk_idx, pk_fields_len) 2687 else: 2688 self.pk_idx = pk_idx 2684 2689 self.related_populators = get_related_populators(klass_info, select, self.db) 2685 2690 self.local_setter = klass_info["local_setter"] 2686 2691 self.remote_setter = klass_info["remote_setter"]
It might be worth adding a test with a model where pk fields are not defined next to each other or just alter the existing models to cover for that
-
tests/composite_pk/models/tenant.py
diff --git a/tests/composite_pk/models/tenant.py b/tests/composite_pk/models/tenant.py index c85869afa7..a8dfd790f6 100644
a b class Token(models.Model): 17 17 class BaseModel(models.Model): 18 18 pk = models.CompositePrimaryKey("tenant_id", "id") 19 19 tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) 20 between = models.IntegerField(null=True) 20 21 id = models.SmallIntegerField(unique=True) 21 22 22 23 class Meta: … … class Comment(models.Model): 34 35 on_delete=models.CASCADE, 35 36 related_name="comments", 36 37 ) 37 id = models.SmallIntegerField(unique=True, db_column="comment_id")38 38 user_id = models.SmallIntegerField() 39 id = models.SmallIntegerField(unique=True, db_column="comment_id") 39 40 user = models.ForeignObject( 40 41 User, 41 42 on_delete=models.CASCADE,
comment:3 by , 6 months ago
| Owner: | set to |
|---|---|
| Status: | new → assigned |
comment:4 by , 6 months ago
| Has patch: | set |
|---|
comment:5 by , 6 months ago
| Patch needs improvement: | set |
|---|
comment:6 by , 6 months ago
| Patch needs improvement: | unset |
|---|
comment:7 by , 6 months ago
| Triage Stage: | Accepted → Ready for checkin |
|---|
Note:
See TracTickets
for help on using tickets.
Thanks for the report, it's worth pointing out that the issue is not specific to calling
select_relatedwithout a subset of relationship the following test fails as well and is a more common usage ofselect_relatedtests/composite_pk/tests.py