手摸手,带你用Django REST Framework撸接口系列二(序列化器篇)
序列化的作用就是进行数据校验,对数据对象进行转换的过程,其核心原理就是讲数据库数据序列化为前端所需要的格式并返回;将前端发送的数据反序列化为模型类对象,并保存在数据库中。
Django REST Framework系列文章
- 手摸手,带你用Django REST Framework撸接口系列一(基础篇)
- 手摸手,带你用Django REST Framework撸接口系列二(序列化器篇)
- 手摸手,带你用Django REST Framework撸接口系列三(视图篇)
- 手摸手,带你用Django REST Framework撸接口系列四(渲染器篇)
- 手摸手,带你用Django REST Framework撸接口系列五(路由篇)
- 手摸手,带你用Django REST Framework撸接口系列六(认证篇)
- 手摸手,带你用Django REST Framework撸接口系列七(权限篇)
- 手摸手,带你用Django REST Framework撸接口系列八(限流篇)
- 手摸手,带你用Django REST Framework撸接口系列九(过滤篇)
- 手摸手,带你用Django REST Framework撸接口系列十(排序篇)
- 手摸手,带你用Django REST Framework撸接口系列十一(分页篇)
- 手摸手,带你用Django REST Framework撸接口系列十二(异常处理篇)
- 手摸手,带你用Django REST Framework撸接口系列十三(自动生成接口文档篇)
需要序列化字段
- 在不做任何处理的情况下,我们定义的类里面的字段必须在
model
类中必须存在该字段 - 参与序列化的属性名必须与
model
类的属性相同 - 只出现在序列化中不出现在反序列化中我们要加只读属性
read_only=True
- 如果我们
Serializer
类中定义字段类型为SerializerMethodField
及自定义字段类型不用遵守类里面的字段必须在model
类中必须存在该字段
不需要序列化字段
- 不需要序列化的属性字段在序列化类中不需要声明
- 不需要序列化的属性字段在序列化类中设置只写属性
write_only=True
在开发REST API的视图中,虽然每个视图具体操作的数据不同,但增、删、改、查的实现流程基本套路化,所以这部分代码也是可以复用简化编写的:
增:校验请求数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
删:判断要删除的数据是否存在 -> 执行数据库删除
改:判断要修改的数据是否存在 -> 校验请求的数据 -> 执行反序列化过程 -> 保存数据库 -> 将保存的对象序列化并返回
查:查询数据库 -> 将数据序列化并返回
不同情况下用的序列化方法
- 单表:最简单定义
fileds
字段 ForeignKey
:基于source
参数链表操作ManyToManyField
:基于SerializerMethodField
+自定义一个钩子方法。
注意:choice
显示中文可以使用source
字段 “get_字段名_display
“
定义serializer
定义方法
Django REST framework中的Serializer使用类来定义,须继承自rest_framework.serializers.Serializer
。
例如,我们已有了一个数据库模型类Article
1 | class Article(models.Model): |
我们想为这个模型类提供一个序列化器,可以定义如下:
1 | class ArticleSerializer(serializers.Serializer): |
或者还可以创建基于模型的序列化器
1 | class ArticleSerializer(serializers.ModelSerializer): |
注意:serializer不是只能为数据库模型类定义,也可以为非数据库模型类的数据定义。serializer是独立于数据库之外的存在。
字段与选项
常用字段类型
字段 | 字段构造方式 |
---|---|
BooleanField | BooleanField() |
NullBooleanField | NullBooleanField() |
CharField | CharField(max_length=None, min_length=None, allow_blank=False, trim_whitespace=True) |
EmailField | EmailField(max_length=None, min_length=None, allow_blank=False) |
RegexField | RegexField(regex, max_length=None, min_length=None, allow_blank=False) |
SlugField | SlugField(maxlength=50, min_length=None, allow_blank=False) 正则字段,验证正则模式 [a-zA-Z0-9-]+ |
URLField | URLField(max_length=200, min_length=None, allow_blank=False) |
UUIDField | UUIDField(format=’hex_verbose’) format: 1) 'hex_verbose' 如"5ce0e9a5-5ffa-654b-cee0-1238041fb31a" 2) 'hex' 如 "5ce0e9a55ffa654bcee01238041fb31a" 3)'int' - 如: "123456789012312313134124512351145145114" 4)'urn' 如: "urn:uuid:5ce0e9a5-5ffa-654b-cee0-1238041fb31a" |
IPAddressField | IPAddressField(protocol=’both’, unpack_ipv4=False, **options) |
IntegerField | IntegerField(max_value=None, min_value=None) |
FloatField | FloatField(max_value=None, min_value=None) |
DecimalField | DecimalField(max_digits, decimal_places, coerce_to_string=None, max_value=None, min_value=None) max_digits: 最多位数 decimal_palces: 小数点位置 |
DateTimeField | DateTimeField(format=api_settings.DATETIME_FORMAT, input_formats=None) |
DateField | DateField(format=api_settings.DATE_FORMAT, input_formats=None) |
TimeField | TimeField(format=api_settings.TIME_FORMAT, input_formats=None) |
DurationField | DurationField() |
ChoiceField | ChoiceField(choices) choices与Django的用法相同 |
MultipleChoiceField | MultipleChoiceField(choices) |
FileField | FileField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ImageField | ImageField(max_length=None, allow_empty_file=False, use_url=UPLOADED_FILES_USE_URL) |
ListField | ListField(child=, min_length=None, max_length=None) |
DictField | DictField(child=) |
选项参数
参数名称 | 作用 |
---|---|
max_length | 最大长度 |
min_lenght | 最小长度 |
allow_blank | 是否允许为空 |
trim_whitespace | 是否截断空白字符 |
max_value | 最小值 |
min_value | 最大值 |
通用参数
参数名称 | 说明 |
---|---|
read_only | 表明该字段仅用于序列化输出,默认False |
write_only | 表明该字段仅用于反序列化输入,默认False |
required | 表明该字段在反序列化时必须输入,默认True |
default | 反序列化时使用的默认值 |
allow_null | 表明该字段是否允许传入None,默认False |
validators | 该字段使用的验证器 |
error_messages | 包含错误编号与错误信息的字典 |
label | 用于HTML展示API页面时,显示的字段名称 |
help_text | 用于HTML展示API页面时,显示的字段帮助提示信息 |
创建Serializer对象
定义好Serializer类后,就可以创建Serializer对象了。
Serializer的构造方法为:
1 | Serializer(instance=None, data=empty, **kwarg) |
说明:
1)用于序列化时,将模型类对象传入instance参数
2)用于反序列化时,将要被反序列化的数据传入data参数
3)除了instance和data参数外,在构造Serializer对象时,还可通过context参数额外添加数据,如
1 | serializer = AccountSerializer(account, context={'request': request}) |
通过context参数附加的数据,可以通过Serializer对象的context属性获取。
前提准备
我们为了更直观的说明序列器的使用方式,为大家准备好了两个模型和初始的序列化类如下:
模型:
1 | from django.contrib.auth.models import User |
序列化类:
1 | #!/usr/bin/env python |
序列化的使用
如果我们想要使用序列化器对应的是Django的模型类,DRF为我们提供了ModelSerializer模型类序列化器来帮助我们快速创建一个Serializer类。
ModelSerializer与常规的Serializer相同,但提供了:
- 基于模型类自动生成一系列字段
- 基于模型类自动为Serializer生成validators,比如unique_together
- 包含默认的create()和update()的实现
基于以上原因我们以下都以ModelSerializer为例来进行说明
序列化基本数据
序列化单条数据
1 | # 查询主键为2的文章 |
输出结果:
1 | { |
序列化多条数据
1 | # 查询所有文章 |
输出结果:
1 | [ |
SerializerMethodField
用户根据该字段的回调函数自定义输出结果
获取所有文章列表及文章对应的用户id,根据用户id输出用户对应的自定义身份
1 | class ArticleSerializer(serializers.ModelSerializer): |
输出结果
1 | [ |
指定source来源
查询文章对应的作者用户名和文章状态中文显示
新建两个可读字段author和status
字段,用以覆盖原来Article模型默认的字段,其中指定author字段的来源(source)为单个author对象的username,status字段为get_status_display
方法返回的完整状态。
1 | class ArticleSerializer(serializers.ModelSerializer): |
输出:
1 | [ |
关联对象嵌套序列化
如果我们要查询发布该文章的用户信息要怎么做呢?如果要查询所有用户和他发布的所有文章又该如何做呢?这就需要用到关联对象嵌套序列化了。
PrimaryKeyRelatedField
该字段被序列化为关联对象的主键
参数:
queryset
- 验证字段输入时用于模型实例查询的查询集。必须显式地设置查询集,或设置read_only=True
。many
- 如果应用于一对多关系,则应将此参数设置为True
。allow_null
- 如果设置为True
,那么该字段将接受None
值或可为空的关系的空字符串。默认为False
。pk_field
- 设置为一个字段来控制主键值的序列化/反序列化。例如,pk_field=UUIDField(format='hex')
会将 UUID 主键序列化为其紧凑的十六进制表示形式。
查询所有用户及其发表过的文章主键,我们的用户序列化器应该如下定义:
1 | class UserSerializer(serializers.ModelSerializer): |
查询结果:
1 | [ |
StringRelatedField
此字段将被序列化为关联对象的字符串表示方式(即__str__
方法的返回值)
参数:
many
- 如果是一对多的关系,就将此参数设置为True
.
查询所有用户及其发表的文章标题
1 | class UserSerializer(serializers.ModelSerializer): |
输出结果:
1 | [ |
HyperlinkedRelatedField
HyperlinkedRelatedField
用于使用超链接来表示关系,必须指定视图别名view_name
注意:该字段是为映射到接受单个 URL 关键字参数的 URL 的对象而设计的,如使用 lookup_field
和 lookup_url_kwarg
参数设置的对象。
这适用于包含单个主键或 slug 参数作为 URL 一部分的 URL。
如果需要更复杂的超链接表示,你需要自定义该字段,稍后会详解。
参数:
view_name
- 用作关系目标的视图名称。如果你使用的是标准路由器类,则这将是一个格式为<modelname>-detail
的字符串。必填.queryset
- 验证字段输入时用于模型实例查询的查询集。必须显式地设置查询集,或设置read_only=True
。many
- 如果应用于一对多关系,则应将此参数设置为True
。allow_null
- 如果设置为True
,那么该字段将接受None
值或可为空的关系的空字符串。默认为False
。lookup_field
- 用于查找的目标字段。对应于引用视图上的 URL 关键字参数。默认是'pk'
.lookup_url_kwarg
- 与查找字段对应的 URL conf 中定义的关键字参数的名称。默认使用与lookup_field
相同的值。format
- 如果使用格式后缀,则超链接字段将使用与目标相同的格式后缀,除非使用format
参数进行覆盖。
查询所用用户及其发表的文章超链接
1 | class UserSerializer(serializers.ModelSerializer): |
输出结果:
1 | [ |
SlugRelatedField
SlugRelatedField
用于使用目标上的字段来表示关系。
默认情况下,该字段是可读写的,但您可以使用 read_only
标志更改此行为。
将 SlugRelatedField
用作读写字段时,通常需要确保 slug 字段与 unique=True
的模型字段相对应。
参数:
slug_field
- 用来表示目标的字段。这应该是唯一标识给定实例的字段。例如,username
。必填queryset
- 验证字段输入时用于模型实例查询的查询集。必须显式地设置查询集,或设置read_only=True
。many
- 如果应用于一对多关系,则应将此参数设置为True
。allow_null
- 如果设置为True
,那么该字段将接受None
值或可为空的关系的空字符串。默认为False
。
查询所有用户及其发表的文章内容列表
1 | class UserSerializer(serializers.ModelSerializer): |
输出结果:
1 | [ |
使用关联对象的序列化器
查询所有用户及其发表的文章信息
1 | class ArticleSerializer(serializers.ModelSerializer): |
输出结果:
1 | [ |
深度自动化连表
UserInfo类中有外键和多对多的键,直接序列化的结果是只显示id,如果想得到更多的信息可以用depth字段,可以理解为深度读取,默认为0,就是我们默认显示的结果。如果改为1,表示深度为1,就可以再往下读取一层所有的信息,在这里就可以读取到用户的角色和组别两个类中所有的信息。
查询所有文章及其发表的用户
1 | class ArticleSerializer(serializers.ModelSerializer): |
输出结果:
1 | [ |
数据验证
在反序列化数据时,必需对用户提交的数据进行验证。在尝试访问经过验证的数据或保存对象实例之前,总是需要显示地调用 is_valid()
方法。如果发生任何验证错误,.errors
属性将包含表示结果错误消息的字典,如下所示:
1 | serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'}) |
字典中的每个键都是字段名称,值是与该字段对应的任何错误消息的字符串列表。
.is_valid()
方法使用可选的 raise_exception
标志,如果存在验证错误,将会抛出 serializers.ValidationError
异常。
这些异常由 REST framework 提供的默认异常处理程序自动处理,默认情况下将返回 HTTP 400 Bad Request
响应。
1 | # 如果数据未通过验证,返回400 |
在DRF中编写序列化类时,你可以进行字段级别和对象级别的验证,还可以使用自定义验证器或使用DRF提供的验证器。
字段级别验证 (Field-level validation)
您可以通过向您的 Serializer
子类中添加 .validate_<field_name>
方法来指定自定义字段级的验证。这些类似于 Django 表单中的 .clean_<field_name>
方法。这些方法采用单个参数,即需要验证的字段值。
您的 validate_<field_name>
方法应该返回已验证的值或抛出 serializers.ValidationError
异常。例如我们要验证文章标题不能包含CNM:
1 | class ArticleSerializer(serializers.ModelSerializer): |
我们提交标题中带有CNM的数据后台响应的结果为:
1 | { |
对象级别验证 (Object-level validation)
要执行需要访问多个字段的任何其他验证,请添加名为 .validate()
的方法到您的 Serializer
子类中。此方法采用单个参数,该参数是字段值的字典。如果需要,它应该抛出 ValidationError
异常,或者只返回经过验证的值。
例如标题不能出现CNM并且内容也不能出现CNM:
1 | class ArticleSerializer(serializers.ModelSerializer): |
我们在标题或者内容中携带了CNM看下结果返回:
1 | { |
验证器 (Validators)
序列化器上的各个字段都可以包含验证器,通过在字段实例上声明.
例如还是刚才的问题,验证标题或者内容是否有脏字:
1 | def validate_fuck(value): |
故意违规返回结果:
1 | { |
重写序列化器的create和update方法
假设我们有个Profile模型与User模型是一对一的关系,当用户注册时我们希望把用户提交的数据分别存入User和Profile模型,这时我们就不得不重写序列化器自带的create方法了。下例演示了如何通过一个序列化器创建两个模型对象。
1 | class UserSerializer(serializers.ModelSerializer): |
同时更新两个关联模型实例时也同样需要重写update方法。
1 | def update(self, instance, validated_data): |
因为序列化器使用嵌套后,创建和更新的行为可能不明确,并且可能需要相关模型之间的复杂依赖关系,REST framework要求你始终显式的编写这些方法。默认的 ModelSerializer
.create()
和 .update()
方法不包括对可写嵌套表示的支持,所以我们总是需要对create和update方法进行重写。
动态加载序列化器类
有时你在类视里不希望通过通过serializer_class
指定固定的序列化器类,而是希望动态的加载序列化器,你可以重写get_serializer_class
方法,如下所示:
1 | class UserViewSet(CreateModelMixin,RetrieveModelMixin,UpdateModelMixin,viewsets.GenericViewSet): |
小结
- 改变序列化输出数据的格式可以通过指定字段的source来源,使用SerializerMethodField自定义方法以及使用嵌套序列化器。
- 反序列化时需要对客户端发送的数据进行验证。你可以通过自定义validate方法进行字段或对象级别的验证,你还可以使用自定义的validators或DRF自带的验证器。
- 当你使用嵌套序列化器后,多个关联模型的创建和更新的行为并不明确,你需要显示地重写create和update方法。
Django REST Framework系列文章
- 手摸手,带你用Django REST Framework撸接口系列一(基础篇)
- 手摸手,带你用Django REST Framework撸接口系列二(序列化器篇)
- 手摸手,带你用Django REST Framework撸接口系列三(视图篇)
- 手摸手,带你用Django REST Framework撸接口系列四(渲染器篇)
- 手摸手,带你用Django REST Framework撸接口系列五(路由篇)
- 手摸手,带你用Django REST Framework撸接口系列六(认证篇)
- 手摸手,带你用Django REST Framework撸接口系列七(权限篇)
- 手摸手,带你用Django REST Framework撸接口系列八(限流篇)
- 手摸手,带你用Django REST Framework撸接口系列九(过滤篇)
- 手摸手,带你用Django REST Framework撸接口系列十(排序篇)
- 手摸手,带你用Django REST Framework撸接口系列十一(分页篇)
- 手摸手,带你用Django REST Framework撸接口系列十二(异常处理篇)
- 手摸手,带你用Django REST Framework撸接口系列十三(自动生成接口文档篇)