1
0
mirror of https://github.com/django/django.git synced 2025-06-02 10:09:12 +00:00

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.
This commit is contained in:
Simon Charette 2025-05-08 14:02:35 -04:00 committed by Sarah Boyce
parent 42ab99309d
commit 8be0c0d690
4 changed files with 21 additions and 6 deletions

View File

@ -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"]

View File

@ -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`).

View File

@ -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)

View File

@ -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)