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字段,多对多关系的表没有这个字段,要注意.

posted @ 2019-11-21 20:10  Xu67  阅读(458)  评论(0编辑  收藏  举报