序列化器
普通序列化器
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 结果
- 多条数据的序列化
如果使用像filter、all这样的一些ORM方法,获取到的是QuerySet结果集,不是单独数据对象,那么使用序列化器时,需要传入many=True参数,用来表示:哇哦~😲,好多数据呢
objects = models.objects.all()
ser_data = Serializer(instance=objects, many=True)
ser_data.data # 这就是这一群数据的 JSON 结果
普通序列化器反序列化创建
反序列化的概念很简单,就是把JSON等数据变为ORM数据对象,甚至是入库或者是修改
DRF要求序列化器必须对数据进行校验,才能获取验证成功的数据或保存成模型类对象
- 在操作过程中,反序列化首先需要通过data传参
- 接着调用is_valid进行校验,验证成功返回True,反之返回False
- 如果校验失败,还可以通过结果的errors属性返回错误值
- is_valid调用后方法会进行**字段属性(max_value=10)**的校验、自定义的校验等等
- 对校验过后的对象调用save方法,这个save方法会触发序列化器中的create方法
- 普通序列化器中,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'})
普通序列化器反序列化更新
反序列化经过校验的数据,不光可以创建数据,还可以更新数据呢
- 更新首先需要一个已经存在的数据,所以需要通过instance参数传递已有的一个ORM对象
- 还需要待更新的新值,那么就需要传data参数
- 之后同样需要is_valid方法调用,检查即将更新进入的数据是否合法
- 最终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'})
模型序列化器
模型序列化器编写方式
之前的序列化器,很明显可以感觉到,如果模型类字段少了,还行,但是模型字段越来越多,那么开发者在序列化器里所要复刻的字段也要越来越多,很麻烦奥
而且还得手动实现update和create方法,而且光写了序列化器字段还不行,还得有字段属性
于是乎,有了现在的与模型类关联的序列化器,可以更加方便的进行字段映射以及内置方法的编写,简直是太棒了👍
模型类关联序列化器大概总结有如下三个特性,一个缺点
- 特点
- 基于模型类自动生成一系列字段
- 自动生成的系列字段,同时还包含unique、max_length等属性校验
- 包含默认的create和update的实现
- 缺点
- 不会自动映射模型类字段的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'
按照之前的普通序列化写法,你需要同步一个字段,并将字段属性也要记得同步,非常麻烦 但通过与模型类关联的序列化器就很简单了
- 首先通过继承ModelSerializer基类
- 通过序列化器元类属性中的model属性关联模型类
- 通过序列化器元类属性中的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方法,大致流程如下:
- 为序列化器绑定数据,ser=Serializer(data=request.data)
- 校验数据,ser.is_valid()
- 存储入库,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字段,该字段需要包含 queryset或 read_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数据提交 teacher与 name字段,此时 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_字段
这种办法是单独字段的校验,可以对序列化器类对象所写的字段进行判断验证
- 在序列化器里定义校验字段的钩子方法:validate_字段
- 获取字段的数据,就是参数接收到的value值
- 验证不通过,抛出异常:raise serializers.ValidationError("校验不通过的说明")
- 验证通过,返回字段数据
def validate_name(self, value):
if len(value) == 1:
raise serializers.ValidationError('名字长度无法为1')
return value
validate
多字段联合校验,这种校验方法是可以一次性获取到所有序列化器字段里的全部值,可以进行一个统一整体的校验判断
- 在序列化器定义validate方法,参数为attrs
- attrs是所有数据组成的字典
- 不符合抛出异常:raise serializers.ValidationError("校验不通过的说明")
- 如果校验全部成功,那么返回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