diff --git a/django/db/models/base.py b/django/db/models/base.py index ce1c7d1046..9c8ab7bfa6 100644 --- a/django/db/models/base.py +++ b/django/db/models/base.py @@ -29,6 +29,7 @@ from django.db import ( from django.db.models import NOT_PROVIDED, ExpressionWrapper, IntegerField, Max, Value from django.db.models.constants import LOOKUP_SEP from django.db.models.deletion import CASCADE, Collector +from django.db.models.expressions import DatabaseDefault from django.db.models.fields.related import ( ForeignObjectRel, OneToOneField, @@ -1633,6 +1634,9 @@ class Model(AltersData, metaclass=ModelBase): raw_value = getattr(self, f.attname) if f.blank and raw_value in f.empty_values: continue + # Skip validation for empty fields when db_default is used. + if isinstance(raw_value, DatabaseDefault): + continue try: setattr(self, f.attname, f.clean(raw_value, self)) except ValidationError as e: diff --git a/docs/releases/5.0.4.txt b/docs/releases/5.0.4.txt index 52d3bdfb0e..d15c28d83d 100644 --- a/docs/releases/5.0.4.txt +++ b/docs/releases/5.0.4.txt @@ -9,4 +9,7 @@ Django 5.0.4 fixes several bugs in 5.0.3. Bugfixes ======== -* ... +* Fixed a bug in Django 5.0 that caused a crash of ``Model.full_clean()`` on + fields with expressions in ``db_default``. As a consequence, + ``Model.full_clean()`` no longer validates for empty values in fields with + ``db_default`` (:ticket:`35223`). diff --git a/tests/field_defaults/tests.py b/tests/field_defaults/tests.py index c05d966bdb..6a5c75c36a 100644 --- a/tests/field_defaults/tests.py +++ b/tests/field_defaults/tests.py @@ -2,6 +2,7 @@ from datetime import datetime from decimal import Decimal from math import pi +from django.core.exceptions import ValidationError from django.db import connection from django.db.models import Case, F, FloatField, Value, When from django.db.models.expressions import ( @@ -169,6 +170,23 @@ class DefaultTests(TestCase): years = DBDefaultsFunction.objects.values_list("year", flat=True) self.assertCountEqual(years, [2000, datetime.now().year]) + def test_full_clean(self): + obj = DBArticle() + obj.full_clean() + obj.save() + obj.refresh_from_db() + self.assertEqual(obj.headline, "Default headline") + + obj = DBArticle(headline="Other title") + obj.full_clean() + obj.save() + obj.refresh_from_db() + self.assertEqual(obj.headline, "Other title") + + obj = DBArticle(headline="") + with self.assertRaises(ValidationError): + obj.full_clean() + class AllowedDefaultTests(SimpleTestCase): def test_allowed(self):