#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 django-updates+unsubscr...@googlegroups.com.
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