DRF 序列化器 反序列化

116 阅读16分钟

序列化器

普通序列化器

Response是不能直接返回ORM数据的,所以需要我们进行序列化操作,可以通过手动将其转为字典或JSON,也可以使用DRF所提供的序列化器,我们一般建议您使用序列化器,这个更专业,更简单,并且格式更为统一

如果你经常使用的是自己去将数据封装为JSON,那么常见的代码模型就像这样

data = models.objects.all()
json_ = {}
for d in data:
    json_['age'] = d.age
    json_['name'] = d.name
return Response(json_)

字段多了的话,这个工作量就会越来越麻烦了,而且有关于时间(DateTimeField、DateField)等字段类型的序列化直接通过JSON也是不行的,需要自己手动编写JSON的序列化器,非常麻烦,于是乎 DRF 就提供了更为便捷的两种序列化器,普通序列化器模型序列化器

普通序列化器编写方式

from rest_framework import serializers

普通序列化器,可以按照给定字段,将所匹配的ORM数据字段转换为JSON数据,不光可以对一条数据,也可以对一个QuerySet所对应的结果集


比如一个课程表有如下一些字段

class Course(models.Model):
    name = models.CharField(max_length=20, verbose_name='课程名字')
    created = models.DateField(verbose_name='课程创建时间')
    def __str__(self):
        return self.name

测试数据可以通过数据库手动添加 那么普通序列化器就可以定义如下,按照模型类字段支持即可,非常简单

class CourseSer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    created = serializers.DateField(auto_now_add=True)

普通的序列化器,不光可以为数据库模型类定义,也可以为非数据库模型类的数据定义,只是这里为模型类定义时,需要根据模型类字段一一映射

普通序列化器序列化

序列化就是将ORM数据放入序列化器加工,诞生出JSON数据对象,序列化器对象的 data 属性即为处理好的 JSON 数据对象

  • 单条数据的序列化

单挑数据的序列化很简单,直接通过序列化器类对象的参数instance传入查询得到的结果即可

object = models.objects.get(pk=1)
ser_data = Serializer(instance=object)
ser.data # 这就是这一条数据的 JSON 结果
  • 多条数据的序列化

如果使用像filterall这样的一些ORM方法,获取到的是QuerySet结果集,不是单独数据对象,那么使用序列化器时,需要传入many=True参数,用来表示:哇哦~😲,好多数据呢

objects = models.objects.all()
ser_data = Serializer(instance=objects, many=True)
ser_data.data # 这就是这一群数据的 JSON 结果

普通序列化器反序列化创建

反序列化的概念很简单,就是把JSON等数据变为ORM数据对象,甚至是入库或者是修改

DRF要求序列化器必须对数据进行校验,才能获取验证成功的数据或保存成模型类对象


  1. 在操作过程中,反序列化首先需要通过data传参
  2. 接着调用is_valid进行校验,验证成功返回True,反之返回False
    1. 如果校验失败,还可以通过结果的errors属性返回错误值
    2. is_valid调用后方法会进行**字段属性(max_value=10)**的校验、自定义的校验等等
  3. 对校验过后的对象调用save方法,这个save方法会触发序列化器中的create方法
    1. 普通序列化器中,create方法默认是没有实现的,需要手动根据模型类进行编写

data = {
    'name':"张三",
    ...
}
ser = Serializer(data=data)
if ser.is_valid():
    ser.save() # 只传 data 参数的时候,save 方法会触发序列化器器中的 create 方法

比如现在,还是之前练习需要提交数据创建课程的接口,此时可以这么做

为了能够保证数据成功入库,默认的普通序列化器是不具备入库功能的,需要编写create方法

class CourseSer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    created = serializers.DateField()
    def create(self, validated_data):
        object = Course.objects.create(
            **validated_data
        )
        return object

成功之后,就可以通过像之前一样的数据提交,编写视图完成数据入库,序列化器可以直接处理request所提交的数据data,并且可以剔除在request.data中其他多余的字段,只会处理序列化器里的字段

