• 序列化和反序列化


    序列化模块

    序列化模块是 drf 的核心

    为什么要使用序列化

    后台数据大多以后台类的对象存在,无法直接传给前端,所以经过序列化后,就可以格式化成能返回给前台的数据。

    所以要如何完成序列化,就要靠他的三种类了

    Serializer类(了解)- 偏底层,开发效率不高
    ModelSerializer类(重中之重) - 开发运用阶段才有的序列化方式,开发效率高
    ListSerializer类(正常)- 完成群增,群改接口的辅助序列化类
    

    首先来用Serializer类实现序列化。

    序列化实例

    urls

    from django.conf.urls import url
    from . import views
    
    urlpatterns = [
        url(r'^v1/users/$', views.UserAPIView.as_view()),
        url(r'^v1/users/(?P<pk>d+)/$', views.UserAPIView.as_view()),
    ]
    
    

    这里接收无论你是get 还是post 请求。两条url 都可以匹配get和post请求。至于为什么,看下面的代码

    views

    #这个类用来实现单查,群查和单增
    class UserAPIView(APIView):
        #判断来的请求是get的话就会走这个方法
        def get(self, request, *args, **kwargs):
            #这里为什么能从kwargs里面拿到pk?看下面注释1.
            pk = kwargs.get('pk')
        
    		#这里判断pk存在之后就意味着这是一个单增。
            if pk:
                #查找到这个数据库里的记录,拿到的就是一个对象。
                user_obj = models.User.objects.filter(pk=pk).first()
                #判断你拿到的数据是不是空,也就是传进来查询的pk值存不存在对应记录,不存				在就报错。
                if not user_obj:
                    return Response({
                        'status': 1,
                        'msg': '单查 error'
                    })
    			#走到这里就代表数据库里存在记录,然后就是通过自己写的继承serializers			的类来做序列化。
                user_ser = serializers.UserSerializer(user_obj, many=False)
                user_data = user_ser.data
                #返回查询成功的结果。
                return Response({
                    'satatus':0,
                    'msg':'单查 ok',
                    'result':user_data
                })
    		#如果pk不存在的话就代表是群查了
            user_query = models.User.objects.all()
            #群查的话就把这里的many参数的值改成True,内部会列表推导式来循环序列化一个个			对象。
            user_list_data = serializers.UserSerializer(user_query, many=True).data
    		#返回群查结果
            return Response({
                'satatus': 0,
                'msg': '群查 ok',
                'result': user_list_data
            })
        
    	 #判断来的请求是post的话就会走这个方法,就代表是单增
        def post(self, request, *args, **kwargs):
            #先从data里面拿取数据包
            request_data = request.data
    		#直接把数据包塞进自己写的类里面进行校验和反序列化。
            #为什么要校验,因为前段传过来的东西可能不符合要求
            user_ser = serializers.USerDeserializer(data=request_data)
            #获取is_valid方法的值
            result = user_ser.is_valid()
    		#判断是否全部符合要求
            if result:
               #符合的话调用,但是这里我们得在自己写的类中重写create方法。
                user_obj = user_ser.save()
                #返回保存成功结果,并且返回保存内容,只需再去调用一下
                return Response({
                    'status':0,
                    'msg':'ok',
                    'results':serializers.UserSerializer(user_obj).data
                })
            else:
                #否则就返回错误结果
                return Response({
                    'status': 1,
                    'msg': user_ser.errors
                },status=status.HTTP_400_BAD_REQUEST)
    

    注释1:为什么能从kwargs里面拿到pk?

    解释:因为 url(r'^v1/users/(?P<pk>d+)/$', views.UserAPIView.as_view()) ,这里调用的是as_view,相当于就是 views.view ,当路由匹配上的时候,就会自己加括号调用,然后有名分组的pk就传进这个方法,在上面得栗子是单查的时候,调用的就相当于是 get方法,所以kwargs里面就有pk了。你反问的url里面后面带上的是多少,这里的pk就是多少。

    然后就看一下到底是怎么序列化,反序列化,和校验数据的。

    把我们自己写的UserSerializer类卸载api下,api就是应用。

    from rest_framework import serializers
    #序列化方法
    class UserSerializer(serializers.Serializer):
        # model已有的属性字段
        #   如果要参与序列化,名字一定要与model的属性同名
        #   如果不参与序列化的model属性,在序列化类中不做声明
        
        #有哪些不要序列化的属性呢?比如说密码就是,总不可能把密码返回回去给用户看了吧。
    
        name = serializers.CharField()
        age = serializers.IntegerField()
        height = serializers.DecimalField(max_digits=5, decimal_places=2)
        #如果我们sex这样返回的话,那用户拿到的就是 1 或者 0 了,不合理。所以要用自定义序列化字段
        # sex = serializers.IntegerField()
    
        # 自定义序列化字段,序列化的属性值由方法来提供,
        #   方法的名字:固定为 get_属性名,
        #   方法的参数:序列化对象,序列化的model对象
        #   强烈建议自定义序列化字段名不要与model已有的属性名重名
    
        #固定写法
        gender = serializers.SerializerMethodField()
        def get_gender(self, obj):
    
            return obj.get_sex_display()
    
    
    from . import models
    #反序列化方法
    class UserDeserializer(serializers.Serializer):
        # 系统校验规则
    
        # 系统必须反序列化的字段
        # 序列化属性名不是必须与model属性名对应,但是与之对应会方便序列化将校验通过的数据与数据库进行交互
        
        #那些设置了默认值的字段可以不用反序列化。
        name = serializers.CharField(min_length=3, max_length=64, error_messages={
            'required': '姓名必填',
            'min_length': '太短',
        })
        #这里我们没有自己配置错误信息,所以不会像name一样报中文错误。
        pwd = serializers.CharField(min_length=3, max_length=64)
    
        # 系统可选的反序列化字段:没有提供不进行校验(数据库中有默认值或可以为空),提供了就进行校验,这里的required设置成了 False ,所以我们前端可以不传 age
        age = serializers.IntegerField(min_value=0, max_value=150, required=False)
    
        # 自定义反序列化字段:一定参与校验,且要在校验过程中,将其从入库的数据中取出,剩余与model对应的数据才会入库
        #简言之,这个字段是我们自己自定义的,要求前端传来的数据中一定要有re_pwd,但是我们 model表里没有这个字段,所以判断完之后要把这个re_pwd从数据包中删除,然后在存入数据库。
        re_pwd = serializers.CharField(min_length=3, max_length=64)
    
        # 自定义校验规则:局部钩子,全局钩子
        # 局部钩子:validate_字段名(self, 字段值)
        # 规则:成功返回value,失败抛异常
        def validate_aaa(self, value):
            if 'g' in value.lower():
                raise serializers.ValidationError('名字中不能有g')
            return value
    
        # 全局钩子:validate(self, 所有校验的数据字典)
        # 规则:成功返回attrs,失败抛异常
        def validate(self, attrs):
            # 取出联合校验的字段们:需要入库的值需要拿到值,不需要入库的需要从校验字段中取出
            pwd = attrs.get('pwd')
            #pop方法会删除re_pwd对应的值,然后返回这个值
            re_pwd = attrs.pop('re_pwd')
            if pwd != re_pwd:
                raise serializers.ValidationError({'re_pwd': '两次密码不一致'})
            return attrs
    
        # create重写,完成入库
        #这里为什么要重写create方法? 因为在views里面调用了save方法,save里面会进行判断是执行update还是create,这里是执行self.create,self代表的就是这个类本身,如果我们没有重写的话,就要往上蹭找,找Serializer中的create,发现也没有,再往上找serializers,发现里面也没有create方法,在往上找BaseSerializer进去一看,发现里面只有一句抛出异常,所以这就是强制我们必须要重写create方法,我们只好重写了。
        def create(self, validated_data):
            return models.User.objects.create(**validated_data)
    
    
    

    以上就实现了单查、群查和单增的功能,但是发现步骤有点繁琐,开发效率并不高。

    于是就应该使用 ModelSerializer 类,这个类可以把序列化和反序列化写成一个类。

    首先把views里的代码改一点点

    from rest_framework.views import APIView
    from rest_framework.response import Response
    from . import models, serializers
    from rest_framework import status
    class UserV2APIView(APIView):
        # 单查群查
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            if pk:
                user_obj = models.User.objects.filter(pk=pk).first()
                if not user_obj:
                    return Response({
                        'status': 1,
                        'msg': '单查 error'
                    })
    
                # 完成序列化
                user_data = serializers.UserModelSerializer(user_obj, many=False).data
    
                return Response({
                    'status': 0,
                    'msg': '单查 ok',
                    'results': user_data
                })
    
            # 群查
            user_query = models.User.objects.all()
            # 完成序列化
            user_list_data = serializers.UserModelSerializer(user_query, many=True).data
            return Response({
                'status': 0,
                'msg': '群查 ok',
                'results': user_list_data
            })
    
        # 单增
        def post(self, request, *args, **kwargs):
            request_data = request.data
    
            user_ser = serializers.UserModelSerializer(data=request_data)
    
            # 配置了 raise_exception=True ,校验失败,直接抛异常,反馈异常信息给前台,只要校验通过,代码才会往下执行,所以直接删除我们自己写的else
            result = user_ser.is_valid( raise_exception=True)
            if result:
                user_obj = user_ser.save()
                return Response({
                    'status': 0,
                    'msg': 'ok',
                    'results': serializers.UserModelSerializer(user_obj).data
                })
    
    from rest_framework.serializers import ModelSerializer
    from . import models
    # 整合序列化与反序列化,都整合到这个类里面
    class UserModelSerializer(ModelSerializer):
        #一共是这三步骤
        # 将序列化类与Model类进行绑定
        # 设置序列化与反序列化所有字段(并划分序列化字段与反序列化字段)
        # 设置反序列化的局部与全局钩子
    
        # 自定义反序列化字段,校验规则只能在声明自定义反序列化字段时设置,且一定是write_only
        re_pwd = serializers.CharField(min_length=3, max_length=64, write_only=True)
    	#写一个 Meta 类
        class Meta:
            #绑定model类,形成对应
            model = models.User
            #所有参与了序列化和反序列化的字段都要写进去,这里的gender就是插头,插上了,就能用了,可插拔式字段。
            fields = ['name', 'age', 'height', 'gender', 'pwd', 're_pwd']
            #系统校验规则
            extra_kwargs = {
                'name': {
                    'required': True,
                    'min_length': 3,
                    'error_messages': {
                        'min_length': '太短'
                    }
                },
                'age': {
                    'required': True,  # 数据库有默认值或可以为空字段,required默认为False
                    'min_value': 0
                },
                'pwd': {
                    'required': True,
                    'write_only': True,  # 只参与反序列化,只能写,不会返回
                },
                'gender': {
                    'read_only': True,  # 只参与序列化,自定义字段不会参与反序列化
                },
            }
    	#钩子函数不变
        def validate_name(self, value):
            if 'g' in value.lower():
                raise serializers.ValidationError('名字中不能有g')
            return value
    
        def validate(self, attrs):
            pwd = attrs.get('pwd')
            re_pwd = attrs.pop('re_pwd')
            if pwd != re_pwd:
                raise serializers.ValidationError({'re_pwd': '两次密码不一致'})
            return attrs
    
    

    models

    from django.db import models
    
    class User(models.Model):
        CHOICES_SEX = ((0, '男'), (1, '女'))
        name = models.CharField(max_length=64)
        age = models.IntegerField(default=0)
        height = models.DecimalField(max_digits=5, decimal_places=2, default=0)
        icon = models.ImageField(upload_to='icon', default='default.png')
        sex = models.IntegerField(choices=CHOICES_SEX, default=0)
        # 已经迁移的数据库,新增字段需要允许为空或者设置默认值
        pwd = models.CharField(max_length=64, null=True)
    
    
    
        # 自定义插拔序列化字段:替换了在Serializer类中自定义的序列化字段(SerializerMethodField)
        # 自定义插拔序列化字段一定不参与反序列化过程,用来多表的时候作用尤其明显
        @property
        def gender(self):
            return self.get_sex_display()
    
    
        class Meta:
            db_table = 'ob_user'
            verbose_name = '用户表'
            verbose_name_plural = verbose_name
    
        def __str__(self):
            return self.name
    
    
    

    总结:

    """ 序列化
    一、视图类的三步操作
    1)ORM操作数据库拿到资源数据
    2)格式化(序列化)成能返回给前台的数据
    3)返回格式化后的数据
    
    二、视图类的序列化操作
    1)直接将要序列化的数据传给序列化类
    2)要序列化的数据如果是单个对象,序列化的参数many为False,数据如果是多个对象(list,queryset)序列化的参数many为True
    
    三、序列化类
    1)model了中要反馈给前台的字段,在序列化类中要进行声明,属性名必须就是model的字段名,且Field类型也要保持一致(不需要明确规则)
    2)model了中不需要反馈给前台的字段,在序列化类中不需要声明(省略)
    3)自定义序列化字段用 SerializerMethodField() 作为字段类型,该字段的值来源于 get_自定义字段名(self, obj) 方法的返回值
    """
    
    """ 反序列化
    一、视图类的三步操作
    1)从请求对象中拿到前台的数据
    2)校验前台数据是否合法
    3)反序列化成后台Model对象与数据库交互
    
    二、视图类的反序列化操作
    1)将要反序列化的数据传给序列化类的data参数
    2)要反序列化的数据如果是单个字典,反序列化的参数many为False,数据如果是多个字典的列表,反序列化的参数many为True
    
    三、反序列化类
    1)系统的字段,可以在Field类型中设置系统校验规则(name=serializers.CharField(min_length=3))
    2)required校验规则绝对该字段是必校验还是可选校验字段(默认required为True,数据库字段有默认值或可以为空的字段required可以赋值为False)
    3)自定义的反序列字段,设置系统校验规则同系统字段,但是需要在自定义校验规则中(局部、全局钩子)将自定义反序列化字段取出(返回剩余的数据与数据库交互)
    4)局部钩子的方法命名 validate_属性名(self, 属性的value),校验规则为 成功返回属性的value 失败抛出校验错误的异常
    5)全局钩子的方法命名 validate(self, 所有属性attrs),校验规则为 成功返回attrs 失败抛出校验错误的异常
    """
    
    

    单表序列化总结:

    """ 单表序列化总结
    1)序列化与反序列功能可以整合成一个类,该类继承ModelSerializer
    2)继承ModelSerializer类的资源序列化类,内部包含三部分
        Meta子类、局部钩子、全局钩子
        注:create和update方法ModelSerializer已经重写了,使用不需要重写
    3)在Meta子类中:
        用model来绑定关联的Model类
        用fields来设置所有的序列化反序列化字段
        用extra_kwargs来设置系统的校验规则
    4)重要的字段校验规则:
        read_only校验规则,代表该字段只参与序列化
        write_only校验规则,代表该字段只参与反序列化
        required校验规则,代表该字段在反序列化是是否是必填(True)还是选填(False),不能和read_only一起使用(规则冲突)
        规则细节:
            如果一个字段有默认值或是可以为空,没设置required规则,默认为False,反之默认值为True
            如果一个Model字段即没有设置read_only也没设置write_only,该字段默认参与序列化及反序列化
    5)自定义序列化字段:在Model类中,定义方法属性(可以返回特殊值,还可以完成连表操作),在序列化类的fields属性中可以选择性插拔
    6)自定义反序列化字段:在Serializer类中,自定义校验字段,校验规则也只能在声明字段时设置,自定义的反序列化字段(如re_pwd),
    必须设置write_only为True
    """
    
    
    
  • 相关阅读:
    按升序合并如下两个list, 并去除重复的元素
    python数据结构
    驼峰式命名改下划线命名
    求某个数出现的次数超过了总数的一半
    翻转字符串
    复习
    RESTful
    Flask wtforms
    数据库连接池(DBUtils)
    iOS
  • 原文地址:https://www.cnblogs.com/chanyuli/p/11907508.html
Copyright © 2020-2023  润新知