Django編寫RESTful API(二):請求和響應
歡迎訪問我的個人網站:www.comingnext.cn
前言
在上一篇文章,已經實現了訪問指定URL就返回了指定的資料,這也體現了RESTful API的一個理念,每一個URL代表著一個資源。當然我們還知道RESTful API的另一個特性就是,傳送不同的請求動作,會返還不同的響應,這篇文章就講一下django-rest-framework這個工具在這方面給我們帶來的便捷操作。
Request物件
平時我們在寫Django的檢視函式的時候,都會帶上一個request引數,這樣就能處理平時搭建網站時,瀏覽器訪問網頁時發出的常規的HttpRequest。但是現在我們匯入了django-rest-framework,它能夠對request進行拓展,並且提供更靈活的請求解析。這個特性體現在哪呢?請看下面這個例子:
request.POST # 只能處理表單資料.只能處理POST請求 request.data # 能處理各種資料。 可以處理'POST', 'PUT' 和 'PATCH'模式的請求
這個例子裡面的註釋已經說得很清楚,拓展後的request使用request.data就可以處理各種各樣的請求了,而原本的request在處理時需要指定請求模式。
Response物件
和request物件一樣,django-rest-framework也對其進行了很實用的拓展,在我上一篇文章的snippets/views.py中,我們匯入了JsonResponse用於返回json格式的響應,在檢視函式中是這樣的:
@csrf_exempt def snippet_list(request): """ 列出所有已經存在的snippet或者建立一個新的snippet """ if request.method == 'GET': snippets = Snippet.objects.all() serializer = SnippetSerializer(snippets, many=True) return JsonResponse(serializer.data, safe=False) elif request.method == 'POST': data = JSONParser().parse(request) serializer = SnippetSerializer(data=data) if serializer.is_valid(): serializer.save() return JsonResponse(serializer.data, status=201) return JsonResponse(serializer.errors, status=400)
也就是說,在return的時候就需要指明json格式,這樣顯得很不實用而且很單一,所以經過拓展後的Reponse物件就很方便了,它會根據客戶端的請求頭部資訊來確定正確的內容型別以返回給客戶端。只需如下程式碼:
return Response(data)
稍後會改進上一篇文章的程式對此進一步的具體講解。
狀態碼
我們知道傳送http請求時會返回各種各樣的狀態嗎,但是都是簡單的數字,比如200、404等,這些純數字識別符號有時候可能不夠明確或者客戶端在使用的時候不清楚錯誤資訊甚至是沒注意看不到,所以django-rest-framework也對此進行了優化,狀態碼會是HTTP_400_BAD_REQUEST、HTTP_404_NOT_FOUND這種,極大的提高可讀性。
裝飾API檢視
REST框架還提供了一個裝飾器和一個類來包裝檢視函式,可以使用它們來寫API檢視,讓程式能處理的情況更多。
- @api_view裝飾器用在基於檢視的方法上。
- APIView類用在基於檢視的類上。
注意:本文使用的是基於檢視方法,所以使用的是裝飾器@api_view,APIview這個類暫時不會提及。
這兩個東西提供的一些功能,讓我們省去很多工作,比如說確保你在檢視中收到Request物件或在你的Response物件中新增上下文,這樣就能實現內容通訊。
另外裝飾器可以在接收到輸入錯誤的request.data時丟擲ParseError異常,或者在適當的時候返回405 Method Not Allowed狀態碼。
把這些都使用起來
上面說了這麼多拓展和優化,接下來就把它們都使用起來,改進一下原本的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): """ 列出所有已經存在的snippet或者建立一個新的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)
可以看出,經過改進的程式碼已經把上面所說的幾個django-rest-framework帶來的特性都應用起來了,我們可以看出程式程式碼量變少,並且能處理的情況更多了。 比如說,在原本的檢視函式snippet_detail中,處理'PUT'請求的時候,需要先解析json格式的資料再進一步處理:
data = JSONParser().parse(request)
serializer = SnippetSerializer(snippet, data=data)
也就是說需要分成兩步實現,而且這裡有一個限制就是隻能解析json格式的資料流。而改進後的程式只需一行程式碼:
serializer = SnippetSerializer(data=request.data)
直接使用之前說的request.data就可以獲取到提交過來的資料了,並且可以處理各種資料和各種請求動作,方便了開發。
還有在return的時候也不需要指定json格式了,由原本的
return JsonResponse(serializer.data, status=201)
改成了
return Response(serializer.data,status=status.HTTP_201_CREATED)
這也意味著返回給客戶端的可以是json或者html等格式的內容,返回HTML格式的內容的話,會在瀏覽器返回經過渲染的、更美觀的頁面。同時可以看出狀態碼也改進成了django-rest-framework給我們帶來的可讀性更高的狀態標識碼,以上這些措施都很大程度的提高了對客戶的友好度。
對於另一個檢視函式的修改也是同樣的原理,這裡就不做同樣的講解了,程式碼如下:
@api_view(['GET', 'PUT', 'DELETE']) def snippet_detail(request, pk): """ Retrieve, update or delete a snippet instance. """ 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)
以上就是對原有的常規的Django檢視函式的改進。
總結一下就是處理request提交過來的資料不需要一定是json格式的資料,返回的響應也不需要一定是json資料,也可以是經過渲染的HTML頁面。稍後就會示範使用。
向URL新增可選的格式字尾
既然上面已經說了返回給客戶端的Response可是json或者是HTML等格式的內容,那麼使用者在使用的時候是如何指定返回哪種格式的內容呢,那就是在URL的最後加上字尾。比如http://127.0.0.1:8000/snippets.json,這樣就是使用者自己指定了返回json格式的Response,而不是我們在後臺指定返回固定的格式。
只需對我們的程式稍加改進就可以了,在兩個檢視函式新增關鍵詞引數format:
def snippet_list(request, format=None):
以及
def snippet_detail(request, pk, format=None):
再修改一下snippets/urls.py,匯入format_suffix_patterns(格式字尾模式):
from django.conf.urls import url from rest_framework.urlpatterns import format_suffix_patterns from snippets import views
urlpatterns = [ url(r'^snippets/$', views.snippet_list), url(r'^snippets/(?P<pk>[0-9]+)$', views.snippet_detail), ] urlpatterns = format_suffix_patterns(urlpatterns)
改進後的使用
首先當然還是可以像上一篇文章中那樣的使用:
也可以通過設定Accept頭部資訊來控制返回的格式:
http http://127.0.0.1:8000/snippets/ Accept:application/json # Request JSON http http://127.0.0.1:8000/snippets/ Accept:text/html # Request HTML
效果如下(返回的是頁面的HTML程式碼,只展示了一部分):
還可以直接加格式字尾:
http http://127.0.0.1:8000/snippets.json # JSON suffix http http://127.0.0.1:8000/snippets.api # Browsable API suffix
當然啦,在命令列檢視HTML程式碼就沒啥意思了,我們可以直接在瀏覽器輸入 http://127.0.0.1:8000/snippets.api 進行檢視,會得到一個美觀的頁面:
如果我們要增添資料怎麼辦?我們可以控制 Content-Type 頭部資訊來提交POST請求:
http --form POST http://127.0.0.1:8000/snippets/ code="print 123" http --json POST http://127.0.0.1:8000/snippets/ code="print 456"
它會自動在原有的資料後面新增你提交過去的資料,效果如下:
上面說了,改進後可以處理錯誤的提交,比如把code改成了codes,就會給出錯誤資訊:
圖中給出的錯誤資訊是 400 Bad Request,這和我們在檢視函式中定義的是一樣的:
return Response(serializer.data,status=status.HTTP_400_BAD_REQUEST)
在請求中如果加入了--debug可以檢視到詳細的請求資訊和型別:
在上面介紹@api_view和APIView的時候,提到了在適當的時候返回405 Method Not Allowed狀態碼。這個所謂適當的時候就要回看到剛才寫檢視函式的時候,修飾器的程式碼:
@api_view(['GET','POST'])
以及
@api_view(['GET','PUT','DELETE'])
這兩行程式碼就規定了在呼叫這兩個函式,也就是訪問到相關的URL時,只能使用指定的請求動作,否則就會報出405 Method Not Allowed錯誤。例如訪問 http://127.0.0.1:8000/snippets.json 時用了PUT請求就會報這個錯:
正確的更改資料應該如下:
http --json PUT http://127.0.0.1:8000/snippets/1.json code="hello world"
這樣就把 id=1 的資料修改了。想要刪除也是一樣的:
這樣就可以把 id=3 的資料刪除掉了。