class CourseCreate(APIView):
    def post(self, request):
        ser = CourseSer(data=request.data)
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({'msg': error if error else 'success'})

普通序列化器反序列化更新

反序列化经过校验的数据,不光可以创建数据,还可以更新数据

  1. 更新首先需要一个已经存在的数据,所以需要通过instance参数传递已有的一个ORM对象
  2. 还需要待更新的新值,那么就需要传data参数
  3. 之后同样需要is_valid方法调用,检查即将更新进入的数据是否合法
  4. 最终save触发序列化器中的update方法

默认普通序列化器是没有自带对于数据的更新方法的,现在需要在序列化器里创建update方法

class CourseSer(serializers.Serializer):
    name = serializers.CharField(max_length=20)
    created = serializers.DateField()
    def update(self, instance, validated_data):
        # instance 要更新的数据,validated_data 是新数据
        instance.name = validated_data.get('name', instance.name)
        instance.type = validated_data.get('create', instance.type)
        instance.save()
        return instance

然后通过PUT传递要更新数据的ID,以及更新后的值,来为某条数据更新

class CourseUpdate(APIView):
    def put(self, request):
        id_ = request.data.get("id") # 获取当前更新数据的 ID
        try:
            course = Course.objects.get(pk=id_)
        except Course.DoesNotExist:
            return Response({'msg': '更新失败,不存在这条数据'})
        ser = CourseSer(instance=course, data=request.data)
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({'msg': error if error else 'success'})

模型序列化器

模型序列化器编写方式

之前的序列化器,很明显可以感觉到,如果模型类字段少了,还行,但是模型字段越来越多,那么开发者序列化器里所要复刻的字段也要越来越多,很麻烦奥

而且还得手动实现updatecreate方法,而且光写了序列化器字段还不行,还得有字段属性

于是乎,有了现在的与模型类关联的序列化器,可以更加方便的进行字段映射以及内置方法的编写,简直是太棒了👍


模型类关联序列化器大概总结有如下三个特性,一个缺点

  • 特点
    • 基于模型类自动生成一系列字段
    • 自动生成的系列字段,同时还包含uniquemax_length等属性校验
    • 包含默认的createupdate的实现
  • 缺点
    • 不会自动映射模型类字段的default属性

那么模型类关联序列化器用啥呢?用的是新的序列化器基

from rest_framework.serializers import ModelSerializer

比如一个商品模型类

class Goods(models.Model):
    title = models.CharField(max_length=200, verbose_name='商品标题')
    description = models.TextField(verbose_name='描述')
    inventory = models.IntegerField(default=0, verbose_name='库存量')
    price = models.DecimalField(
        max_digits=10, decimal_places=2, verbose_name='商品价格')
    cover = models.CharField(max_length=200, verbose_name='封面图')
    issale = models.BooleanField(default=False, verbose_name='是否促销')
    saleprice = models.DecimalField(
        max_digits=10, decimal_places=2, verbose_name='促销价格')
    ishow = models.BooleanField(default=True,verbose_name='是否上架')
    createtime = models.DateTimeField(auto_now_add=True, verbose_name='创建时间')
    def __str__(self):
        return self.title
    class Meta:
        db_table = 'goods'

按照之前的普通序列化写法,你需要同步一个字段,并将字段属性也要记得同步,非常麻烦 但通过与模型类关联的序列化器就很简单了

  1. 首先通过继承ModelSerializer基类
  2. 通过序列化器元类属性中的model属性关联模型类
  3. 通过序列化器元类属性中的fields属性指明序列化器需要处理的字段
class GoodsSer(serializers.ModelSerializer):
    class Meta:
        model = Goods
        fields = '__all__' # 指明所有模型类字段
        # exclude = ('createtime',) # 排除掉的字段
        # read_only_fields = ('title','description') # 只用于序列化的字段
        # fields = ('title','description','inventory') # 手动指明字段
        # extra_kwargs = {
        #     'price':{'min_value':0, 'required':True},
        # } # 修改原有字段的选项参数

模型类关联的序列化器和普通的序列化器使用方法一样

