前言
使用Serializer序列化器之前,首先应理解并遵守RESTful的API设计风格。
推荐一篇能比较好了解RESTful的文章:RESTful API 最佳实践
REST API接口核心任务
开发REST API接口时,常做的是三件事:
- 将请求的数据(如JSON格式)转换为模型类对象
- 操作数据库
- 将模型类对象转换为响应的数据(如JSON格式)
而数据格式化转换的操作往往是繁琐且重复的。
序列化Serialization
为了简化这些操作,引入新的功能:序列化和反序列化
1. 序列化
可将序列化的含义简单理解为:
将程序中的一个数据结构类型转换为其他格式(字典、JSON、XML等),例如将Django中的模型类对象装换为JSON字符串,这个转换过程我们称为序列化。
比如在一个查询数据的API中,我们查询数据库数据得到模型类对象,将其按照格式重组并转换为json的过程就是序列化:
from . models import BookInfo
def get(request):
# 数据库中查询获取模型类对象
queryset = BookInfo.objects.all()
book_list = []
# 序列化
for book in queryset:
book_list.append({
'id': book.id,
'btitle': book.btitle,
'bpub_date': book.bpub_date,
'bread': book.bread,
'bcomment': book.bcomment,
})
return JsonResponse(book_list, safe=False)
2. 反序列化
相反的,可将反序列化的含义简单理解为:
将其他格式(字典、JSON、XML等)转换为程序中的数据,例如将JSON字符串转换为Django中的模型类对象,这个过程我们称为反序列化。
比如在一个新增数据的API中,我们接收前端传递来的json数据,将其转换为模型类对象的过程就是反序列化:
from . models import BookInfo
def post(self, request):
data = request.body.decode()
# 反序列化
data_dict = json.loads(data)
title = data_dict.get("btitle")
pub_date = data_dict.get("bpub_date")
book = BookInfo.objects.create(btitle=title, bpub_date=pub_date)
)
# 又再次序列化返回
return http.JsonResponse({
'id': book.id,
'btitle': book.btitle,
'bpub_date': book.bpub_date
})
可以看出,在开发REST API接口时,我们在视图中需要做的最核心的事是:
将数据库数据序列化为前端所需要的格式,并返回;
将前端发送的数据反序列化为模型类对象,并保存到数据库中。
Django REST framework
在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:
- 增:校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
- 删:判断要删除的数据是否存在 -> 执行数据库删除
- 改:判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
- 查:查询数据库 -> 将数据序列化并返回
Django REST framework(DRF) 提供了Serializer可以快速根据 Django ORM 或者其它库自动序列化/反序列化可以帮助我们简化上述部分代码编写,大大提高开发速度。
环境安装和配置
- 安装DRF
pip install djangorestframework
- 添加应用
将rest_framework
添加到配置文件INSTALLED_APPS
中:
INSTALLED_APPS = [
'rest_framework',
]
配置完成。
Serializer序列化器的使用
接下来的Serialize使用示例基于以下书籍数据模型类、与其关联的英雄数据模型类:
from django.db import models
class BookInfo(models.Model):
btitle = models.CharField(max_length=20, verbose_name='名称')
bpub_date = models.DateField(verbose_name='发布日期')
bread = models.IntegerField(default=0, verbose_name='阅读量')
bcomment = models.IntegerField(default=0, verbose_name='评论量')
is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta:
db_table = 'tb_books' # 指明数据库表名
verbose_name = '图书' # 在admin站点中显示的名称
verbose_name_plural = verbose_name # 显示的复数名称
def __str__(self):
"""定义每个数据对象的显示信息"""
return self.btitle
class HeroInfo(models.Model):
GENDER_CHOICES = (
(0, 'female'),
(1, 'male')
)
hname = models.CharField(max_length=20, verbose_name='名称')
hgender = models.SmallIntegerField(choices=GENDER_CHOICES, default=0, verbose_name='性别')
hcomment = models.CharField(max_length=200, null=True, verbose_name='描述信息')
hbook = models.ForeignKey(BookInfo, on_delete=models.CASCADE, verbose_name='图书') # 外键
is_delete = models.BooleanField(default=False, verbose_name='逻辑删除')
class Meta:
db_table = 'tb_heros'
verbose_name = '英雄'
verbose_name_plural = verbose_name
def __str__(self):
return self.hname
Serializer类有两种,一种是基于Serializer类的基本序列化器,另一种是封装度更高,使用更简单的模型类ModelSerializer序列化器,其实为了效率,我们更多使用的是ModelSerializer类。
1. 基于Serializer类的序列化器
在app中创建新文件serializers,在其内定义序列化器,继承于Serializer类。
(1)定义Serializer序列化器
为了简便,假设我们需要的字段只包括标题和发布时间,定义如下:
from rest_framework import serializers
class BookSerializes(serializers.Serializer):
btitle = serializers.CharField(min_length=0, max_length=20)
bpub_date = serializers.DateField()
# 使用外键连接时的字段
# heroinfo_set = serializers.StringRelatedField(read_only=True,many=True) #关联__str__方法指定字段
# heroinfo_set = serializers.PrimaryKeyRelatedField(read_only=True, many=True) #关联主键对象
(2)字段与参数
- 字段命名需要与模型类中的字段名一致;
- 对应字段类型需要与模型类中属于同一类;
- 选项参数:
用于后面将介绍的反序列化is_valid()
数据验证
选择项名 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
min_value | 最小值 |
max_value | 最大值 |
- 核心参数:
参数名 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
这里的字段名必须与模型类的字段名相同,数据类型也需与模型类属同一类模型。关于完整字段类型及参数选项的说明可参考官方文档:https://www.django-rest-framework.org/api-guide/fields/
(3)序列化的使用
使用serializer对象直接构建序列化数据
from .serializers import BookSerializes
from . models import BookInfo
def get(self,request):
books = BookInfo.objects.all()
# 构建序列化对象。(非单个数据时,需要many=True参数)
serializer = BookSerializes(books, many=True)
# data方法可直接返回序列化的json数据
data = serializer.data
return http.JsonResponse(data, safe=False)
返回的序列化data数据结构如下:
[OrderedDict([('btitle', '射雕英雄传'), ('bpub_date', '1980-05-01')]), OrderedDict([('btitle', '天龙八部'), ('bpub_date', '1986-07-24')]), OrderedDict([('btitle', '笑傲江湖'), ('bpub_date', '1995-12-24')]), OrderedDict([('btitle', '雪山飞狐'), ('bpub_date', '1987-11-11')]]
对比最初的自行写代码序列化数据,简洁了很多。
需要注意的是:
-
many
参数:如果关联的对象数据不是只有一个,而是包含多个数据,需要使用many=True指定。
(4)反序列化的使用
反序列化相对序列化复杂一些,因为接收前端数据后,需要先对数据进行验证后,才能获取验证成功的数据或保存成模型类对象。
在视图函数中调用验证方法is_valid()
及save()
法会自动调用
定义的序列化器中的validate
验证和create
、save
反序列化方法。
- 在获取反序列化的数据前,必须调用
is_valid()
方法进行验证,验证成功返回True,否则返回False。 -
is_valid()
方法校验的条件有两个。- 定义BookSerializes序列化器类的时传入的选项参数,如max_length=20限定字段最大长度为20.
- 自定义
validate()
方法,更加灵活可配置,is_valid()在对比字段选项参数后,将自动调用validate()
的过滤方法,示例如下:
class BookSerializes(serializers.Serializer):
...
def validate(self, attrs):
if attrs["btitle"] == "python":
raise serializers.ValidationError("书名不允许")
if len(attrs["btitle"]) < 2:
raise serializers.ValidationError("书名长度太小")
return attrs
attrs参数为字典类型,自动接收所有我们定义的序列化字段数据。
- 封装
create()
和update()
方法
class BookSerializes(serializers.Serializer):
...
def validate(self, attrs):
if attrs["btitle"] == "python":
raise serializers.ValidationError("书名不允许")
if len(attrs["btitle"]) < 2:
raise serializers.ValidationError("书名长度太小")
return attrs
# validated_data为完成过滤后的数据
def create(self, validated_data):
return BookInfo.objects.create(btitle=validated_data["btitle"],bpub_date=validated_data["bpub_date"])
# instance参数为传递的需要修改的模型类对象
def update(self, instance, validated_data):
instance.btitle = validated_data["btitle"]
instance.bpub_date = validated_data["bpub_date"]
instance.save()
- 视图函数中反序列化代码示例:
def post(self, request):
data = request.body.decode()
data_dict = json.loads(data)
# 构建序列化对象,将接收到的json格式数据传给data参数
serializer = BookSerializes(data=data_dict)
# 验证数据是否符合要求,raise_exception=True指定不符合条件时自动抛出异常
serializer.is_valid(raise_exception=True)
# 根据serializer 中是否传递对象参数,自动判断调用调用序列化器中定义的create和update方法
serializer.save()
return http.JsonResponse(serializer.data)
2. 基于模型类ModelSerializer序列化器
相比于1中的Serializer类序列化器,DRF还提供了一个更加深度封装的ModelSerializer模型类序列化器,可以帮助我们更快的、傻瓜式的创建一个Serializer类。
ModelSerializer与常规的Serializer相同,但提供了:
- 基于模型类自动生成一系列字段(可以不再自己定义字段)
- 基于模型类自动为Serializer生成validators,比如unique_together()
- 包含默认的create()和update()的实现(可以不再自己定义update和create方法)
(1)使用ModelSerializer定义Serializer序列化器
不再继承于serializers.Serializer类,而是继承模型序列化器类serializers.ModelSerializer
,无需再自定义字段。
class BookModelSerializer(serializers.ModelSerializer):
# 这里不用再自定义字段
class Meta:
model = BookInfo
fields = '__all__'
read_only_fields = ('id', 'bread', 'bcomment')
def validate(self, attrs):
if attrs['btitle'] == 'python':
raise serializers.ValidationError('书名python错误')
return attrs
在class Meta下可自定义类功能。
指定参照的模型类:
1)model = BookInfo
指定生成哪些字段:
1)fields = "__all__"
:对应生成数据库模型类的所有字段
2)fields = ('btitle',)
:只生成btitle字段,此参数类型为元祖
3)exclude = ('id' ,'btitle')
: 反选,生成除id及btitle外的所有字段
指定只读字段,不参与反序列化:
1)read_only_fields = ('id', 'bread', 'bcomment')
定制化添加额外参数:
1)extra_kwargs = {字段:{参数选项:参数}}
# 定义额外参数
extra_kwargs = {
'bread': {'min_value': 0, 'required': True},
'bcomment': {'min_value': 0, 'required': True},
}
(2)序列化的使用
序列化器定义完成后,视图函数中的使用方法完全不变。
其内部也同时封装好了之前需要我们自定义的create方法和update方法,只需在视图中使用save()方法调用即可。
如果需要使用过滤,那validate方法还是需要我们自己定义的。
完。