Django+React全栈开发:关联用户

创建于更新于文集:Django+React全栈开发

上一篇文章其实已经讲了一点登录验证相关的内容,不过主要还是为了回答一位群友关于定制DJango用户模型的提问而临时写的,认证(authentication)与授权(authorization)实质上是两个步骤,但是一般都放在一起讲,认证是识别身份,你用管理员账户登录,密码正确,身份被确认为管理员,这是认证,因为是文章作者,所以有编辑文章的权限,这是授权。

文章关联用户

用户与文章应该是一对多的关系,首先要修改我们的Article模型:

class Article(models.Model):
    author = models.ForeignKey(
        User,
        on_delete=models.CASCADE,
        related_name='articles'
    )
# ......

接着去执行迁移操作:

$ python manage.py makemigrations
$ python manage.py migrate

注意到之前的文章都是没有作者的,所以执行迁移的时候会要求要么退出迁移,在模型上添加默认值,或者现在给出默认值。建议多熟悉迁移操作并且适当掌握一些SQL用法,这种涉及到数据变更的,有时候还得手动解决冲突。这里给我们之前创建的管理员账户ID做默认值就行,当然开发环境简单粗暴点也可以直接删库。

先修改一下序列化器,加入author字段:

class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = ['id', 'title', 'body', 'author', 'created', 'updated']

然后用命令行工具httpie测试一下API:

$ http POST 127.0.0.1:8000/api/articles/ title="user" body="relationship" author=1

可以正常创建新文章,但是,我们只要在POST请求里带上用户ID,就可以为任意用户创建文章,这是不可取的,现在来解决这个问题。

权限类

上一节已经讲过了自定义权限类,现在来写一个只允许管理员创建删除修改,其他用户只读的权限类。

新建article/permissions.py

from rest_framework import permissions


class IsAdminOrReadOnly(permissions.BasePermission):
    def has_permission(self, request, view):
        if request.method in permissions.SAFE_METHODS:
            return True
        return request.user.is_superuser

继承父类的has_permission方法,这个方法的返回值是布尔值,True即表示授权通过,对任意用户的请求在SAFE_METHODS内的,直接通过,否则就看用户是否是管理员。

# SAFE_METHODS源码
SAFE_METHODS = ('GET', 'HEAD', 'OPTIONS')

现在要修改一下视图类:

# 引入自定义的权限类
from article.permissions import IsAdminOrReadOnly


class ArticleViewSet(viewsets.ModelViewSet):
    # 加上这一行
    permission_classes = [IsAdminOrReadOnly]

现在再使用httpie测试,会得到错误响应:

{
    "detail": "Authentication credentials were not provided."
}

现在带上验证信息再请求一次:

$ http -a elliot:test1234 POST 127.0.0.1:8000/api/articles/ title="user" body="relationship" author=1

......
{
    "author": 1,
    "body": "relationship",
    "created": "2021-04-18T07:24:40.144287Z",
    "id": 5,
    "title": "user",
    "updated": "2021-04-18T07:24:40.144560Z"
}

这次成功了,但是还有个问题,就是author这个字段仍然需要传递,并且只要是管理员,这个author值可以填任何已存在的用户ID。现在再来修复这个问题。

请求对象

class ArticleViewSet(viewsets.ModelViewSet):

    # 添加这个方法
    def perform_create(self, serializer):
        serializer.save(author=self.request.user)

这里覆写了父类的perform_create方法,在序列化器保存时,从请求对象中获取user值并赋值给author参数,但是用户仍然可以传递author字段,可以在序列化器中把它设置为只读:

class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        # 添加属性
        read_only_fields = ['author', ]

再次测试:

$ http -a elliot:test1234 POST 127.0.0.1:8000/api/articles/ title="author is readonly" body="author is readonly" author=2
.....
{
    "author": 1,
    "body": "author is readonly",
    "created": "2021-04-18T07:39:31.175273Z",
    "id": 8,
    "title": "author is readonly",
    "updated": "2021-04-18T07:39:31.175525Z"
}

这样即使传递了非法的author字段也会被忽略掉。

Copyright © 2020-2021 公子政的宅日常