Opened 3 years ago
Last modified 3 years ago
#34319 closed Bug
ValidationError handling during model.validate_constraints — at Version 4
| Reported by: | Mateusz Kurowski | Owned by: | nobody | 
|---|---|---|---|
| Component: | Database layer (models, ORM) | Version: | 4.1 | 
| Severity: | Release blocker | Keywords: | Model, validate_constraints, ValidationError, code, message | 
| Cc: | Gagaro | Triage Stage: | Ready for checkin | 
| Has patch: | yes | Needs documentation: | no | 
| Needs tests: | no | Patch needs improvement: | no | 
| Easy pickings: | no | UI/UX: | no | 
Description (last modified by )
Imagine scenario when i want to explicitly mark a field that model constraint should raise ValidationError for:
class CustomUniqueConstraint(UniqueConstraint):
    def validate(self, *args, **kwargs):
        try:
            value = super().validate(*args, **kwargs)
        except ValidationError as e:
            raise ValidationError(
                {
                    'email': e,
                }
            )
        return value
class AbstractUser(django.contrib.auth.models.AbstractUser):
    class Meta:
        abstract = True
        constraints = [
            CustomUniqueConstraint(
                Lower("email"),
                name="%(app_label)s_%(class)s_email_unique",
            )
        ]
This wont work  because:
1425, in validate_constraints
    if e.code == "unique" and len(constraint.fields) == 1:
       ^^^^^^
AttributeError: 'ValidationError' object has no attribute 'code'
Maybe all unique constraints should allow raising validation error for specific field like ?
from django.core.exceptions import ValidationError
from django.db import models
class ViolationFieldNameMixin:
    """
    Mixin for BaseConstraint subclasses that builds custom
    ValidationError message for the `violation_field_name`.
    By this way we can bind the error to the field that caused it.
    This is useful in ModelForms where we can display the error
    message next to the field and also avoid displaying unique
    constraint violation error messages more than once for  the same field.
    """
    def __init__(self, *args, **kwargs):
        self.violation_field_name = kwargs.pop("violation_field_name", None)
        self.violation_code = kwargs.pop("violation_code", None)
        super().__init__(*args, **kwargs)
    def validate(self, *args, **kwargs):
        try:
            value = super().validate(*args, **kwargs)
        except ValidationError as e:
            e.code = self.violation_code
            # Create a new ValidationError with the violation_field_name attribute as the key
            e = ValidationError({self.violation_field_name: e})
            # Set the error code to None
            # See https://code.djangoproject.com/ticket/34319#ticket
            e.code = self.violation_code
            raise e
        return value
    def deconstruct(self):
        path, args, kwargs = super().deconstruct()
        kwargs["violation_field_name"] = self.violation_field_name
        kwargs["violation_code"] = self.violation_code
        return path, args, kwargs
    def __eq__(self, other):
        return (
                super().__eq__(other)
                and self.violation_field_name == getattr(other, "violation_field_name", None)
                and self.violation_code == getattr(other, "violation_code", None)
        )
class UniqueConstraint(ViolationFieldNameMixin, models.UniqueConstraint):
    ...
      Change History (3)
comment:2 by , 3 years ago
| Description: | modified (diff) | 
|---|---|
| Summary: | Model.validate_constraints check for ValidationError code → ValidationError handling during model.validate_constraints | 
| Type: | Bug → Cleanup/optimization | 
comment:3 by , 3 years ago
| Description: | modified (diff) | 
|---|
comment:4 by , 3 years ago
| Description: | modified (diff) | 
|---|
  Note:
 See   TracTickets
 for help on using tickets.