5.寻光集后台管理系统-用户管理(序列化器)

2022-12-02 15:40:02 浏览数 (1)

序列化器

drf的核心概念

drf提供了一个serializer类,它可以非常方便的序列化模型对象和查询集为json或者其他形式的内容。

还可以提供反序列化,允许在通过验证传入数据后将解析的数据转换为复杂的类型对象。

序列化

obj->json/html

反序列化

json->obj

小结

  • 处理请求到服务器的时候会对数据进行反序列化成python的对象然后再处理
  • 发送请求到前端时,会将python对象转化成json发送出去

注册序列化器

users路径下新建一个序列化器:

backend/apps/users/serializers.py

在注册账号的时候,需要用户通过前端发送用户名密码邮箱姓名电话这些信息过来,然后后台将他们存到数据库里面

在一些网站注册的时候还会要求用户输入两遍密码,防止用户输入错误,所以我们也做一下这个功能

编写一个类继承于ModelSerializer

代码语言:javascript复制
from rest_framework import serializers

class UserRegisterSerializer(serializers.ModelSerializer):

新增一个再次输入密码的字段,写法和model类似,只是变为了serializers.CharField

也是表示这个字段是个字符串

代码语言:javascript复制
password_confirm = serializers.CharField(
        label='确认密码', help_text='确认密码', write_only=True,
        min_length=, max_length=,
        error_messages={
            'min_length': '仅允许6-20个字符的密码',
            'max_length': '仅允许6-20个字符的密码',
        })

write_only=True:表示只有在进行写入操作的时候才会用到这个字段

就是说只有前端往后端发送请求的时候(写)才需要这个字段

再写一个Meta类,标注使用的数据库,需要的字段

代码语言:javascript复制
class Meta:
    model = User
    fields = ('id', 'username', 'password', 'password_confirm', 'email', 'mobile', 'name')
    extra_kwargs = {
        'username': {
            'label': '用户名',
            'help_text': '用户名',
            'min_length': ,
            'max_length': ,
            'error_messages': {
                'min_length': '仅允许6-20个字符的用户名',
                'max_length': '仅允许6-20个字符的用户名',
            },
        },
        'password': {
            'label': '密码',
            'help_text': '密码',
            'min_length': ,
            'max_length': ,
            'write_only': True,
            'error_messages': {
                'min_length': '仅允许6-20个字符的密码',
                'max_length': '仅允许6-20个字符的密码',
            },
        },
        'email': {
            'label': '邮箱',
            'help_text': '邮箱',
            'required': True,
            'allow_blank': True,
            'validators': [UniqueValidator(queryset=User.objects.all(), message='此邮箱已注册')]
        },
    }

drf中自带了一个判重的校验,如果有定制的校验的话就需要编写一个函数

代码语言:javascript复制
from rest_framework.validators import UniqueValidator

...
'validators': [UniqueValidator(queryset=User.objects.all(), message='此邮箱已注册')]
...

校验某个字段也可以直接使用validate_字段名来进行校验

比如校验两次输入的密码是否一致:

代码语言:javascript复制
def validate_password_confirm(self, value):
    """
    校验两次输入的密码是否一致
    :param value:
    :return:
    """
    if value != self.initial_data['password']:
        raise serializers.ValidationError('两次输入的密码不一致!')
    return value

注册账号,其实就是往用户表中插入一条用户信息,但是我们用户表中其实是没有password_confirm字段的,所以注册的时候需要剔除它

所以需要重写下create方法

代码语言:javascript复制
def create(self, validated_data):
    validated_data.pop('password_confirm')
    return User.objects.create_user(**validated_data)

整体代码

代码语言:javascript复制
class UserRegisterSerializer(serializers.ModelSerializer):
    # password_confirm设置 write_only 只有注册的时候需要使用,返回的时候不需要
    password_confirm = serializers.CharField(label='确认密码', help_text='确认密码', write_only=True,
                                             min_length=, max_length=,
                                             error_messages={
                                                 'min_length': '仅允许6-20个字符的密码',
                                                 'max_length': '仅允许6-20个字符的密码',
                                             })

    class Meta:
        model = User
        fields = ('id', 'username', 'password', 'password_confirm', 'email', 'mobile', 'name')
        # model指定的模型中没有的字段,不能在extra_kwargs中定义
        extra_kwargs = {
            'username': {
                'label': '用户名',
                'help_text': '用户名',
                'min_length': ,
                'max_length': ,
                'error_messages': {
                    'min_length': '仅允许6-20个字符的用户名',
                    'max_length': '仅允许6-20个字符的用户名',
                },
            },
            'password': {
                'label': '密码',
                'help_text': '密码',
                'min_length': ,
                'max_length': ,
                'write_only': True,
                'error_messages': {
                    'min_length': '仅允许6-20个字符的密码',
                    'max_length': '仅允许6-20个字符的密码',
                },
            },
            'email': {
                'label': '邮箱',
                'help_text': '邮箱',
                'required': True,
                'allow_blank': True,
                'validators': [UniqueValidator(queryset=User.objects.all(), message='此邮箱已注册')]
            },
        }

    def validate_password_confirm(self, value):
        """
        校验两次输入的密码是否一致
        :param value:
        :return:
        """
        if value != self.initial_data['password']:
            raise serializers.ValidationError('两次输入的密码不一致!')
        return value

    def create(self, validated_data):
        validated_data.pop('password_confirm')
        # 注意调用create_user方法来创建用户,会对密码进行加密
        return User.objects.create_user(**validated_data)

