#36580: Model validation of constraints fails if condition's Q object references ForeignObject -------------------------------------+------------------------------------- Reporter: Jacob Walls | Type: Bug Status: new | Component: Database | layer (models, ORM) Version: dev | Severity: Release | blocker Keywords: | Triage Stage: | Unreviewed Has patch: 0 | Needs documentation: 0 Needs tests: 0 | Patch needs improvement: 0 Easy pickings: 0 | UI/UX: 0 -------------------------------------+------------------------------------- Similar to #36433, just for `ForeignObject` instead of `ForeignKey`.
With this adjusted test model and corresponding adjustment to unrelated test, `test_full_clean_update` passes on stable/5.2.x (the constraint is purposefully not very imaginative, can polish in PR review): {{{#!diff diff --git a/tests/composite_pk/models/tenant.py b/tests/composite_pk/models/tenant.py index 65eb0feae8..954a5519f8 100644 --- a/tests/composite_pk/models/tenant.py +++ b/tests/composite_pk/models/tenant.py @@ -48,6 +48,14 @@ class Comment(models.Model): text = models.TextField(default="", blank=True) integer = models.IntegerField(default=0) + class Meta: + constraints = [ + models.CheckConstraint( + condition=models.Q(user__isnull=False), + name="user_not_null", + ), + ] + class Post(models.Model): pk = models.CompositePrimaryKey("tenant_id", "id") diff --git a/tests/composite_pk/tests.py b/tests/composite_pk/tests.py index c4a8e6ca8c..cade405dee 100644 --- a/tests/composite_pk/tests.py +++ b/tests/composite_pk/tests.py @@ -205,12 +205,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"] }}} ... but fails on main: {{{#!py ====================================================================== ERROR: test_full_clean_update (composite_pk.test_models.CompositePKModelsTests.test_full_clean_update) ---------------------------------------------------------------------- Traceback (most recent call last): File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/case.py", line 58, in testPartExecutor yield File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/case.py", line 651, in run self._callTestMethod(testMethod) File "/Library/Frameworks/Python.framework/Versions/3.13/lib/python3.13/unittest/case.py", line 606, in _callTestMethod if method() is not None: ^^^^^^^^^^^^^^^ File "/Users/jwalls/django/tests/composite_pk/test_models.py", line 123, in test_full_clean_update self.comment_1.full_clean() ^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/base.py", line 1638, in full_clean self.validate_constraints(exclude=exclude) ^^^^^^^ File "/Users/jwalls/django/django/db/models/base.py", line 1586, in validate_constraints constraint.validate(model_class, self, exclude=exclude, using=using) ^^^ File "/Users/jwalls/django/django/db/models/constraints.py", line 212, in validate if not Q(self.condition).check(against, using=using): ^^^^^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/query_utils.py", line 176, in check query.add_q(Q(Coalesce(self, True, output_field=BooleanField()))) ^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1670, in add_q clause, _ = self._add_q(q_object, can_reuse) ^^^^^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1702, in _add_q child_clause, needed_inner = self.build_filter( ^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1541, in build_filter condition = filter_expr.resolve_expression( ^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/expressions.py", line 301, in resolve_expression expr.resolve_expression(query, allow_joins, reuse, summarize, for_save) ^^^^^^^ File "/Users/jwalls/django/django/db/models/query_utils.py", line 91, in resolve_expression clause, joins = query._add_q( ^^^^^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1702, in _add_q child_clause, needed_inner = self.build_filter( ^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1527, in build_filter return self._add_q( ^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1702, in _add_q child_clause, needed_inner = self.build_filter( ^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1550, in build_filter lookups, parts, reffed_expression = self.solve_lookup_type(arg, summarize) ^^^^^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1357, in solve_lookup_type _, field, _, lookup_parts = self.names_to_path(lookup_splitted, self.get_meta()) ^^^^^^^^^^^^^^^ File "/Users/jwalls/django/django/db/models/sql/query.py", line 1830, in names_to_path raise FieldError( ^^^ django.core.exceptions.FieldError: Cannot resolve keyword 'user' into field. Choices are: _check ---------------------------------------------------------------------- Ran 178 tests in 1.224s }}} --- There is a prior comment in ticket:36433#comment:1 about improving the error message. -- Ticket URL: <https://code.djangoproject.com/ticket/36580> 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/01070198f2bc627d-5118a485-1ea3-40bd-bbbe-e35db83c3be7-000000%40eu-central-1.amazonses.com.