使用序列化器返回当前所有的商品数据,还是像之前一样传入instance参数即可,还要记得由于是多个商品,不是单独数据,要记得加many=True参数

模型序列化器反序列化创建、更新

模型序列化器的创建就更简单了,不需要手动实现create方法,大致流程如下:

  1. 为序列化器绑定数据ser=Serializer(data=request.data)
  2. 校验数据,ser.is_valid()
  3. 存储入库,ser.save()

创建商品接口

class GoodsCreate(APIView):
    def post(self, request):
        ser = GoodsSer(data=request.data)
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({'msg': error if error else 'success'})

注意:反序列化自动生成的字段属性中,不会包含原始模型类字段中的default字段属性

更新某一个商品数据,模型序列化器也是自带了update方法

更细商品接口

class GoodsUpdate(APIView):
    def put(self, request):
        id_ = request.data.get("id")
        try:
            goods = Goods.objects.get(pk=id_)
        except Goods.DoesNotExist:
            return Response({'msg': '更新失败,不存在这条数据'})

        ser = GoodsSer(instance=goods, data=request.data)
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({'msg': error if error else 'success'})

模型序列化器与普通序列化器的对比

  • 序列化时,将模型类对象传入instance参数
    • 序列化结果使用序列化器对象的data属性获取得到
  • 反序列化创建时,将要被反序列化的数据传入data参数
    • 反序列化一定要记得先使用is_valid校验
  • 反序列化更新时,将要更新的数据对象传入instance参数,更新后的数据传入data参数
  • 模型序列化器普通序列化器更加方便,自动生成序列化映射字段,create方法等
  • 关联外键序列化,字段属性外键为时要记得加many=True

关联序列化

普通的序列化,只是简单的对一张表进行处理,进行基本的单表增删改查,但是你一不小心遇到了多表的情况,比如一对一多对一多对多的情况该咋办呢

序列化

比如现在有两张很常见的关联表老师表学生表

  • 一个老师可以有一群学生,但是一个学生只能有一个老师
class Teacher(models.Model):

    name = models.CharField(max_length=30, verbose_name='老师名')
    age = models.IntegerField(verbose_name='年纪')
    
    def __str__(self):
        return self.name

class Student(models.Model):

    name = models.CharField(max_length=30, verbose_name='学生名')
    teacher = models.ForeignKey(
        Teacher, on_delete=models.SET_NULL, 
        null=True, blank=True, 
        verbose_name='关联老师'
    )

    def __str__(self):
        return self.name

学生表中,包含外键 teacher,外键可以通过如下一些方式进行序列化

PrimaryKeyRelatedField

关联表主键作为结果返回,使用 PrimaryKeyRelatedField字段,该字段需要包含 querysetread_only属性

设置 read_only代表该字段不进行反序列化校验

class StudentSer(serializers.ModelSerializer):
    teacher = serializers.PrimaryKeyRelatedField(read_only=True)
    class Meta:
        model = Student
        fields = '__all__'

编写返回一个学生的接口

class StudentDetail(APIView):
    def get(self, request):
        id_ = request.query_params.get('id')
        # 通过 GET 传参获取当前学生 ID
        try:
            stu = Student.objects.get(pk=id_)
        except Student.DoesNotExist:
            return Response({'error': '查不到结果'})
        ser = StudentSer(instance=stu)
        return Response(ser.data, status=200)

返回的数据类似如下效果

{
    "id": 1,
    "teacher": 1,
    "name": "小红"
}

StringRelatedField

关联表str方法作为结果返回

class StudentSer(serializers.ModelSerializer):
    teacher = serializers.StringRelatedField()

    class Meta:
        model = Student
        fields = '__all__'

接口返回效果如下

{
    "id": 1,
    "teacher": "张老师", # teacher 表的__str__方法所返回的
    "name": "小红"
}

SlugRelatedField

使用关联表的指定字段作为结果返回

class StudentSer(serializers.ModelSerializer):
    teacher = serializers.SlugRelatedField(read_only=True, slug_field='name')

    class Meta:
        model = Student
        fields = '__all__'

