- 1. 關(guān)系和超鏈接 API(Relationships & hyperlinked APIs)
- 2. 視圖集合和路由(ViewSets & Routers)
- 3. 架構(gòu)和客戶端庫(Schemas & client libraries)
1. 關(guān)系和超級鏈接API
目前我們的API中的關(guān)系的通過主鍵來表示。我們下面將改進(jìn)API的內(nèi)聚力和可現(xiàn)性,而不是使用超鏈接來進(jìn)來進(jìn)行關(guān)系。
為我們的API的根地址創(chuàng)建端點(diǎn)
現(xiàn)在我們有snippets和users的端點(diǎn),但是我們沒有一個指向我們API的入口。我們需要創(chuàng)建一個,我們將使用基于函數(shù)的常規(guī)視圖和我們前面介紹的@api_view裝飾器。在你的quickstart/view.py中添加:
# quickstart/view.py
from rest_framework.decorators import api_view
from rest_framework.response import Response
from rest_framework.reverse import reverse
@api_view(['GET'])
def api_root(request, format=None):
return Response({
'users' : reverse('user-list', request=request, format=format),
'snippets': reverse('snippet-list', request=request, format=format)
})
這里應(yīng)該注意兩件事,首先我們使用
REST framework和reverse函數(shù)來返回完全限定的URL;
其次,URL模式通過我們下面的quickstart/urls.py中聲明的便利名稱進(jìn)行表示。
為高亮顯示snippets創(chuàng)建端點(diǎn)
另一個明顯的事情是我們的pastebin API仍然缺少高亮的顯示代碼端點(diǎn)。
與其他API端點(diǎn)不同,我們不想使用JSON,而是只呈現(xiàn)HTML表示。REST framework提供了兩種HTML渲染器,一種是使用模板來處理渲染HTML,另一種渲染器是我們要用于此端點(diǎn)的渲染器。
在創(chuàng)建代碼高亮顯示視圖時候,我們需要考慮的另一件事是:不存在我們可以使用的通用視圖。我們不是返回一個對象實(shí)例,而是返回宇哥對象實(shí)例屬性。
我們將使用基類來表示實(shí)例,并創(chuàng)建我們自己的.get()方法,而不是使用具體的通用視圖。在你的quickstart/view.py中添加:
# quickstart/view.py
from rest_framework import permissions, renderers
class SnippetHighlight(generics.GenericAPIView):
queryset = Snippet.objects.all()
renderer_classes = (renderers.StaticHTMLRenderer,)
def get(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
像往常一樣,我們需要將我們新創(chuàng)建的視圖添加到URLconf中,我們將在quickstart/urls.py中添加一個新的url模式:
url('^$', api_root),
然后為高亮代碼snippet添加一個url模式:
url('^snippets/(?P<pk>[0-9]+)/highlight/$',SnippetHighlight.as_view()),
為我們API加上超鏈接
處理實(shí)體之間的關(guān)系是我們Web API設(shè)計中更具挑戰(zhàn)性的方面之一,這里有一些我們選擇代表關(guān)系的不同方法:
- 使用主鍵
- 在實(shí)體之間使用超鏈接
- 在相關(guān)實(shí)體上使用唯一的標(biāo)識字段
- 使用默認(rèn)的字符串代表相關(guān)實(shí)體
- 將相關(guān)的實(shí)體嵌套在父代表中
- 一切其他自定義的表示
REST framework支持這些樣式,并且可在正式或者反向關(guān)系中應(yīng)用他們?;蛘咄ㄟ^自定義管理器(如通用外鍵)應(yīng)用他們。
在這種情況下,我們希望在實(shí)體之間使用超鏈接樣式,為了做到這一點(diǎn),我們將修改我們的序列化器來擴(kuò)展HyperlinkedModelSerializer,而不是現(xiàn)有的ModelSerializer。
HyperLinkedModelSerializer與ModelSerializer有一下區(qū)別:
- 它不包含id字段
- 它包含一個
url字段,使用HyperlinkedIdentityField. - 關(guān)系使用
HyperlinkedRelatedField,而不是PrimaryKeyRelatedField。
我們可以輕松的重寫現(xiàn)有的序列化器來使用超鏈接:
# quickstart/serialzers.py
from rest_framework import serializers
from .models import Snippet
from django.contrib.auth.models import User
class SnippetSerializer(serializers.HyperlinkedModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
highlight = serializers.HyperlinkedIdentityField(view_name='snippet-highlight', format='html')
class Meta:
model = Snippet
fields = ('url', 'id', 'highlight', 'owner', 'title', 'code', 'linenos', 'language', 'style')
class UserSerializer(serializers.HyperlinkedModelSerializer):
snippets = serializers.HyperlinkedRelatedField(many=True, view_name='snippet-detail',read_only=True)
class Meta:
model = User
fields = ('url', 'id', 'username', 'snippets')
注意,我們還新添加了一個新的
highlight字段。該字段的url字段類型相同,不同之處在于它指向的是snippet-highlighturl模式,而不是snippet-detailurl模式。因?yàn)槲覀円呀?jīng)包含了格式后綴的Url。例如
.json,我們還需要在highlight字段上指出,任何格式后綴的超鏈接返回都應(yīng)該使用.html后綴。
確保我們的URL模式被命名
如果我們要有超鏈接的API,我們需要確保命名URL模式。我們看看我們需要命名的URL模式。
- 我們的API根地址是指
user-list和snippet-list。 - 我們的snippet序列化器包含一個指向
snippet-highlight的字段。 - 我們的user序列化器包含一個指向'snippet-list'的字段
- 我們的snippet和user序列化器包含
url字段,默認(rèn)情況下將指向{model_name}-detail,在這個例子中就是snippet-detail和user-detail。
我們將所有這些名稱添加到我們的URLconf后,我們最后在quickstart/urls.py文件應(yīng)該如下:
quickstart/urls.py
from django.conf.urls import url
from rest_framework.urlpatterns import format_suffix_patterns
from .views import SnippetList, SnippetDetail, UserList, UserDetail, api_root,SnippetHighlight
from django.conf.urls import include
urlpatterns = format_suffix_patterns([
url('^api-auth/', include('rest_framework.urls')),
url('^$',api_root),
url('snippets/$',SnippetList.as_view(), name='snippet-list'),
url('^snippets/(?P<pk>[0-9]+)/$', SnippetDetail.as_view(), name='snippet-detail'),
url('^snippets/(?P<pk>[0-9])/highlight/$', SnippetHighlight.as_view(), name='snippet-highlight'),
url('^users/$', UserList.as_view(), name='user-list'),
url('^users/(?P<pk>[0-9]+)/$',view=UserDetail.as_view(), name='user-detail'),
])
添加分頁
users和snippets的列表視圖最終會返回很多實(shí)例,所以我們真的要確保結(jié)果進(jìn)行分頁。并允許API客戶端遍歷每個單獨(dú)頁面。
我們可以通過稍微修改tutorial/setting.py,文件來更改默認(rèn)列表樣式可以使用分頁。添加一下設(shè)置:
# tutorial/setting.py
REST_FRAMEWORK = {
'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
'PAGE_SIZE': 10
}
注意,REST framework中的所有設(shè)置都放在一個名為
REST——FRMEWORK的字典中,這有助于他們與其他項(xiàng)目保持良好的分離。如果需要,我們也可以自定義分頁的樣式,但是這里,我們會一直使用默認(rèn)。
瀏覽API
啟動服務(wù)·
python manage.py runserver
在瀏覽器輸入http://127.0.0.1:8000/quickstart/snippets/,結(jié)果如下圖所示:

