DRF嚮導之認證和權限

src

目前爲止,我們的API對誰能編輯或刪除snippet(代碼片段)還沒有任何限制。我們將增加一些擴展功能來確保以下:

  • snippets總關聯一個創建者;
  • 只有認證後的用戶才能創建一個snippets.
  • 只有創建者才能更新或刪除snippet;
  • 非認證請求只擁有隻讀權限。

1. 爲model增加信息

我們先需要對Snippet的model做些修改。首先,增加一些fields. 其中一個用來表示創建者,另一個用來存儲代碼中的HTML高亮。

在模型中增加這兩個字段。

owner = models.ForeignKey('auth.User', related_name='snippets')
highlighted = models.TextField()

我們還需要確保model存儲時,我們能生成高亮字段內容,這裏使用 pygments 代碼高亮庫。


首先需要一些額外的imports:

from pygments.lexers import get_lexer_by_name
from pygments.formatters.html import HtmlFormatter
from pygments import highlight

我們現在爲模型增加一個 .save() 方法:

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)

這些完成後還需要更新一下數據庫裏面的表。通常我們要創建一個數據庫遷移(database migration)來完成,但教程中,我們就只是刪除數據庫然後重新創建:

rm tmp.db
python ./manage.py syncdb 

你可能想創建一些其他用戶來測試這些API,最快捷的方式是利用 createsuperuser 命令。

python ./manage.py createsuperuser 

2. 爲用戶模型增加endpoints

現在我們需要一些用戶,我們最好把用戶呈現也增加到API上,創建一個新的serializer很容易:

from django.contrib.auth.models import User

class UserSerializer(serializers.ModelSerializer):
    snippets = serializers.PrimaryKeyRelatedField(many=True)

    class Meta:
        model = User
        fields = ('id', 'username', 'snippets')

因爲 'snippets' 和用戶model是反向關聯(reverse relationship,即多對一),所以在使用 ModelSerializer時並不會缺省加入,所以我們需要顯式的來實現。

我們還需要創建一些views,對用戶呈現而言,我們最好使用只讀的view,所以使用 ListAPIViewRetrieveAPIView 泛型類Views。

class UserList(generics.ListAPIView):
    model = User
    serializer_class = UserSerializer

class UserDetail(generics.RetrieveAPIView):
    model = User
    serializer_class = UserSerializer

最後,我們需要修改URL conf:

url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()), 

3. 把Snippets與Users關聯

現在,如果我們創建一個code snippet,還沒有方法指定其創建者。User並沒有作爲序列化內容的一部分發送,而是作爲request的一個屬性。

這裏的處理方法是重載snippet view中的 .pre_save() 方法,它可以讓我們處理request中隱式的信息。

SnippetListSnippetDetail 的view類中,都需要添加如下的方法:

def pre_save(self, obj):
    obj.owner = self.request.user

4. 更新 serializer

現在snippets已經和創建者關聯起來了,我們接下來還需要更新SnippetSerializer,在其定義中增加一個新的字段:

owner = serializers.Field(source='owner.username')

Note: 確定你在嵌入類Meta的字段列表中也加入了 'owner'

這個字段所做的十分有趣。source 參數表明增加一個新字段,可以指定序列化實例任何屬性。它可以採用如上的點式表示(dotted notation),這時他可以直接遍歷到指定的屬性。在Django’s template中使用時,也可以採用類似的方式。

我們增加字段是一個無類型 Field 類,而我們之前的字段都是有類型的,例如 CharField, BooleanField etc… 無類型字段總是隻讀的,它們只用在序列化表示中,而在反序列化時(修改model)不被使用。

5. 給view增加權限控制

現在代碼片段 snippets 已經關聯了用戶,我們需要確保只有認證用戶才能增、刪、改snippets.

REST framework 包括許多權限類可用於view的控制。這裏我們使用 IsAuthenticatedOrReadOnly, 它可確保認證的request獲取read-write權限,而非認證的request只有read-only 權限.

現需要在views模塊中增加 import。

from rest_framework import permissions

然後需要在 SnippetListSnippetDetail view類中都增加如下屬性:

permission_classes = (permissions.IsAuthenticatedOrReadOnly,) 

6. 爲可瀏覽API(Browseable API)增加login

如果你打開瀏覽器,訪問可瀏覽API,你會發現只有登錄後才能創建新的snippet了。

我們可以編輯URLconf來增加一個登錄view。首先增加新的import:

from django.conf.urls import include

然後,在文件末尾增加一個pattern來爲browsable API增加 login 和 logout views.

urlpatterns += patterns('',
    url(r'^api-auth/', include('rest_framework.urls',
                               namespace='rest_framework')),
)

具體的, r'^api-auth/' 部分可以用任何你想用的URL來替代。這裏唯一的限制就是 urls 必須使用'rest_framework' 命名空間。

現在如果你打開瀏覽器,刷新頁面會看到頁面右上方的 ‘Login’ 鏈接。如果你用之前的用戶登錄後,你就又可以創建 snippets了。

一旦你創建了一些snippets,當導航至’/users/'時,你會看到在每個user的snippets字段都包含了一系列snippet的pk。


7. 對象級別的權限

我們希望任何人都可以瀏覽snippets,但只有創建snippet的用戶才能編輯或刪除它。

爲了實現這個需求,我們需要創建定製的權限(custom permission)。

在 snippets 應用中,創建一個新文件: 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 

現在我們可以爲snippet實例增加定製權限了,需要編輯 SnippetDetail 類的 permission_classes 屬性:

permission_classes = (permissions.IsAuthenticatedOrReadOnly,
                      IsOwnerOrReadOnly,)

別忘了import 這個IsOwnerOrReadOnly 類。

from snippets.permissions import IsOwnerOrReadOnly 

現在打開瀏覽器,你可以看見 ‘DELETE’ 和 ‘PUT’ 動作只會出現在那些你的登錄用戶創建的snippet頁面上了.


8. 通過API認證

我們已經有了一系列的權限,如果我們需要編輯任何snippet,我們需要認證我們的request。因爲我們還沒有建立任何 authentication classes, 所以目前是默認的SessionAuthenticationBasicAuthentication在起作用。

當我們通過Web瀏覽器與API互動時,我們登錄後、然後瀏覽器session可以爲所有的request提供所需的驗證。

如果我們使用程序訪問這些API,我們則需要顯式的爲每個request提供認證憑證(authentication credentials)。

如果我們試圖未認證就創建一個snippet,將得到錯誤如下:

curl -i -X POST http://127.0.0.1:8000/snippets/ -d "code=print 123"
{"detail": "Authentication credentials were not provided."}

如果我們帶着用戶名和密碼來請求時則可以成功創建:

curl -X POST http://127.0.0.1:8000/snippets/ -d "code=print 789" -u tom:password
{"id": 5, "owner": "tom", "title": "foo", "code": "print 789", "linenos": false, "language": "python", "style": "friendly"}

9. 小結

我們已經爲我們的Web API創建了相當細粒度的權限控制和相應的系統用戶。

在教程第5部分 part 5 ,我們將把所有的內容串聯起來,爲我們的高亮代碼片段創建HTML節點,並利用系統內的超鏈接關聯來提升API的一致性表現。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章