效果就很直观了,返回的就是 slug_field字段所对应的模型层里这个字段代表的值

{
    "id": 1,
    "teacher": 20,
    "name": "小红"
}

嵌套序列化器关联

上面几种办法只能返回单独定义的字段,适合某些返回数据简单的情况下,如果希望能返回当前关联字段的详细数据,那么可以通过额外定义关联表的序列化器,让当前外键字段使用关联表序列化器即可

class TeacherSer(serializers.ModelSerializer):
    class Meta:
        model = Teacher
        fields = '__all__'



class StudentSer(serializers.ModelSerializer):
    teacher = TeacherSer(read_only=True)

    class Meta:
        model = Student
        fields = '__all__'

此时外键 teacher 字段所对应的序列化器是关联表的序列化器,需要返回的关联表数据可以通过另外这个序列化器进行单独定义,返回的接口数据如下

{
    "id": 1,
    "teacher": {
        "id": 1,
        "name": "张老师",
        "age": 20
    },
    "name": "小红"
}

关联反向序列化

反向关联:model_set

如果查询一个老师,想返回所有关联这个老师的学生们,通过序列化器可以使用关联表模型_set字段完成,还要记得由于是多个学生关联,所以这个序列化字段要加 many=True属性

查询所有老师的信息,并且连带返回所有老师所拥有的学生

class TeacherFullDetail(APIView):
    def get(self, request):
        teachers = Teacher.objects.all()
        ser = TeacherSerializer(instance=teachers, many=True)
        return Response(ser.data, status=200)

PrimaryKeyRelatedField

使用反向关联表的主键进行返回

class TeacherSerializer(serializers.ModelSerializer):
    
    student_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True)

    class Meta:
        model = Teacher
        fields = '__all__'

结果如下,反向关联字段使用关联表的主键进行返回, 并且封装在一个列表中

