#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 , 9 days ago
Severity: | Normal → Release blocker |
---|---|
Triage Stage: | Unreviewed → Accepted |
comment:2 by , 9 days 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 , 8 days ago
Owner: | set to |
---|---|
Status: | new → assigned |
comment:4 by , 8 days ago
Has patch: | set |
---|
comment:5 by , 8 days ago
Patch needs improvement: | set |
---|
comment:6 by , 7 days ago
Patch needs improvement: | unset |
---|
comment:7 by , 5 days 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_related
without a subset of relationship the following test fails as well and is a more common usage ofselect_related
tests/composite_pk/tests.py