diff --git a/django/db/models/base.py b/django/db/models/base.py index 8c8a74158d9..493831759aa 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 0a21d39bbd1..e74d43c0e5a 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 223b4b3cd5f..e52d15233cf 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."