Proposal: To have db_default considered in model forms

Currently, db_default has no effect on model forms. I was mistakenly under the assumption that it would behave somewhat similarly to default (if default is not set in addition to db_default).

There are 3 behaviours that I think we should be considered for either alteration or clarification in the docs:

1. Supplying an initial value

When default is set, the model form’s field gets it’s initial set to the default. This makes sense for default but should it set it for db_default if we have simple scalar values like Value("asdf")?

I’m not a big fan of too much magic so I’d probably lean towards no - and document this saying that if you want the default value to show on the form then it would need to be declared on the form or via default if that makes sense to set that on top of db_default.

2. Setting the field’s required flag

Currently a non-null field with db_default set has required=True. This can be solved with blank=True on the model field but perhaps again the model form field generation could be changed? If not then some doc updates to explain this might be nice.

3. Setting the form field’s empty value

When a form is submitted, the form field’s empty value is passed to the ORM and db_default is never used. I found that if setting a field’s empty_value=DatabaseDefault() then this allows delegating the value to db_default.

Currently users need to manually create a model form and set the empty_value but this isn’t an obvious solution because DatabaseDefault is currently undocumented.

Since this is a non-obvious, I strongly recommend that either DatabaseDefault is documented; or we could add this small change to Django in db.models.fields.Field.formfield():

    def formfield(self, …):
        …
        if self.db_default is not NOT_PROVIDED:
            defaults["empty_value"] = DatabaseDefault()
        …

Another option may be to include DatabaseDefault in the field’s initial upon rendering but this is a non-trivial approach as we’d need to manage it (eg not rendering it) and additionally Field.has_default() vs Field.get_default() both deal with db_default differently.

Good spot! I think we need to do something here to make this smoother.

I think point 3 in particular needs a change. db_default should be used automatically in this case in my opinion. Point 2 is similar, but not quite as big a problem.

1 Like

Thanks for the quick response Lily :pray:

I just realised that setting the value to DatabaseDefault() now syncs up nicely with this patch for a model’s full clean: Fixed #35223 -- Made Model.full_clean() ignore fields with db_default when validating empty values. by bcail · Pull Request #17939 · django/django · GitHub

There is one last thing that is blocking me from delegating to the default though - during a full clean validation of constraints is included and I have a check constraint on this particular field that is now failing because the select statement doesn’t know what DEFAULT means. :thinking:

I couldn’t see how to workaround this in my code but altering Django to refer to the underlying expression instead during Q.check() seems to work:

(Note this isn’t ideal as the supplied DatabaseDefault ends up being wrapped in a Value by Model._get_field_value_map().)

I might raise a separate follow-up ticket to #35223

Edit I realise doing value.value below will cause exceptions, but this is just for demonstrative purposes :sweat_smile:

index 1bf396723e..4238397de2 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():
+            if isinstance(value.value, DatabaseDefault):
+                value = value.field.db_default
             if not hasattr(value, "resolve_expression"):
                 value = Value(value)
             query.add_annotation(value, name, select=False)

Filed a ticket for the validate_constraints() issue: #35638 (validate_constraints() fails on models with fields using db_default) – Django

I need to sit down at look at the details here in more detail but, just from the forms perspective, I would think it should be transparent to the user whether default or db_default is used. (I.e. one would expect the behaviour to match, I think)

1 Like

Actually one thing I just thought of RE point 1:

You could set it up so that if declared db_default is a Python literal (or any Python expression/non-db-expression that evaluates to something that could be passed to Value) - use that for the form’s default; otherwise if it’s an expression, ignore setting the default.

I think checking if an expression is a Value instance is too unreliable?

:thinking: