目录:
- 自定义异常的目的
- DRF默认异常
- 自定义异常
- APIView使用自定义异常
- 报具体错误
一、自定义异常的目的
其实呐我们之前在写代码的时候,也碰到过一些异常,对吧,这些异常对于我们的PC端其实也还好,但是移动端就不行了,为啥呐?移动端处理异常的时候,如果处理的不及时,会有致命性的bug,如我们最害怕的闪退等一系列的问题。
目前我们返回的一些异常信息,是这个样子的:
{ "detail": "Authentication credentials were not provided." }
对于这样的结构,我们移动端的同学看到是极其不友好的,所以我们一般给对方的返回这样的数据结构:
{ "code": 401, "message": "Authentication credentials were not provided.", "data": [] }
那这个时候我们就需要自己异常来捕获DRF里面的异常信息。
二、DRF默认异常
其实DRF给我们自定义了默认异常,我们来看看,在APIView中是如何定义的,来走一个:Ctrl + APIView:
class APIView(View): .... permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES ....
好啦,继续: Ctrl + api_settings :
api_settings = APISettings(None, DEFAULTS, IMPORT_STRINGS)
来,走你,看看默认的是啥: Ctrl + DEFAULTS:
DEFAULTS = { ... # Exception handling 'EXCEPTION_HANDLER': 'rest_framework.views.exception_handler', #默认异常 'NON_FIELD_ERRORS_KEY': 'non_field_errors', .... }
哈哈,找到,那我们只需要重写这个异常,然后覆盖掉,变成自己的不就行啦。 来继续。
三、自定义异常
3.1、目录结构
说明:我们在app下需要手动创建一个文件custom_exception.py 来定义我们的自定义异常。
... -app06 -migrations ... -admin.py -apps.py -custom_exception.py #新建自定义异常文件 ... ....
3.2、自定义异常
说明:自定义异常,就是继承exception_handler,然后重新定义:
from rest_framework.views import exception_handler #继承默认exception_handler def custom_exception_handler(exc, context): response = exception_handler(exc, context) #重新定义 if response is not None: response.data.clear() response.data['code'] = response.status_code response.data['data'] = [] if response.status_code == 404: try: response.data['message'] = response.data.pop('detail') response.data['message'] = "未找到" except KeyError: response.data['message'] = "未找到" if response.status_code == 400: response.data['message'] = '输入错误' elif response.status_code == 401: response.data['message'] = "未认证" elif response.status_code >= 500: response.data['message'] = "服务器错误" elif response.status_code == 403: response.data['message'] = "权限不允许" elif response.status_code == 405: response.data['message'] = '请求不允许' else: response.data['message'] = '未知错误' return response
3.3、 全局配置
说明:自定义后,需要在 settings.py 中去配置 自定义异常,从而覆盖 默认的,告诉DRF,我不用你的自己默认的,我用我自定义的。
REST_FRAMEWORK = { 'DEFAULT_AUTHENTICATION_CLASSES': (...), .... #自定义异常 'EXCEPTION_HANDLER': 'app06.custom_exception.custom_exception_handler' }
3.4、postman测试:
说明:那我们就测试一下,测试之前我们先在我们之前定义的 View中加上认证权限:
... from rest_framework.permissions import IsAuthenticated #认证权限 class GameView(ModelViewSet): permission_classes = [IsAuthenticated] #加上认证权限 queryset = Game.objects.all() serializer_class = GameSerializer
效果如图:
四、 APIView使用自定义异常
我们如果继承的APIView方法,那么我们如何使用自己的自定义异常呐,很简单的,只要在验证的时候传入raise_exception=True即可。
@api_view(['GET', 'POST'])api_view已经帮我们做了验证 def user_list(request): if request.method == "GET": .... elif request.method == "POST": ser = UserSerializer(data=request.data, context={'request': request}) if ser.is_valid(raise_exception=True): #只需要在在验证的时候 传入raise_exception=True 说明需要使用自定义异常 ser.save() return Response(ser.data, status=status.HTTP_201_CREATED) return JSONResponse(ser.errors, status=status.HTTP_401_UNAUTHORIZED)
五、 报具体错误
5.1、报具体报错
说明:怎么个意思呐?就是我们在响应的时候,我们想把自己的具体报错弄出来,所以我们需要对custom_exception_handler代码需要优化,做一个兼容:
from rest_framework.views import exception_handler from rest_framework.exceptions import ValidationError def custom_exception_handler(exc, context): response = exception_handler(exc, context) #对具体报错做了兼容 if isinstance(exc, ValidationError): response.data['code'] = response.status_code response.data['data'] = [] if isinstance(response.data, dict): response.data['message'] = list(dict(response.data).values())[0][0] for key in dict(response.data).keys(): if key not in ['code', 'data', 'message']: response.data.pop(key) else: response.data['message'] = '输入有误' return response if response is not None: .... return response
好,这次我们用 postman测试一下:
异常提示,我们在序列化的时候,不仅可以使用 validate_字段名,也可以在具体的字段后面用 error_message:
class UserSerializer(serializers.ModelSerializer): phone = serializers.CharField(max_length=11,min_length=11,required=True,error_messages={"required":"手机号码必填"}) #可以在字段这边给出异常提示 .... class Meta: .... def validate_phone(self, phone): #单个验证 if not re.match(r'1[3456789]\d{9}',phone): raise serializers.ValidationError("手机号码不合法") ..... return phone
5.2、ModelViewSet使用自定义异常
说明:哈哈,那不禁的有小伙伴要问,我ModelViewSet封装的那么厉害,我如何使用自定义异常呐,其实人家已经建议 使用自定义异常了,不信我们来看看源码:Ctrl + ModelViewSet:
class ModelViewSet(mixins.CreateModelMixin, mixins.RetrieveModelMixin, mixins.UpdateModelMixin, mixins.DestroyModelMixin, mixins.ListModelMixin, GenericViewSet): """ A viewset that provides default `create()`, `retrieve()`, `update()`, `partial_update()`, `destroy()` and `list()` actions. """ pass
好啦,我们随便找一个我们就进入 CreateModelMixin 进去看看吧:Ctrl + CreateModelMixin:
class CreateModelMixin(object): """ Create a model instance. """ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) #看到了吧raise_exception=True 已经建议你使用自定义异常了 self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) ....
哈哈,真香。所以我们在使用 generics.ListCreateApiView 其实都是一样的。都是已经帮你 使用自定义异常了。
5.2、JWT登录使用自定义异常
说明:JWT我们在使用登录的时候,也是需要的,但是我们来看看 JWT登录 其实是没有使用 自定义异常的,不信我们来看看:
Crtl + obtain_jwt_token => ObtainJSONWebToken => JSONWebTokenAPIView
我们找到了,我们定位一下它的视图:
哈哈,我们找到post方法:
class JSONWebTokenAPIView(APIView): """ Base API View that various JWT interactions inherit from. """ permission_classes = () authentication_classes = () def get_serializer_context(self): .... def get_serializer_class(self): .... def get_serializer(self, *args, **kwargs): ... def post(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) if serializer.is_valid(): #没有使用我们自定义的,所以只需要 serializer.is_valid(raise_exception=True) 即可,然后保存 .... return response return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
但是我们发现,给出的异常提示还是英文的,那咋办呐,所以我们就需要 去改人家序列化的东西了,那就要碰serializers.py:
哈哈,都在这边拉。