#36518: full_clean crashes on model with both a CheckConstraint and a
GeneratedField with a Case expression (possible regression in Django 5.2)
-------------------------------------+-------------------------------------
     Reporter:  Olivier Dalang       |                    Owner:  (none)
         Type:  Uncategorized        |                   Status:  new
    Component:  Database layer       |                  Version:  5.2
  (models, ORM)                      |
     Severity:  Normal               |               Resolution:
     Keywords:                       |             Triage Stage:
                                     |  Unreviewed
    Has patch:  0                    |      Needs documentation:  0
  Needs tests:  0                    |  Patch needs improvement:  0
Easy pickings:  0                    |                    UI/UX:  0
-------------------------------------+-------------------------------------
Description changed by Olivier Dalang:

Old description:

> Hello !
>
> I think I ran accross a regression when upgrading the a model containing
> a CheckConstraint and a GeneratedField (that are unrelated) from Django
> 5.1 to 5.2.
>
> Below is a simplified case that reproduces the issue.
>
> I found is a similar ticket: https://code.djangoproject.com/ticket/34871
> (affecting 4.2 already, and thus not flagged as regression, for a
> slightly simpler use case).
>
> That ticket links to recent PR which could quite likely has introduced
> this regression: https://github.com/django/django/pull/19218
>
> I'm not yet familiar with contributing to Django, and fear this is (way)
> above my level to fix, but please let me know if I can do anything to
> help fixing this, as it currently prevents us from upgrading.
>
> Cheers !!
>
> Olivier
>

> {{{#!python
> # models.py
>
> class MyModel(models.Model):
>     class Meta:
>         constraints = [
>             models.CheckConstraint(name="age_valid",
> check=Q(age__lt=100)),
>         ]
>
>     age = models.IntegerField()
>     is_old_enough = models.GeneratedField(
>         expression=Case(
>             When(
>                 age__gte=18,
>                 then=Value(True),
>             ),
>             default=Value(False),
>         ),
>         db_persist=True,
>         output_field=models.BooleanField(),
>     )
> }}}
>

> {{{#!python
> # tests.py
>
> class MyTests(TestCase):
>     def test_fullclean(self):
>         bob = MyModel.objects.create(age=17)
>         bob.full_clean()
> }}}
>
> The test succeeds on Django 5.1, but fails on 5.2.
>

