#36611: Model validation of constraint involving ForeignObject considers only 
first
column
-------------------------------------+-------------------------------------
     Reporter:  Jacob Walls          |                    Owner:  (none)
         Type:  Bug                  |                   Status:  new
    Component:  Database layer       |                  Version:  5.2
  (models, ORM)                      |
     Severity:  Release blocker      |               Resolution:
     Keywords:                       |             Triage Stage:  Accepted
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Comment (by Sarah Boyce):

 Note that I have written a test
 {{{#!diff
 --- a/tests/foreign_object/models/__init__.py
 +++ b/tests/foreign_object/models/__init__.py
 @@ -1,5 +1,5 @@
  from .article import Article, ArticleIdea, ArticleTag,
 ArticleTranslation, NewsArticle
 -from .customers import Address, Contact, Customer, CustomerTab
 +from .customers import Address, Contact, ContactCheck, Customer,
 CustomerTab
  from .empty_join import SlugPage
  from .person import Country, Friendship, Group, Membership, Person

 @@ -10,6 +10,7 @@ __all__ = [
      "ArticleTag",
      "ArticleTranslation",
      "Contact",
 +    "ContactCheck",
      "Country",
      "Customer",
      "CustomerTab",
 diff --git a/tests/foreign_object/models/customers.py
 b/tests/foreign_object/models/customers.py
 index 085b7272e9..f9a2e932c5 100644
 --- a/tests/foreign_object/models/customers.py
 +++ b/tests/foreign_object/models/customers.py
 @@ -41,6 +41,27 @@ class Contact(models.Model):
      )


 +class ContactCheck(models.Model):
 +    company_code = models.CharField(max_length=1)
 +    customer_code = models.IntegerField()
 +    customer = models.ForeignObject(
 +        Customer,
 +        on_delete=models.CASCADE,
 +        related_name="contact_checks",
 +        to_fields=["customer_id", "company"],
 +        from_fields=["customer_code", "company_code"],
 +    )
 +
 +    class Meta:
 +        required_db_features = {"supports_table_check_constraints"}
 +        constraints = [
 +            models.CheckConstraint(
 +                condition=models.Q(customer__lt=(1000, "c")),
 +                name="customer_company_limit",
 +            ),
 +        ]
 +
 +
  class CustomerTab(models.Model):
      customer_id = models.IntegerField()
      customer = models.ForeignObject(
 diff --git a/tests/foreign_object/tests.py b/tests/foreign_object/tests.py
 index 09fb47e771..539c1a4fec 100644
 --- a/tests/foreign_object/tests.py
 +++ b/tests/foreign_object/tests.py
 @@ -15,6 +15,7 @@ from .models import (
      ArticleTag,
      ArticleTranslation,
      Country,
 +    ContactCheck,
      CustomerTab,
      Friendship,
      Group,
 @@ -798,3 +799,9 @@ class ForeignObjectModelValidationTests(TestCase):
      def test_validate_constraints_excluding_foreign_object_member(self):
          customer_tab = CustomerTab(customer_id=150)
          customer_tab.validate_constraints(exclude={"customer_id"})
 +
 +    @skipUnlessDBFeature("supports_table_check_constraints")
 +    def
 test_validate_constraints_with_foreign_object_multiple_fields(self):
 +        contact = ContactCheck(company_code="d", customer_code=1500)
 +        with self.assertRaisesMessage(ValidationError,
 "customer_company_limit"):
 +            contact.validate_constraints()
 }}}

 When applied to Django 5.2, this fails with `AssertionError:
 ValidationError not raised`

 When applied to Django main, this fails with:
 {{{
 ======================================================================
 ERROR: test_validate_constraints_with_foreign_object_multiple_fields
 
(foreign_object.tests.ForeignObjectModelValidationTests.test_validate_constraints_with_foreign_object_multiple_fields)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "/path_to_django/tests/foreign_object/tests.py", line 807, in
 test_validate_constraints_with_foreign_object_multiple_fields
     contact.validate_constraints()
   File "/path_to_django/db/models/base.py", line 1653, in
 validate_constraints
     constraint.validate(model_class, self, exclude=exclude, using=using)
   File "/path_to_django/django/db/models/constraints.py", line 212, in
 validate
     if not Q(self.condition).check(against, using=using):
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/query_utils.py", line 187, in
 check
     return compiler.execute_sql(SINGLE) is not None
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/compiler.py", line 1611, in
 execute_sql
     sql, params = self.as_sql()
                   ^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/compiler.py", line 795, in
 as_sql
     self.compile(self.where) if self.where is not None else ("", [])
     ^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/compiler.py", line 578, in
 compile
     sql, params = node.as_sql(self, self.connection)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/where.py", line 151, in
 as_sql
     sql, params = compiler.compile(child)
                   ^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/compiler.py", line 578, in
 compile
     sql, params = node.as_sql(self, self.connection)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/lookups.py", line 404, in as_sql
     lhs_sql, params = self.process_lhs(compiler, connection)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/lookups.py", line 230, in
 process_lhs
     lhs_sql, params = super().process_lhs(compiler, connection, lhs)
                       ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/lookups.py", line 110, in
 process_lhs
     sql, params = compiler.compile(lhs)
                   ^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/compiler.py", line 576, in
 compile
     sql, params = vendor_impl(self, self.connection)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/expressions.py", line 29, in
 as_sqlite
     sql, params = self.as_sql(compiler, connection, **extra_context)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/expressions.py", line 1107, in
 as_sql
     arg_sql, arg_params = compiler.compile(arg)
                           ^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/db/models/sql/compiler.py", line 578, in compile
     sql, params = node.as_sql(self, self.connection)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/where.py", line 151, in
 as_sql
     sql, params = compiler.compile(child)
                   ^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/sql/compiler.py", line 578, in
 compile
     sql, params = node.as_sql(self, self.connection)
                   ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/fields/related_lookups.py", line
 128, in as_sql
     return super().as_sql(compiler, connection)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/lookups.py", line 239, in as_sql
     rhs_sql, rhs_params = self.process_rhs(compiler, connection)
                           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/django/db/models/lookups.py", line 138, in
 process_rhs
     return self.get_db_prep_lookup(value, connection)
            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/db/models/lookups.py", line 259, in
 get_db_prep_lookup
     field = getattr(self.lhs.output_field, "target_field", None)
             ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
   File "/path_to_django/db/models/fields/related.py", line 506, in
 target_field
     raise exceptions.FieldError(
 django.core.exceptions.FieldError: The relation has multiple target
 fields, but only single target field was asked for
 }}}

 So note to self that any fix, we should also make sure we have applied to
 5.2 and tested (in case it is relying on a commit currently applied to
 main, not to 5.2)
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36611#comment:5>
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/010701995dd7484d-8736062e-2eeb-4dac-9a7d-5021145725dc-000000%40eu-central-1.amazonses.com.

Reply via email to