你還可以在 snippet 實(shí)例上看到 “highlight” 鏈接,這會帶您跳轉(zhuǎn)到代碼高亮顯示的 HTML 頁面。
還有url的鏈接
2.視圖集合和路由(ViewSets & Routers)
REST framework 包括一個用于處理ViewSets的抽象,它允許開發(fā)人員集中精力對API的狀態(tài)和交互進(jìn)行建模,并保留URL結(jié)構(gòu),根據(jù)通用的約定自動處理。
ViewSet類與View類幾乎相同。只是他們提供諸如read或update操作。而不提供諸如GET或則PUT等方法處理程序。
一個ViewSet類最后時刻只能綁定一組方法處理程序,當(dāng)它被實(shí)例化為一組視圖的時候,通常通過使用一個Router類來處理定義復(fù)雜的URL。
使用ViewSet重構(gòu)
我們來看看當(dāng)前的一組視圖,并將它們重構(gòu)為視圖集。
首先讓我們將UserList和UserDetail視圖重構(gòu)為單個UserViewSet。我們可以刪除這兩個視圖,并用一個類替換它們:
# quickstart/view.py
from rest_framework import viewsets #新增導(dǎo)入
class UserViewSet(viewsets.ReadOnlyModelViewSet):
# 這個視圖集自動提供 list 和 detail 操作
queryset = User.objects.all()
serializer_class = UserSerializer
這里我們使用了
ReadOnlyModelViewSet類來自動提供默認(rèn)的”只讀“操作。我們?nèi)匀幌裎覀兪褂贸R?guī)駛?cè)霑r一樣設(shè)置queryset和serializer_class屬性,但是我們不需要為兩個單獨(dú)的類,提供相同的信息。
接下來我們將替換SnippetList,SnippetDetail和SnippetHihtlight視圖類。我們可以刪除這三個視圖,并再次使用一個類去替換它們。
# quickstart/view.py
class SnippetViewSet(viewsets.ModelViewSet):
"""
這個視圖集自動提供 'list' 'create' 'retrieve' 'update' 和 'destroy'
另外我們還提供一個額外的'highlight'操作
"""
queryset = Snippet.objects.all()
serializer_class = SnippetSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
@action(detail=True, renderers_classed = [renderers.StaticHTMLRenderer])
def highlight(self, request, *args, **kwargs):
snippet = self.get_object()
return Response(snippet.highlighted)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
這次我們使用了
ModelViewSet類來獲得完整的默認(rèn)讀寫操作集。注意,我們還使用了
@action裝飾器來創(chuàng)建一個名為highlight的自定義操作。這個裝飾器可以用來添加任何不符合標(biāo)準(zhǔn)create/update/delete樣式的自定義端點(diǎn)。使用
@action裝飾器的自定義操作默認(rèn)會響應(yīng)GET請求。如果我們想要響應(yīng)POST請求的操作,我們可以使用methods參數(shù)。自定義操作的URL默認(rèn)取決于方法名稱本身。如果需要更改URL的構(gòu)造方式,可以包含
url_path作為裝飾器的關(guān)鍵字參數(shù)。
明確地將ViewSet綁定到URL
當(dāng)我們定義URLConf時,處理程序方法只能綁定到操作上。為了看看到底發(fā)生了什么,讓我們首先從我們的ViewSets中明確地創(chuàng)建一組視圖。
在quickstart/urls.py文件中,我們將ViewSet類綁定到一組具體視圖中。
# quickstart/urls.py
from .views import api_root, SnippetViewSet, UserViewSet
from rest_framework import renderers
snippet_list = SnippetViewSet.as_view({
'get': 'list',
'post': 'create',
})
snippet_detail = SnippetViewSet.as_view({
'get': 'retrieve',
'put': 'update',
'patch': 'partial_update',
'delete': 'destroy',
})
snippet_highlight = SnippetViewSet.as_view({
'get': 'highlight'
}, renderers_class=[renderers.StaticHTMLRenderer])
user_list = UserViewSet.as_view({
'get': 'list'
})
user_detail = UserViewSet.as_view({
'get': 'retrieve'
})
注意我們是如何從每個
ViewSet類創(chuàng)建多個視圖,通過將HTTP方法綁定到每個視圖所需的操作中。
現(xiàn)在我們已經(jīng)將資源綁定到具體的視圖中,我們可以像往常一樣在URL conf中注冊視圖。
urlpatterns = format_suffix_patterns([
url('^api-auth/', include('rest_framework.urls')),
url('^$',api_root),
url('snippets/$', snippet_list, name='snippet-list'),
url('^snippets/(?P<pk>[0-9]+)/$', snippet_detail, name='snippet-detail'),
url('^snippets/(?P<pk>[0-9])/highlight/$', snippet_highlight, name='snippet-highlight'),
url('^users/$', user_list, name='user-list'),
url('^users/(?P<pk>[0-9]+)/$', user_detail, name='user-detail'),
])
使用Routers
因?yàn)槲覀兪褂?code>ViewSet類而不是View類,所以實(shí)際上我們不需要自己設(shè)計URL。將資源鏈接到視圖和URL的約定可以使用Router類自動處理,我們需要做的就使用路由器注冊相應(yīng)的視圖集,然后讓他執(zhí)行其余操作。
這里我們重寫quickstart/urls.py文件:
# quickstart/urls.py
from django.conf.urls import include, url
from rest_framework.routers import DefaultRouter
# 創(chuàng)建路由器并注冊我們的視圖。
router = DefaultRouter()
router.register('snippets', views.SnippetViewSet)
router.register('users', views.UserViewSet)
# API url 現(xiàn)在有路由器自動確定
urlpatterns = [
url('^', include(router.urls))
]
用路由器注冊視圖集類似于提供urlpattern.我們包括兩個參數(shù)--視圖的URL前綴和視圖集本身。
我們使用
DefaultRouter類為我們自動創(chuàng)建了API根視圖,所以我們現(xiàn)在可以從views模塊中刪除api_root方法。
視圖與視圖集之間的權(quán)衡
使用視圖集可以是一個常用的抽象。它有助于確保URL約定在你的API中保存一致,最大限度地減少編寫所需的代碼量,并允許你專注于API提供的交互,而不是URL conf的細(xì)節(jié)。
這并不意味著它總是正確的做法。但是用基于類的視圖而不是基于函數(shù)的視圖的時候,也有類似的權(quán)衡考慮。使用視圖集不像單獨(dú)構(gòu)建視圖那樣明確。
3.概要和客戶端庫(Schemas & client libraries)
概要是一種機(jī)器可閱讀文檔,用于描述可用API路徑,器URLS以及他們支持的操作。
高腰可以是自動生成文檔的有用工具,也可以是用于驅(qū)動,可以與API進(jìn)行交互的動態(tài)客戶端庫。
Core API
為了提供概要支持REST框架使用Core API.
CoreAPI是用于描述API的文檔規(guī)范,它用于提供可用路徑的內(nèi)部表示形式和API公開的可能的交互。他可以用于服務(wù)器端或者客戶端。
當(dāng)使用服務(wù)端時候,Core API 允許支持API支持呈現(xiàn)范圍廣泛的概要或者超媒體格式。
當(dāng)使用客戶端時,核心API允許動態(tài)驅(qū)動的客戶端庫,它可以與任何公開受支持的概要或者超媒體格式的API交互。
添加概要
REST框架支持明確定義的概要視圖或者自動生成概要。由于我們使用的是視圖集和路由,我們可以簡單地使用自動概要生成。
你需要安裝coreapi,python包才能包含API概要。
pip install coreapi
現(xiàn)在我們可以通過在url配置中包含一個主動生成的概要視圖來為API添加概要。
# quickstart/urls.py
from rest_framework.schemas import get_schema_view
urlpatterns = [
url('^schema/$', schema_view), #新增
url('^', include(router.urls))
]
如果你在瀏覽器中訪問API的根路徑,那么你現(xiàn)在應(yīng)該就可以看到core json表示形式是另一個可用選項(xiàng)。

