From 2fd755b361d3da2cd0440fc9839feb2bb69b027b Mon Sep 17 00:00:00 2001
From: Mariusz Felisiak <felisiak.mariusz@gmail.com>
Date: Wed, 8 Feb 2023 16:38:55 +0100
Subject: [PATCH] Fixed #34319 -- Fixed Model.validate_constraints() crash on
 ValidationError with no code.

Thanks Mateusz Kurowski for the report.

Regression in 667105877e6723c6985399803a364848891513cc.
---
 django/db/models/base.py   |  5 ++++-
 docs/releases/4.1.7.txt    |  3 ++-
 tests/constraints/tests.py | 22 +++++++++++++++++++++-
 3 files changed, 27 insertions(+), 3 deletions(-)

diff --git a/django/db/models/base.py b/django/db/models/base.py
index 06bab385a3..344508e0e2 100644
--- a/django/db/models/base.py
+++ b/django/db/models/base.py
@@ -1444,7 +1444,10 @@ class Model(AltersData, metaclass=ModelBase):
                 try:
                     constraint.validate(model_class, self, exclude=exclude, using=using)
                 except ValidationError as e:
-                    if e.code == "unique" and len(constraint.fields) == 1:
+                    if (
+                        getattr(e, "code", None) == "unique"
+                        and len(constraint.fields) == 1
+                    ):
                         errors.setdefault(constraint.fields[0], []).append(e)
                     else:
                         errors = e.update_error_dict(errors)
diff --git a/docs/releases/4.1.7.txt b/docs/releases/4.1.7.txt
index 0a21d39bbd..e74d43c0e5 100644
--- a/docs/releases/4.1.7.txt
+++ b/docs/releases/4.1.7.txt
@@ -12,4 +12,5 @@ in 4.1.6.
 Bugfixes
 ========
 
-* ...
+* Fixed a bug in Django 4.1 that caused a crash of model validation on
+  ``ValidationError`` with no ``code`` (:ticket:`34319`).
diff --git a/tests/constraints/tests.py b/tests/constraints/tests.py
index 223b4b3cd5..e52d15233c 100644
--- a/tests/constraints/tests.py
+++ b/tests/constraints/tests.py
@@ -3,7 +3,7 @@ from unittest import mock
 from django.core.exceptions import ValidationError
 from django.db import IntegrityError, connection, models
 from django.db.models import F
-from django.db.models.constraints import BaseConstraint
+from django.db.models.constraints import BaseConstraint, UniqueConstraint
 from django.db.models.functions import Lower
 from django.db.transaction import atomic
 from django.test import SimpleTestCase, TestCase, skipIfDBFeature, skipUnlessDBFeature
@@ -589,6 +589,26 @@ class UniqueConstraintTests(TestCase):
         with self.assertRaisesMessage(ValidationError, msg):
             UniqueConstraintConditionProduct(name=obj2.name).validate_constraints()
 
+    def test_model_validation_constraint_no_code_error(self):
+        class ValidateNoCodeErrorConstraint(UniqueConstraint):
+            def validate(self, model, instance, **kwargs):
+                raise ValidationError({"name": ValidationError("Already exists.")})
+
+        class NoCodeErrorConstraintModel(models.Model):
+            name = models.CharField(max_length=255)
+
+            class Meta:
+                constraints = [
+                    ValidateNoCodeErrorConstraint(
+                        Lower("name"),
+                        name="custom_validate_no_code_error",
+                    )
+                ]
+
+        msg = "{'name': ['Already exists.']}"
+        with self.assertRaisesMessage(ValidationError, msg):
+            NoCodeErrorConstraintModel(name="test").validate_constraints()
+
     def test_validate(self):
         constraint = UniqueConstraintProduct._meta.constraints[0]
         msg = "Unique constraint product with this Name and Color already exists."