summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSimon Charette <charette.s@gmail.com>2025-05-08 14:02:35 -0400
committerSarah Boyce <42296566+sarahboyce@users.noreply.github.com>2025-05-12 12:33:07 +0100
commit8be0c0d6901669661fca578f474cd51cd284d35a (patch)
tree9a0caf26bfa3e6aa3d92064433186c8524f0a8bc
parent42ab99309d347f617d60751c2e8d627fb2963049 (diff)
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.
-rw-r--r--django/db/models/query.py6
-rw-r--r--docs/releases/5.2.2.txt3
-rw-r--r--tests/composite_pk/models/tenant.py10
-rw-r--r--tests/composite_pk/tests.py8
4 files changed, 21 insertions, 6 deletions
diff --git a/django/db/models/query.py b/django/db/models/query.py
index 4ba96928d4..663f8bade4 100644
--- a/django/db/models/query.py
+++ b/django/db/models/query.py
@@ -2678,7 +2678,11 @@ class RelatedPopulator:
)
self.model_cls = klass_info["model"]
- self.pk_idx = self.init_list.index(self.model_cls._meta.pk.attname)
+ # A primary key must have all of its constituents not-NULL as
+ # NULL != NULL and thus NULL cannot be referenced through a foreign
+ # relationship. Therefore checking for a single member of the primary
+ # key is enough to determine if the referenced object exists or not.
+ self.pk_idx = self.init_list.index(self.model_cls._meta.pk_fields[0].attname)
self.related_populators = get_related_populators(klass_info, select, self.db)
self.local_setter = klass_info["local_setter"]
self.remote_setter = klass_info["remote_setter"]
diff --git a/docs/releases/5.2.2.txt b/docs/releases/5.2.2.txt
index 6d5f3bb61d..1af581e60c 100644
--- a/docs/releases/5.2.2.txt
+++ b/docs/releases/5.2.2.txt
@@ -9,4 +9,5 @@ Django 5.2.2 fixes several bugs in 5.2.1.
Bugfixes
========
-* ...
+* Fixed a crash when using ``select_related`` against a ``ForeignObject``
+ originating from a model with a ``CompositePrimaryKey`` (:ticket:`36373`).
diff --git a/tests/composite_pk/models/tenant.py b/tests/composite_pk/models/tenant.py
index c85869afa7..65eb0feae8 100644
--- a/tests/composite_pk/models/tenant.py
+++ b/tests/composite_pk/models/tenant.py
@@ -14,17 +14,18 @@ class Token(models.Model):
secret = models.CharField(max_length=10, default="", blank=True)
-class BaseModel(models.Model):
+class AbstractUser(models.Model):
pk = models.CompositePrimaryKey("tenant_id", "id")
tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE)
+ email = models.EmailField(unique=True)
id = models.SmallIntegerField(unique=True)
class Meta:
abstract = True
-class User(BaseModel):
- email = models.EmailField(unique=True)
+class User(AbstractUser):
+ pass
class Comment(models.Model):
@@ -35,13 +36,14 @@ class Comment(models.Model):
related_name="comments",
)
id = models.SmallIntegerField(unique=True, db_column="comment_id")
- user_id = models.SmallIntegerField()
+ user_id = models.SmallIntegerField(null=True)
user = models.ForeignObject(
User,
on_delete=models.CASCADE,
from_fields=("tenant_id", "user_id"),
to_fields=("tenant_id", "id"),
related_name="comments",
+ null=True,
)
text = models.TextField(default="", blank=True)
integer = models.IntegerField(default=0)
diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py
index 5dea23c9f2..91cbee0635 100644
--- a/tests/composite_pk/tests.py
+++ b/tests/composite_pk/tests.py
@@ -184,6 +184,14 @@ class CompositePKTests(TestCase):
with self.assertNumQueries(1):
self.assertEqual(user.email, self.user.email)
+ def test_select_related(self):
+ Comment.objects.create(tenant=self.tenant, id=2)
+ with self.assertNumQueries(1):
+ comments = list(Comment.objects.select_related("user").order_by("pk"))
+ self.assertEqual(len(comments), 2)
+ self.assertEqual(comments[0].user, self.user)
+ self.assertIsNone(comments[1].user)
+
def test_model_forms(self):
fields = ["tenant", "id", "user_id", "text", "integer"]
self.assertEqual(list(CommentForm.base_fields), fields)