所以发送注册请求的时候使用UserRegisterSerializer序列化器来将json数据进行处理和校验,然后写入数据库完成注册

登录序列化器

为了减少前端的修改,我们将后端部分尽量修改的兼容前端处理

运行一下前端服务,查看前端的登录请求

请求地址:http://localhost:2800/api/token

请求方式:POST

请求参数:

代码语言:javascript复制
{"username":"admin","password":"21232f297a57a5a743894a0e4a801fc3"}

响应参数:

代码语言:javascript复制
{
    "code": ,
    "data": {
        "token": "SCUI.Administrator.Auth",
        "userInfo": {
            "userId": "1",
            "userName": "Administrator",
            "dashboard": "0",
            "role": [
                "SA",
                "admin",
                "Auditor"
            ]
        }
    },
    "message": ""
}

所以仿照一下它的响应内容,加上jwt编写一个MyTokenObtainPairSerializer序列化器

JWT是JSON Web Token的缩写,是为了在网络应用环境间传递声明而执行的- -种基于JSON的开放标准((RFC 7519)。JWT本身没有定义任何技术实现,它只是定义了一种基于Token的会话管理的规则,涵盖Token需要包含的标准内容和Token的生成过程,特别适用于分布式站点的单点登录(SSO) 场景。

在校验(返回)的时候处理下准备返回的数据,把原来的字段access替换为token

新增userInfo部分,将对应的用户id姓名权限返回

代码语言:javascript复制
from rest_framework_simplejwt.serializers import TokenObtainPairSerializer

class MyTokenObtainPairSerializer(TokenObtainPairSerializer):

    def validate(self, attrs):
        data = super().validate(attrs)
        data['token'] = data.pop('access')
        data["userInfo"] = {
            "userId": self.user.id,
            "userName": self.user.name or "匿名",
            "dashboard": "0",
            "is_superuser": self.user.is_superuser,
            "role": self.user.roles and self.user.roles.split(",") or []
        }
        return data

因为权限存的时候是存的字符串,所以返回的时候为了和前端一致,使用split进行分割

代码语言:javascript复制
 self.user.roles and self.user.roles.split(",") or []

用户增删改查序列化器

在需求中,管理员需要能对其他账号进行增删改查操作,所以需要编写一个比较全的序列化器来处理

  • 密码在响应的时候不展示,所以在extra_kwargs中特别标注
  • 更改密码的时候由于密码是加密的,所以需要使用自带的set_password方法来进行处理
  • 创建账号由于这个是用于管理员的创建账号,所以就不用再次输入密码了,因为就算密码写错了,管理员也可以很方便的直接修改密码
代码语言:javascript复制
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['id', 'username', 'password', 'mobile', 'email', 'is_staff', 'is_active', 'is_superuser', 'name',
                  'date_joined', 'roles']
        extra_kwargs = {
            'password': {
                'write_only': True  # 展示的时候不渲染密码
            }
        }

    def create(self, validated_data):
        user = super().create(validated_data)
        # 手动的处理密码
        user.set_password(validated_data['password'])
        user.save()
        return user

    def update(self, instance, validated_data):
        obj = super().update(instance, validated_data)
        # 判断是否有password过来
        password = validated_data.get('password')
        if password is not None:
            obj.set_password(password)
            obj.save()
        return obj

小结

序列化器是DRF框架最关键的一部分,用好序列化器可以大大减少不必要的方法重写

建议所有的请求和响应都要经过序列化器,也就是

  • 想要返回一个json,先写一个对应数据结构的序列化器
  • 想要处理一个json,先写一个对应数据结构的序列化器

在序列化器的校验(validate方法)中,可以对数据进行一定程度上的改造

0 人点赞