目錄:
- 自定義異常的目的
- 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:
哈哈,都在這邊拉。