How to change validation error responses in DRF?

2019-04-21 01:01发布

问题:

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

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 dont 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 doesnt 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')
        #print email
        #print 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 thankfull for any ideas.

回答1:

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.



回答2:

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