#36611: Model validation of constraint involving ForeignObject considers only 
first
column
-------------------------------------+-------------------------------------
     Reporter:  Jacob Walls          |                     Type:  Bug
       Status:  new                  |                Component:  Database
                                     |  layer (models, ORM)
      Version:  5.2                  |                 Severity:  Normal
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
 Discovered during #36580 in
 [https://github.com/django/django/pull/19798/files#r2350234373 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):
 {{{#!diff
 diff --git a/tests/composite_pk/models/tenant.py
 b/tests/composite_pk/models/tenant.py
 index 65eb0feae8..c818ec4de7 100644
 --- a/tests/composite_pk/models/tenant.py
 +++ b/tests/composite_pk/models/tenant.py
 @@ -48,6 +48,16 @@ class Comment(models.Model):
      text = models.TextField(default="", blank=True)
      integer = models.IntegerField(default=0)

 +    class Meta:
 +        # TODO: use new model instead
 +        required_db_features = {"supports_table_check_constraints"}
 +        constraints = [
 +            models.CheckConstraint(
 +                condition=models.Q(user__lt=(1000, 1000)),
 +                name="user_limit",
 +            ),
 +        ]
 +

  class Post(models.Model):
      pk = models.CompositePrimaryKey("tenant_id", "id")
 diff --git a/tests/composite_pk/test_models.py
 b/tests/composite_pk/test_models.py
 index 27157a52ad..05aafd5306 100644
 --- a/tests/composite_pk/test_models.py
 +++ b/tests/composite_pk/test_models.py
 @@ -1,6 +1,8 @@
  from django.contrib.contenttypes.models import ContentType
  from django.core.exceptions import ValidationError
 +from django.db import connection
  from django.test import TestCase
 +from django.test.utils import CaptureQueriesContext

  from .models import Comment, Tenant, Token, User

 @@ -119,7 +121,23 @@ class CompositePKModelsTests(TestCase):
                  self.assertSequenceEqual(ctx.exception.messages,
 messages)

      def test_full_clean_update(self):
 -        with self.assertNumQueries(1):
 +        with CaptureQueriesContext(connection) as ctx:
 +            self.comment_1.full_clean()
 +        select_queries = [
 +            query["sql"]
 +            for query in ctx.captured_queries
 +            if "select" in query["sql"].lower()
 +        ]
 +        self.assertEqual(len(select_queries), 2, select_queries)  # 1 on
 5.2.x
 +
 +    def test_full_clean_update_invalid(self):
 +        self.comment_1.tenant_id = 1001
 +        with self.assertRaises(ValidationError):
 +            self.comment_1.full_clean()
 +
 +        self.comment_1.tenant_id = 1
 +        self.comment_1.user_id = 1001
 +        with self.assertRaises(ValidationError):
              self.comment_1.full_clean()

      def test_field_conflicts(self):
 diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py
 index 2245a472e4..5b7e34a0bc 100644
 --- a/tests/composite_pk/tests.py
 +++ b/tests/composite_pk/tests.py
 @@ -187,12 +187,17 @@ class CompositePKTests(TestCase):
              self.assertEqual(user.email, self.user.email)

      def test_select_related(self):
 -        Comment.objects.create(tenant=self.tenant, id=2)
 +        user2 = User.objects.create(
 +            tenant=self.tenant,
 +            id=2,
 +            email="[email protected]",
 +        )
 +        Comment.objects.create(tenant=self.tenant, id=2, user=user2)
          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)
 +            self.assertEqual(comments[1].user, user2)

      def test_model_forms(self):
          fields = ["tenant", "id", "user_id", "text", "integer"]
 }}}
 ----
 Notice `1001` only appears in the first query of the
 `test_full_clean_update_invalid`.
 {{{#!py
 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)
 }}}
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36611>
Django <https://code.djangoproject.com/>
The Web framework for perfectionists with deadlines.

-- 
You received this message because you are subscribed to the Google Groups 
"Django updates" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion visit 
https://groups.google.com/d/msgid/django-updates/0107019950633331-91edb4d2-1c33-4ad0-9288-71fbbd939cc1-000000%40eu-central-1.amazonses.com.

Reply via email to