自定义异常格式
# 1. 自定义异常的目的
其实呐我们之前在写代码的时候,也碰到过一些异常,对吧,这些异常对于我们的PC端其实也还好,但是移动端就不行了,为啥呐?移动端处理异常的时候,如果处理的不及时,会有致命性的bug,如我们最害怕的闪退等一系列的问题。
目前我们返回的一些异常信息,是这个样子的:
{
"detail": "Authentication credentials were not provided."
}
2
3
对于这样的结构,我们移动端的同学看到是极其不友好的,所以我们一般给对方的返回这样的数据结构:
{
"code": 401,
"message": "Authentication credentials were not provided.",
"data": []
}
2
3
4
5
那这个时候我们就需要自己异常来捕获DRF里面的异常信息。
# 2. DRF默认异常
其实DRF给我们自定义了默认异常,我们来看看,在APIView中是如何定义的,来走一个:Ctrl + APIView:
class APIView(View):
....
permission_classes = api_settings.DEFAULT_PERMISSION_CLASSES
....
2
3
4
好啦,继续: 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',
....
}
2
3
4
5
6
7
哈哈,找到,那我们只需要重写这个异常,然后覆盖掉,变成自己的不就行啦。 来继续。
# 3. 自定义异常
# 3.1 目录结构
说明:我们在app下需要手动创建一个文件custom_exception.py 来定义我们的自定义异常。
...
-app06
-migrations
...
-admin.py
-apps.py
-custom_exception.py #新建自定义异常文件
...
....
2
3
4
5
6
7
8
9
# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# 3.3 全局配置
说明:自定义后,需要在 settings.py 中去配置 自定义异常,从而覆盖 默认的,告诉DRF,我不用你的自己默认的,我用我自定义的。
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (...),
....
#自定义异常
'EXCEPTION_HANDLER': 'app06.custom_exception.custom_exception_handler'
}
2
3
4
5
6
# 3.4、postman测试:
from rest_framework.permissions import IsAuthenticated #认证权限
class GameView(ModelViewSet):
permission_classes = [IsAuthenticated] #加上认证权限
queryset = Game.objects.all()
serializer_class = GameSerializer
2
3
4
5
6
7
说明:那我们就测试一下,测试之前我们先在我们之前定义的 View中加上认证权限:
效果如图:
# 4. 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)
2
3
4
5
6
7
8
9
10
# 5. 报具体错误
# 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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
好,这次我们用 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
2
3
4
5
6
7
8
9
10
11
12
13
# 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
2
3
4
5
6
7
8
9
10
11
好啦,我们随便找一个我们就进入 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)
....
2
3
4
5
6
7
8
9
10
11
12
哈哈,真香。所以我们在使用 generics.ListCreateApiView 其实都是一样的。都是已经帮你 使用自定义异常了。
# 5.3 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)
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
但是我们发现,给出的异常提示还是英文的,那咋办呐,所以我们就需要 去改人家序列化的东西了,那就要碰serializers.py: