24

I want to change the JSON, which rest_framework or Django returns when a validation error occurs.

I will use one of my views as example, but I want to change error messages for all of my views. So let say I have this view meant to login users, providing email and password. If these are correct it returns access_token.

If I post only password, it returns error 400:

{"email": ["This field is required."]}

and if password and email don't match:

{"detail": ["Unable to log in with provided credentials."]}

what I want would be more like:

{"errors": [{"field": "email", "message": "This field is required."}]}

{"errors": [{"non-field-error": "Unable to log in with provided credentials."}]}

Now this is my view:

class OurLoginObtainAuthToken(APIView):
    permission_classes = (AllowAny,)
    serializer_class = serializers.AuthTokenSerializer
    model = Token

    def post(self, request):
        serializer = self.serializer_class(data=request.DATA)
        if serializer.is_valid():
            #some magic
            return Response(token)           
        return Response(serializers.errors, status=status.HTTP_400_BAD_REQUEST)

I can access the serializer.errors and alter them, but it looks like only field errors can be accessed that way, how to change also validation errors created in my serializer's validate method?

This is my serializer (it is the same serializer as rest_framework.authtoken.serializers.AuthTokenSerializer) but edited, so authentication doesn't require username but email:

class AuthTokenSerializer(serializers.Serializer):
    email = serializers.CharField()
    password = serializers.CharField()

    def validate(self, attrs):
        email = attrs.get('email')
        password = attrs.get('password')
        if email and password:
            user = authenticate(email=email, password=password)

            if user:
                if not user.is_active:
                    msg = _('User account is disabled.')
                    raise ValidationError(msg)
                attrs['user'] = user
                return attrs
            else:
                msg = _('Unable to log in with provided credentials.')
                raise ValidationError(msg)
        else:
            msg = _('Must include "username" and "password"')
            raise ValidationError(msg)

Or maybe there is a completely different approach? I will be really thankful for any ideas.

cegas
  • 2,823
  • 3
  • 16
  • 16
Matúš Bartko
  • 2,425
  • 2
  • 30
  • 42

3 Answers3

20

The easiest way to change the error style through all the view in your application is to always use serializer.is_valid(raise_exception=True), and then implement a custom exception handler that defines how the error response is created.

Tom Christie
  • 33,394
  • 7
  • 101
  • 86
  • 1
    For a different approach see http://stackoverflow.com/questions/26943985/custom-error-messages-in-django-rest-framework-serializer – frnhr Apr 03 '17 at 19:16
  • @TomChristie `serializer.is_valid(raise_exception=True)` works, but how to set message about exception – Deep 3015 Sep 20 '17 at 10:06
  • @MatúšBartko Can you post your custom exception that you made to accomplish this, please? – Peter Sobhi Mar 15 '18 at 03:03
15

The default structure of DRF when handling errors is something like this:

{"email": ["This field is required."]}

And you can change this structure to your need by writing a custom exception handler.

Now let's say you want to achieve the following structure:

{"errors": [{"field": "email", "message": "This field is required."}]}

Your custom exception handler could be something like this:

from rest_framework.views import exception_handler


def custom_exception_handler(exc, context):
    # Call REST framework's default exception handler first,
    # to get the standard error response.
    response = exception_handler(exc, context)

    # Update the structure of the response data.
    if response is not None:
        customized_response = {}
        customized_response['errors'] = []

        for key, value in response.data.items():
            error = {'field': key, 'message': value}
            customized_response['errors'].append(error)

        response.data = customized_response

    return response
Abdullah Alharbi
  • 311
  • 1
  • 5
  • 9
1
if not serializer.is_valid(raise_exception=False)
    return Response(serializer.errors.values(), status=status.HTTP_400_BAD_REQUEST)
Benjamin
  • 25
  • 1
  • 5