目前,我們的API對(duì)誰(shuí)可以編輯或刪除代碼段沒(méi)有任何限制。我們希望有更高級(jí)的行為,以確保:
我們將對(duì)我們的Snippet模型類(lèi)進(jìn)行幾次更改。首先,我們添加幾個(gè)字段。其中一個(gè)字段將用于表示創(chuàng)建代碼段的用戶,另一個(gè)字段將用于存儲(chǔ)代碼的高亮顯示的HTML內(nèi)容。
將以下兩個(gè)字段添加到models.py文件中的Snippet模型中。
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
我們還需要確保在保存模型時(shí),使用pygments代碼高亮顯示庫(kù)填充要高亮顯示的字段。
我們需要導(dǎo)入額外的模塊:
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
現(xiàn)在我們可以在我們的模型類(lèi)中添加一個(gè).save()方法:
def save(self, *args, **kwargs):
"""
使用`pygments`庫(kù)創(chuàng)建一個(gè)高亮顯示的HTML表示代碼段。
"""
lexer = get_lexer_by_name(self.language)
linenos = self.linenos and 'table' or False
options = self.title and {'title': self.title} or {}
formatter = HtmlFormatter(style=self.style, linenos=linenos,
full=True, **options)
self.highlighted = highlight(self.code, lexer, formatter)
super(Snippet, self).save(*args, **kwargs)
完成這些工作后,我們需要更新我們的數(shù)據(jù)庫(kù)表。 通常這種情況我們會(huì)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù)遷移(migration)來(lái)實(shí)現(xiàn)這一點(diǎn),但現(xiàn)在我們只是個(gè)教程示例,所以我們選擇直接刪除數(shù)據(jù)庫(kù)并重新開(kāi)始。
rm -f tmp.db db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
你可能還需要?jiǎng)?chuàng)建幾個(gè)不同的用戶,以用于測(cè)試API。最快的方法是使用createsuperuser命令。
python manage.py createsuperuser
現(xiàn)在我們已經(jīng)創(chuàng)建了一些用戶,我們最好在API中添加這些用戶的表示。創(chuàng)建一個(gè)新的序列化器非常簡(jiǎn)單,在serializers.py文件中添加:
from django.contrib.auth.models import User
class UserSerializer(serializers.ModelSerializer):
snippets = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'snippets')
因?yàn)?snippets' 在用戶模型中是一個(gè)反向關(guān)聯(lián)關(guān)系。在使用 ModelSerializer 類(lèi)時(shí)它默認(rèn)不會(huì)被包含,所以我們需要為它添加一個(gè)顯式字段。
我們還會(huì)在views.py中添加幾個(gè)視圖。我們只想將用戶展示為只讀視圖,因此我們將使用ListAPIView和RetrieveAPIView通用的基于類(lèi)的視圖。
from django.contrib.auth.models import User
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
確保導(dǎo)入了UserSerializer類(lèi)
from snippets.serializers import UserSerializer
最后,我們需要通過(guò)在URL conf中引用它們來(lái)將這些視圖添加到API中。將以下內(nèi)容添加到urls.py文件的patterns中。
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
現(xiàn)在,如果我們創(chuàng)建了一個(gè)代碼片段,并不能將創(chuàng)建該代碼片段的用戶與代碼段實(shí)例相關(guān)聯(lián)。用戶不是作為序列化表示的一部分發(fā)送的,而是作為傳入請(qǐng)求的屬性。(譯者注:user不在傳過(guò)來(lái)的數(shù)據(jù)中,而是通過(guò)request.user獲得)
我們處理的方式是在我們的代碼片段視圖中重寫(xiě)一個(gè).perform_create()方法,這樣我們可以修改實(shí)例保存的方法,并處理傳入請(qǐng)求或請(qǐng)求URL中隱含的任何信息。
在SnippetList視圖類(lèi)中,添加以下方法:
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
我們的序列化器的create()方法現(xiàn)在將被傳遞一個(gè)附加的'owner'字段以及來(lái)自請(qǐng)求的驗(yàn)證數(shù)據(jù)。
現(xiàn)在,這些代碼片段和創(chuàng)建它們的用戶相關(guān)聯(lián),讓我們更新我們的SnippetSerializer來(lái)體現(xiàn)這個(gè)關(guān)聯(lián)關(guān)系。將以下字段添加到serializers.py中的序列化器定義: Add the following field to the serializer definition in serializers.py:
owner = serializers.ReadOnlyField(source='owner.username')
注意:確保你還將'owner',添加到內(nèi)部Meta類(lèi)的字段列表中。
這個(gè)字段非常有趣。source參數(shù)控制哪個(gè)屬性用于填充字段,并且可以指向序列化實(shí)例上的任何屬性。它也可以采用如上所示點(diǎn)加下劃線的方式,在這種情況下,它將以與Django模板語(yǔ)言一起使用的相似方式遍歷給定的屬性。
我們添加的字段是無(wú)類(lèi)型的ReadOnlyField類(lèi),區(qū)別于其他類(lèi)型的字段(如CharField,BooleanField等)。無(wú)類(lèi)型的ReadOnlyField始終是只讀的,只能用于序列化表示,不能用于在反序列化時(shí)更新模型實(shí)例。我們也可以在這里使用CharField(read_only=True)。
現(xiàn)在,代碼片段與用戶是相關(guān)聯(lián)的,我們希望確保只有經(jīng)過(guò)身份驗(yàn)證的用戶才能創(chuàng)建,更新和刪除代碼片段。
REST框架包括許多權(quán)限類(lèi),我們可以使用這些權(quán)限類(lèi)來(lái)限制誰(shuí)可以訪問(wèn)給定的視圖。 在這種情況下,我們需要的是IsAuthenticatedOrReadOnly類(lèi),這將確保經(jīng)過(guò)身份驗(yàn)證的請(qǐng)求獲得讀寫(xiě)訪問(wèn)權(quán)限,未經(jīng)身份驗(yàn)證的請(qǐng)求將獲得只讀訪問(wèn)權(quán)限。
首先要在視圖模塊中導(dǎo)入以下內(nèi)容
from rest_framework import permissions
然后,將以下屬性添加到SnippetList和SnippetDetail視圖類(lèi)中。
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
如果你打開(kāi)瀏覽器并瀏覽我們的API,那么你會(huì)發(fā)現(xiàn)不能創(chuàng)建新的代碼片段。只有登陸用戶才能創(chuàng)建新的代碼片段。
我們可以通過(guò)編輯項(xiàng)目級(jí)別的urls.py文件中的URLconf來(lái)添加可瀏覽的API使用的登錄視圖。
在文件頂部添加以下導(dǎo)入:
from django.conf.urls import include
而且,在文件末尾添加一個(gè)模式(pattern)以包括可瀏覽的API的登錄和注銷(xiāo)視圖。
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
模式的r'^api-auth/'部分實(shí)際上可以是你要使用的任何URL。唯一的限制是包含的URL必須使用'rest_framework'命名空間。在Django 1.9以上的版本中,REST框架將設(shè)置命名空間,因此你可以將其刪除。
現(xiàn)在,如果你再次打開(kāi)瀏覽器并刷新頁(yè)面,你將在頁(yè)面右上角看到一個(gè)“登錄”鏈接。如果你用早期創(chuàng)建的用戶登錄,就可以再次創(chuàng)建代碼片段。
一旦你創(chuàng)建了一些代碼片段后,在'/users/'路徑下你會(huì)注意到每個(gè)用戶的'snippets'字段都包含與每個(gè)用戶相關(guān)聯(lián)的代碼片段的列表。
我們希望所有的代碼片段都可以被任何人看到,但也要確保只有創(chuàng)建代碼片段的用戶才能更新或刪除它。
為此,我們將需要?jiǎng)?chuàng)建一個(gè)自定義權(quán)限。
在snippets app中,創(chuàng)建一個(gè)新文件permissions.py。
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定義權(quán)限只允許對(duì)象的所有者編輯它。
"""
def has_object_permission(self, request, view, obj):
# 讀取權(quán)限允許任何請(qǐng)求,
# 所以我們總是允許GET,HEAD或OPTIONS請(qǐng)求。
if request.method in permissions.SAFE_METHODS:
return True
# 只有該snippet的所有者才允許寫(xiě)權(quán)限。
return obj.owner == request.user
現(xiàn)在,我們可以通過(guò)在SnippetDetail視圖類(lèi)中編輯permission_classes屬性將該自定義權(quán)限添加到我們的代碼片段實(shí)例路徑:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
確保要先導(dǎo)入IsOwnerOrReadOnly類(lèi)。
from snippets.permissions import IsOwnerOrReadOnly
現(xiàn)在,如果再次打開(kāi)瀏覽器,你會(huì)發(fā)現(xiàn)如果你以代碼片段創(chuàng)建者的身份登錄的話,“DELETE”和“PUT”操作才會(huì)顯示在代碼片段實(shí)例路徑上。
現(xiàn)在因?yàn)槲覀冊(cè)贏PI上有一組權(quán)限,如果我們要編輯任何代碼片段,我們都需要驗(yàn)證我們的請(qǐng)求。我們還沒(méi)有設(shè)置任何身份驗(yàn)證類(lèi),所以應(yīng)用的是默認(rèn)的SessionAuthentication和BasicAuthentication。
當(dāng)我們通過(guò)Web瀏覽器與API進(jìn)行交互時(shí),我們可以登錄,然后瀏覽器會(huì)話將為請(qǐng)求提供所需的身份驗(yàn)證。
如果我們?cè)诖a中與API交互,我們需要在每次請(qǐng)求上顯式提供身份驗(yàn)證憑據(jù)。
如果我們通過(guò)沒(méi)有驗(yàn)證就嘗試創(chuàng)建一個(gè)代碼片段,我們會(huì)像下面展示的那樣收到報(bào)錯(cuò):
http POST http://127.0.0.1:8000/snippets/ code="print 123"
{
"detail": "Authentication credentials were not provided."
}
我們可以通過(guò)加上我們之前創(chuàng)建的一個(gè)用戶的用戶名和密碼來(lái)成功創(chuàng)建:
http -a tom:password123 POST http://127.0.0.1:8000/snippets/ code="print 789"
{
"id": 5,
"owner": "tom",
"title": "foo",
"code": "print 789",
"linenos": false,
"language": "python",
"style": "friendly"
}
我們現(xiàn)在已經(jīng)在我們的Web API上獲得了相當(dāng)精細(xì)的一組權(quán)限控制,并為系統(tǒng)的用戶和他們創(chuàng)建的代碼片段提供了API路徑。
更多建議: