Django能夠用LoginRequiredMixin
和PermissionRequiredMixin
給類視圖添加認證和權限,DRF作了高級封裝,提供了更簡潔的實現方式。咱們經過繼續學習官網教程來進行了解。html
首先修改Snippet
模型,添加2個字段:owner
,存儲snippet建立者,highlighted
,存儲高亮HTML。同時重寫save
方法,在同步數據庫的時候,使用pygments
包把code
格式化後存到highlighted
字段。修改後的snippets/models.py
完整代碼以下:python
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() class Meta: ordering = ['created'] 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 = 'table' if self.linenos else False options = {'title': self.title} if self.title else {} formatter = HtmlFormatter(style=self.style, linenos=linenos, full=True, **options) self.highlighted = highlight(self.code, lexer, formatter) super(Snippet, self).save(*args, **kwargs)
接着刪除數據庫和migrations
,從新遷移數據庫:sql
rm -f db.sqlite3 rm -r snippets/migrations python manage.py makemigrations snippets python manage.py migrate
並建立超級管理員:shell
python manage.py createsuperuser
Endpoint,表示API的具體網址。咱們按照models.py
→serializers.py
→views.py
→urls.py
的代碼編寫順序,給User模型添加Endpoint。數據庫
models.pydjango
直接使用Django默認User模型,不須要修改代碼。api
serializers.py瀏覽器
添加UserSerializer
,因爲User沒有snippets
字段,因此須要顯式添加:學習
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']
views.py測試
添加只讀的列表視圖UserList
和詳情視圖UserDetail
,分別用到了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
urls.py
添加訪問路徑:
path('users/', views.UserList.as_view()), path('users/<int:pk>/', views.UserDetail.as_view()),
若是使用POST方法請求http://127.0.0.1:8000/snippets/
,嘗試添加1條數據:
會發現接口報錯了:
owner_id不能爲空?由於前面只給Snippet
添加了owner
字段,尚未寫反序列化更新模型的代碼,因此經過請求訪問視圖,再嘗試反序列化的時候,報錯了。咱們先修改視圖SnippetList
來修復這個問題:
def perform_create(self, serializer): serializer.save(owner=self.request.user)
在SnippetList
視圖中重寫perform_create()
方法,意思是在保存時,把request.user
值賦給owner
字段。perform_create()
方法的源碼是:
class CreateModelMixin: """ Create a model instance. """ def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer): serializer.save()
再修改snippets/serializers.py
,添加owner
字段,支持序列化:
class SnippetSerializer(serializers.ModelSerializer): # ReadOnlyField表示只能序列化爲JSON,不能反序列化更新模型 # 也能夠改爲CharField(read_only=True) owner = serializers.ReadOnlyField(source='owner.username') class Meta: model = Snippet fields = ['id', 'title', 'code', 'linenos', 'language', 'style', 'owner']
注意Meta.fields也要加上
owner
哦。
再請求一次:
剛纔的錯誤沒有了,可是報了個新的錯誤:Snippet.owner
必須是User
實例,給它賦值的是AnonymousUser
(匿名用戶),致使ValueError了。這個報錯是發生這條代碼:
serializer.save(owner=self.request.user)
也就是說請求訪問視圖後,進行反序列化了,可是反序列化失敗了。很是奇怪!咱們的請求中並無用戶信息,正常來講在訪問視圖的時候就該被攔截了。
咱們須要讓API更符合常規,讓未認證的用戶不能執行視圖中的代碼。DRF提供了rest_framework .permissions
來給視圖添加認證:
其中IsAuthenticatedOrReadOnly
表示只有認證了才能讀寫,不然只能讀。把它添加到SnippetList
和SnippetDetail
視圖中:
from rest_framework import permissions permission_classes = [permissions.IsAuthenticatedOrReadOnly]
再請求試試,剛纔的錯誤沒有了,API返回的是須要提供用戶憑證:
若是用瀏覽器打開http://127.0.0.1:8000/snippets/
,會發現只有GET方法沒有POST,這是由於須要添加DRF登陸視圖,在tutorial/urls.py
中添加rest_framework.urls
:
urlpatterns += [ path('api-auth/', include('rest_framework.urls')), ]
api-auth/能夠自定義。
刷新頁面右上角就會出現Log in
按鈕,登陸後就能POST了。
爲了更細粒度的控制權限,讓用戶只能編輯本身建立的snippet
,新建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
新增IsOwnerOrReadOnly
權限,繼承了permissions.BasePermission
,重寫了has_object_permission()
方法。接着在snippets/views.py
中給SnippetDetail
加上:
from snippets.permissions import IsOwnerOrReadOnly permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsOwnerOrReadOnly]
試下訪問其餘用戶建立的snippet
,發現只能查看:
訪問本身建立的snippet
,能夠修改和刪除:
以上是官網的示例,我在Postman測試了下,發現超管dongfanger能夠建立snippet
:
普通用戶player也能夠建立snippet
:
我想讓普通用戶不能建立,只能超管建立。仿照官網示例,在snippets/permissions.py
中添加IsAdminOrReadOnly
:
class IsAdminOrReadOnly(permissions.BasePermission): def has_permission(self, request, view): return request.user.is_superuser
接着給SnippetList
加上:
permission_classes = [permissions.IsAuthenticatedOrReadOnly, IsAdminOrReadOnly]
用普通用戶嘗試建立,提示沒有權限:
用超級管理員嘗試建立,成功:
本文使用的認證方式是默認的SessionAuthentication
和BasicAuthentication
,只要數據庫的用戶名、密碼和請求中的用戶憑證(用戶名、密碼)匹配上了,就認爲認證成功。若是要實現token或jwt認證,須要使用到rest_framework.authentication
:
或rest_framework_jwt.authentication
:
pip install djangorestframework-jwt
這一部份內容官網教程中並無說起,等咱們把教程學完了,之後再找時間來介紹。
DRF實現認證和權限的關鍵在於新增permissions.py
模塊,編寫class,繼承permissions.BasePermission
,重寫has_permission()
或has_object_permission()
方法,再添加class到類視圖的permission_classes
中。這塊的內容比Django的認證系統那套簡潔,可是有點混淆,另外我以前參照網上實現了一版JWT,也有點不同。看來還得寫篇對比的文章才行。
參考資料:
https://www.django-rest-framework.org/tutorial/4-authentication-and-permissions/