认证和权限

2018-04-18  本文已影响24人  SingleDiego

目前,我们的 API 对谁可以编辑或删除代码段没有任何限制。我们希望有更高级的行为,以确保:




准备工作

首先我们需要修改我们的 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 展示为只读视图,因此我们将使用 ListAPIViewRetrieveAPIView

# 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 类,区别于其他类型的字段(如 CharFieldBooleanField 等)。无类型的 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 字段添加到 BookListBookDetail 的视图类:

# 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 创建者的身份登录的话,DELETEPUT 操作才会显示。




给 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' 命名空间。

上一篇下一篇

猜你喜欢

热点阅读