#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="
user...@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.