Repository: django/django
Issue category: Bug Report

Issue:
System check for default database values with expressions prohibits non-expressions
Description
	 
Since its introduction in Django 5.0, ​the fields.E011 system check for database backends that have DatabaseFeatures.supports_expression_defaults = False ​requires literal defaults to be wrapped in Value.
There are a number of test models that have ​int, float and ​string db_defaults that will raise a system check error if DatabaseFeatures.supports_expression_defaults = False
since these models don't have required_db_features = {"supports_expression_defaults"}.
I'm working on MongoDB which doesn't support any database defaults, literal or expressions.


Hints:
["I'm not sure if the appropriate solution is to add Value wrapping to all literal defaults in Django's test models or to modify the \u200bisinstance(db_default, Value) check to also accept int/float, str, etc.", 1728031068.0]
["I think either solution could work, but maybe there's a subtlety I don't remember. I think wrapping all literals in Value is most likely to just work.", 1728272037.0]
['The issue I see with requiring Value wrapping is that third-party apps may not know to do it and therefore run into compatibility issues with backends with supports_expression_defaults = False.\u200b For the other solution I suggested, I\'m not sure we can make the isinstance()check exhaustive for all possible non-expression types. Perhaps it\'s more appropriate to replace isinstance(self.db_default, Value) with not hasattr(self.db_default, "resolve_expression") to reject expressions.', 1728376052.0]

Patch:
diff --git a/django/db/models/fields/__init__.py b/django/db/models/fields/__init__.py
index d1f31f021135..f9cafdb4bb40 100644
--- a/django/db/models/fields/__init__.py
+++ b/django/db/models/fields/__init__.py
@@ -392,7 +392,10 @@ def _check_db_default(self, databases=None, **kwargs):
 
         if (
             self.db_default is NOT_PROVIDED
-            or isinstance(self.db_default, Value)
+            or (
+                isinstance(self.db_default, Value)
+                or not hasattr(self.db_default, "resolve_expression")
+            )
             or databases is None
         ):
             return []


Test patch:
diff --git a/tests/invalid_models_tests/test_ordinary_fields.py b/tests/invalid_models_tests/test_ordinary_fields.py
index e30d41113809..1fcf3f708d47 100644
--- a/tests/invalid_models_tests/test_ordinary_fields.py
+++ b/tests/invalid_models_tests/test_ordinary_fields.py
@@ -1207,6 +1207,23 @@ class Model(models.Model):
         expected_error = Error(msg=msg, obj=field, id="fields.E012")
         self.assertEqual(errors, [expected_error])
 
+    def test_literals_not_treated_as_expressions(self):
+        """
+        DatabaseFeatures.supports_expression_defaults = False shouldn't
+        prevent non-expression literals (integer, float, boolean, etc.) from
+        being used as database defaults.
+        """
+
+        class Model(models.Model):
+            field = models.FloatField(db_default=1.0)
+
+        field = Model._meta.get_field("field")
+        with unittest.mock.patch.object(
+            connection.features, "supports_expression_defaults", False
+        ):
+            errors = field.check(databases=self.databases)
+        self.assertEqual(errors, [])
+
 
 @isolate_apps("invalid_models_tests")
 class GeneratedFieldTests(TestCase):