Proposal: BaseConstraint.violation_error_code and BaseConstraint.violation_error_message for ValidationError customization

Hello, esteemed Django community!

I would like to propose an interesting enhancement that, in my opinion, could significantly simplify the process of model constraint validation. Currently, when detecting model constraint violations, Django generates a ValidationError object, which code and message attributes are based on ‘violation_error_code’ and violation_error_message of BaseConstraint class. However, this mechanism has certain limitations, such as not being triggered in all scenarios, for example, when UniqueConstraint.fields is set, but UniqueConstraint.condition is not.

Example:

class Article(models.Model):
    user = models.ForeignKey(settings.AUTH_USER_MODEL, models.CASCADE)
    folder = models.ForeignKey(Folder, models.CASCADE)
    title = models.CharField(max_length=100)
    content = models.TextField()
    status = models.CharField(max_length=32)

    class Meta:
        constraints = [
            models.UniqueConstraint(
                fields=["user"],
                condition=Q(status="DRAFT"),
                name="unique_draft_user",
                # These 2 parameters will be used for generating ValidationError
                violation_error_code='unique_draft_user',
                violation_error_message=_('You can have only one draft.')
            ),
            models.UniqueConstraint(
                fields=['user', 'folder', 'title'],
                name='unique_path',
                # These 2 parameters will not be used for generating ValidationError (no condition set)
                violation_error_code='unique_path',
                violation_error_message=_('Article with this Name already exists in this Folder.')
            ),
        ]

My proposal involves using the attributes BaseConstraint.violation_error_code and BaseConstraint.violation_error_message to set the corresponding values of ValidationError.code and ValidationError.message in all scenarios.

When validating the model using full_clean(), it would be wonderful to have the ability to explicitly set the error code and message in case of constraint violations. This enhancement would allow us to more flexibly manage validation error behavior and better tailor it to our needs.

I would like to highlight an additional benefit that this mechanism would offer. It would provide us with the ability to override ModelForm.Meta.error_messages specifically for certain NON_FIELD_ERRORS exceptions.

For example:

from django import forms
from django.core.exceptions import NON_FIELD_ERRORS
from django.utils.translation import gettext_lazy as _


class ArticleForm(forms.ModelForm):
    """
    Form for handling Article model data.
    """
    class Meta:
        model = Article
        fields = ["title", "content", "status", "folder", "user"]
        
        # Override error messages for specific NON_FIELD_ERRORS exceptions
        error_messages = {
            NON_FIELD_ERRORS: {
                'unique_draft_user': _('This message will override the original "unique_draft_user" error message'),
                'unique_path': _('This message will override the original "unique_path" error message'),
            }
        }

With this enhancement, we can provide more customized and meaningful error messages for specific non-field errors, enhancing the overall user experience.

Also, I propose the addition of default parameters for ValidationError.params (the Model.unique_error_message() method builds interesting default params). This would simplify the process of customizing error messages by providing a consistent set of parameters.

class Article(models.Model):

    ...

    class Meta:
        constraints = [
            ...
            models.UniqueConstraint(
                ...
                # Use the `model_class` default param
                violation_error_message=_('%(model_class)s with this Name already exists in this Folder.')
            ),
        ]

What are your thoughts on this idea? I appreciate your comments and discussions!

Thank you!

As far as I know, this should already work. There are no documented limitations and it would be very surprising if condition was required as that’s quite a niche feature.

I think you may be experiencing this behaviour due to an issue with your project, such as out-of-sync migrations, rather than due to Django. Can you provide an example project, or a test in Django’s test suite, where the message does not work as expected?

It appears that the observed limitation is intentional and documented in the testing code, specifically at this line. In this link to the Django test suite, the test case is structured to enforce the behavior where violation_error_code and violation_error_message are not utilized when constructing the ValidationError object. Instead, the Model.unique_error_message method is hardcoded to create a ValidationError with a specific text message in format “%(model_name)s with this %(field_labels)s already exists.”.

I will try (if still needed) to isolate this issue by creating a minimal, reproducible example that showcases the problem.