Skip to content

Commit e23dd72

Browse files
charettessarahboyce
authored andcommitted
[5.2.x] Fixed #36373 -- Fixed select_related() crash on foreign object for a composite pk.
Thanks Jacob Walls for the report and Sarah for the in-depth review. Backport of 8be0c0d from main.
1 parent ab5c066 commit e23dd72

File tree

4 files changed

+21
-6
lines changed

4 files changed

+21
-6
lines changed

django/db/models/query.py

+5-1
Original file line numberDiff line numberDiff line change
@@ -2715,7 +2715,11 @@ def __init__(self, klass_info, select, db):
27152715
)
27162716

27172717
self.model_cls = klass_info["model"]
2718-
self.pk_idx = self.init_list.index(self.model_cls._meta.pk.attname)
2718+
# A primary key must have all of its constituents not-NULL as
2719+
# NULL != NULL and thus NULL cannot be referenced through a foreign
2720+
# relationship. Therefore checking for a single member of the primary
2721+
# key is enough to determine if the referenced object exists or not.
2722+
self.pk_idx = self.init_list.index(self.model_cls._meta.pk_fields[0].attname)
27192723
self.related_populators = get_related_populators(klass_info, select, self.db)
27202724
self.local_setter = klass_info["local_setter"]
27212725
self.remote_setter = klass_info["remote_setter"]

docs/releases/5.2.2.txt

+2-1
Original file line numberDiff line numberDiff line change
@@ -9,4 +9,5 @@ Django 5.2.2 fixes several bugs in 5.2.1.
99
Bugfixes
1010
========
1111

12-
* ...
12+
* Fixed a crash when using ``select_related`` against a ``ForeignObject``
13+
originating from a model with a ``CompositePrimaryKey`` (:ticket:`36373`).

tests/composite_pk/models/tenant.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,18 @@ class Token(models.Model):
1414
secret = models.CharField(max_length=10, default="", blank=True)
1515

1616

17-
class BaseModel(models.Model):
17+
class AbstractUser(models.Model):
1818
pk = models.CompositePrimaryKey("tenant_id", "id")
1919
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
20+
email = models.EmailField(unique=True)
2021
id = models.SmallIntegerField(unique=True)
2122

2223
class Meta:
2324
abstract = True
2425

2526

26-
class User(BaseModel):
27-
email = models.EmailField(unique=True)
27+
class User(AbstractUser):
28+
pass
2829

2930

3031
class Comment(models.Model):
@@ -35,13 +36,14 @@ class Comment(models.Model):
3536
related_name="comments",
3637
)
3738
id = models.SmallIntegerField(unique=True, db_column="comment_id")
38-
user_id = models.SmallIntegerField()
39+
user_id = models.SmallIntegerField(null=True)
3940
user = models.ForeignObject(
4041
User,
4142
on_delete=models.CASCADE,
4243
from_fields=("tenant_id", "user_id"),
4344
to_fields=("tenant_id", "id"),
4445
related_name="comments",
46+
null=True,
4547
)
4648
text = models.TextField(default="", blank=True)
4749
integer = models.IntegerField(default=0)

tests/composite_pk/tests.py

+8
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,14 @@ def test_only(self):
170170
with self.assertNumQueries(1):
171171
self.assertEqual(user.email, self.user.email)
172172

173+
def test_select_related(self):
174+
Comment.objects.create(tenant=self.tenant, id=2)
175+
with self.assertNumQueries(1):
176+
comments = list(Comment.objects.select_related("user").order_by("pk"))
177+
self.assertEqual(len(comments), 2)
178+
self.assertEqual(comments[0].user, self.user)
179+
self.assertIsNone(comments[1].user)
180+
173181
def test_model_forms(self):
174182
fields = ["tenant", "id", "user_id", "text", "integer"]
175183
self.assertEqual(list(CommentForm.base_fields), fields)

0 commit comments

Comments
 (0)