Как поднять 404 в качестве кода состояния в функции Serializer Validate в DRF?

Я написал функцию validate() внутри моего сериализатора. По умолчанию Serializer Errors возвращает 400 в качестве кода состояния. Но я хочу возвращать 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 ValidationError({'detail': 'Not found.'}, code=404)
        return data  

Но он по-прежнему возвращает 400 в качестве ответа в коде состояния. Как это сделать?

Это исходный код Django, который обрабатывает ошибки валидации.

    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 проверяет наличие ValidationError, затем берет детали из этого объекта ошибки и пересоздает с кодом состояния 400, поэтому ваш код не работает.

Итак, из класса представления поймайте ошибку django и измените код состояния, как показано на рисунке.

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

Вы можете сделать это из представления, обработав валидацию сериализации:

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)

Альтернативно вы можете сделать это, определив собственный обработчик исключений:

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)

Но как вы сказали, если вы хотите сделать это из сериализатора, то вы должны определить пользовательское исключение и поднять его из сериализатора :

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
Вернуться на верх