一、Serializers
Serializers(序列化器)是什么?序列化器允许将诸如查询集(QuerySet)和模型实例之类的复杂数据转换为原生Python数据类型,然后可以将它们轻松地呈现为JSON
,XML
或其他内容类型。序列化器还提供反序列化,在首次验证传入数据之后,可以将解析的数据转换回复杂类型。
REST framework中的序列化类与Django的Form
和ModelForm
类非常相似。我们提供了一个Serializer
类,它提供了一种强大的通用方法来控制响应的输出,以及一个ModelSerializer
类,它为创建处理模型实例和查询集的序列化提供了有效的快捷方式。
总结来说,serializers有以下几个作用:
– 将queryset与model实例等进行序列化,转化成json格式,返回给用户(api接口)。
– 将post与patch/put的上来的数据进行验证。
– 对post与patch/put数据进行处理。
简单来说,针对get来说,serializers的作用体现在第一条,但如果是其他请求,serializers能够发挥2,3条的作用!下面就来把serializers相关内容过一遍。
二、序列化字段
我们知道在django中,form也有许多field,那serializers其实也是drf中发挥着这样的功能。我们先简单了解常用的几个field。
首先,我们创建一个简单的对象用于示例:
1 2 3 4 5 6 7 8 9 |
from datetime import datetime class Comment(object): def __init__(self, email, content, created=None): self.email = email self.content = content self.created = created or datetime.now() comment = Comment(email='leila@example.com', content='foo bar') |
声明一个序列化类,使用它来序列化和反序列化与Comment
对象相对应的数据。
声明一个序列化类看起来非常类似于声明一个表单:
1 2 3 4 5 6 |
from rest_framework import serializers class CommentSerializer(serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200) created = serializers.DateTimeField(read_only=True<span class="hljs-string">'</span>) |
不同的是,我们在django中,form更强调对提交的表单进行一种验证,而serializer的field不仅在进行数据验证时起着至关重要的作用,在将数据进行序列化后返回也发挥着重要作用!!更多序列化字段参考“序列化-字段”
我们可以看出,不同的field可以用不同的关键参数,除此之外,还有一些十分重要有用的参数。待会介绍。
三、核心参数
每个序列化字段类的构造函数都需要一些参数,某些字段类需要附加特定于该字段的参数,但所有构造函数应始终接受以下参数:
read_only
True表示不允许用户自己上传,只能用于API的输出。如果某个字段设置了read_only=True,那么就不需要进行数据验证,只会在返回时,将这个字段序列化后返回。默认为False。
举个简单的例子:在用户进行购物的时候,用户post订单时,肯定会产生一个订单号,而这个订单号应该由后台逻辑完成,而不应该由用户post过来,如果不设置read_only=True,那么验证的时候就会报错。
write_only
将其设置为True以确保在更新或创建实例时可以使用该字段,但在序列化表示时不包括该字段,默认为False。比如用户注册后返回用户名和相关信息,但不需要返回密码信息,就需要将密码字段设置为write_only。
required
如果在反序列化过程中没有该提供字段,通常会出现错误。如果在反序列化过程中不需要此字段,则应该设置为false,默认为True。
将此设置为False还允许在序列化实例时从输出中省略对象属性或字典密钥。如果密钥不存在,它将不会包含在输出表示中。
allow_null
如果把None传递给序列化字段,通常会引发错误。如果 None 应被视为有效值,则将此关键字参数设置为True 。请注意,将此参数设置为 True 将意味着序列化输出的缺省值为 null,但并不意味着输入反序列化的缺省值。默认为False。
allow_blank
如果为True表示允许为空,空跟None不是一个概念哈。默认为False。
error_messages
出错时的信息提示,格式为一个字典,key是错误代码, value是对应的错误信息。
default
如果设置,则会给出默认值,在没有提供输入值时,将使用该默认值。如果未设置,则默认行为是不填充该属性。
label
字段显示设置,如label=’验证码’。可用作HTML表单字段或其他描述性元素中字段的名称。
source
将用于填充字段的属性的名称,可以是一个只接受 self 参数的方法,如URLField(source=’get_absolute_url’),或者使用点符号来遍历属性,如EmailField(source=’user.email’)。在使用点符号时,如果在属性遍历期间任何对象不存在或为空,则可能需要提供缺省值。
validators
应该应用于传入字段输入的验证函数列表,该列表中的函数应该引发验证错误或仅返回。验证器函数通常应该引发serializers.ValidationError ,但 Django 的内置 ValidationError 也支持与 Django 代码库或第三方 Django 包中定义的验证器兼容。
四、序列化和反序列化对象
现在可以使用 CommentSerializer
来序列化评论或评论列表。同样,使用 Serializer
类看起来很像使用 Form
类。
1 2 3 |
serializer = CommentSerializer(comment) serializer.data # {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2018-05-21T11:59:43.880890Z'} |
此时已经将模型实例转换为 Python 原生数据类型。为了完成序列化过程,将数据渲染为json。
1 2 3 4 |
from rest_framework.renderers import JSONRenderer json = JSONRenderer().render(serializer.data) json # b'{"email":"leila@example.com","content":"foo bar","created":"2018-05-21T11:59:43.880890Z"}' |
反序列化是相似的。首先我们将一个流解析为 Python 原生数据类型…
1 2 3 4 5 |
from django.utils.six import BytesIO from rest_framework.parsers import JSONParser stream = BytesIO(json) data = JSONParser().parse(stream) |
然后我们将这些原生数据类型恢复成通过验证的数据字典。
1 2 3 4 5 |
serializer = CommentSerializer(data=data) serializer.is_valid() # True serializer.validated_data # OrderedDict([('email', 'leila@example.com'), ('content', 'foo bar'), ('created', datetime.datetime(2018, 5, 21, 11, 59, 43, 880890, tzinfo=<UTC>))]) |
五、保存实例
如果希望能够基于验证的数据返回完整的对象实例,则需要实现 .create()
和 .update()
方法中的一个或两个。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class CommentSerializer(serializers.Serializer): email = serializers.EmailField() content = serializers.CharField(max_length=200) created = serializers.DateTimeField() def create(self, validated_data): return Comment(**validated_data) def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.content = validated_data.get('content', instance.content) instance.created = validated_data.get('created', instance.created) return instance |
如果对象实例与 Django 模型相对应,还需要确保这些方法将对象保存到数据库。如果 Comment
是一个 Django 模型,这些方法可能如下所示:
1 2 3 4 5 6 7 8 9 |
def create(self, validated_data): return Comment.objects.create(**validated_data) def update(self, instance, validated_data): instance.email = validated_data.get('email', instance.email) instance.content = validated_data.get('content', instance.content) instance.created = validated_data.get('created', instance.created) instance.save() return instance |
现在,当反序列化数据时,我们可以调用 .save()
根据验证的数据返回一个对象实例。
1 |
comment = serializer.save() |
调用.save()
将创建一个新实例或更新现有实例,具体取决于在实例化序列化类时是否传递了现有实例:
1 2 3 4 5 |
# .save() will create a new instance. serializer = CommentSerializer(data=data) # .save() will update the existing `comment` instance. serializer = CommentSerializer(comment, data=data) |
这一部分功能是专门为这POST和PUT/PATCH请求所设计的,如果只是简单的GET请求,那么在设置了前面的field可能就能够满足这个需求。
将初始对象或查询集传递给序列化类实例时,该对象将作为
.instance
提供。如果没有传递初始对象,则.instance
属性将为None
。将数据传递给序列化类实例时,未修改的数据将作为.initial_data
提供。如果 data 关键字参数未被传递,那么.initial_data
属性将不存在。
默认情况下,序列化程序必须为所有必填字段传递值,否则会引发验证错误。你可以使用partial
参数以允许部分更新。
1 2 |
# Update `comment` with partial data serializer = CommentSerializer(comment, data={'content': u'foo bar'}, partial=True) |
我们在views以及mixins的博客中提及到,post请求对应create方法,而put/patch请求对应update方法,这里提到的create方法与update方法,是指mixins中特定类中的方法。我们看一下源代码,源代码具体分析可以看到另外一篇博客mixins:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
# 只截取一部分 class CreateModelMixin(object): def create(self, request, *args, **kwargs): serializer = self.get_serializer(data=request.data) serializer.is_valid(raise_exception=True) self.perform_create(serializer) headers = self.get_success_headers(serializer.data) return Response(serializer.data, status=status.HTTP_201_CREATED, headers=headers) def perform_create(self, serializer): serializer.save() class UpdateModelMixin(object): def update(self, request, *args, **kwargs): partial = kwargs.pop('partial', False) instance = self.get_object() serializer = self.get_serializer(instance, data=request.data, partial=partial) serializer.is_valid(raise_exception=True) self.perform_update(serializer) if getattr(instance, '_prefetched_objects_cache', None): # If 'prefetch_related' has been applied to a queryset, we need to # forcibly invalidate the prefetch cache on the instance. instance._prefetched_objects_cache = {} return Response(serializer.data) def perform_update(self, serializer): serializer.save() |
可以看出,无论是create与update都写了一行:serializer.save( ),那么,这一行,到底做了什么事情,分析一下源码。
1 2 3 4 5 6 7 8 9 10 11 |
# serializer.py def save(self, **kwargs): # 略去一些稍微无关的内容 ··· if self.instance is not None: self.instance = self.update(self.instance, validated_data) ··· else: self.instance = self.create(validated_data) ··· return self.instance |
显然,serializer.save的操作,它去调用了serializer的create或update方法,不是mixins中的!!!我们看一下流程图(以post为例)
那么,系统是怎么知道,我们需要调用serializer的create方法,还是update方法,我们从save( )方法可以看出,判断的依据是:
1 |
if self.instance is not None:pass |
如果实例不为None,那么就调用update方法,如果实例为None就调用create方法。简单说就是,如果序列化器传入实例,就表示是PUT/PATCH请求,就要调用update方法;如果未传入实例,就表示是POST请求,就需要调用create方法。
那么我们的mixins的create与update也已经在为开发者设置好了,并且允许部分更新:
1 2 3 4 |
# CreateModelMixin serializer = self.get_serializer(data=request.data) # UpdateModelMixin serializer = self.get_serializer(instance, data=request.data, partial=partial) |
也就是说,在update通过get_object( )的方法获取到了instance,然后传递给serializer,serializer再根据是否有传递instance来判断来调用哪个方法!
另外,有时你会希望你的视图代码能够在保存实例的时候注入额外的数据。这些附加数据可能包含当前用户,当前时间或其他任何不属于请求数据的信息。
1 |
serializer.save(owner=request.user) |
调用.create()
或.update()
时,任何其他关键字参数都将包含在validated_data
参数中。
六、验证数据
在反序列化数据时,你总是需要在尝试访问验证数据之前调用is_valid()
方法,或者保存对象实例。如果发生任何验证错误,那么.errors
属性将包含一个代表错误消息的字典。例如:
1 2 3 4 5 |
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'}) serializer.is_valid() # False serializer.errors # {'email': [u'Enter a valid e-mail address.'], 'created': [u'This field is required.']} |
字典中的每个键都是字段名称,值是与该字段相对应的错误消息(字符串列表)。non_field_errors
键也可能存在,并会列出任何常规验证错误。可以使用 NON_FIELD_ERRORS_KEY
(在 settings 文件中设置)来定制 non_field_errors
关键字的名称。
反序列化 item 列表时,错误将作为代表每个反序列化 item 的字典列表返回。
- 数据验证时抛出异常
.is_valid()
方法带有一个可选的raise_exception
标志,如果存在验证错误,将导致它引发serializers.ValidationError
异常。
这些异常由REST framework提供的默认异常处理程序自动处理,并且默认情况下将返回HTTP 400 Bad Request
。
1 2 |
# Return a 400 response if the data was invalid. serializer.is_valid(raise_exception=True) |
- 字段级验证
你可以通过向Serializer
子类添加.validate_<field_name>
方法来指定自定义字段级验证。这些与 Django 表单上的.clean_<field_name>
方法类似。这些方法只有一个参数,就是需要验证的字段值。
您的validate_<field_name>
方法应返回验证值或引发serializers.ValidationError
。例如:
1 2 3 4 5 6 7 8 9 10 11 12 |
from rest_framework import serializers class BlogPostSerializer(serializers.Serializer): title = serializers.CharField(max_length=100) content = serializers.CharField() def validate_title(self, title): # 注意参数,self以及字段名 # 注意函数名写法,validate_ + 字段名字 if 'django' not in title.lower(): raise serializers.ValidationError("Blog post is not about Django") return title |
当然,这里面还可以加入很多逻辑,比如判断title是否在数据库中已经存在等。
注意:如果你的序列化程序中声明的 <field_name> 参数为 required = False ,那么如果未包含该字段,则不会执行此验证步骤。
- 对象级验证
如果要对多个字段进行其他的验证,请将一个名为.validate()
的方法添加到您的Serializer
子类中。这个方法只有一个参数,它是一个字段值(field
–value
)的字典。如果有必要,它应该引发一个ValidationError
,或者只是返回验证的值。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
from rest_framework import serializers class EventSerializer(serializers.Serializer): description = serializers.CharField(max_length=100) start = serializers.DateTimeField() finish = serializers.DateTimeField() def validate(self, data): """ Check that the start is before the stop. """ if data['start'] > data['finish']: raise serializers.ValidationError("finish must occur after start") return data |
- 验证器
序列化器上的各个字段可以包含验证器,方法是在字段实例上声明它们,例如:
1 2 3 4 5 6 7 |
def multiple_of_ten(value): if value % 10 != 0: raise serializers.ValidationError('Not a multiple of ten') class GameRecord(serializers.Serializer): score = IntegerField(validators=[multiple_of_ten]) ... |
当然,drf提供的validators还有很好的功能:UniqueValidator,UniqueTogetherValidator等。UniqueValidator指定某一个对象是唯一的,如,用户名只能存在唯一:
1 2 3 4 5 |
username = serializers.CharField( max_length=11, min_length=11, validators=[UniqueValidator(queryset=UserProfile.objects.all()) ) |
UniqueTogetherValidator表示联合唯一,如用户收藏某个课程,这个时候就不能单独作用于某个字段,我们在Meta中设置。
1 2 3 4 5 6 7 |
class Meta: validators = [ UniqueTogetherValidator( queryset=UserFav.objects.all(), fields=('user', 'course'), message='已经收藏' )] |
七、ModelSerializer
讲了很多Serializer的,在这个时候,我还是强烈建议使用ModelSerializer,因为在大多数情况下,我们都是基于model字段去开发。ModelSerializer类提供了一个快捷方式,可让你自动创建一个Serializer类,其中的字段与模型类字段对应。
ModelSerializer类与常规Serializer类相同,不同之处在于:
- 它会根据模型自动生成一组字段。
- 它会自动为序列化类生成验证器,例如unique_together验证器。
- 它包含.create()和.update()的简单默认实现。它能够满足将post或patch上来的数据进行进行直接地创建与更新,除非有额外需求,那么就可以重载create与update方法。
声明ModelSerializer如下,在Meta中设置fields字段,系统会自动进行映射成序列化字段,省去每个字段再写一个field。
1 2 3 4 5 6 |
class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ('id', 'account_name', 'users', 'created') # fields = '__all__' # exclude = ('add_time',) |
你还可以将fields
属性设置为特殊值'__all__'
,以指示应该使用模型中的所有字段。你也可以将exclude
属性设置为从序列化程序中排除的字段列表。这三种方式,存在一个即可,且必须有一种方式。
任何关系(如模型上的外键)都将映射到PrimaryKeyRelatedField 。除非在序列化关系文档中指定,否则默认不包括反向关系。
序列化类能够生成一个表示字符串,可以让你充分检查其字段的状态。在使用 ModelSerializer 进行工作时,这是特别有用的,你需要确定它为你自动创建了哪些字段和验证器。为此,使用python manage.py shell
进入Django shell,然后导入序列化类,实例化它并打印对象表示形式…
1 2 3 4 5 6 7 |
>>> from myapp.serializers import AccountSerializer >>> serializer = AccountSerializer() >>> print(repr(serializer)) AccountSerializer(): id = IntegerField(label='ID', read_only=True) name = CharField(allow_blank=True, max_length=100, required=False) owner = PrimaryKeyRelatedField(queryset=User.objects.all()) |
你可以将额外的字段添加到ModelSerializer,或者通过在类上声明字段来覆盖默认字段,就像你对Serializer类所做的那样。
1 2 3 4 5 6 |
class AccountSerializer(serializers.ModelSerializer): url = serializers.CharField(source='get_absolute_url', read_only=True) groups = serializers.PrimaryKeyRelatedField(many=True) class Meta: model = Account |
额外的字段可以对应于模型上的任何属性或可调用的字段。
你可能希望将多个字段指定为只读。不要显式给每个字段添加 read_only = True
属性,你可以使用快捷方式 Meta 选项 read_only_fields
。该选项应该是字段名称的列表或元组,声明如下:
1 2 3 4 5 |
class AccountSerializer(serializers.ModelSerializer): class Meta: model = Account fields = ('id', 'account_name', 'users', 'created') read_only_fields = ('account_name',) |
含有 editable = False
的模型字段,AutoField
字段默认设置为只读,并且不需要添加到 read_only_fields
选项。
- ModelSerializer需要解决的两个问题
1. 某个字段不属于指定model,它是write_only,需要用户传进来,但我们不能对它进行save( ),因为ModelSerializer是基于Model,这个字段在Model中没有对应,这个时候,我们需要重载validate!如在用户注册时,我们需要填写验证码,这个验证码只需要验证,不需要保存到用户这个Model中:
1 2 3 |
def validate(self, attrs): del attrs["code"] return attrs |
2. 某个字段不属于指定model,它是read_only,只需要将它序列化传递给用户,但是在这个model中,没有这个字段!我们需要用到SerializerMethodField。假设需要返回用户加入这个网站多久了,不可能维持这样加入的天数这样一个数据,一般会记录用户加入的时间点,然后当用户获取这个数据,我们再计算返回给它。
1 2 3 4 5 6 7 8 9 |
class UserSerializer(serializers.ModelSerializer): days_since_joined = serializers.SerializerMethodField() # 方法写法:get_ + 字段 def get_days_since_joined(self, obj): # obj指这个model的对象 return (now() - obj.date_joined).days class Meta: model = User |
当然,这个的SerializerMethodField用法还相对简单一点,后面还会有比较复杂的情况。
- 关于外键的Serializers
其实,外键的field也比较简单,如果我们直接使用serializers.Serializer,那么直接用PrimaryKeyRelatedField就解决了。假设现在有虚拟机信息类别VirtualHost。
1 2 |
# Serializer owner = PrimaryKeyRelatedField(queryset=VirtualHost.objects.all(), required=True) |
ModelSerializer就更简单了,直接通过映射就好了,不过这样用户获得的只是一个外键类别的id,并不能获取到详细的信息(使用PrimaryKeyRelatedField也同样只是获得外键类别的id)。如下,owner字段属于user表的外键。
1 2 3 4 5 6 7 8 9 10 11 |
# Serializer class VirtualSerializer(ModelSerializer): class Meta: model = VirtualHost fields = ('id', 'owner') # 呈现结果 { "id": 1, "owner": 1 } |
如果想要获取到具体信息,那就需要嵌套serializer。使用depth
选项轻松生成嵌套表示(自关联)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
# Serializer class VirtualSerializer(ModelSerializer): class Meta: model = VirtualHost fields = ('id', 'owner') depth = 1 # 呈现结果 { "id": 1, "owner": { "id": 1, "is_superuser": true, "username": "admin", .... "groups": [], "user_permissions": [] } } |
depth选项应设置为一个整数值,该值指示在还原为平面表示之前应该遍历的关联的深度。
更多情况下,你可能只需要一个外键类别id对应的对象的某个属性值,比如这里只需要在owner处显示其username,可以这么做。
1 2 3 4 5 6 7 8 9 10 11 12 |
# Serializer class VirtualSerializer(ModelSerializer): owner = ReadOnlyField(source='owner.username') class Meta: model = VirtualHost fields = ('id', 'owner') # 呈现结果 { "id": 1, "owner": 'admin' } |
那么这个owner对象从哪里来呢?在DRF中,我们会需要定义权限类,在权限类中返回,类似下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
class IsOwnerOrReadOnly(permissions.BasePermission): """ Custom permission to only allow owners of an object to edit it. """ def has_object_permission(self, request, view, obj): # Read permissions are allowed to any request, # so we'll always allow GET, HEAD or OPTIONS requests. if request.method in permissions.SAFE_METHODS: return True # Write permissions are only allowed to the owner of the snippet. return obj.owner == request.user |
或者你需要关于用户的更多信息,可以再定义一个关于用户的Serializer,然后在当前序列化类嵌套用户Serializer即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
# Serializer class UserSerializer(ModelSerializer): class Meta: model = User fields = ('id', 'username') class VirtualSerializer(ModelSerializer): owner = UserSerializer() class Meta: model = VirtualHost fields = ('id', 'owner') # 呈现结果 { "id": 1, "owner": { "id": 1, "username": "admin" } } |
上面两种方式,外键都是正向取得,下面介绍怎么反向去取。如,我们需要获取这个用户有哪些主机。首先,在虚拟机VirtualHost的model中,需要在外键中设置related_name。
1 2 |
class VirtualHost(models.Model): owner = models.ForeignKey('auth.User', related_name='virtualhost', on_delete=models.CASCADE) |
通过related_name就可以反向取主机信息,一对多关系,一个用户下有多个主机,一定要设定many=True。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
# ModelSerializer class UserSerializer(ModelSerializer): virtualhost = PrimaryKeyRelatedField(many=True, read_only=True) class Meta: model = User fields = ('id', 'username', 'virtualhost') # 呈现结果 { "id": 1, "username": "admin", "virtualhost": [ 1 ] } |
外键就基本上就这样了!但是这里还有一个问题,从上面的结果可以看出,我们新增加的virtualhost列默认通过外键获取到用户id,如果想显示用户全部信息就需要使用到SerializerMethodField(method_name=None)
,这是一个只读字段。它通过调用它所连接的序列化类的方法来获得它的值。它可用于将任何类型的数据添加到对象的序列化表示中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
# 定义virtualhost字段需要的序列化类 class VirtualDetailSerializer(ModelSerializer): class Meta: model = VirtualHost fields = ('hostname','ip') # 定义user序列化类 class UserSerializer(ModelSerializer): virtualhost = SerializerMethodField() class Meta: model = User fields = ('id', 'username', 'virtualhost') def get_virtualhost(self, obj): all_virtualhost = VirtualHost.objects.filter(id=obj.id) virtualhost_serializer = VirtualDetailSerializer(all_virtualhost, many=True) return virtualhost_serializer.data |
想要什么字段信息,就在VirtualDetailSerializer序列化类中定义就好,呈现结果如下:
1 2 3 4 5 6 7 8 9 10 |
{ "id": 1, "username": "admin", "virtualhost": [ { "hostname": "MySQL-01", "ip": "10.10.0.1" } ] } |
<参考>
https://juejin.im/post/5a68934551882573443cddf8
http://drf.jiuyou.info/#/api-guide/fields?id=jsonfield