认证和权限
目前,我们的 API 对谁可以编辑或删除代码段没有任何限制。我们希望有更高级的行为,以确保:
- book 对象与创建者相关联。
- 只有登录用户可以创建 book。
- 只有 book 的创建者可以更新或删除它。
- 未登录用户只有只读访问的权限。
准备工作
首先我们需要修改我们的 Models 用 ForeignKey
让 Book 和 User 两个模型关联起来。
编辑 models.py
,添加 owner
字段:
# models.py
from django.db import models
class Book(models.Model):
title = models.CharField(null=True, blank=True, max_length=50)
author = models.CharField(null=True, blank=True, max_length=50)
amount = models.IntegerField()
# related_name 是必须要写的
owner = models.ForeignKey('auth.User', related_name='books')
def __str__(self):
return self.title
创建 User API
模型修改完毕,接下来我们修改序列化器,添加一个 User 模型的序列化器,编辑 serializers.py
文件:
# serializers.py
from django.contrib.auth.models import User
from rest_framework import serializers
class UserSerializer(serializers.ModelSerializer):
# 这里的 book 和模型中 related_name 定义的名字要相同
books = serializers.PrimaryKeyRelatedField(many=True, queryset=Book.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'books')
因为 book 字段在 User 模型中是一个反向关联关系。在使用 ModelSerializer
类时它默认不会被包含,所以我们需要为它添加一个显式字段(这和 ModelForm
添加新字段很相似)。
接下来在 apis.py
中添加几个视图,通过 UserSerializer
做出展示 User 模型的 API。
我们只想将 User 展示为只读视图,因此我们将使用 ListAPIView
和 RetrieveAPIView
:
# apis.py
from rest_framework import generics
from django.contrib.auth.models import User
from myApp.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
添加相应的路径:
url(r'^api/users/$', apis.UserList.as_view()),
url(r'^api/users/(?P<pk>[0-9]+)/$', apis.UserDetail.as_view()),
打开:http://127.0.0.1:8000/api/users/ 看看我们的 User API:
[
{
"id": 1,
"username": "diego",
"books": [
1,
2
]
},
{
"id": 2,
"username": "user1",
"books": []
}
]
现在每一个用户都会带上来 book 字段,没有 book 与之关联的 user1 也包含一个空的 book 字段。
将 Book 和 User 关联
现在,如果我们创建了一个 book 对象时,并不能将创建该 book 的用户与
book 对象相关联。因为用户不是作为序列化表示的一部分发送的,而是作为请求的一部分发送的。(user 不在传过来的 json 数据中,而是通过 request.user
获得)
我们处理的方式是在我们的 book
视图中重写一个 .perform_create()
方法,这样我们可以修改序列化器中实例保存的方法,添加上 User 信息。
# apis.py
class BookList(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
这样我们的序列化器的 create()
方法执行时将被传递一个附加的 'owner'
字段。
现在,这些 book 和创建它们的用户相关联,让我们更新我们的 BookSerializer
来体现这个关联关系。将 owner
字段添加到 serializers.py
中的序列化器定义:
# serializers.py
from django.contrib.auth.models import User
from myapp.models import Book
from rest_framework import serializers
class BookSerializer(serializers.ModelSerializer):
owner = serializers.ReadOnlyField(source='owner.username')
class Meta:
model = Book
fields = ('id', 'title', 'author', 'amount', 'owner')
class UserSerializer(serializers.ModelSerializer):
books = serializers.PrimaryKeyRelatedField(many=True, queryset=Book.objects.all())
class Meta:
model = User
fields = ('id', 'username', 'books')
owner 的 source
参数控制哪个属性用于填充字段,并且可以指向序列化实例上的任何属性。
我们添加的字段是无类型的 ReadOnlyField
类,区别于其他类型的字段(如 CharField
,BooleanField
等)。无类型的 ReadOnlyField
始终是只读的,只能用于序列化表示,不能用于在反序列化时更新模型实例。我们也可以在这里使用 CharField(read_only=True)
。
更新完 Book 的 API 后我们能看到的数据是这样的:
{
"id": 1,
"title": "book1",
"author": "aythor1",
"amount": 1,
"owner": "diego"
},
owner 字段被填充了 user 的 username
属性。
如果我们不显示地指定 owner
字段:
class BookSerializer(serializers.ModelSerializer):
class Meta:
model = Book
fields = ('id', 'title', 'author', 'amount', 'owner')
得到的 API 是这样的:
{
"id": 1,
"title": "book1",
"author": "aythor1",
"amount": 1,
"owner": 1
},
owner
保存的只是 user
的主键。
添加视图所需的权限
现在,book 与用户是相关联的,我们希望确保只有经过身份验证(可理解为已登录或属于 book 的创建者)的用户才能创建,更新和删除 book。
REST 框架包括许多权限类,我们可以使用这些权限类来限制谁可以访问给定的视图。 在这种情况下,我们需要的是 IsAuthenticatedOrReadOnly
类,这将确保经过身份验证的请求获得读写访问权限,未经身份验证的请求将获得只读访问权限。
要使用权限模块,首先我们先把它引入:
from rest_framework import permissions
然后,将相关 permission_classes
字段添加到 BookList
和 BookDetail
的视图类:
# apis.py
from rest_framework import generics
from rest_framework import permissions
from myApp.serializers import UserSerializer
from myApp.serializers import BookSerializer
from django.contrib.auth.models import User
from myapp.models import Book
class BookList(generics.ListCreateAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 权限相关字段
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
def perform_create(self, serializer):
serializer.save(owner=self.request.user)
class BookDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
# 权限相关字段
permission_classes = (permissions.IsAuthenticatedOrReadOnly,)
class UserList(generics.ListAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
class UserDetail(generics.RetrieveAPIView):
queryset = User.objects.all()
serializer_class = UserSerializer
完成这些操作后未登录用户就只能对 API 进行只读的操作。
对象级别的权限
我们希望所有的 book 都可以被任何人看到,但也要确保只有 book 的创建者(owner
)才能更新或删除它。
为此,我们将需要创建一个自定义权限。在 myApp
中,创建一个新文件permissions.py
。
# permissions.py
from rest_framework import permissions
class IsOwnerOrReadOnly(permissions.BasePermission):
"""
自定义权限只允许对象的所有者编辑它。
"""
def has_object_permission(self, request, view, obj):
# 读取权限允许任何请求,
# 所以我们总是允许GET,HEAD或OPTIONS请求。
if request.method in permissions.SAFE_METHODS:
return True
# 只有该 book 的所有者才允许写权限。
return obj.owner == request.user
现在,我们可以通过在 BookDetail
视图类中的 permission_classes
属性添加自定义的权限。
# apis.py
from myapp.permissions import IsOwnerOrReadOnly # 自定义权限
class BookDetail(generics.RetrieveUpdateDestroyAPIView):
queryset = Book.objects.all()
serializer_class = BookSerializer
permission_classes = (
permissions.IsAuthenticatedOrReadOnly,
IsOwnerOrReadOnly,
)
现在,如果再次打开浏览器,你会发现如果你以 book 创建者的身份登录的话,DELETE
和 PUT
操作才会显示。
给 Browsable API 添加登陆功能
REST 框架中内置了 API 的登录页面,只需要在 urls.py
添加就能直接使用。
from django.conf.urls import include
urlpatterns = [
......
]
urlpatterns += [
url(r'^api-auth/', include('rest_framework.urls', namespace='rest_framework')),
]
现在 API 的登录页面就是:http://127.0.0.1:8000/api-auth/login/
r'^api-auth/'
部分实际上可以是你自定义的任何 URL。唯一的限制是包含的 URL 必须使用 'rest_framework'
命名空间。