Opened 109 minutes ago
#36611 new Bug
Model validation of constraint involving ForeignObject considers only first column
Reported by: | Jacob Walls | Owned by: | |
---|---|---|---|
Component: | Database layer (models, ORM) | Version: | 5.2 |
Severity: | Normal | Keywords: | |
Cc: | Triage Stage: | Unreviewed | |
Has patch: | no | Needs documentation: | no |
Needs tests: | no | Patch needs improvement: | no |
Easy pickings: | no | UI/UX: | no |
Description
Discovered during #36580 in review.
Similar to #36431, where only the first column of a ForeignObject
was considered in values()
, only the first column is considered during model validation of constraints.
Composite PK's are not affected, because they raise system checks if you try to use them in a constraint. But ForeignObject
has been broken since its introduction in this regard. Reproduced on 5.2, but thus, not a release blocker.
Rough test (needs adjusting to avoid hijacking this model and unnecessarily skipping tests on backends not supporting constraints):
-
tests/composite_pk/models/tenant.py
diff --git a/tests/composite_pk/models/tenant.py b/tests/composite_pk/models/tenant.py index 65eb0feae8..c818ec4de7 100644
a b class Comment(models.Model): 48 48 text = models.TextField(default="", blank=True) 49 49 integer = models.IntegerField(default=0) 50 50 51 class Meta: 52 # TODO: use new model instead 53 required_db_features = {"supports_table_check_constraints"} 54 constraints = [ 55 models.CheckConstraint( 56 condition=models.Q(user__lt=(1000, 1000)), 57 name="user_limit", 58 ), 59 ] 60 51 61 52 62 class Post(models.Model): 53 63 pk = models.CompositePrimaryKey("tenant_id", "id") -
tests/composite_pk/test_models.py
diff --git a/tests/composite_pk/test_models.py b/tests/composite_pk/test_models.py index 27157a52ad..05aafd5306 100644
a b 1 1 from django.contrib.contenttypes.models import ContentType 2 2 from django.core.exceptions import ValidationError 3 from django.db import connection 3 4 from django.test import TestCase 5 from django.test.utils import CaptureQueriesContext 4 6 5 7 from .models import Comment, Tenant, Token, User 6 8 … … class CompositePKModelsTests(TestCase): 119 121 self.assertSequenceEqual(ctx.exception.messages, messages) 120 122 121 123 def test_full_clean_update(self): 122 with self.assertNumQueries(1): 124 with CaptureQueriesContext(connection) as ctx: 125 self.comment_1.full_clean() 126 select_queries = [ 127 query["sql"] 128 for query in ctx.captured_queries 129 if "select" in query["sql"].lower() 130 ] 131 self.assertEqual(len(select_queries), 2, select_queries) # 1 on 5.2.x 132 133 def test_full_clean_update_invalid(self): 134 self.comment_1.tenant_id = 1001 135 with self.assertRaises(ValidationError): 136 self.comment_1.full_clean() 137 138 self.comment_1.tenant_id = 1 139 self.comment_1.user_id = 1001 140 with self.assertRaises(ValidationError): 123 141 self.comment_1.full_clean() 124 142 125 143 def test_field_conflicts(self): -
tests/composite_pk/tests.py
diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py index 2245a472e4..5b7e34a0bc 100644
a b class CompositePKTests(TestCase): 187 187 self.assertEqual(user.email, self.user.email) 188 188 189 189 def test_select_related(self): 190 Comment.objects.create(tenant=self.tenant, id=2) 190 user2 = User.objects.create( 191 tenant=self.tenant, 192 id=2, 193 email="user0002@example.com", 194 ) 195 Comment.objects.create(tenant=self.tenant, id=2, user=user2) 191 196 with self.assertNumQueries(1): 192 197 comments = list(Comment.objects.select_related("user").order_by("pk")) 193 198 self.assertEqual(len(comments), 2) 194 199 self.assertEqual(comments[0].user, self.user) 195 self.assert IsNone(comments[1].user)200 self.assertEqual(comments[1].user, user2) 196 201 197 202 def test_model_forms(self): 198 203 fields = ["tenant", "id", "user_id", "text", "integer"]
Notice 1001
only appears in the first query of the test_full_clean_update_invalid
.
FAIL: test_full_clean_update_invalid (composite_pk.test_models.CompositePKModelsTests.test_full_clean_update_invalid) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Users/jwalls/django/tests/composite_pk/test_models.py", line 140, in test_full_clean_update_invalid with self.assertRaises(ValidationError): ~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^ AssertionError: ValidationError not raised ---------------------------------------------------------------------- (0.000) SELECT 1 AS "a" FROM "composite_pk_tenant" WHERE "composite_pk_tenant"."id" = 1001 LIMIT 1; args=(1, 1001); ALIAS=DEFAULT (0.000) SELECT 1 AS "a" FROM "composite_pk_tenant" WHERE "composite_pk_tenant"."id" = 1 LIMIT 1; args=(1, 1); ALIAS=DEFAULT ---------------------------------------------------------------------- Ran 2 tests in 0.003s FAILED (failures=1)