| 41 | |
| 42 | Maybe all unique constraints should allow raising validation error for specific field like ? |
| 43 | |
| 44 | |
| 45 | {{{ |
| 46 | |
| 47 | from django.core.exceptions import ValidationError |
| 48 | from django.db import models |
| 49 | |
| 50 | |
| 51 | class ViolationFieldNameMixin: |
| 52 | """ |
| 53 | Mixin for BaseConstraint subclasses that builds custom |
| 54 | ValidationError message for the `violation_field_name`. |
| 55 | By this way we can bind the error to the field that caused it. |
| 56 | This is useful in ModelForms where we can display the error |
| 57 | message next to the field and also avoid displaying unique |
| 58 | constraint violation error messages more than once for the same field. |
| 59 | """ |
| 60 | |
| 61 | def __init__(self, *args, **kwargs): |
| 62 | self.violation_field_name = kwargs.pop("violation_field_name", None) |
| 63 | self.violation_code = kwargs.pop("violation_code", None) |
| 64 | super().__init__(*args, **kwargs) |
| 65 | |
| 66 | def validate(self, *args, **kwargs): |
| 67 | try: |
| 68 | value = super().validate(*args, **kwargs) |
| 69 | except ValidationError as e: |
| 70 | # Create a new ValidationError with the violation_field_name attribute as the key |
| 71 | e = ValidationError({self.violation_field_name: e}) |
| 72 | # Set the error code to None |
| 73 | # See https://code.djangoproject.com/ticket/34319#ticket |
| 74 | e.code = self.violation_code |
| 75 | raise e |
| 76 | return value |
| 77 | |
| 78 | def deconstruct(self): |
| 79 | path, args, kwargs = super().deconstruct() |
| 80 | kwargs["violation_field_name"] = self.violation_field_name |
| 81 | kwargs["violation_code"] = self.violation_code |
| 82 | return path, args, kwargs |
| 83 | |
| 84 | def __eq__(self, other): |
| 85 | return ( |
| 86 | super().__eq__(other) |
| 87 | and self.violation_field_name == getattr(other, "violation_field_name", None) |
| 88 | and self.violation_code == getattr(other, "violation_code", None) |
| 89 | ) |
| 90 | |
| 91 | |
| 92 | class UniqueConstraint(ViolationFieldNameMixin, models.UniqueConstraint): |
| 93 | ... |
| 94 | |
| 95 | }}} |
| 96 | |