> {{{
> > uv run --with django==5.2.4 manage.py test
> Found 1 test(s).
> Creating test database for alias 'default'...
> System check identified no issues (0 silenced).
> E
> ======================================================================
> ERROR: test_fullclean (myproj.myapp.tests.MyTests)
> ----------------------------------------------------------------------
> Traceback (most recent call last):
>   File "/home/app/myproj/myapp/tests.py", line 8, in test_fullclean
>     bob.full_clean()
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/base.py", line 1674, in full_clean
>     self.validate_constraints(exclude=exclude)
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/base.py", line 1622, in validate_constraints
>     constraint.validate(model_class, self, exclude=exclude, using=using)
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/constraints.py", line 261, in validate
>     against = instance._get_field_expression_map(meta=model._meta,
> exclude=exclude)
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/base.py", line 1372, in
> _get_field_expression_map
>     generated_field.expression.replace_expressions(replacements),
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/expressions.py", line 427, in
> replace_expressions
>     [
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/expressions.py", line 428, in <listcomp>
>     expr.replace_expressions(replacements) if expr else None
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/expressions.py", line 427, in
> replace_expressions
>     [
>   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
> packages/django/db/models/expressions.py", line 428, in <listcomp>
>     expr.replace_expressions(replacements) if expr else None
> AttributeError: 'Q' object has no attribute 'replace_expressions'
> ----------------------------------------------------------------------
> Ran 1 test in 0.003s
>
> FAILED (errors=1)
> Destroying test database for alias 'default'...
> }}}
>
> {{{
> > uv run --with django==5.1.11 manage.py test
> Found 1 test(s).
> Creating test database for alias 'default'...
> System check identified no issues (0 silenced).
> .
> ----------------------------------------------------------------------
> Ran 1 test in 0.002s
>
> OK
> Destroying test database for alias 'default'...
> }}}

New description:

 Hello !

 I think I ran accross a regression when upgrading a model containing a
 CheckConstraint and a GeneratedField (that are unrelated) from Django 5.1
 to 5.2.

 Below is a simplified case that reproduces the issue.

 I found is a similar ticket: https://code.djangoproject.com/ticket/34871
 (affecting 4.2 already, and thus not flagged as regression, for a slightly
 simpler use case).

 That ticket links to recent PR which could quite likely has introduced
 this regression: https://github.com/django/django/pull/19218

 I'm not yet familiar with contributing to Django, and fear this is (way)
 above my level to fix, but please let me know if I can do anything to help
 fixing this, as it currently prevents us from upgrading.

 Cheers !!

 Olivier


 {{{#!python
 # models.py

 class MyModel(models.Model):
     class Meta:
         constraints = [
             models.CheckConstraint(name="age_valid",
 check=Q(age__lt=100)),
         ]

     age = models.IntegerField()
     is_old_enough = models.GeneratedField(
         expression=Case(
             When(
                 age__gte=18,
                 then=Value(True),
             ),
             default=Value(False),
         ),
         db_persist=True,
         output_field=models.BooleanField(),
     )
 }}}


 {{{#!python
 # tests.py

 class MyTests(TestCase):
     def test_fullclean(self):
         bob = MyModel.objects.create(age=17)
         bob.full_clean()
 }}}

 The test succeeds on Django 5.1, but fails on 5.2.


 {{{
 > uv run --with django==5.2.4 manage.py test
 Found 1 test(s).
 Creating test database for alias 'default'...
 System check identified no issues (0 silenced).
 E
 ======================================================================
 ERROR: test_fullclean (myproj.myapp.tests.MyTests)
 ----------------------------------------------------------------------
 Traceback (most recent call last):
   File "/home/app/myproj/myapp/tests.py", line 8, in test_fullclean
     bob.full_clean()
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/base.py", line 1674, in full_clean
     self.validate_constraints(exclude=exclude)
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/base.py", line 1622, in validate_constraints
     constraint.validate(model_class, self, exclude=exclude, using=using)
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/constraints.py", line 261, in validate
     against = instance._get_field_expression_map(meta=model._meta,
 exclude=exclude)
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/base.py", line 1372, in
 _get_field_expression_map
     generated_field.expression.replace_expressions(replacements),
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/expressions.py", line 427, in
 replace_expressions
     [
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/expressions.py", line 428, in <listcomp>
     expr.replace_expressions(replacements) if expr else None
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/expressions.py", line 427, in
 replace_expressions
     [
   File "/home/cache/archive-v0/kkaSi9So5gXBgF1hYbu9X/lib/site-
 packages/django/db/models/expressions.py", line 428, in <listcomp>
     expr.replace_expressions(replacements) if expr else None
 AttributeError: 'Q' object has no attribute 'replace_expressions'
 ----------------------------------------------------------------------
 Ran 1 test in 0.003s

 FAILED (errors=1)
 Destroying test database for alias 'default'...
 }}}

 {{{
 > uv run --with django==5.1.11 manage.py test
 Found 1 test(s).
 Creating test database for alias 'default'...
 System check identified no issues (0 silenced).
 .
 ----------------------------------------------------------------------
 Ran 1 test in 0.002s

 OK
 Destroying test database for alias 'default'...
 }}}

--
-- 
Ticket URL: <https://code.djangoproject.com/ticket/36518#comment:1>
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/0107019833010418-6f64b615-5ca1-45cb-af19-b9ed1b115301-000000%40eu-central-1.amazonses.com.

Reply via email to