驗(yàn)證與授權(quán)
目前來(lái)看铡恕,我們的 API 并沒(méi)有權(quán)限上的限制(即任何人都可以編輯或刪除我們的 Movies )材蹬,這不是我們想要的嗤形。所以我們需要在 API 上做些限制以確保:
- Movies 與 Users 關(guān)聯(lián)起來(lái)无埃。
- 只有授權(quán)了的用戶才能創(chuàng)建新的 Movies鹤树。
- 只有 Movies 的創(chuàng)建者才可以更新或刪除它赴背。
- 未授權(quán)的用戶只能進(jìn)行查看椰拒。
在 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)
關(guān)聯(lián)導(dǎo)演類的注釋解開(kāi),來(lái)看看多張表在生成的 api 里的關(guān)聯(lián)性凰荚。
接著在 models.py
中的 Movies 類中加入以下代碼來(lái)確定 Movies 的創(chuàng)建者:
owner = models.ForeignKey('auth.User', related_name='Movies')
最后 models.py
代碼為:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from django.db import models
# 舉個(gè)栗子
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 關(guān)聯(lián)了 Movies 類 和 celecrity 類, 在第4章會(huì)用到 celebrity 類
director = models.ForeignKey('celebrity', related_name='movies')
# 關(guān)聯(lián) User 類來(lái)確定 Movies 的創(chuàng)建者
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)
修改完了模型燃观,我們需要更新一下數(shù)據(jù)表。
通常來(lái)講便瑟,我們會(huì)創(chuàng)建一個(gè)數(shù)據(jù)庫(kù) migration 來(lái)更新數(shù)據(jù)表缆毁,但是為了圖省事兒,寶寶我索性刪了整張 Movies 表直接重建到涂!
在數(shù)據(jù)庫(kù)中刪除 douban_movies 表后在終端中執(zhí)行以下命令:
$ python manage.py syncdb
接著我們可能會(huì)需要多個(gè) User 來(lái)測(cè)試 API 脊框,如果之前你沒(méi)有創(chuàng)建 Django Super User 的話,用以下命令創(chuàng)建:
$ python manage.py createsuperuser
然后進(jìn)入 http://127.0.0.1/admin/
界面践啄,登錄并找到 /user/
表浇雹,然后在里面手動(dòng)創(chuàng)建 user 并賦予權(quán)限。
為新增的模型增加 endpoints
既然現(xiàn)在我們已經(jīng)有了 users 模型和 celebrity 模型屿讽,那么現(xiàn)在需要做的就是在 serializer.py
中讓他們?cè)?API 中展現(xiàn)出來(lái)昭灵,加入以下代碼:
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')
因?yàn)槲覀冎霸?models.py
中添加了 owner = models.ForeignKey('auth.User', related_name='movies')
其中 related_name
設(shè)置了可以通過(guò) User.movies 來(lái)逆向訪問(wèn)到 movies 表。所以在 ModelSerializer
類中我們需要在 fields 中添加一個(gè) movies
來(lái)實(shí)現(xiàn)逆向訪問(wèn)。同理 DirectorSerializer
類中也進(jìn)行相應(yīng)修改虎锚。
接著硫痰,我們還需要在 views.py
中添加相應(yīng)的視圖。
為 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
把視圖關(guān)聯(lián)起來(lái)柱徙,在 urlpatterns
中加入以下4個(gè) 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 關(guān)聯(lián)起來(lái)
現(xiàn)在,如果我們新建一部 movie 护侮,那它和 director 還有 user 是沒(méi)有關(guān)聯(lián)的敌完,因?yàn)?director 和 user 信息是通過(guò) request 接收到的,而不是通過(guò)序列器接收的羊初,這意味著滨溉,數(shù)據(jù)庫(kù)中收到 director 和 user 信息是沒(méi)有(和 movies 存在)外鍵關(guān)系的。
而要讓他們發(fā)生關(guān)系 长赞,我們的做法是在視圖中重寫 .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 時(shí)將其傳回給序列器里的 owner 和 director 了得哆。
更新序列器
在視圖中重寫了 .perform_create()
方法后還需要更新下序列器才能實(shí)現(xiàn)他們之間的關(guān)聯(lián)脯颜,在 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
關(guān)鍵字負(fù)責(zé)控制在 fields 中展現(xiàn)的數(shù)據(jù)的源,它可以指向這個(gè)序列器實(shí)例的任意一個(gè)屬性贩据。
對(duì) owner 屬性栋操,我們用的是 ReadOnlyField
在確保它始終是只讀的,我們也可以用 CharField(read_only=True)
來(lái)等效替代饱亮,但是我嫌它太長(zhǎng)了矾芙,其余的 Field 還有諸如 CharField
、 BooleanField
等近尚,你可以在 「這里」查到蠕啄。
添加權(quán)限
我們希望授權(quán)的用戶才能新建场勤、更新和刪除 movies戈锻,所以需要添加權(quán)限管理的功能。
DRF 包含了一系列的 permission 類來(lái)實(shí)現(xiàn)權(quán)限管理和媳,你可以在「這里」 查到格遭。
在這個(gè)栗子中,我們使用 IsAuthenticatedOrReadOnly
來(lái)確保授權(quán)的請(qǐng)求得到讀寫的權(quán)限留瞳,未授權(quán)的請(qǐng)求只有只讀權(quán)限拒迅。
首先,在 views.py
中 import 以下模塊:
from rest_framework import permissions
接著,在 MoviesList
和 MoviesDetail
中加入以下代碼:
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
添加可瀏覽的授權(quán) api
如果你在瀏覽器中訪問(wèn)我們的 api Web 界面璧微,你會(huì)發(fā)現(xiàn)我們沒(méi)法創(chuàng)建新的 movies 了作箍,因?yàn)樵谏弦徊轿覀冊(cè)O(shè)置了權(quán)限管理。
所以我需要在瀏覽器中添加用戶登錄來(lái)實(shí)現(xiàn)帶界面的權(quán)限管理前硫。(之所以說(shuō)帶界面是因?yàn)榭梢栽诮K端中直接使用 httpie 來(lái)訪問(wèn) api )
在 restapi/urls.py
中加入以下代碼:
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls',
namespace='rest_framework')),
]
這樣通過(guò)在瀏覽器中訪問(wèn) Web api 界面就能在右上角發(fā)現(xiàn)一個(gè)登錄按鈕胞得,進(jìn)行登錄授權(quán)了。
對(duì)象級(jí)權(quán)限
之前提到要使 movies 可以被任何人訪問(wèn)屹电,但是只能被創(chuàng)建者編輯阶剑,所以需要賦予其游客訪問(wèn)的權(quán)限以及創(chuàng)建者編輯權(quán)限。
下面我們新建一個(gè) permissions.py
來(lái)詳細(xì)解決這個(gè)權(quán)限問(wèn)題:
#!/usr/bin/python
# -*- coding: utf-8 -*-
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
游客訪問(wèn)權(quán)限及創(chuàng)建者編輯權(quán)限
"""
def has_object_permission(self, request, view, obj):
# 游客權(quán)限
if request.method in permissions.SAFE_METHODS:
return True
# 編輯權(quán)限
return obj.owner == request.user
修改 views.py
中 MoviesDetail
的 permission_class
:
from douban.permissions import IsOwnerOrReadOnly
permission_classes = (permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,)
終于危号,我們完成了整個(gè) api 授權(quán)的過(guò)程牧愁!
https://github.com/thehackercat/django-rest-framework-tutorial/blob