1. 程式人生 > >DRF對Django請求響應做了技術升級

DRF對Django請求響應做了技術升級

Django檢視是用來處理請求和響應的,Django預設是按Form和Template來設計的,如果要處理以JSON格式為主的RESTful API,那麼就需要對Django請求和響應的處理程式碼進行優化改造,本文就來介紹DRF在這一部分的技術升級。 # Request DRF把Django的`HttpRequest`擴充套件成了`Request`:
其中最核心的屬性是`request.data`,它和`request.POST`的區別如下: ```python request.POST # 只處理表單(Form)資料,只支援POST方法 request.data # 處理任何資料,支援POST、PUT、PATCH方法 ``` # Response DRF的`Response`繼承自Django的`django.template.response.SimpleTemplateResponse`:
`Response`可以根據客戶端的請求render合適的content type: ```python return Response(data) ``` 我摘取了`rendered_content()`函式的程式碼: ```python @property def rendered_content(self): renderer = getattr(self, 'accepted_renderer', None) accepted_media_type = getattr(self, 'accepted_media_type', None) context = getattr(self, 'renderer_context', None) assert renderer, ".accepted_renderer not set on Response" assert accepted_media_type, ".accepted_media_type not set on Response" assert context is not None, ".renderer_context not set on Response" context['response'] = self media_type = renderer.media_type charset = renderer.charset content_type = self.content_type if content_type is None and charset is not None: content_type = "{}; charset={}".format(media_type, charset) elif content_type is None: content_type = media_type self['Content-Type'] = content_type ret = renderer.render(self.data, accepted_media_type, context) if isinstance(ret, str): assert charset, ( 'renderer returned unicode, and did not specify ' 'a charset value.' ) return ret.encode(charset) if not ret: del self['Content-Type'] return ret ``` # Status codes 如果在程式碼中直接寫數字形式的狀態碼如`400`,是不容易閱讀的,於是DRF提供了識別符號如`HTTP_400_BAD_REQUEST`來替代。我列一些常見的狀態碼識別符號: ```python HTTP_200_OK = 200 HTTP_201_CREATED = 201 HTTP_204_NO_CONTENT = 204 HTTP_400_BAD_REQUEST = 400 HTTP_401_UNAUTHORIZED = 401 HTTP_403_FORBIDDEN = 403 HTTP_404_NOT_FOUND = 404 HTTP_405_METHOD_NOT_ALLOWED = 405 HTTP_500_INTERNAL_SERVER_ERROR = 500 HTTP_502_BAD_GATEWAY = 502 HTTP_503_SERVICE_UNAVAILABLE = 503 HTTP_504_GATEWAY_TIMEOUT = 504 ``` 全部的狀態碼識別符號可以在`rest_framework.status`模組中看到。 # @api_view和APIView DRF對API檢視做了2個封裝: 1. `@api_view`用於函式檢視。 2. `APIView`用於類檢視。 它們提供了一些新功能,比如: - 檢查請求是`Request`物件 - 新增上下文到`Response`物件 - 返回請求錯誤如`405 Method Not Allowed` - 當`request.data`格式有誤時,丟擲`ParseError`異常 # 改造views.py 接著就用上面這幾個新實現對我們之前寫的`snippets/views.py`進行改造: ```python from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response from snippets.models import Snippet from snippets.serializers import SnippetSerializer @api_view(['GET', 'POST']) def snippet_list(request): """ List all code snippets, or create a new snippet. """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return Response(serializer.data) elif request.method == 'POST': serializer = SnippetSerializer(data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data, status=status.HTTP_201_CREATED) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) @api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a code snippet. """ try: snippet = Snippet.objects.get(pk=pk) except Snippet.DoesNotExist: return Response(status=status.HTTP_404_NOT_FOUND) if request.method == 'GET': serializer = SnippetSerializer(snippet) return Response(serializer.data) elif request.method == 'PUT': serializer = SnippetSerializer(snippet, data=request.data) if serializer.is_valid(): serializer.save() return Response(serializer.data) return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) elif request.method == 'DELETE': snippet.delete() return Response(status=status.HTTP_204_NO_CONTENT) ``` 改動點有這些,添加了`@api_view`,如: ```python @api_view(['GET', 'POST']) ``` 使用了狀態碼識別符號,如: ```python status.HTTP_404_NOT_FOUND ``` 使用`request.data`替代了` data = JSONParser().parse(request)`,如: ```python serializer = SnippetSerializer(data=request.data) ``` 使用`Response()`替代了`JsonResponse()`,如: ```python return Response(serializer.data, status=status.HTTP_201_CREATED) ``` >
`request.data`和`Response()`能根據請求的JSON自動處理content type。 # 新增字尾格式(可選) 既然DRF能自動處理content type,那麼也可以給URL指定具體的字尾格式,比如`http://example.com/api/items/4.json`。具體新增步驟是,先給view增加1個可選引數`format`: ```python def snippet_list(request, format=None): ``` ```python def snippet_detail(request, pk, format=None): ``` 再更新`snippets/urls.py`,新增`format_suffix_patterns`: ```python from django.urls import path from rest_framework.urlpatterns import format_suffix_patterns from snippets import views urlpatterns = [ path('snippets/', views.snippet_list), path('snippets/', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns) ``` > 這並不是必須的,實際上也無需這麼做。 > # 測試API ```python http http://127.0.0.1:8000/snippets/ HTTP/1.1 200 OK ... [ { "id": 1, "title": "", "code": "foo = \"bar\"\n", "linenos": false, "language": "python", "style": "friendly" }, { "id": 2, "title": "", "code": "print(\"hello, world\")\n", "linenos": false, "language": "python", "style": "friendly" } ] ``` 跟之前的結果一樣。再分別用form和json試試: ```python # POST using form data http --form POST http://127.0.0.1:8000/snippets/ code="print(123)" { "id": 3, "title": "", "code": "print(123)", "linenos": false, "language": "python", "style": "friendly" } # POST using JSON http --json POST http://127.0.0.1:8000/snippets/ code="print(456)" { "id": 4, "title": "", "code": "print(456)", "linenos": false, "language": "python", "style": "friendly" } ``` # API文件 DRF提供了視覺化的API HTML文件,把API URL在瀏覽器中開啟即可看到:
# 東方說 最近測試開發和業務測試的話題頻頻出現在TesterHome論壇上,討論激烈,我覺得從公司的角度來說,只會關注員工的產出有沒有給公司帶來價值,無論技術多厲害,不能創造價值終究是會優先被裁的。從個人的角度來說,只會業務測試的出路肯定是會越來越窄的,努力提高技術,輔助業務測試,同時提升效率,才是更好的發展方向。千萬要謹慎選擇只做純測試工具,要依託於業務,讓技術落地,在業務中發揮技術的價值,產生從業務到技術,從技術到業務的良好迴圈。當然,會技術是個大前提,對技術的學習不能停,比如Django REST framework。 > 參考資料: > > https://www.django-rest-framework.org/tutorial/2-requests-and-res