mirror of
				https://github.com/django/django.git
				synced 2025-10-31 09:41:08 +00:00 
			
		
		
		
	[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 8be0c0d690 from main.
			
			
This commit is contained in:
		
				
					committed by
					
						 Sarah Boyce
						Sarah Boyce
					
				
			
			
				
	
			
			
			
						parent
						
							ab5c066472
						
					
				
				
					commit
					e23dd72880
				
			| @@ -2715,7 +2715,11 @@ class RelatedPopulator: | |||||||
|             ) |             ) | ||||||
|  |  | ||||||
|         self.model_cls = klass_info["model"] |         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.related_populators = get_related_populators(klass_info, select, self.db) | ||||||
|         self.local_setter = klass_info["local_setter"] |         self.local_setter = klass_info["local_setter"] | ||||||
|         self.remote_setter = klass_info["remote_setter"] |         self.remote_setter = klass_info["remote_setter"] | ||||||
|   | |||||||
| @@ -9,4 +9,5 @@ Django 5.2.2 fixes several bugs in 5.2.1. | |||||||
| Bugfixes | Bugfixes | ||||||
| ======== | ======== | ||||||
|  |  | ||||||
| * ... | * Fixed a crash when using ``select_related`` against a ``ForeignObject`` | ||||||
|  |   originating from a model with a ``CompositePrimaryKey`` (:ticket:`36373`). | ||||||
|   | |||||||
| @@ -14,17 +14,18 @@ class Token(models.Model): | |||||||
|     secret = models.CharField(max_length=10, default="", blank=True) |     secret = models.CharField(max_length=10, default="", blank=True) | ||||||
|  |  | ||||||
|  |  | ||||||
| class BaseModel(models.Model): | class AbstractUser(models.Model): | ||||||
|     pk = models.CompositePrimaryKey("tenant_id", "id") |     pk = models.CompositePrimaryKey("tenant_id", "id") | ||||||
|     tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) |     tenant = models.ForeignKey(Tenant, on_delete=models.CASCADE) | ||||||
|  |     email = models.EmailField(unique=True) | ||||||
|     id = models.SmallIntegerField(unique=True) |     id = models.SmallIntegerField(unique=True) | ||||||
|  |  | ||||||
|     class Meta: |     class Meta: | ||||||
|         abstract = True |         abstract = True | ||||||
|  |  | ||||||
|  |  | ||||||
| class User(BaseModel): | class User(AbstractUser): | ||||||
|     email = models.EmailField(unique=True) |     pass | ||||||
|  |  | ||||||
|  |  | ||||||
| class Comment(models.Model): | class Comment(models.Model): | ||||||
| @@ -35,13 +36,14 @@ class Comment(models.Model): | |||||||
|         related_name="comments", |         related_name="comments", | ||||||
|     ) |     ) | ||||||
|     id = models.SmallIntegerField(unique=True, db_column="comment_id") |     id = models.SmallIntegerField(unique=True, db_column="comment_id") | ||||||
|     user_id = models.SmallIntegerField() |     user_id = models.SmallIntegerField(null=True) | ||||||
|     user = models.ForeignObject( |     user = models.ForeignObject( | ||||||
|         User, |         User, | ||||||
|         on_delete=models.CASCADE, |         on_delete=models.CASCADE, | ||||||
|         from_fields=("tenant_id", "user_id"), |         from_fields=("tenant_id", "user_id"), | ||||||
|         to_fields=("tenant_id", "id"), |         to_fields=("tenant_id", "id"), | ||||||
|         related_name="comments", |         related_name="comments", | ||||||
|  |         null=True, | ||||||
|     ) |     ) | ||||||
|     text = models.TextField(default="", blank=True) |     text = models.TextField(default="", blank=True) | ||||||
|     integer = models.IntegerField(default=0) |     integer = models.IntegerField(default=0) | ||||||
|   | |||||||
| @@ -170,6 +170,14 @@ class CompositePKTests(TestCase): | |||||||
|         with self.assertNumQueries(1): |         with self.assertNumQueries(1): | ||||||
|             self.assertEqual(user.email, self.user.email) |             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): |     def test_model_forms(self): | ||||||
|         fields = ["tenant", "id", "user_id", "text", "integer"] |         fields = ["tenant", "id", "user_id", "text", "integer"] | ||||||
|         self.assertEqual(list(CommentForm.base_fields), fields) |         self.assertEqual(list(CommentForm.base_fields), fields) | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user