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