Django REST framework 序列化器
DRF的序列化器类的继承关系如下图所示:
一、Serializer
对于Serializer类,是比较底层和基础,可定制程度最高的类,需要编写代码量最大的继承方式,当你需要高度定制DRF的序列化器的时候选择它,否则请选择后面的类!
序列化器一边把像查询集queryset和模型实例instance这样复杂的基于Django自定义的数据类型转换为可以轻松渲染成JSON,XML或其他内容类型的互联网通用的交换语言,让不知道后端到底是什么的前端能够轻松的解析数据内容,而不至于摸瞎。另一边,序列化器还提供反序列化功能,在验证前端传入过来的数据之后,将它们转换成Django使用的模型实例等复杂数据类型,从而使用ORM与数据库进行交互,也就是CRUD。
序列化器存在的核心原因是前后端分离,前端不知道后端,后端又无法定义前端。为了数据交换,只好都说json话,都将自己的内容翻译成json格式。然而对于前端,json属于自带,对于后端,则需要使用序列化器将Django自定义的数据类型转换成json格式。(当然,也可以是别的格式。)
事实上Django本身也自带序列化功能的,参考http://www.liujiangblog.com/course/django/171,只不过这里的序列化器不是专门为了前后端分离和API开发的,功能较弱。
REST framework中的serializers与Django的Form和ModelForm类非常像。DRF提供了一个最底层最基础的Serializer类,它类似Django的Form类,为我们提供了强大的通用方法来控制响应的输出。以及一个ModelSerializer类,类似Django的ModelForm类,为创建用于处理模型实例和查询集的序列化程序提供了快捷实现方式,但可自定义程度较低。
声明序列化器
让我们从创建一个简单的对象开始,我们可以使用下面的例子:
注意,下面是一个原生的Python类,而不是Django的model类,这里介绍的是序列化器的初步原理,不是最终做法:
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对象。
声明一个序列化器在代码结构上看起来非常像声明一个Django的Form类:
from rest_framework import serializers
class CommentSerializer(serializers.Serializer):
email = serializers.EmailField()
content = serializers.CharField(max_length=200)
created = serializers.DateTimeField()
序列化对象
现在,我们可以用CommentSerializer类去序列化一个comment对象或多个comment的列表。同样,使用Serializer类看起来很像使用Form类。
serializer = CommentSerializer(comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}
这样,我们就将一个comment实例转换为了Python原生的数据类型,看起来似乎是个字典类型,但type一下却不是。为了完成整个序列化过程,我们还需要使用渲染器将数据转化为成品json格式。
from rest_framework.renderers import JSONRenderer
json = JSONRenderer().render(serializer.data)
json
# b'{"email":"leila@example.com","content":"foo bar","created":"2016-01-27T15:17:10.375877"}'
注意结果里的b,在Python里,这表示Bytes类型,是Python3以后的数据传输格式。
反序列化对象
反序列化的过程大体类似,但差别还是不小。首先,为了举例方便我们将一个流解析为Python原生的数据类型,实际过程中不会有这种多此一举的做法:
import io
from rest_framework.parsers import JSONParser
stream = io.BytesIO(json)
data = JSONParser().parse(stream)
...然后我们将这些原生数据类型恢复到已验证数据的字典中。
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
注意:依然是调用CommentSerializer类的构造函数,但是给data参数传递数据,而不是第一位置参数,这表示反序列化过程。其次,数据有一个验证过程is_valid()。
这个步骤做完,只是从json变成了原生的Python数据类型,还不是前面自定义的Comment类的对象。
保存实例
如果我们想要返回基于验证数据的完整对象实例,我们需要实现.create()或者update()方法。例如:
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
上面的两个方法在其继承关系中的父类里定义了具体的参数形式,instance和validated_data都是由继承体系里定义了的,分别表示要返回的Comment对象和经过验证的数据。
如果你的对象实例,比如这里的Comment对象,假设它对应了某个Django的模型,你还需要将这些对象通过Django的ORM保存到数据库中。具体的方法如下所示:
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
相比普通的Python的Comment类,对Django模型的保存多了一些ORM操作,比如save()方法。
等等,前面所作的改进只是在serializer层面的代码逻辑实现,在对应的视图操作中,我们还需要调用save()方法:
serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# {'content': 'foo bar', 'email': 'leila@example.com', 'created': datetime.datetime(2012, 08, 22, 16, 20, 09, 822243)}
comment = serializer.save() # if serializer.is_valid()
调用.save()方法将创建新实例或者更新现有实例,具体取决于实例化序列化器类的时候是否传递了一个现有实例:
# 这样的情况下.save() 将创建一个新实例
serializer = CommentSerializer(data=data)
# 这样将更新已有的comment实例
serializer = CommentSerializer(comment, data=data)
原则上.create()和.update()方法都是可选的。你可以根据你的序列化器类的用例不实现、实现它们之一或都实现,随你。但是新手,请千万不要随意。
传递附加属性到.save()
有时你会希望你的视图代码能够在保存实例时注入额外的数据。此额外数据可能是当前用户,当前时间或不是请求数据一部分的其他信息。
你可以通过在调用.save()时添加其他关键字参数来执行此操作。例如:
serializer.save(owner=request.user)
在.create()或.update()被调用时,所有其他的关键字参数将被包含在validated_data参数中。
重写.save()方法
在某些情况下.create()和.update()方法可能无意义。例如在联系人名单中,我们可能不会创建新的实例,而是发送电子邮件或其他消息。
在这些情况下,你可以选择直接重写.save()方法,也许更可读和有意义。
例如:
class ContactForm(serializers.Serializer):
email = serializers.EmailField()
message = serializers.CharField()
def save(self):
email = self.validated_data['email']
message = self.validated_data['message']
send_email(from=email, message=message)
请注意在上述情况下,我们需要直接访问serializer的.validated_data属性,因为没有写create或者update方法,没人给你传递validated_data参数,只能从self里面自己取。
重写save方法的例子给我们自定义保存数据做了个很好的演示,活学活用,会有很大的惊喜。
验证
反序列化数据的时候,你需要先调用is_valid()方法,然后再尝试去访问经过验证的数据或保存对象实例。如果发生任何验证错误,.errors属性将包含错误消息,以字典的格式。例如:
serializer = CommentSerializer(data={'email': 'foobar', 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'email': ['Enter a valid e-mail address.'], 'created': ['This field is required.']}
字典里的每一个键都是字段名称,值是与该字段对应的错误消息的字符串列表。non_field_errors键可能存在,它将列出任何一般验证错误信息。non_field_errors的名称可以通过REST framework设置中的NON_FIELD_ERRORS_KEY来自定义。 当对对象列表进行序列化时,返回的错误是每个反序列化项的字典列表。
内置无效数据的异常
.is_valid()方法具有raise_exception异常标志,如果存在验证错误将会抛出一个serializers.ValidationError异常。
这些异常由REST framework提供的默认异常处理程序自动处理,默认情况下将返回HTTP 400 Bad Request响应。
# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)
字段级别的验证
你可以通过向你的Serializer子类中添加.validate_方法来指定自定义字段级别的验证。类似于Django表单中的.clean_方法。
这些方法采用单个参数,即需要验证的字段值。
validate_方法应该返回一个验证过的数据或者抛出一个serializers.ValidationError异常。例如:
from rest_framework import serializers
class BlogPostSerial