From 1570ef02f34037d32218d463342592debccf915c Mon Sep 17 00:00:00 2001
From: Ben Cail <bcail@crossway.org>
Date: Tue, 5 Mar 2024 16:36:11 -0500
Subject: [PATCH] Fixed #35223 -- Made Model.full_clean() ignore fields with
 db_default when validating empty values.

Thanks Brian Ibbotson for the report.

Regression in 7414704e88d73dafbcfbb85f9bc54cb6111439d3.
---
 django/db/models/base.py      |  4 ++++
 docs/releases/5.0.4.txt       |  5 ++++-
 tests/field_defaults/tests.py | 18 ++++++++++++++++++
 3 files changed, 26 insertions(+), 1 deletion(-)

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):