zoukankan      html  css  js  c++  java
  • DRF序列化组件

    DRF的序列化组件

    首先我们要知道序列化是干嘛的,在此之前我们应该知道json格式的数据,一般在前后端交互或者是跨平台交互的时候,会默认使用Json格式拉进行数据的传输,所以当我们把普通的数据转换成json格式的时候就会使用序列化组件,将其序列化成json格式,然后前端接收到json格式之后,再反序列化把json格式数据转换成普通格式的数据,再进行逻辑运算,判断以及渲染页面等.

    而DRF的序列化组件,所完成的功能也是如此,负责将对象数据序列化前台所需要的数据,或者反序列化前台的数据,进行校验,来确保数据的安全.

    下面我们就介绍三种DRF中最常用的序列化组件,Serializer,ModelSerializer,以及ListModelSerializer.

    Serializer组件

    在使用Serializer组件之前,我们要先生成一个序列化器,在我们django的项目名(这里我们定义项目名为api)下面新建一个serializer.py文件,在里面继承serializers,生成序列器,供我们在视图函数里面调用:

    #/api/models.py
    from django.db import models
    
    class User(models.Model):
        CHOICES_SEX = ((0, '男'), (1, '女'))
        name = models.CharField(max_length=64)
        pwd = models.CharField(max_length=64, null=True)
        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)
        
    
    #/api/serializers.py,在这里生成序列化器
    from rest_framework import serializers
    
    class UserSerializer(serializers.Serializer):
        '''
        这里是声明序列化类,都是models.py已有的字段
            如果要参与序列化,这里的字段名字一定要和models.py里面的属性同名,如果不参与序列化,就不要在这里声明字段
        '''
        name = serializers.CharField()
        age = serializers.IntegerField()
        height = serializers.DecimalField(max_digits=5, decimal_places=2)
        '''
        下面是自定义序列化字段,序列化的属性值由方法来提供,
           方法的名字:固定为 get_属性名,
           方法的参数:self为序列化对象,obj为序列化的model对象
           注意 : 建议自定义序列化字段名不要与model已有的属性名重名,否则会覆盖model已有字段的声明
           注意 : 自定义序列化字段要用SerializerMethodField()作为字段类型
        '''
        gender = serializers.SerializerMethodField()
        def get_gender(self, obj):
            return obj.get_sex_display()
    

    在完成以上序列化器生成之后,我们就可以在views.py里面去定义我们的方法,从而来使用序列化器,当然,在views.py定义之前,我们需要在urls.py里面写入路由匹配关系,实例如下,分为序列化和反序列化:

    序列化

    序列化数据通常是通get请求里面取出来的,常用来查询数据库

    # api/urls.py
    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()),
    ]
    
    # api/views.py,
    '''在视图函数里写我们的业务逻辑,大致分三步:
        1. 通过ORM操作数据库取到前端需要的数据
        2. 将数据序列化
        3. 将序列化后的数据返回给前端
    '''
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from django.conf import settings
    from . import models
    
    class UserAPIView(APIView):
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            # 这里是单查的写法,即前端发来的url里面有拼接参数的时候,会走if里面的流程,即我们有匹配条件,通过这些条件去数据库里面查询相应的数值
            if pk:
                # 1. 通过ORM取数据
                user_obj = models.User.objects.filter(pk=pk).first()
                if not user_obj:
                    return Response({
                        'status': 1,
                        'msg': '单查 error'
                    })
    
                # 2. 将数据序列化
                # 序列化时里面除了需要序列化的对象以外,还有一个many参数,该参数的意义是: 如果要序列化的数据是单个对象,many=False,如果要序列化的对象数据是多个对象,many=True
                
                user_ser = serializers.UserSerializer(user_obj, many=False)
                user_data = user_ser.data
                # class MySerializer: pass
                # user_data = MySerializer(user_obj)
                # 3. 把序列化后的数据返回给前端
                return Response({
                    'status': 0,
                    'msg': '单查 ok',
                    'results': user_data
                })
    
            # 群查,即前端发来的url连接不携带拼接参数,会将所有的数据都查出来
            # 1. 通过ORM取数据
            user_query = models.User.objects.all()
            # 2. 将数据序列化
            user_list_data = serializers.UserSerializer(user_query, many=True).data
            # 3. 把序列化后的数据返回给前端
            return Response({
                'status': 0,
                'msg': '群查 ok',
                'results': user_list_data
            })
    

    反序列化

    反序列化我们需要生成另一个序列化生成器,反序列化的数据一般是从post请求里面取出来的,且通常用来增加数据库里面的数据

    # /api/serializers.py,生成反序列化生成器
    class UserDeserializer(serializers.Serializer):
        '''
        反序列器的生成要注意以下几点:
        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 失败抛出校验错误的异常
        '''
        name = serializers.CharField(min_length=3, max_length=64, error_messages={
            'required': '姓名必填',
            'min_length': '太短',
        })
        pwd = serializers.CharField(min_length=3, max_length=64)
    
        # 系统可选的反序列化字段:没有提供不进行校验(数据库中有默认值或可以为空),提供了就进行校验
        age = serializers.IntegerField(min_value=0, max_value=150, required=False)
        # 自定义反序列化字段:一定参与校验,且要在校验过程中,将其从入库的数据中取出,剩余与model对应的数据才会入库
        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')
            re_pwd = attrs.pop('re_pwd')
            if pwd != re_pwd:
                raise serializers.ValidationError({'re_pwd': '两次密码不一致'})
            return attrs
    
        # create重写,完成入库
        def create(self, validated_data):
            return models.User.objects.create(**validated_data)
    
    
    
    # /api/views.py
    from rest_framework.views import APIView
    from rest_framework.response import Response
    from django.conf import settings
    from . import models
    
    class UserAPIView(APIView):    
        def post(self, request, *args, **kwargs):
            '''
            反序列化通常也有三步操作:
            1. 从请求对象中拿到前台的数据
            2. 校验前台数据是否合法
            3. 反序列化成后台Model对象与数据库交互
            '''
            request_data = request.data
            # 反序列化时里面除了需要反序列化的对象以外,同样一个many参数,如果要序列化的数据是单个对象,many=False,如果要序列化的对象数据是多个对象,many=True,不写的话默认是False            
            user_ser = serializers.UserDeserializer(data=request_data)
            # 调用反序列化的校验规则有两种,即系统规则和自定义规则(局部钩子,全局钩子)
            result = user_ser.is_valid()
    
            if result:
                # 校验通过,可以与数据库进行交互:增(create),改(update)
                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)
    

    以上就是Serializer组件序列化以及反序列化的用法和注意事项,实际上这是一种较底层的用法,所以我们在生产中常用ModelSerializer来完成数据的序列化和反序列化,整个的代码量会更少,开发效率更高.

    ModelSerializer组件

    ModelSerializer组件除了Serializer组件所有的功能以外,另外提供了一些功能,比如:

    1. 可以基于模型类自动生成一系列字段,无需手动生成
    2. 可以基于模型类自动为Serializer生成validators,比如uinque_together
    3. ModelSerializer组件会自动实现默认的create()和update()功能,无需手动去实现

    序列化和反序列化

    # ModelSerializer可以将序列化和反序列化的功能整合成一个类,这个类继承自rest_framework.serializers.ModelSerializer
    
    
    # api/serializers.py
    from rest_framework.serializers import ModelSerializer
    from . import models
    
    class UserModelSerializer(ModelSerializer):
        '''
        该生成器包括三个部分:
        1. Meta子类:
            里面用model来绑定关联model表
            用fields来设置所有的序列化反序列化字段
            用extra_kwargs来设置系统的校验规则,比如长短,报错信息提示等
        2. 局部钩子
        3. 全局钩子
        '''
        '''
        该生成器里可以完成的事情:
        1. 将序列化类与Model类进行绑定
        2. 设置序列化与反序列化所有字段(并划分序列化字段与反序列化字段)
        3. 设置反序列化的局部钩子与全局钩子
        '''
        
    
        # 自定义反序列化字段,校验规则只能在声明自定义反序列化字段时设置,且一定是write_only
        re_pwd = serializers.CharField(min_length=3, max_length=64, write_only=True)
    
        class Meta:
            model = models.User
            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,  # 只参与反序列化,这里注意,required不能和read_only一起使用,规则会冲突
                },
                '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
        
        
        
    # 项目名/urls.py
    from django.conf.urls import url
    from . import views
    urlpatterns = [
        url(r'^v2/users/$', views.UserV2APIView.as_view()),
        url(r'^v2/users/(?P<pk>d+)/$', views.UserV2APIView.as_view()),
    ]
    
    
    #/api/views.py
    from rest_framework.views import APIView
    
    class UserV2APIView(APIView):
        
        def get(self, request, *args, **kwargs):
            pk = kwargs.get('pk')
            # 单查,即url链接有拼接内容
            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
                })
    
            # 群查,url链接没有拼接内容,全查
            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)
    
            # 校验失败,直接抛异常,反馈异常信息给前台,只要校验通过,代码才会往下执行
            result = user_ser.is_valid()
            if result:
                user_obj = user_ser.save()
                return Response({
                    'status': 0,
                    'msg': 'ok',
                    'results': serializers.UserModelSerializer(user_obj).data
                })
            else:
                # 校验失败,返回错误信息
                return Response({
                    'status': 1,
                    'msg': user_ser.errors
                }, status=status.HTTP_400_BAD_REQUEST)
    

    自定义Response方法

    在上面的序列化与反序列化方法中,我们多次用到return Response({})返回值,且里面的内容有诸多相似,经常都有status,msg,results等,所以其实我们可以自定义一个Response方法,继承自原来DRF的response方法,并对其做二次封装,类似于我们之前所了解的把相似类提取出来生成一个基类,别的都继承自该基类即可.

    所以我们在应用名下面新建一个response.py文件,用以写我们二次封装的Response方法,如下:

    # api/response.py
    from rest_framework.response import Response    # 这里导入DRF原本的Response,并在下面作为父类导入,继承
    
    
    class APIResponse(Response):
        '''
        这里我们继承自父类的init,然后重写其中的__init__,其中status和msg给默认值,然后其余默认值均为None,最后的**kwargs可以接收多余的所有键值对并传给前端
        '''
        def __init__(self,status=0,msg='ok',results=None,http_status=None,headers=None,exception=False,**kwargs):
            # data里面所写的是response的基础数据状态码和数据状态信息
            data = {
                'status':status,
                'msg':msg
            }
            # results是后端传给前端的数据,如果有的话,就在data里面添加,一起返回给前端,如果没有就不添加
            if results is not None:
                data['results']=results
            # 更新**kwargs里面接收到的所有键值对,并添加到data里面
            data.update(**kwargs)
            # 下面是直接调用父类的init方法,然后把相应的数据赋值进去
            super().__init__(data=data,status=http_status,headers=headers,exception=exception)
            
            
    # 在封装完我们自己的response之后,我们就可以在View.py方法里面导入然后直接使用了
    # /api/views.py
    from .response import APIResponse
    from rest_framework import status
    
    
    return Response({
                    'status': 1,
                    'msg': user_ser.errors
                }, status=status.HTTP_400_BAD_REQUEST)
    # 上面这个就可以改成下面这种一行的形式,可以极大简化代码量.
    return APIResponse(1,user_ser.errors,http_status=status.HTTP_400_BAD_REQUEST)
    

    基表相关

    基表和基类的概念比较相似,其实就是定义一个继承自models.Model的类,然后用到的表类里面都继承该基表,从而减少代码量,节省操作.

    基表配置的关键属性是abstract=True,且要定义在内部的Meta类里面,实例如下:

    # api/models.py
    class BaseModel(models.Model):
        create_time = models.DateTimeField(auto_now_add=True)
        class Meta:
            # 该属性就表示该表是基表,可以供普通Model类继承使用
            # 另外还有一点就是,设置了abstract的表类不会在执行数据库迁移命令(makemigration | migrate)的时候新建表
            abstract = True
    

    DRF中ORM的多表关联操作

    外键设计

    跳出DRF这个概念来说的话,ORM的多表关联我们应该是知道一些的,多表关系一共三种,其外键常存在的位置也不尽相同,如下:

    1. 一对多关系:外键放在多的一方,也就是一对多关系中多的那一方
    2. 多对多关系:外键放在常用的一方
    3. 一对一关系:外键放在不常用的一方

    而跨表操作我们通常也有一个口诀,就是正向跨表直接点属性,反向跨表表名小写加属性.

    DRF中的跨表操作有些区别,使用起来更加简单,不过models里面定义的时候需要加上related_name反向查询字段,实例如下:

    # api/models.py
    class Author(BaseModel):
        name = models.CharField(max_length=16)
    class AuthorDetail(BaseModel):
        mobile = models.CharField(max_length=11)
        # 这里正向查询点Author,反向查询直接点detail即可,
        author = models.OneToOneField(to='Author',related_name='detail')
    

    断级联

    级联我们都知道是什么,级联的意义在于两个有级联关系的表,一旦一个数据被删除了,跟其级联相关的另一个表的数据也会被删除,实际上这对于数据的增删来说是非常麻烦的一件事,所以在DRF中我们要执行断级联这种操作,断级联的优点在于:

    1. 表与表之间不会再有外键关联,但是有逻辑关联
    2. 断级联之后不会影响数据库查询表的效率,但是会极大的提高数据库的增删改的效率
    3. 断级联之后一定要通过逻辑代码来保证表与表之间数据的安全,而且有特定的关键字on_delete来表示其不同的级联关系

    DRF的models.py里面断级联的参数为db_constraint,我们将其设置为False即可断级联,不同级联关系的表示方式如下:

    # /api/models.py
    '''
    我们假设四种情况,可以用四种on_delete级联方式:
    '''
    # 1. on_delete=models.CASCADE,作者和作者详情表是一对一级联,且详情表会随着作者表的删除而删除,就需要在详情表里面的author字段里加入该属性
    class AuthorDetail(BaseModel):
        author=models.OneToOneField(to='Author',db_constraint=False,on_delete=models.CASCADE)
    
    # 2. on_delet=models.DO_NOTHING,假设有出版社表和图书表,是一对多的关系,且图书不会随着出版社的删除而随之删除,那么就可以设置on_delete=models.DO_NOTHING
    class Book(BaseModel):
        publish = models.ForeignKey(to='Publish', related_name='books', db_constraint=False, on_delete=models.DO_NOTHING)
    
    # 3. null=True,on_delete=models.SET_NULL,假设有部门表和员工表,是一对多的关系,员工没有部门,可以为空
    class Employee(BaseModel):
        section = models.ForeignKey(to='Section',related_name='employee',db_constraint=False,null=True,on_delete=models.SET_NULL)
        
    # 4. default=0,on_delete=models.SET_DEFAULT,一样有部门表和员工表,在部门表删除的时候,员工不会为空部门,而是自动进入一个默认的部门,即default值
    class Employee(BaseModel):
        section = models.ForeignKey(to='Section',related_name='employee',db_constraint=False,default=0,on_delete=models.SET_DEFAULT)
    

    注意一点的是,只有一对一和一对多关系的表才有on_delete字段,多对多关系的表没有这个字段,要注意.

  • 相关阅读:
    走近Qt
    C: 数组形参
    C++: 单例模式和缺陷
    c++: 获取delete[]中的数组大小
    像素格式
    BMP图像格式
    Sublime Text 格式化JSON-pretty json
    Sublime Text3配置SublimeREPL快捷键的方法(Python)
    Sublime Text3安装SublimeREPL插件以及快捷键设置
    Sublime Text3 注册码激活码(持续更新中2018-11-20)
  • 原文地址:https://www.cnblogs.com/aden668/p/11929685.html
Copyright © 2011-2022 走看看