[
    {
        "id": 1,
        "student_set": [
            1,
            2
        ],
        "name": "张老师",
        "age": 20
    },
    {
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

StringRelatedField

通过关联表**__str__**方法进行返回

class TeacherSerializer(serializers.ModelSerializer):
   
   student_set = serializers.StringRelatedField(read_only=True, many=True)

    class Meta:
        model = Teacher
        fields = '__all__'

结果就是反向关联外键的结果列表中包含的都是学生表str方法返回的结果

[
    {
        "id": 1,
        "student_set": [
            "小红",
            "小明"
        ],
        "name": "张老师",
        "age": 20
    },
    {
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

SlugRelatedField

通过指定表中字段作为序列化字段的结果

class TeacherSerializer(serializers.ModelSerializer):
    student_set = serializers.SlugRelatedField(read_only=True, slug_field='name', many=True)

    class Meta:
        model = Teacher
        fields = '__all__'

结果如下所示

[
    {
        "id": 1,
        "student_set": [
            "小红",
            "小明"
        ],
        "name": "张老师",
        "age": 20
    },
    {
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

嵌套序列化器关联

使用关联表的单独序列化器,和关联外键序列化器一样样的

class StudentSer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = '__all__'



class TeacherSerializer(serializers.ModelSerializer):
    student_set = StudentSer(many=True, read_only=True)

    class Meta:
        model = Teacher
        fields = '__all__'

效果就是学生的反向外键成为了具体的数据信息,而不是单独以指定字段或方式进行维护定义的

[
    {
        "id": 1,
        "student_set": [
            {
                "id": 1,
                "name": "小红",
                "teacher": 1
            },
            {
                "id": 2,
                "name": "小明",
                "teacher": 1
            }
        ],
        "name": "张老师",
        "age": 20
    },
    {
        "id": 2,
        "student_set": [],
        "name": "李老师",
        "age": 18
    }
]

外键字段反序列化

还是上面的例子,反序列化创建一个学生,使用模型序列化器很简单,默认是接受外键关联表的主键数据作为创建的依据,也就是代表着外键可以不像原始使用 ORM一样得传一个外键表数据 object,而是直接传一个 id就可以了

主键传值

postman或其他手段进行测试时,直接传老师 id学生名、及其他所需字段即可

学生反序列化器不需要写任何字段,默认的关联字段会接受一个 id数据作为校验依据并创建

class StudentSer(serializers.ModelSerializer):
    class Meta:
        model = Student
        fields = '__all__'

创建接口如下

class StudentCreate(APIView):
    def post(self, request):
        ser = StudentSer(data=request.data)
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({'msg': error if error else 'success'})

postman 测试时,只需要传如下数据即可

{
    "teacher": "2", # 外键直接传入 id 即可
    "name": "小黑"
}

外界传值

在序列化器使用过程中,可以通过 context 传递参数,这个参是一个字典数据对象,设置一些额外的值,使你的序列化器可以更灵活的操作参数

Serializer(instance=objects, data=request.data,context={})

现在序列化器不会自动验证接受并处理 teacher字段,需要手动维护

class StudentSer(serializers.ModelSerializer):
    teacher = TeacherSer(read_only=True)
    # read_only 将该字段设置为只是序列化使用,不会经过反序列化处理
    class Meta:
        model = Student
        fields = '__all__'
        # fields = 'name' # 指明字段也可以实现屏蔽 teacher 字段反序列化的作用

通过之前的 Json数据提交 teachername字段,此时 teacher是无效的,不会被反序列化处理 但是我们可以单独把提交的 teacher提取出来,传入 context参数,重写 create方法,在 create方法中获取 context参数,拿到 teacher,看下面的新接口

新的序列化器是这样的

class StuSer(serializers.ModelSerializer):
    teacher = TeacherSer(read_only=True)

    def create(self, validated_data):
        validated_data['teacher_id'] = self.context.get('teacher')
        ModelClass = self.Meta.model
        object = ModelClass._default_manager.create(**validated_data)
        return object

    class Meta:
        model = Student
        fields = '__all__'

视图稍微处理一下 teacher数据

class StuCreate(APIView):
    def post(self, request):
        teacher = request.data.get('teacher')
        ser = StuSer(data=request.data, context={'teacher': teacher})
        if ser.is_valid():
            ser.save()
        error = ser.errors
        return Response({'msg': error if error else 'success'})

反序列化校验

在之前的操作中,我们已经接触到了 update 以及 create 方法进行更新创建的反序列化操作,验证主要通过调用is_valid方法触发

其实在反序列化过程中,还可以通过由 DRF 所指定的一系列校验方法对字段进行更加细致的验证,比如对于密码强弱判断,直接通过字段属性是无法做到的,但是可以放在以下所提供的校验方法中,自定义其校验规则

validate_字段

这种办法是单独字段的校验,可以对序列化器类对象所写的字段进行判断验证


  1. 在序列化器里定义校验字段的钩子方法:validate_字段
  2. 获取字段的数据,就是参数接收到的value
  3. 验证不通过,抛出异常raise serializers.ValidationError("校验不通过的说明")
  4. 验证通过,返回字段数据

def validate_name(self, value):
    if len(value) == 1:
        raise serializers.ValidationError('名字长度无法为1')
    return value

validate

多字段联合校验,这种校验方法是可以一次性获取到所有序列化器字段里的全部值,可以进行一个统一整体校验判断


  1. 在序列化器定义validate方法,参数为attrs
  2. attrs是所有数据组成的字典
  3. 不符合抛出异常:raise serializers.ValidationError("校验不通过的说明")
  4. 如果校验全部成功,那么返回attrs参数对象

def validate(self,attrs):
    name = attrs.get('name')
    if name != '张三':
        raise serializers.ValidationError('抱歉,不是张三不让注册')
    return attrs

验证器

封装重复校验规则,使用函数机制,定义校验规则,然后将函数作用至序列化器的字段上

def my_name_validate(value):
    if value != '张三':
        raise serializers.ValidationError('你不是张三,不可以创建')
    else:
        return value

生效该验证器

name = serializers.CharField(
    max_length=30,
    validators=[my_name_validate] # 生效校验器,可以有多个
)

权重:验证器方法 > validate_字段 > validate