我們也可以通過在Accept標(biāo)識頭中指定所需的內(nèi)容類型從從命令行請求概要。
$ http http://127.0.0.1:8000/schema/ Accept:application/coreapi+json
HTTP/1.0 200 OK
Allow: GET, HEAD, OPTIONS
Content-Type: application/coreapi+json
{
"_meta": {
"title": "Pastebin API"
},
"_type": "document",
...
默認(rèn)輸入樣式是使用Core JSON編碼。
還支持其他概要格式,如Open API(以前叫Swagger)。
使用命令行客戶端
現(xiàn)在我們的API暴露了一個概要路徑,我們可以使用一個動態(tài)的客戶端庫與API進(jìn)行交互,為了演示這個,我們使用CoreAPI命令行客戶端。
命令行客戶端作為一個coreapi-cli包提供:
pip install coreapi-cli
現(xiàn)在檢查他在命令行上是否可用...

首先我們使用命令行客戶端加載API概要。
$ coreapi get http://127.0.0.1:8000/schema/
<Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
highlight(id)
list()
read(id)
}
users: {
list()
read(id)
}
我們還沒有認(rèn)證,所以我們現(xiàn)在只能看到只讀路徑,這與我們設(shè)置的API權(quán)限是一致的。
我們使用命令行客戶端,嘗試列出現(xiàn)有代碼片段:
$ coreapi action snippets list
[
{
"url": "http://127.0.0.1:8000/snippets/1/",
"id": 1,
"highlight": "http://127.0.0.1:8000/snippets/1/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world!')",
"linenos": true,
"language": "python",
"style": "friendly"
},
...
一些API路徑需要命名參數(shù)。例如,要獲取特定代碼片段的高亮HTML表示,我們需要提供一個id。
$ coreapi action snippets highlight --param id=1
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Example</title>
...
驗(yàn)證我們的客戶端
如果我們想要創(chuàng)建,編輯,和刪除代碼片段,我們需要進(jìn)行有效的用戶身份驗(yàn)證,在這種情況下,我們只需要使用基本的auth。
請確保使用實(shí)際的用戶名和密碼替換下面的<username>和<password>.
$ coreapi credentials add 127.0.0.1 <username>:<password> --auth basic
Added credentials
127.0.0.1 "Basic <...>"
現(xiàn)在,如果我們再次提取概要,我么應(yīng)該可以看到一組可用的交互。
$ coreapi reload
Pastebin API "http://127.0.0.1:8000/schema/">
snippets: {
create(code, [title], [linenos], [language], [style])
delete(id)
highlight(id)
list()
partial_update(id, [title], [code], [linenos], [language], [style])
read(id)
update(id, code, [title], [linenos], [language], [style])
}
users: {
list()
read(id)
}
我們現(xiàn)在能夠與這些路徑行交互。例如,要創(chuàng)建一個新的代碼片段:
$ coreapi action snippets create --param title="Example" --param code="print('hello, world')"
{
"url": "http://127.0.0.1:8000/snippets/7/",
"id": 7,
"highlight": "http://127.0.0.1:8000/snippets/7/highlight/",
"owner": "lucy",
"title": "Example",
"code": "print('hello, world')",
"linenos": false,
"language": "python",
"style": "friendly"
}
然后刪除一個代碼片段:
$ coreapi action snippets delete --param id=7
除了命令行客戶端,開發(fā)人員還可以使用客戶端庫與你的API進(jìn)行交互。Python客戶端庫是第一個可用的庫,并且計劃即將發(fā)布一個Javascript客戶端庫。
有關(guān)定制模式生成和使用Core API客戶端庫的更多詳細(xì)信息,您需要參考完整的文檔。