當前我們的API在編輯或者刪除的時候沒有任何限制,我們不希望有些人有高級的行為,確保:
- 代碼段始終與創建者相關聯
- 只允許授權的用戶可以創建代碼段
- 只允許代碼段創建者可以更新和刪除
- 沒有認證的請求應該有一個完整的只讀權限列表
添加用戶信息在我們的models中
我們將對snippet models 進行一些更改,首先,讓我們添加一對fields,其中一個跟我們的用戶表關聯,剩下一個字段用作存儲高亮的html,還需要導入pygments庫來高亮code,最后需要重新定義save方法,添加一些我們自定義配置
修改后的models如下
from django.db import models
from pygments.lexers import get_all_lexers
from pygments.styles import get_all_styles
from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight
LEXERS = [item for item in get_all_lexers() if item[1]]
LANGUAGE_CHOICES = sorted([(item[1][0], item[0]) for item in LEXERS])
STYLE_CHOICES = sorted((item, item) for item in get_all_styles())
class Snippet(models.Model):
created = models.DateTimeField(auto_now_add=True)
title = models.CharField(max_length=100, blank=True, default='')
code = models.TextField()
linenos = models.BooleanField(default=False)
language = models.CharField(choices=LANGUAGE_CHOICES, default='python', max_length=100)
style = models.CharField(choices=STYLE_CHOICES, default='friendly', max_length=100)
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE)
highlighted = models.TextField()
def save(self, *args, **kwargs):
"""
Use the `pygments` library to create a highlighted HTML
representation of the code snippet.
"""
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)
class Meta:
ordering = ('created',)
接下來我們需要將新字段在數據庫中添加,為了中間不必要的麻煩,現在我們直接刪除原來的庫和migration目錄,重新生成
rm -f db.sqlite3
rm -r snippets/migrations
python manage.py makemigrations snippets
python manage.py migrate
最后我們需要創建一個超級用戶來測試API,使用createsuperuser來快速創建
python manage.py createsuperuser
為我們user models添加serializers
我們需要在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'是User模型上的反向關系,所以在使用ModelSerializer類時,它不會被默認包含,因此我們需要為它添加一個顯式字段。
我們還會向views.py添加幾個視圖。我們只想對用戶表示使用只讀視圖,因此我們將使用ListAPIView和RetrieveAPIView通用類的視圖。
from django.contrib.auth.models import User
from snippets.serializers import UserSerializer
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
最后我們需要為剛才的user 視圖添加url配置,修改urls.py
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
將代碼段與用戶關聯
現在,如果我們創建了代碼段,就無法將創建代碼段的用戶與代碼段實例相關聯。用戶不作為序列化表示的一部分發送,而是傳入請求的屬性。
我們處理它的方式是通過在我們的代碼片段視圖上覆蓋一個.perform_create()方法,允許我們修改實例保存的方式,並處理在傳入請求或請求的URL中隱含的任何信息。
在我們SnippetList 視圖類中,添加下面的方法
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
當我們的serializer里create()方法被調用時,將自動添加owner字段和驗證合法的請求數據
更新我們的seralizer
現在sippets創建的時候已經和我們的用戶關聯起來,讓我們更新我們的SnippetSerializer,添加以下定義的字段:
owner = serializers.ReadOnlyField(source='owner.username')
source參數用於控制那個屬性被用來填充字段,並且可以指向序列化實例上的任何屬性。 它也可以采用上面所示的虛線符號,在這種情況下,它將遍歷給定的屬性,與使用Django的模板語言類似的方式。
我們添加的字段是無類型的ReadOnlyField類,與其他類型的字段,例如CharField,BooleanField等相反。無類型的ReadOnlyField總是只讀的,並且將用於序列化表示,但不會 用於在反序列化時更新模型實例。 我們也可以在這里使用CharField(read_only = True)。
同時需要在meta class中添加owner字段
class Meta:
model = Snippet
fields = ('id', 'title', 'code', 'linenos', 'language', 'style','owner')
在views中添加請求權限
現在snippets 已經和我們的用戶關聯上了,我們需要確保僅僅通過驗證的用戶能夠增刪改
REST framework 包含有一些權限類,我們可以用來限制誰可以訪問給定的視圖,在這種情況下,首先我們查找IsAuthenticatedOrReadOnl,這將確保已驗證的請求獲得讀寫訪問權限,並且未驗證的請求獲得只讀訪問權限。
首先在module中導入permissions,
from rest_framework import permissions
然后在SnippetList 和 SnippetDetail view中添加下面這個屬性
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
添加登陸WEB API
如果您打開瀏覽器並轉到可瀏覽的API,您會發現您無法再創建新的代碼段。為此,我們需要能夠以用戶身份登錄。
我們可以通過編輯項目級urls.py文件中的URLconf來添加一個登錄視圖,以便與可瀏覽的API一起使用
編輯urls.py,添加如下內容
from django.conf.urls import include
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
r'^api-auth/' 這個可以自定義,但是后面的namespace必須使用rest_framework作命名空間,但是在django1.9+版本,自動設置,不需要添加
現在,如果您再次打開瀏覽器並刷新頁面,您將在頁面右上角看到一個“登錄”鏈接。如果您以之前創建的用戶之一登錄,則可以再次創建代碼段
創建幾個代碼段后,轉到“/ users /”端點,並注意該表示中包含每個用戶的“代碼段”字段中與每個用戶相關聯的代碼段ID列表。
對象級別的權限
我們希望所有的代碼片段對任何人都可見,但也確保只有創建代碼片段的用戶能夠更新或刪除它。為了實現這個,我們需要創建一個自定義權限
在snippets.app中,創建一個新的文件,名為permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
Custom permission to only allow owners of an object to edit it.
"""
def has_object_permission(self, request, view, obj):
# Read permissions are allowed to any request,
# so we'll always allow GET, HEAD or OPTIONS requests.
if request.method in permissions.SAFE_METHODS:
return True
# Write permissions are only allowed to the owner of the snippet.
return obj.owner == request.user
現在我們在SnippetDetail view中通過編輯permission_classes屬性為snippet實例中添加自定義的權限
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
同時確保導入了自定的權限類
from snippets.permissions import IsOwnerOrReadOnly
現在,如果您再次打開瀏覽器,則會發現如果您以創建代碼段的同一用戶身份登錄,則“DELETE”和“PUT”操作只會顯示在代碼段實例端點上。
使用api進行身份驗證
因為我們現在有一組API的權限,如果我們想要編輯任何snippet,我們需要使用SessionAuthentication和BasicAuthentication驗證我們的請求
當我們通過Web瀏覽器與API交互時,我們可以登錄,然后瀏覽器會話將為請求提供所需的身份驗證。
如果我們以編程方式與API交互,我們需要在每個請求中顯式提供身份驗證憑據。
如果我們嘗試創建一個未驗證的代碼段,我們會收到一條錯誤:
{
"detail": "Authentication credentials were not provided."
}
我們在請求的時候加入驗證,我們將可以創建:
curl -X POST -s -u test:1234.com -H "Content-Type:application/json" -d '{"title": "sec test","code": "python asdasdas manasdnasd", "linenos": false,"language": "python","style": "vim","owner": "fuzengjie"}' http://127.0.0.1:8000/snippets/ | jq .
{
"id": 2,
"title": "sec test",
"code": "python asdasdas manasdnasd",
"linenos": false,
"language": "python",
"style": "vim",
"owner": "test"
}
