Django中的序列化器

一. 序列化器

序列化器的主要工作就是将前端传入后端的JSON数据转换为ORM模型映射

一个序列化器可以同时包含序列化和反序列化两个功能,想要用哪一个功能具体看你怎么调用

1.序列化器的两个功能

序列化: 将模型转换成json数据返回给前端,可以序列化一个模型类,也可以序列化多个模型类(many=True)

反序列化: 将json数据转换成模型,反序列化还可以用来进一步验证信息和保存数据

但验证的时候不一定保存数据入库,但是保存数据入库的时候一定要验证数据

序列化和反序列化的功能本质上都属于序列化器的功能

想要用到序列化器的序列化功能,就传入 ORM模型对象, 使之 模型转JSON

想要用到序列化器的反序列化功能就 传入 JSON数据,使之验证数据或者保存入库

2.序列化器的序列化功能

2.1有模型继承的序列化器

定义一个ORM模型

from django.db import models
# 导入系统自带的用户模型类,在此基础上对起进行继承重写
from django.contrib.auth.models import AbstractUser
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer, BadData
from django.conf import settings

# 继承系统自带的用户模型类,在原有的基础上重新拓展新的字段
class User(AbstractUser):
    """我们自己的用户的模型类"""

    # 给用户新增加手机号字段
    mobile = models.CharField(max_length=11, verbose_name='手机号', unique=True)
    # 给用户新增加邮箱验证状态属性
    email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
    # 用户新增加默认的地址
    default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True,on_delete=models.SET_NULL, verbose_name='默认地址')

    class Meta:
        # 数据库中显示的表的名称
        db_table = 'tb_users'
        # 在admin站点中显示的名称
        verbose_name = '用户'
        verbose_name_plural = verbose_name

定义一个序列化器(这个序列化器是有模型的)

from rest_framework import serializers
# 在序列化器中导入用户的ORM模型
from .models import User

# 因为有ORM USER 模型,所以继承了 serializers.ModelSerializer
class UserDetailSerializer(serializers.ModelSerializer):
    """用户个人中心详细信息序列化器"""
    
    class Meta:  # 由于要序列化的有模型,因此需要添加 class Meta:
        model = User  # 明确用的是 User 模型
        # 使用fields来明确字段,__all__表名包含所有字段,也可以写明具体哪些字段
        fields = ('id', 'username', 'mobile', 'email', 'email_active')

在视图中使用序列化器(用到了序列化器的序列化功能,把ORM模型转换为JSON数据类型返回给前端)

# 导入登录用户的认证信息
from rest_framework.permissions import IsAuthenticated

class UserDetailView(RetrieveModelMixin, GenericAPIView):
	
    permission_classes = [IsAuthenticated]
    serializer_class = UserDetailSerializer

    # 重写了get_object()方法
    # get_object()方法是用来获取用户模型类对象的
    # 但是前端返回的值只有加密之后的token
    # request可以获取请求的用户对象,这是前面的知识点
    # 所以重写get_object()方法,直接强制赋予用户模型类对象
    def get_object(self):
        return self.request.user

    # 通过get方法把用户模型转换为json之后数据传回前端
    def get(self, request):

        # 调用RetrieveModelMixin类内部的retrieve()方法,该方法内部会调用GenericAPIView类内部的get_serializer()方法,来获得json数据
        return self.retrieve(request)
 
############################################################################
最终把JSON数据类型返回给前端,前端接收到JSON数据进行页面展示

2.2 没有模型继承的序列化器

定义一个序列化器(这个序列化器没有模型)

from rest_framework import serializers

