#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="user0...@example.com",
 +        )
 +        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 django-updates+unsubscr...@googlegroups.com.
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