Home > Software engineering >  How to raise 404 as a status code in Serializer Validate function in DRF?
How to raise 404 as a status code in Serializer Validate function in DRF?

Time:01-27

I have written a validate() function inside my serializer. By default, Serializer Errors return 400 as a status code. But I want to return 404. I tried this:

class MySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.MyClass
        fields = "__all__"

    def validate(self, data):
        current_user = self.context.get("request").user
        user = data.get("user")
        if user!=current_user:
            raise ValidationError({'detail': 'Not found.'}, code=404)
        return data  

But it still returns 400 as a response in status code. How to do it?

CodePudding user response:

This is the Django source code that handles validation errors.

    def is_valid(self, *, raise_exception=False):
        # This implementation is the same as the default,
        # except that we use lists, rather than dicts, as the empty case.
        assert hasattr(self, 'initial_data'), (
            'Cannot call `.is_valid()` as no `data=` keyword argument was '
            'passed when instantiating the serializer instance.'
        )

        if not hasattr(self, '_validated_data'):
            try:
                self._validated_data = self.run_validation(self.initial_data)
            except ValidationError as exc:
                self._validated_data = []
                self._errors = exc.detail
            else:
                self._errors = []

        if self._errors and raise_exception:
            raise ValidationError(self.errors)

        return not bool(self._errors)

Django checks for ValidationError, then takes the details from that error object and re-creates with a 400 status code, which is why your code does not work.

So, from your view class, catch the django error and change the status code as shown.

class NotesList(mixins.CreateModelMixin, mixins.ListModelMixin, generics.GenericAPIView):
    serializer_class = MySerializer

    def get_queryset(self):
        return models.MyClass.objects.filter(owner=self.request.user)

    def post(self, request):
        try:
            return self.create(request)
        except ValidationError as exc:
            exc.status_code = 404
            raise exc

CodePudding user response:

You can do it from view by handling the serializes validation:

class MySerializer(serializers.ModelSerializer):

    class Meta:
        model = models.MyClass
        fields = "__all__"

    def validate(self, data):
        current_user = self.context.get("request").user
        user = data.get("user")
        if user != current_user:
            raise serializers.ValidationError(
                {'detail': 'Not found.'},
                code=404,
            )
        return data


class MyView(APIView):

    def post(self, request):
        serializer = MySerializer(
            data=request.data,
            context={'request': request},
        )
        if not serializer.is_valid():
            return Response(serializer.errors, status=404)
        return Response(serializer.data)

Alternately you can do it by defining own exception handler:

def my_exception_handler(exc, context):
    response = exception_handler(exc, context)
    if response is not None and response.status_code == 400:
        response.status_code = 404
    return response

class MyView(APIView):
    exception_handler = my_exception_handler

    def post(self, request):
        serializer = MySerializer(
            data=request.data, 
            context={'request': request},
        )
        serializer.is_valid(raise_exception=True)
        return Response(serializer.data)

But as you said, if you want to do it from serializer, then you have to define custom exception and raise it from serialize :

class NotFoundError(serializers.ValidationError):
    def __init__(self, detail):
        super().__init__(detail, code=404)


class MySerializer(serializers.ModelSerializer):
    class Meta:
        model = models.MyClass
        fields = "__all__"

    def validate(self, data):
        current_user = self.context.get("request").user
        user = data.get("user")
        if user != current_user:
            raise NotFoundError({'detail': 'Not found.'})
        return data
  • Related