# 因为这个序列化器没有用到模型,所以继承自 serializers.Serializer
class BookInfoSerializer(serializers.Serializer):
    """图书数据序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    name = serializers.CharField(label='名称', max_length=20)
    pub_date = serializers.DateField(label='发布日期', required=False)
    readcount = serializers.IntegerField(label='阅读量', required=False)
    commentcount = serializers.IntegerField(label='评论量', required=False)
    image = serializers.ImageField(label='图片', required=False)

使用序列化器序列化一个模型对象

from book.models import BookInfo

# 查询出 id = 4 的时候的 模型对象
book = BookInfo.objects.get(id=4)

# 构造序列化器对象
from book.serializers import BookInfoSerializer
serializer = BookInfoSerializer(book)

# 通过 serializer.data 可以获取 序列化之后的 json 数据
serializer.data ---> {'is_delete': False, 'pub_date': '1987-11-11', 'readcount': 58}

使用序列化器序列化多个模型对象

from book.models import BookInfo

# 查询出所有的模型类对象(多个)
books = BookInfo.objects.all()
# 序列化多个的时候需要通过加上many=True
serializer = BookInfoSerializer(books, many=True)  

serializer.data
[OrderedDict([('id', 3), ('name', '笑傲江湖'), ('pub_date', '1995-12-24'), ('readcount', 28)), OrderedDict([('id', 4), ('name', '雪山飞狐'), ('pub_date', '1987-11-11'), ('readcount', 58))]

3.序列化器的反序列化功能

反序列化主要有两个功能:

1.验证数据

2.保存数据入库

3.1 验证数据

在获取反序列化的数据前,必须调用**is_valid()**方法进行验证,验证成功返回True,否则返回False。

验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误。

验证成功,可以通过序列化器对象的validated_data属性获取数据。

之前定义过的 BookInfoSerializer 序列化器

class BookInfoSerializer(serializers.Serializer):
    """图书数据序列化器"""
    id = serializers.IntegerField(label='ID', read_only=True)
    name = serializers.CharField(label='名称', max_length=20)
    pub_date = serializers.DateField(label='发布日期', required=False)
    readcount = serializers.IntegerField(label='阅读量', required=False)
    commentcount = serializers.IntegerField(label='评论量', required=False)
    image = serializers.ImageField(label='图片', required=False)

通过构造序列化器对象,并将要反序列化的数据传递给data构造参数,进而进行验证

错误
>>> from book.serializers import BookInfoSerializer
>>> data = {'pub_date':123}
>>> serializer = BookInfoSerializer(data=data)
>>> serializer.is_valid()
False

# 验证失败,可以通过序列化器对象的errors属性获取错误信息,返回字典,包含了字段和字段的错误
>>> serializer.errors
{'pub_date': [ErrorDetail(string='Date has wrong format. Use one of these formats instead: YYYY[-MM[-DD]].', code='invalid')], 'name': [ErrorDetail(string='This field is required.', code='required')]}
# 因为出现了错误,所以获取不到任何数据
>>> serializer.validated_data
{}


正确
>>> from book.serializers import BookInfoSerializer
>>> data = {'pub_date':'2010-1-1','name':'python高级'}
# 传入json数据进行验证
>>> serializer = BookInfoSerializer(data=data)

# 在获取反序列化的数据前,必须调用is_valid()方法进行验证,验证成功返回True,否则返回False。
# is_valid()方法还可以在验证失败时抛出异常serializers.ValidationError,可以通过传递raise_exception=True参数开启,REST framework接收到此异常,会向前端返回HTTP 400 Bad Request响应。
>>> serializer.is_valid()
True
>>> serializer.errors
{}

# 可以通过序列化器对象的validated_data属性获取数据(字典格式)
>>> serializer.validated_data
OrderedDict([('name', 'python高级'), ('pub_date', datetime.date(2010, 1, 1))])

3.2 保存数据入库(必须验证数据)

如果创建序列化器对象的时候,没有传递instance实例,则调用save()方法的时候,create()被调用,相反,如果传递了instance实例,则调用save()方法的时候,update()被调用。

定义一个 ORM 模型

# 导入系统自带的用户模型类,在此基础上对起进行继承重写
from django.contrib.auth.models import AbstractUser

class User(AbstractUser):
    """我们自己的用户的模型类"""

    # 给用户增加手机号字段
    mobile = models.CharField(max_length=11, verbose_name='手机号', unique=True)
    # 给用户增加邮箱验证状态属性
    email_active = models.BooleanField(default=False, verbose_name='邮箱验证状态')
    # 用户默认的地址
    default_address = models.ForeignKey('Address', related_name='users', null=True, blank=True,on_delete=models.SET_NULL, verbose_name='默认地址')

    class Meta:
        # 数据库中显示的表的名称
        db_table = 'tb_users'
        # 在admin站点中显示的名称
        verbose_name = '用户'
        verbose_name_plural = verbose_name

定义一个序列化器,使用里面的校验和入库功能

# 因为需要进行校验入库的是数据是有模型的,所以需要继承自 ModelSerializer
class RegisterCreateSerializer(serializers.ModelSerializer):

    # 用户再一次进行提交的时候有三个数据: 校验的密码,短信验证码,用户是否同意协议
    # write_only 表明该字段仅用于反序列化输入,也就是说不需要入库,默认False
    password2 = serializers.CharField(label='校验密码', allow_null=False, allow_blank=False, write_only=True)
    sms_code = serializers.CharField(label='短信验证码', max_length=6,min_length=6, allow_null=False, allow_blank=False, write_only=True)
    allow = serializers.CharField(label='是否同意协议', allow_null=False, allow_blank=False, write_only=True)
    # 增加对token的字段,因为token是jwt自动生成的,我们只需要把他序列化成模型状态就好了,不需要反序列化进行校验
    token = serializers.CharField(label='登录状态的token', read_only=True)  # token不能被修改,也不需要反序列化校验,只需要入库就可以了

    class Meta:
        # model 指明参照哪个模型类
        model = User

        # 使用fields来明确字段,__all__表名包含所有字段
        fields = ('id', 'username', 'password', 'mobile', 'password2', 'sms_code', 'allow', 'token')

        # 使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数
        extra_kwargs = {
            'id': {'read_only': True},
            'username': {
                'min_length': 5,
                'max_length': 20,
                'error_messages': {
                    'min_length': '仅允许5-20个字符的用户名',
                    'max_length': '仅允许5-20个字符的用户名',
                }
            },
            'password': {
                'write_only': True,
                'min_length': 8,
                'max_length': 20,
                'error_messages': {
                    'min_length': '仅允许8-20个字符的密码',
                    'max_length': '仅允许8-20个字符的密码',
                }
            }
        }

    # 开始进行校验

    # 先进行单字段校验(手机号,是否同意协议)
    # 进行手机号校验
    def validate_mobile(self,value):
        if not re.match(r'1[345789]\d{9}', value):
            raise serializers.ValidationError('手机号格式不正确')

        return value

    # 检查用户是否同意协议
    def validate_allow(self, value):

        # 在前端部分,我们已经把同意的格式从二进制转换为字符串
        if value != 'true':
            raise serializers.ValidationError('您尚未同意本协议')

        return value

    # 多个字段校验(手机验证码的校验,两次密码输入的是否一致)
    def validate(self, attrs):

        # 现获取出来两次输入的密码进行比较
        password = attrs.get('password')
        password2 = attrs.get('password2')

        if password != password2:
            raise serializers.ValidationError('两次密码输入不一致')

        # 获取用户的手机号和用户输入短信验证码,通过手机号取出redis中验证码,然后二者进行比较
        mobile = attrs.get('mobile')
        sms_code = attrs.get('sms_code')

        redis_conn = get_redis_connection('code')
        redis_sms_code = redis_conn.get('sms_%s' % mobile)

        # 从redis中获取出来的手机验证码有过期的可能性,需要校验是否为空
        if redis_sms_code is None:
            raise serializers.ValidationError('验证码已经过期')

        # 从redis中获取到的短信验证码和用户输入的短信验码进行比较
        if sms_code != redis_sms_code.decode():
            raise serializers.ValidationError('验证码输入有误')

        # 把校验之后的结返回回去
        return attrs

 ###################################################################################
 ## 若数据校验没有问题的话,则create 中的 validated_data 和 单字段校验中的attrs 是一个数据  ##  
 ###################################################################################
    
    # 重写入库的方法
    def create(self, validated_data):
        # 删除多余字段 ---> 因为在创建用户模型类的时候下面的三个字段并没有创建,
        # 因为这个三个字段只是为了校验用户注册并不包含用户的具体信息
        # 但是却需要对这三个字段进行校验
        # 用户的模型类里面不包含这三个字段,这三个字段却要辅助用户模型类校验,因此在序列化器的最上面
        # 添加字段,但是在入库的时候却删除这三个字段

        del validated_data['password2']
        del validated_data['sms_code']
        del validated_data['allow']

        # 调用这个方法会返回一个用户模型对象
        user = super().create(validated_data)

        # 对密码进行加密
        user.set_password(validated_data['password'])
        # 保存入库
        user.save()

        # 我们在创建了用户之后,需要通过后端jwt产生token,传递给前端
        # 这样用户登录的时候就可以携带token了
        from rest_framework.settings import api_settings

        # 获取两方法
        # JWT_PAYLOAD_HANDLER可以从api_settings的源代码里面获取
        jwt_payload_handler = api_settings.JWT_PAYLOAD_HANDLER
        jwt_encode_handler = api_settings.JWT_ENCODE_HANDLER

        # 调用这两个方法,把用户的信息传递给方法之中,然后通过加密产生了token
        payload = jwt_payload_handler(user)
        token = jwt_encode_handler(payload)

        # 把token消息传递给token,我们在进行序列化的时候把token传递给浏览器
        user.token = token

        return user

定义一个序列化器

# 因为就验证一个字段,也没有模型,所以继承serializers.Serializer
# 这个序列化器主要对用户浏览商品时的商品编号 sku_id 进行验证并校验存入redis
class UserHistorySerializer(serializers.Serializer):
    sku_id = serializers.CharField(label='商品id', required=True)
	
    # 对单字段进行验证
    def validate_sku_id(self, value):
        try:
            SKU.objects.get(pk=value)
        except SKU.DoesNotExist:
            raise serializers.ValidationError('商品不存在')

        return value

    # 去重写create()方法,把数据保存在redis中,不去重写的话,就要保存mysql中了,但是我们没有定义mysql的模型
    def create(self, validated_data):
        # 通过context把放入的user给取出来
        user = self.context.get('request').user
        sku_id = validated_data.get('sku_id')

        # 连接redis
        redis_conn = get_redis_connection('history')
        # LREM key count value---> count = 0 : 移除表中所有与 value 相等的值
        # 也就是说移除所有与sku_id相同的值
        redis_conn.lrem('history_%s' % user.id, 0, sku_id)

        # 先移除旧的数据,然后添加新的数据
        redis_conn.lpush('history_%s' % user.id, sku_id)

        # 只保留5条记录(redis不能保留太多的记录)
        # LTRIM key start stop 对一个列表进行修剪(trim),就是说,让列表只保留指定区间内的元素,不在指定区间之内的元素都将被删除。
        redis_conn.ltrim('history_%s' % user.id, 0, 4)

        return validated_data

定义一个把展示商品的ORM模型转换为JSON的序列化器

# 因此涉及到展示商品,而展示商品是有模型的,所以继承ModelSerializer
class SKUSerializer(serializers.ModelSerializer):

    class Meta:
        model = SKU
        fields = ('id', 'name', 'price', 'default_image_url', 'comments')

定义一个视图来使用 序列化器

# 导入序列化器
from .serializer import UserHistorySerializer
from goods.models import SKU

# 记录用户浏览历史的类
class UserHistoryView(GenericAPIView):

    # 登录用户才能访问
    permission_classes = [IsAuthenticated]
    # 添加序列化器,对信息尽心校验
    serializer_class = UserHistorySerializer

    def post(self, request):

        # 在构造序列化器对象时,可以通过context参数添加额外的数据给序列化器
        # request里面有user,把request传递给序列化器,可以在序列化器中通过request
        # 取出user,然后作为redis的唯一辨识度的键来使用
        serializer = self.get_serializer(data=request.data, context={'request': request})
        serializer.is_valid(raise_exception=True)
        # 向序列化器中传入的data而没有传入instance,说明serializer.save()的时候,只会调用create()方法,而不会调用update()方法
        serializer.save()

        return Response({'message': 'OK'})

    # 查看用户浏览历史的方法
    def get(self, request):
        # 获取出当前查看浏览历史的对象
        user = request.user

        # 连接redis
        redis_conn = get_redis_connection('history')
        # 通过用的id把浏览记录给获取出来(获取出来的是列表数据)
        ids = redis_conn.lrange('history_%s' % user.id, 0, 5)

        skus = []  # skus列表中存储着多个模型类对象
        for id in ids:
            sku = SKU.objects.get(pk=id)
            skus.append(sku)

       	# 用到了序列化器的序列化功能,把模型转换成json
        serializer = SKUSerializer(skus, many=True)

        # 把模型转换成的json返回给前端
        return Response(serializer.data)

二. 模型类序列化器ModelSerializer

如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。

ModelSerializer与常规的Serializer相同,但提供了:

  • 基于模型类自动生成一系列字段
  • 基于模型类自动为Serializer生成validators,比如unique_together
  • 包含默认的create()和update()的实现

1. 定义

比如我们创建一个BookInfoSerializer

class BookInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = BookInfo
        fields = '__all__'
  • model 指明参照哪个模型类
  • fields 指明为模型类的哪些字段生成

我们可以在python manage.py shell中查看自动生成的BookInfoSerializer的具体实现

>>> from book.serializers import BookInfoSerializer
>>> serializer = BookInfoSerializer()
>>> serializer
BookInfoSerializer():
    id = IntegerField(label='ID', read_only=True)
    name = CharField(label='名称', max_length=20)
    pub_date = DateField(label='发布日期', required=True)
    readcount = IntegerField(label='阅读量', required=True)
    commentcount = IntegerField(label='评论量', required=True)
    image = ImageField(label='图片', required=False)
    peopleinfo_set = PrimaryKeyRelatedField(many=True, read_only=True)

2. 指定字段

  1. 使用fields来明确字段,__all__表名包含所有字段,也可以写明具体哪些字段,如
class BookInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = BookInfo
        fields = '__all__'
  1. 使用exclude可以明确排除掉哪些字段
class BookInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = BookInfo
        exclude = ('image',)
  1. 默认ModelSerializer使用主键作为关联字段,但是我们可以使用depth来简单的生成嵌套表示,depth应该是整数,表明嵌套的层级数量。如:
class PeopleInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = PeopleInfo
        fields = '__all__'
        depth = 1

形成的序列化器如下:

>>> from book.serializers import PeopleInfoSerializer
>>> serializer = PeopleInfoSerializer()
>>> serializer
PeopleInfoSerializer():
    id = IntegerField(label='ID', read_only=True)
    name = CharField(label='名称', max_length=20)
    gender = ChoiceField(choices=((0, 'male'), (1, 'female')), label='性别', required=False, validators=[<django.core.vidators.MinValueValidator object>, <django.core.validators.MaxValueValidator object>])
    description = CharField(allow_null=True, label='描述信息', max_length=200, required=False)
    is_delete = BooleanField(label='逻辑删除', required=False)
    book = NestedSerializer(read_only=True):
        id = IntegerField(label='ID', read_only=True)
        name = CharField(label='名称', max_length=20)
        pub_date = DateField(label='发布日期')
        readcount = IntegerField(label='阅读量', max_value=2147483647, min_value=-2147483648, required=False)
        commentcount = IntegerField(label='评论量', max_value=2147483647, min_value=-2147483648, required=False)
        is_delete = BooleanField(label='逻辑删除', required=False)
        image = ImageField(allow_null=True, label='图片', max_length=100, required=False)
  1. 显示指明字段,如:
class BookInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = BookInfo
        fields = ('id','name', 'readcount', 'commentcount')
  1. 指明只读字段

可以通过read_only_fields指明只读字段,即仅用于序列化输出的字段

class BookInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = BookInfo
        fields = ('id','name', 'readcount', 'commentcount')
        read_only_fields = ('id', 'readcount', 'commentcount')

3. 添加额外参数

我们可以使用extra_kwargs参数为ModelSerializer添加或修改原有的选项参数

class BookInfoSerializer(serializers.ModelSerializer):

    class Meta:
        model = BookInfo
        fields = ('id','name', 'readcount', 'commentcount')
        read_only_fields = ('id', 'readcount', 'commentcount')
        extra_kwargs = {
            'readcount': {'min_value': 0, 'required': True},
            'commentcount': {'max_value': 0, 'required': True},
        }

>>> from book.serializers import BookInfoSerializer
>>> serializer = BookInfoSerializer()
>>> serializer
BookInfoSerializer():
    id = IntegerField(label='ID', read_only=True)
    name = CharField(label='名称', max_length=20)
    readcount = IntegerField(label='阅读量', min_value=0, read_only=True)
    commentcount = IntegerField(label='评论量', max_value=0, read_only=True)

你可能感兴趣的:(python)