2
0
Эх сурвалжийг харах

Fixed #35103 -- Used provided error code and message when fields is set without a condition on UniqueConstraint.

gabn88 1 жил өмнө
parent
commit
e970bb7ca7

+ 12 - 4
django/db/models/constraints.py

@@ -690,11 +690,19 @@ class UniqueConstraint(BaseConstraint):
             queryset = queryset.exclude(pk=model_class_pk)
         if not self.condition:
             if queryset.exists():
-                if self.fields:
-                    # When fields are defined, use the unique_error_message() for
-                    # backward compatibility.
+                if (
+                    self.fields
+                    and self.violation_error_message
+                    == self.default_violation_error_message
+                ):
+                    # When fields are defined, use the unique_error_message() as
+                    # a default for backward compatibility.
+                    validation_error_message = instance.unique_error_message(
+                        model, self.fields
+                    )
                     raise ValidationError(
-                        instance.unique_error_message(model, self.fields),
+                        validation_error_message,
+                        code=validation_error_message.code,
                     )
                 raise ValidationError(
                     self.get_violation_error_message(),

+ 37 - 17
docs/ref/models/constraints.txt

@@ -282,27 +282,47 @@ PostgreSQL 15+.
 
 .. attribute:: UniqueConstraint.violation_error_code
 
-The error code used when ``ValidationError`` is raised during
-:ref:`model validation <validating-objects>`. Defaults to ``None``.
+The error code used when a ``ValidationError`` is raised during
+:ref:`model validation <validating-objects>`.
+
+Defaults to :attr:`.BaseConstraint.violation_error_code`, when either
+:attr:`.UniqueConstraint.condition` is set or :attr:`.UniqueConstraint.fields`
+is not set.
 
-This code is *not used* for :class:`UniqueConstraint`\s with
-:attr:`~UniqueConstraint.fields` and without a
-:attr:`~UniqueConstraint.condition`. Such :class:`~UniqueConstraint`\s have the
-same error code as constraints defined with :attr:`.Field.unique` or in
-:attr:`Meta.unique_together <django.db.models.Options.constraints>`.
+If :attr:`.UniqueConstraint.fields` is set without a
+:attr:`.UniqueConstraint.condition`, defaults to the :attr:`Meta.unique_together
+<django.db.models.Options.unique_together>` error code when there are multiple
+fields, and to the :attr:`.Field.unique` error code when there is a single
+field.
+
+.. versionchanged:: 5.2
+
+    In older versions, the provided
+    :attr:`.UniqueConstraint.violation_error_code` was not used when
+    :attr:`.UniqueConstraint.fields` was set without a
+    :attr:`.UniqueConstraint.condition`.
 
 ``violation_error_message``
 ---------------------------
 
 .. attribute:: UniqueConstraint.violation_error_message
 
-The error message used when ``ValidationError`` is raised during
-:ref:`model validation <validating-objects>`. Defaults to
-:attr:`.BaseConstraint.violation_error_message`.
-
-This message is *not used* for :class:`UniqueConstraint`\s with
-:attr:`~UniqueConstraint.fields` and without a
-:attr:`~UniqueConstraint.condition`. Such :class:`~UniqueConstraint`\s show the
-same message as constraints defined with
-:attr:`.Field.unique` or in
-:attr:`Meta.unique_together <django.db.models.Options.constraints>`.
+The error message used when a ``ValidationError`` is raised during
+:ref:`model validation <validating-objects>`.
+
+Defaults to :attr:`.BaseConstraint.violation_error_message`, when either
+:attr:`.UniqueConstraint.condition` is set or :attr:`.UniqueConstraint.fields`
+is not set.
+
+If :attr:`.UniqueConstraint.fields` is set without a
+:attr:`.UniqueConstraint.condition`, defaults to the :attr:`Meta.unique_together
+<django.db.models.Options.unique_together>` error message when there are
+multiple fields, and to the :attr:`.Field.unique` error message when there is a
+single field.
+
+.. versionchanged:: 5.2
+
+    In older versions, the provided
+    :attr:`.UniqueConstraint.violation_error_message` was not used when
+    :attr:`.UniqueConstraint.fields` was set without a
+    :attr:`.UniqueConstraint.condition`.

+ 5 - 0
docs/releases/5.2.txt

@@ -358,6 +358,11 @@ Miscellaneous
 * ``HttpRequest.accepted_types`` is now sorted by the client's preference, based
   on the request's ``Accept`` header.
 
+* :attr:`.UniqueConstraint.violation_error_code` and
+  :attr:`.UniqueConstraint.violation_error_message` are now always used when
+  provided. Previously, these were ignored when :attr:`.UniqueConstraint.fields`
+  were set without a :attr:`.UniqueConstraint.condition`.
+
 * The :func:`~django.template.context_processors.debug` context processor is no
   longer included in the default project template.
 

+ 1 - 3
tests/constraints/models.py

@@ -72,15 +72,13 @@ class GeneratedFieldVirtualProduct(models.Model):
 class UniqueConstraintProduct(models.Model):
     name = models.CharField(max_length=255)
     color = models.CharField(max_length=32, null=True)
+    age = models.IntegerField(null=True)
 
     class Meta:
         constraints = [
             models.UniqueConstraint(
                 fields=["name", "color"],
                 name="name_color_uniq",
-                # Custom message and error code are ignored.
-                violation_error_code="custom_code",
-                violation_error_message="Custom message",
             )
         ]
 

+ 35 - 0
tests/constraints/tests.py

@@ -953,6 +953,41 @@ class UniqueConstraintTests(TestCase):
                 ChildUniqueConstraintProduct(name=self.p1.name, color=self.p1.color),
             )
 
+    def test_validate_unique_custom_code_and_message(self):
+        product = UniqueConstraintProduct.objects.create(
+            name="test", color="red", age=42
+        )
+        code = "custom_code"
+        message = "Custom message"
+        multiple_fields_constraint = models.UniqueConstraint(
+            fields=["color", "age"],
+            name="color_age_uniq",
+            violation_error_code=code,
+            violation_error_message=message,
+        )
+        single_field_constraint = models.UniqueConstraint(
+            fields=["color"],
+            name="color_uniq",
+            violation_error_code=code,
+            violation_error_message=message,
+        )
+
+        with self.assertRaisesMessage(ValidationError, message) as cm:
+            multiple_fields_constraint.validate(
+                UniqueConstraintProduct,
+                UniqueConstraintProduct(
+                    name="new-test", color=product.color, age=product.age
+                ),
+            )
+        self.assertEqual(cm.exception.code, code)
+
+        with self.assertRaisesMessage(ValidationError, message) as cm:
+            single_field_constraint.validate(
+                UniqueConstraintProduct,
+                UniqueConstraintProduct(name="new-test", color=product.color),
+            )
+        self.assertEqual(cm.exception.code, code)
+
     @skipUnlessDBFeature("supports_table_check_constraints")
     def test_validate_fields_unattached(self):
         Product.objects.create(price=42)