目錄:
- 自定義異常的目的
- 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:

哈哈,都在這邊拉。
