歡迎訪問我的個人網站:www.comingnext.cn
前言:
按照前面幾篇文章里那樣做,使用Django編寫RESTful API的基本功能已經像模像樣了。我們可以通過不同的URL訪問到不同的資源,通過不同的HTTP請求來實現對資源的不同操作。
但是現在我們的API還有一個很明顯的缺陷,那就是沒有認證和權限功能,任何資源都會任何用戶被隨意更改,所以我們要改進程序,實現以下功能:
- snippet與其創建者相互關聯
- 只有經過身份驗證(登錄)的用戶才可以創建snippets
- 只有創建該snippet的用戶才可以對其進行更改或者刪除
- 未經驗證的用戶只具有訪問(只讀)的功能
修改snippet模型
首先,我們想讓snippets都和它們的創建用戶關聯起來,所以我們自然的要在Snippet模型添加一個owner字段來表示。另外,我們還添加一個highlighted字段用來實現代碼高亮,修改snippets/models.py的Snippet:
owner = models.ForeignKey('auth.User', related_name='snippets', on_delete=models.CASCADE) highlighted = models.TextField()
想要實現代碼高亮,當然不是上面一行代碼就搞定了,它現在還只是一個普通的字段而已。我們要做的是在保存的時候,也就是當執行save()時, 我們使用pygments生成高亮后的HTML,還是在model.py,首先導入相關的庫:
from pygments.lexers import get_lexer_by_name from pygments.formatters.html import HtmlFormatter from pygments import highlight
然后在Snippet類中添加save()方法:
def save(self, *args, **kwargs): """ 使用pygments庫來生成能使代碼高亮的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)
在保存數據的時候就會執行上面這個方法,整個方法的功能如注釋所示,在這一篇文章中還不會具體的展示這個功能,在接下來的文章中會展示。
修改了模型當然需要同步一下數據庫了,在這里我們和官方文檔一樣把數據庫刪了在重新生成,首先把工程目錄下的db.sqlite3以及snippets下的migrations文件夾刪除,然后再執行遷移步驟:
python manage.py makemigrations snippets
python manage.py migrate
同時,由於我們想要實現的是訪問各個snippet時顯示相應的創建者,所以這里需要創建幾個不同的賬戶稍后才可以顯示。
python manage.py createsuperuser
為我們的用戶模型添加端點
原理和之前的SnippetSerializer基本一樣,在snippets/serializers.py中添加一個User序列化器:
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 = serializers.PrimaryKeyRelatedField(many=True, queryset=Snippet.objects.all())
因為snippets在User模型中是一個反向關系,在使用ModelSerializer類時默認情況是不會包括這個關系,就是說通過Snippet的owner能查詢到User,而User這邊查詢不到一個用戶創建的snippet,所以我們需要手動為用戶序列添加這個字段。
弄好了User的序列化器,接着就要讓其能夠顯示出來,所以要添加相關的視圖類,編輯view.py:
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
寫好了視圖函數,想要通過URL訪問到它們,肯定是配置一下路由分發啦,編輯snippets/urls.py,添加下面的匹配模式:
url(r'^users/$', views.UserList.as_view()), url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
把Snippets和Users關聯起來
到了這里,如果像之前那樣創建代碼段的話,我們還不能把Snippets和Users關聯起來。因為在使用的時候User的數據是通過request傳入的,而不是以序列化的數據傳遞過來。
而我們剛才添加了一個owner作為外鍵,這個時候就要看到它的用處了,編輯view.py,為SnippetList視圖類添加一個方法:
class SnippetList(generics.ListCreateAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly,) def perform_create(self, serializer): serializer.save(owner=self.request.user)
這個perform_create() 可以讓用戶在通過POST請求創建一個新的Snippet時,在保存新的Snippet數據的時候會把request中的user賦值給Snippet的owner。等下具體使用的時候就可以輕松的理解了。
更新serializer
上一步已經把兩者關聯起來了,owner會在創建新的Snippet的時候擁有User的各個屬性,那么在API中要讓owner顯示id還是用戶名,為了提高可讀性,答案當然是顯示用戶名了,所以我們在SnippetSerializer 下面增加一個字段:
owner = serializers.ReadOnlyField(source='owner.username')
這里的source參數就指定了哪個屬性用於填充字段,為了在使用的時候顯示owner,但是還要把它添加進Meta類里面,所以整個SnippetSerializer如下:
class SnippetSerializer(serializers.ModelSerializer): # 這里可以使用也 CharField(read_only=True) 來替換 owner = serializers.ReadOnlyField(source='owner.username') class Meta: model = Snippet fields = ('id', 'title', 'code', 'linenos', 'language', 'style','owner')
添加權限
現在Snippet和User已經關聯起來並且是可瀏覽的。接下來我們要實現的及時權限的問題了。也就是我們一開始說的幾點中的:
-
只有經過身份驗證(登錄)的用戶才可以創建snippet
-
只有創建該snippet的用戶才可以對其進行更改或者刪除
-
未經驗證的用戶只具有訪問(只讀)的功能
首先在views.py導入一個庫:
from rest_framework import permissions
接着為SnippetList 和 SnippetDetail添加權限判斷,在這兩個視圖類中都加入:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
這里要特別注意,有一個坑,就是那個逗號一定要加上去,不然就會報錯。
這行代碼的作用就是判斷當前用戶是否為該Snippet的創建者,而其他用戶只有只讀屬性,就是只能查看。
為可瀏覽的API添加登錄功能
剛才添加了權限判斷,如果沒有登錄用戶,那就相當於游客啦,什么功能都沒有只能看,所以在瀏覽器瀏覽API的時候就需要登錄 功能。在這里,強大的django-rest-framework又為我們做了很多事情,想要在添加登錄按鈕和頁面,只需要修改一個rest_tutorial/urls.py,添加一個URL匹配:
urlpatterns += [ url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')), ]
這里的r'^api-auth/'你可以設置成任意你喜歡的,但是命名空間一定要相同,就是namespace='rest_framework'。
好了,現在打開瀏覽器,就可以看到在我們的API頁面的右上角有一個登錄的按鈕,點擊之后就可以使用之前創建的用戶登錄了。
這個時候訪問單個用戶的詳情,就可以看到該用戶創建的所有Snippet的id值(需要先創建好幾個Snippet,可以按照本系列第一篇文章中在shell模式中的方法來創建)。比如訪問:
http://127.0.0.1:8000/users/2/
可以看到:
添加對象權限
接着我們要實現的是讓所有的Snippet可以被所有人訪問到,但是每個Snippet只有其創建者才可以對其進行更改、刪除等操作。
因此,我們需要設置一下自定義權限,使每個Snippet只允許其創建者編輯它。在snippets目錄下新建一個permissions.py:
from rest_framework import permissions class IsOwnerOrReadOnly(permissions.BasePermission): """ 使每個Snippet只允許其創建者編輯它 """ def has_object_permission(self, request, view, obj): # 任何用戶或者游客都可以訪問任何Snippet,所以當請求動作在安全范圍內, # 也就是GET,HEAD,OPTIONS請求時,都會被允許 if request.method in permissions.SAFE_METHODS: return True # 而當請求不是上面的安全模式的話,那就需要判斷一下當前的用戶 # 如果Snippet所有者和當前的用戶一致,那就允許,否則返回錯誤信息 return obj.owner == request.user
代碼的邏輯已在注釋中,簡單說就是提供判斷功能,然后我們要把它運用起來,在view.py中的SnippetDetail 修改一下:
class SnippetDetail(generics.RetrieveUpdateDestroyAPIView): queryset = Snippet.objects.all() serializer_class = SnippetSerializer permission_classes = (permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly,)
注意要導入IsOwnerOrReadOnly類:
from snippets.permissions import IsOwnerOrReadOnly
現在用瀏覽器打開單個Snippet詳情頁,如果你當前登錄的用戶是這個Snippet的創建者,那你會發現多了DELETE和PUT兩個操作,比如訪問http://127.0.0.1:8000/snippets/2/,效果如下:
使用API授權
由於現在我們還沒使用authentication 類,所以項目目前還是使用默認的SessionAuthentication 和 BasicAuthentication.
在使用瀏覽器訪問API的時候,瀏覽器會幫我們保存會話信息,所以當權限滿足時就可以對一個Snippet進行刪除或者更改,或者是創建一個新的Snippet。
當如果是通過命令行來操作API,我們就必須在每次發送請求的時候添加授權信息,也就是用戶名和密碼,沒有的話就會報錯,比如:
http POST http://127.0.0.1:8000/snippets/ code="print 123" { "detail": "Authentication credentials were not provided." }
正確的操作如下:
http -a username1:password POST http://127.0.0.1:8000/snippets/ code="print 789" { "id": 1, "owner": "username1", "title": "", "code": "print 789", "linenos": false, "language": "python", "style": "friendly" }
我們可以看出owner就是提交過來的用戶名,這就是上面代碼的功能體現:
def perform_create(self, serializer): serializer.save(owner=self.request.user)
通過實際使用更能理解程序,owner會在一個用戶創建Snippet時得到該用戶的信息就是這么來的。
關於認證和權限的部分就先到這了。我們在上面寫的代碼中,highlight部分,我們說它的功能是生成能讓代碼段高亮的HTML代碼,這一部分還沒有使用到,接下來就介紹它。
本文地址:http://www.cnblogs.com/zivwong/p/7456591.html
作者博客:ziv
歡迎轉載,請在明顯位置給出出處及鏈接