驗證與授權
目前來看,我們的 API 并沒有權限上的限制(即任何人都可以編輯或刪除我們的 Movies ),這不是我們想要的。所以我們需要在 API 上做些限制以確保:
- Movies 與 Users 關聯起來。
- 只有授權了的用戶才能創建新的 Movies。
- 只有 Movies 的創建者才可以更新或刪除它。
- 未授權的用戶只能進行查看。
在 models 中增加以下信息
我們先把之前注釋掉的
director = models.ForeignKey('celebrity', related_name='Movies')
class celebrity(models.Model):
name = models.CharField(max_length=100, blank=True, default='')
age = models.IntegerField()
gender = models.CharField(choices=GENDER_CHOICES, default='male', max_length=20)
關聯導演類的注釋解開,來看看多張表在生成的 api 里的關聯性。
接著在 models.py
中的 Movies 類中加入以下代碼來確定 Movies 的創建者:
owner = models.ForeignKey('auth.User', related_name='Movies')
最后 models.py
代碼為:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from django.db import models
# 舉個栗子
COUNTRY_CHOICES = (
('US', 'US'),
('Asia', 'Asia'),
('CN', 'CN'),
('TW', 'TW'),
)
TYPE_CHOICES = (
('Drama', 'Drama'),
('Thriller', 'Thriller'),
('Sci-Fi', 'Sci-Fi'),
('Romance', 'Romance'),
('Comedy', 'Comedy')
)
GENDER_CHOICES = (
('male', 'male'),
('female', 'female')
)
class Movies(models.Model):
title = models.CharField(max_length=100, blank=True, default='')
year = models.CharField(max_length=20)
# 在 director 關聯了 Movies 類 和 celecrity 類, 在第4章會用到 celebrity 類
director = models.ForeignKey('celebrity', related_name='movies')
# 關聯 User 類來確定 Movies 的創建者
owner = models.ForeignKey('auth.User', related_name='movies')
country = models.CharField(choices=COUNTRY_CHOICES, default='US', max_length=20)
type = models.CharField(choices=TYPE_CHOICES, default='Romance', max_length=20)
rating = models.DecimalField(max_digits=3, decimal_places=1)
created = models.DateTimeField(auto_now_add=True)
class Meta:
ordering = ('created',)
class celebrity(models.Model):
name = models.CharField(max_length=100, blank=True, default='')
age = models.IntegerField()
gender = models.CharField(choices=GENDER_CHOICES, default='male', max_length=20)
修改完了模型,我們需要更新一下數據表。
通常來講,我們會創建一個數據庫 migration 來更新數據表,但是為了圖省事兒,寶寶我索性刪了整張 Movies 表直接重建!
在數據庫中刪除 douban_movies 表后在終端中執行以下命令:
$ python manage.py syncdb
接著我們可能會需要多個 User 來測試 API ,如果之前你沒有創建 Django Super User 的話,用以下命令創建:
$ python manage.py createsuperuser
然后進入 http://127.0.0.1/admin/
界面,登錄并找到 /user/
表,然后在里面手動創建 user 并賦予權限。
為新增的模型增加 endpoints
既然現在我們已經有了 users 模型和 celebrity 模型,那么現在需要做的就是在 serializer.py
中讓他們在 API 中展現出來,加入以下代碼:
class UserSerializer(serializers.ModelSerializer):
movies = serializers.PrimaryKeyRelatedField(many=True, queryset=Movies.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'movies')
class DirectorSerializer(serializers.ModelSerializer):
movies = serializers.PrimaryKeyRelatedField(many=True, queryset=Movies.objects.all())
class Meta:
model = celebrity
fields = ('id', 'name', 'age', 'gender', 'movies')
因為我們之前在 models.py
中添加了 owner = models.ForeignKey('auth.User', related_name='movies')
其中 related_name
設置了可以通過 User.movies 來逆向訪問到 movies 表。所以在 ModelSerializer
類中我們需要在 fields 中添加一個 movies
來實現逆向訪問。同理 DirectorSerializer
類中也進行相應修改。
接著,我們還需要在 views.py
中添加相應的視圖。
為 User 添加只讀 API ,使用 ListAPIView
和 RetrieveAPIView
為 Director 添加讀寫 API ,使用 ListCreateAPIView
和 RetrieveUpdateDestroyAPIView
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class DirectorList(generics.ListCreateAPIView):
queryset = celebrity.objects.all()
serializer_class = DirectorSerializer
class DirectorDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = celebrity.objects.all()
serializer_class = DirectorSerializer
最后,修改 urls.py
把視圖關聯起來,在 urlpatterns
中加入以下4個 patterns:
urlpatterns = [
url(r'^users/$', views.UserList.as_view()),
url(r'^users/(?P<pk>[0-9]+)/$', views.UserDetail.as_view()),
url(r'^directors/$', views.DirectorList.as_view()),
url(r'^directors/(?P<pk>[0-9]+)/$', views.DirectorDetail.as_view()),
]
把 Movies 和 Director 、 User 關聯起來
現在,如果我們新建一部 movie ,那它和 director 還有 user 是沒有關聯的,因為 director 和 user 信息是通過 request 接收到的,而不是通過序列器接收的,這意味著,數據庫中收到 director 和 user 信息是沒有(和 movies 存在)外鍵關系的。
而要讓他們發生關系 ,我們的做法是在視圖中重寫 .perform_create()
方法。
.perform_create()
方法允許我們處理 request 或 requested URL 中的任何信息。
在 MoviesList
和 MoviesDetail
中添加以下代碼:
def perform_create(self, serializer):
serializer.save(owner=self.request.user, director=self.request.celebrity)
這樣 create()
方法就能夠在接收到 request.data 時將其傳回給序列器里的 owner 和 director 了。
更新序列器
在視圖中重寫了 .perform_create()
方法后還需要更新下序列器才能實現他們之間的關聯,在 serializer.py
中的 MoviesSerializer
類添加以下代碼:
owner = serializers.ReadOnlyField(source='owner.username')
director = serializers.CharField(source='celebrity.name')
接著在 class Meta
的 fields 中加入 owner 和 director :
class Meta:
model = Movies
fields = ('id', 'title', 'director', 'year', 'country', 'type', 'rating', 'owner')
source
關鍵字負責控制在 fields 中展現的數據的源,它可以指向這個序列器實例的任意一個屬性。
對 owner 屬性,我們用的是 ReadOnlyField
在確保它始終是只讀的,我們也可以用 CharField(read_only=True)
來等效替代,但是我嫌它太長了,其余的 Field 還有諸如 CharField
、 BooleanField
等,你可以在 「這里」查到。
添加權限
我們希望授權的用戶才能新建、更新和刪除 movies,所以需要添加權限管理的功能。
DRF 包含了一系列的 permission 類來實現權限管理,你可以在「這里」 查到。
在這個栗子中,我們使用 IsAuthenticatedOrReadOnly
來確保授權的請求得到讀寫的權限,未授權的請求只有只讀權限。
首先,在 views.py
中 import 以下模塊:
from rest_framework import permissions
接著,在 MoviesList
和 MoviesDetail
中加入以下代碼:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
添加可瀏覽的授權 api
如果你在瀏覽器中訪問我們的 api Web 界面,你會發現我們沒法創建新的 movies 了,因為在上一步我們設置了權限管理。
所以我需要在瀏覽器中添加用戶登錄來實現帶界面的權限管理。(之所以說帶界面是因為可以在終端中直接使用 httpie 來訪問 api )
在 restapi/urls.py
中加入以下代碼:
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
這樣通過在瀏覽器中訪問 Web api 界面就能在右上角發現一個登錄按鈕,進行登錄授權了。
對象級權限
之前提到要使 movies 可以被任何人訪問,但是只能被創建者編輯,所以需要賦予其游客訪問的權限以及創建者編輯權限。
下面我們新建一個 permissions.py
來詳細解決這個權限問題:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
游客訪問權限及創建者編輯權限
"""
def has_object_permission(self, request, view, obj):
# 游客權限
if request.method in permissions.SAFE_METHODS:
return True
# 編輯權限
return obj.owner == request.user
修改 views.py
中 MoviesDetail
的 permission_class
:
from douban.permissions import IsOwnerOrReadOnly
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
終于,我們完成了整個 api 授權的過程!
https://github.com/thehackercat/django-rest-framework-tutorial/blob