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

dongfanger發表於2020-12-18

Django檢視是用來處理請求和響應的,Django預設是按Form和Template來設計的,如果要處理以JSON格式為主的RESTful API,那麼就需要對Django請求和響應的處理程式碼進行優化改造,本文就來介紹DRF在這一部分的技術升級。

Request

DRF把Django的HttpRequest擴充套件成了Request

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

其中最核心的屬性是request.data,它和request.POST的區別如下:

request.POST  # 只處理表單(Form)資料,只支援POST方法
request.data  # 處理任何資料,支援POST、PUT、PATCH方法

Response

DRF的Response繼承自Django的django.template.response.SimpleTemplateResponse

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

Response可以根據客戶端的請求render合適的content type:

return Response(data)

我摘取了rendered_content()函式的程式碼:

@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來替代。我列一些常見的狀態碼識別符號:

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進行改造:

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,如:

@api_view(['GET', 'POST'])

使用了狀態碼識別符號,如:

status.HTTP_404_NOT_FOUND

使用request.data替代了 data = JSONParser().parse(request),如:

 serializer = SnippetSerializer(data=request.data)

使用Response()替代了JsonResponse(),如:

return Response(serializer.data, status=status.HTTP_201_CREATED)

request.dataResponse()能根據請求的JSON自動處理content type。

新增字尾格式(可選)

既然DRF能自動處理content type,那麼也可以給URL指定具體的字尾格式,比如http://example.com/api/items/4.json。具體新增步驟是,先給view增加1個可選引數format

def snippet_list(request, format=None):
def snippet_detail(request, pk, format=None):

再更新snippets/urls.py,新增format_suffix_patterns

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/<int:pk>', views.snippet_detail),
]

urlpatterns = format_suffix_patterns(urlpatterns)

這並不是必須的,實際上也無需這麼做。

測試API

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試試:

# 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在瀏覽器中開啟即可看到:

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

東方說

最近測試開發和業務測試的話題頻頻出現在TesterHome論壇上,討論激烈,我覺得從公司的角度來說,只會關注員工的產出有沒有給公司帶來價值,無論技術多厲害,不能創造價值終究是會優先被裁的。從個人的角度來說,只會業務測試的出路肯定是會越來越窄的,努力提高技術,輔助業務測試,同時提升效率,才是更好的發展方向。千萬要謹慎選擇只做純測試工具,要依託於業務,讓技術落地,在業務中發揮技術的價值,產生從業務到技術,從技術到業務的良好迴圈。當然,會技術是個大前提,對技術的學習不能停,比如Django REST framework。

參考資料:

https://www.django-rest-framework.org/tutorial/2-requests-and-responses/

相關文章