DEV Community

Soldatov Serhii
Soldatov Serhii

Posted on

Why Django REST Framework doesn't show your custom validation error messages (and what to do about it)

The GitHub Discussion: link

Image description

Issue developers face:

When developing with Django REST Framework (DRF), developers typically run into issues where error messages for custom validation specified on Django model constraints (e.g., UniqueConstraint.violation_error_message) are not passed through to API responses. DRF returns default validation error messages instead.

For example, a custom error message like:

UniqueConstraint(
    fields=['email', 'username'],
    violation_error_message='This email and username combination already exists.'
)
Enter fullscreen mode Exit fullscreen mode

...might never actually reach the API client.

Instead, you'll see something generic like:

{"non_field_errors": ["The fields email, username must make a unique set."]}
Enter fullscreen mode Exit fullscreen mode

This is what the DRF maintainers had to say about it:

  • DRF validation is deliberately occurring at the serializer level, before the creation of the model instance.

  • It intentionally avoids calling Django's full_clean() in this processes, as serializers are meant to validate incoming data instead of an already-created model instance.

  • Due to the complexity and backward compatibility issues, there are no immediate plans from DRF for changing this behavior.

In other words - DRF prioritises serialiser checking for API input, which means that unless you explicitly call model checking (e.g. via full_clean()), constraint errors and their custom messages will never be called or shown in API responses.

Possible solutions:

Developers can tackle this issue through several practical approaches:

  • Explicitly calling full_clean():
def create(self, validated_data):
    instance = MyModel(**validated_data)
    try:
        instance.full_clean()
    except DjangoValidationError as e:
        raise DRFValidationError(e.message_dict)
    instance.save()
    return instance
Enter fullscreen mode Exit fullscreen mode
  • Custom serializer validators:

This method just overrides DRF's built-in UniqueTogetherValidator just to replace the error message.

class CustomUniqueValidator(UniqueTogetherValidator):
    def __call__(self, attrs, serializer):
        try:
            super().__call__(attrs, serializer)
        except serializers.ValidationError:
            raise serializers.ValidationError("This email and username combination already exists.")
Enter fullscreen mode Exit fullscreen mode
  • Sometimes just double your validation code: Sometimes, custom validators and extra abstractions aren't needed. Although we all know the DRY principle, sometimes it's acceptable to just dublicate simple validation logicdirectly into your serializer
class MyModelSerializer(serializers.ModelSerializer):
    class Meta:
        model = MyModel
        fields = ['email', 'username']
        validators = [
            UniqueTogetherValidator(
                queryset=MyModel.objects.all(),
                fields=['email', 'username'],
                message="This email and username combination already exists."
            )
        ]
Enter fullscreen mode Exit fullscreen mode

Summary:

Issue: DRF doesn't carry forward any custom validation messages defined in Django.

Reason: For reasons of architecture and backward compatibility, DRF validates its data independently from Django's model validation.

Important Warnings:

Be careful where you place validation logic. Django models can be modified via the admin interface, serializers, or directly with bulk operations (.update()).

Placing the critical validation logic into save() or full_clean() is problematic, as those aren't called when performing bulk updates or certain direct model manipulations. Always bear these edge cases in mind when developing your validation plans.

This approach keeps your validation logic consistent, reusable, and well-documented throughout your project.

Top comments (0)