Opened 4 months ago

Closed 3 months ago

Last modified 3 months ago

#35638 closed Bug (fixed)

validate_constraints() fails on models with fields using db_default

Reported by: David Sanders Owned by: David Sanders
Component: Database layer (models, ORM) Version: 5.0
Severity: Release blocker Keywords: db_default
Cc: Triage Stage: Ready for checkin
Has patch: yes Needs documentation: no
Needs tests: no Patch needs improvement: no
Easy pickings: no UI/UX: no

Description

Follow up to #35223

Related forum thread: https://forum.djangoproject.com/t/proposal-to-have-db-default-considered-in-model-forms/33358/3

Given the model

class Foo(models.Model):
    bar = models.CharField(db_default="bar")

    class Meta:
        constraints = [
            models.CheckConstraint(check=models.Q(bar="bar"), name="is_bar"),
        ]

validating constraints should ideally not fail but currently does:

>>> foo = Foo()
>>> foo.validate_constraints()
Traceback (most recent call last):
  File "<console>", line 1, in <module>
  File "/path/to/django/django/db/models/base.py", line 1598, in validate_constraints
    raise ValidationError(errors)
django.core.exceptions.ValidationError: {'__all__': ['Constraint “is_bar” is violated.']}

I couldn't find an easy workaround for this.

If I apply this small patch to Q.check(), then validate_constraints() uses the default expression and runs a suitable check query. Perhaps can take this as a starting point and add something similar to Django?

index 1bf396723e..5380cc17d0 100644
--- a/django/db/models/query_utils.py
+++ b/django/db/models/query_utils.py
@@ -120,12 +120,16 @@ class Q(tree.Node):
         """
         # Avoid circular imports.
         from django.db.models import BooleanField, Value
+        from django.db.models.expressions import DatabaseDefault
         from django.db.models.functions import Coalesce
         from django.db.models.sql import Query
         from django.db.models.sql.constants import SINGLE

         query = Query(None)
         for name, value in against.items():
+            # not ideal, value is wrapped in a Value by Model._get_field_value_map()
+            if isinstance(getattr(value, "value"), DatabaseDefault):
+                value = value.field.db_default
             if not hasattr(value, "resolve_expression"):
                 value = Value(value)
             query.add_annotation(value, name, select=False)

Change History (16)

comment:1 by Simon Charette, 4 months ago

Severity: NormalRelease blocker
Triage Stage: UnreviewedAccepted

Thank you for your report!

We should adjust _get_field_value_map to take into account db_default (and possibly rename to _get_field_expr_map) so all constraints are adjusted appropriately. I suspect that other constraints as well as the unique=True and unique_together validations that validate_unique provides are also affected e.g.

class Foo(models.Model):
    bar = models.CharField(unique=True, db_default="bar")

Foo.objects.create()
Foo().validate_unique()  # No ValidationError raised but would result in an integrity error on `save`

I haven't tested the above but I don't see any special handling of db_default in validate_unique

Marking as release blocker as db_default was introduced in 5.0.

comment:2 by David Sanders, 4 months ago

We should adjust _get_field_value_map to take into account db_default (and possibly rename to _get_field_expr_map) so all constraints are adjusted appropriately.

👍 Yup was also thinking this so good to see some confirmation there

I suspect that other constraints as well as the unique=True and unique_together validations that validate_unique provides are also affected e.g.

Ah nice, I didn't think of this. Whoever picks this test up will need to write a test for that too ☝️

I was thinking of starting a PR tomorrow if I have time, but if someone else want to pick it up before then - you're welcome to do so.

comment:3 by David Sanders, 4 months ago

Has patch: set
Needs documentation: set
Needs tests: set
Patch needs improvement: set

comment:4 by Sarah Boyce, 4 months ago

Owner: set to David Sanders
Status: newassigned

comment:5 by Sarah Boyce, 4 months ago

Version: dev5.0

comment:6 by Sarah Boyce, 4 months ago

Needs documentation: unset
Needs tests: unset
Patch needs improvement: unset

comment:7 by David Sanders, 4 months ago

Patch needs improvement: unset

Just need to verify whether we still want to keep the changes to _get_field_value_map() … may no longer necessary with Simon's more recent suggestion

in reply to:  7 comment:8 by Sarah Boyce, 4 months ago

Patch needs improvement: set

Replying to David Sanders:

Just need to verify whether we still want to keep the changes to _get_field_value_map() … may no longer necessary with Simon's more recent suggestion

Ah sorry! I'll let you unset when you're ready

comment:9 by David Sanders, 4 months ago

Patch needs improvement: unset

Turns out we did need it… unsetting pending any further reviews & follow-ups 😅

comment:10 by Sarah Boyce, 3 months ago

Triage Stage: AcceptedReady for checkin

comment:11 by Sarah Boyce <42296566+sarahboyce@…>, 3 months ago

In 91a03875:

Refs #35638 -- Avoided wrapping expressions with Value in _get_field_value_map() and renamed to _get_field_expression_map().

comment:12 by Sarah Boyce <42296566+sarahboyce@…>, 3 months ago

Resolution: fixed
Status: assignedclosed

In 509763c7:

Fixed #35638 -- Updated validate_constraints to consider db_default.

comment:13 by Sarah Boyce <42296566+sarahboyce@…>, 3 months ago

In 78654a2:

[5.1.x] Refs #35638 -- Avoided wrapping expressions with Value in _get_field_value_map() and renamed to _get_field_expression_map().

Backport of 91a038754bb516d29cb79f0fed4025436b5c5346 from main.

comment:14 by Sarah Boyce <42296566+sarahboyce@…>, 3 months ago

In aed4ffe1:

[5.1.x] Fixed #35638 -- Updated validate_constraints to consider db_default.

Backport of 509763c79952cde02d9f5b584af4278bdbed77b2 from main.

comment:15 by Sarah Boyce <42296566+sarahboyce@…>, 3 months ago

In e88ef6a:

[5.0.x] Refs #35638 -- Avoided wrapping expressions with Value in _get_field_value_map() and renamed to _get_field_expression_map().

Backport of 91a038754bb516d29cb79f0fed4025436b5c5346 from main.

comment:16 by Sarah Boyce <42296566+sarahboyce@…>, 3 months ago

In 333cfab5:

[5.0.x] Fixed #35638 -- Updated validate_constraints to consider db_default.

Backport of 509763c79952cde02d9f5b584af4278bdbed77b2 from main.

Note: See TracTickets for help on using tickets.
Back to Top