DRF對Django請求響應做了技術升級
阿新 • • 發佈:2020-12-18
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