处理权限类中的异常

问题描述

我正在使用带有简单 JWT 的 Django REST 框架,我不喜欢权限类如何生成响应而不给我发送给客户端的最终决定权。使用其他不限制权限(登录注册等)的基于类的视图,我可以控制处理异常的方式,并且可以选择响应数据的结构。

然而,每当我引入权限类时,都会出现不希望的行为。我想要的结构最好在我的 LoginView 中表示(参见 try/except 块):

NON_FIELD_ERRORS_KEY = settings.REST_FRAMEWORK['NON_FIELD_ERRORS_KEY']

class LoginView(GenericAPIView):
    """
    View for taking in an existing user's credentials and authorizing them if valid or denying access if invalid.
    """
    serializer_class = LoginSerializer

    def post(self,request):
        """
        POST method for taking a token from a query string,checking if it is valid,and logging in the user if valid,or returning an error response if invalid.
        """
        serializer = self.serializer_class(data=request.data)

        try:
            serializer.is_valid(raise_exception=True)
        except AuthenticationFailed as e:
            return Response({NON_FIELD_ERRORS_KEY: [e.detail]},status=e.status_code)

        return Response(serializer.data,status=status.HTTP_200_OK)

但是,当我尝试使用权限类定义视图时会发生什么?

class logoutView(GenericAPIView):
    """
    View for taking in a user's access token and logging them out.
    """
    serializer_class = logoutSerializer
    permission_classes = [IsAuthenticated]

    def post(self,request):
        """
        POST method for taking a token from a request body,and logging out the user if valid,or returning an error response if invalid.
        """
        access = request.Meta.get('HTTP_AUTHORIZATION','')
        serializer = self.serializer_class(data={'access': access})

        try:
            serializer.is_valid(raise_exception=True)
        except AuthenticationFailed as e:
            return Response({NON_FIELD_ERRORS_KEY: [e.detail]},status=e.status_code)

        return Response(serializer.save(),status=status.HTTP_205_RESET_CONTENT)

当我去测试这个时,带有无效授权标头的请求在 post() 的范围之外被处理,所以执行根本不会到达方法。相反,我被迫处理与我的项目的其余部分不一致的响应。举个例子:

# Desired output
{
    'errors': [
        ErrorDetail(string='Given token not valid for any token type',code='token_not_valid')
    ]
}

# Actual output
{
    'detail': ErrorDetail(string='Given token not valid for any token type',code='token_not_valid'),'code': ErrorDetail(string='token_not_valid','messages': [
        {
            'token_class': ErrorDetail(string='Accesstoken','token_type': ErrorDetail(string='access','message': ErrorDetail(string='Token is invalid or expired',code='token_not_valid')
        }
    ]
}

是否有一种简单的方法可以更改这些响应的格式?

解决方法

在单步执行代码后,我发现APIView.initial 的最后一个注释部分引发了我所关注的异常。

# rest_framework/views.py (lines 399-416)

def initial(self,request,*args,**kwargs):
    """
    Runs anything that needs to occur prior to calling the method handler.
    """
    self.format_kwarg = self.get_format_suffix(**kwargs)

    # Perform content negotiation and store the accepted info on the request
    neg = self.perform_content_negotiation(request)
    request.accepted_renderer,request.accepted_media_type = neg

    # Determine the API version,if versioning is in use.
    version,scheme = self.determine_version(request,**kwargs)
    request.version,request.versioning_scheme = version,scheme

    # Ensure that the incoming request is permitted
    self.perform_authentication(request)
    self.check_permissions(request)
    self.check_throttles(request)

要生成自定义响应,请使用重载的 APIView 方法创建 initial() 的自定义实现,或者在 APIView 的特定实例(或后代,例如 {{ 1}}).

GenericAPIView