侧边栏壁纸
  • 累计撰写 218 篇文章
  • 累计创建 59 个标签
  • 累计收到 5 条评论

Serializer 1: serialize, deserialize & validate

barwe
2023-01-07 / 0 评论 / 0 点赞 / 996 阅读 / 4,940 字
温馨提示:
本文最后更新于 2023-01-07,若内容或图片失效,请留言反馈。部分素材来自网络,若不小心影响到您的利益,请联系我们删除。

两种数据:

  • 便于传输的数据:JSON、XML、……
  • 语言内置对象:Python Native Object

相互转换的过程:

  • serialize: 将语言内置对象转换为便于传输的数据
  • deserialize: 将便于传输的数据转换为语言内置对象

DRF 的 Serializers 实现了以上两个功能:

  • Serializer实现了语言对象与序列化数据之间的通用转换方法
  • ModelSerializer进一步为模型实例和查询集合实现了接口

Serializer 用来序列化/反序列化普通对象

serialize & deserialize

我们先定义一个普通类:

from datetime import datetime

class Comment:
    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')

然后我们定义一个 Serializer 类,这个类类似于表单数据对象(Django Form):

from rest_framework import Serializers

class CommentSerializer(serializer.Serializer):
    email = serializers.EmailField()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

注意,上面定义的 Comment 类和 CommentSerializer 类在语法上是完全不相干的。

然后我们可以用 CommentSerializer 来序列化/实例化 Comment 对象:

serialize

serializer = CommentSerializer(comment)
# serializer = CommentSerializer(instance=comment)
serializer.data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

deserialize

data = {'email': 'leila@example.com', 'content': 'foo bar', 'created': datetime.now()}
serializer = CommentSerializer(data=data)
serializer.is_valid()
# if True
serializer.validated_data
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2016-01-27T15:17:10.375877'}

serialize对象实例 转换成 序列数据,而 deserialize 按照预先定义的规则(*Field)对 序列数据 进行校验,校验成功时可通过.validated_data获取到数据。

deserialize & instantiate

deserialize 过程并不会主动将序列数据实例化成对象,因为序列化器(CommentSerializer)并不直接持有对象类(Comment)的引用。

如果需要在 deserialize 时自动实例化,即将 deserialize 过程升级为 instantiate 过程,我们需要单独为序列化器实现.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

我们可以看到:

  • create()方法接收经过验证的数据字典作为参数,返回一个实例对象
  • update()方法接收实例对象和经过验证的数据字典作为参数,对实例属性经过适当修改后返回了修改后的实例

通过复写这两个增、改方法,现在序列化器直接持有了实例类的引用。

现在使用序列化器进行 deserialize 后,调用其save()方法可以直接获得持有的实例对象的引用:

data = {'email': 'leila@example.com', 'content': 'foo bar', 'created': datetime.now()}
serializer = CommentSerializer(data=data)
if serializer.is_valid():
	print(serializer.validated_data)
    comment = serializer.save()

save()方法通常在数据库模型类的实例对象反序列化时用到,当我们从序列化数据创建一条记录时,create()方法通常这样写:

def create(self, validated_data):
    return Comment.objects.create(**validated_data)

save()方法和update()方法的自定义实现可以根据自己对序列化器的功能需求进行修改。

序列化器对数据库模型数据的序列化/反序列化已经在ModelSerialize类中实现了,不需要我们重复造轮子。

在创建/修改数据库模型实例时,save()方法还可以接收额外的关键字参数,这些关键字参数都会直接被添加到validated_data中,保存到数据库。

例如我们可以将设置 owner 的操作放在save()方法中:

serialize.save(owner=request.user)

ms 平台中关于 owner 的设置就可以从前端移除,改成这种形式!

custom save method

Serializer 的 save 一般用于序列化器接受序列化数据并验证数据之后,执行某些操作,在 DRF 中一般是保存实例数据到数据库。

但是有些情况下,我们可以扩展 save 的概念,它不一定需要永久保存数据,也可以是对验证的数据进行一些特定的操作,然后丢弃它,或者在保存之前另外做一些事情。

例如在一个 Contact 模型中,我们希望 Serializer 验证请求数据后,直接向用户发送邮件,而不是将该数据保存到数据库:

class Contact(serializers.Serializer):
    email = serializer.EmailField()
    message = serializer.CharField()
    
    def save(self):
        email = self.validated_data['email']
        message = self.validated_data['message']
        send_email(from=email, message=message)

需要注意的是,save 方法是从已经验证的数据字典(validated_data)中取数据。

validation

反序列化时才需要验证

Serializer 在反序列化数据时,必须先调用is_valid()方法验证数据。

只有经过验证后,才能通过validated_data属性拿到验证后的数据。

验证错误时的原因

验证失败时,可以通过errors属性获取验证失败的原因,其格式是:

{ [field: string]: string[] }

当不是字段验证出错时,errors 字典的键是"non_field_errors",这个键可以通过NON_FIELD_ERRORS_KEY配置。

当反序列化多个数据字典时,errors 会以数组形式返回,每个元素代表每个序列数据的验证结果。

验证失败时直接抛出异常

is_valid(raise_exception=True)会在数据验证出错时直接抛出异常,这个异常会被 DRF 自动处理,返回HTTP 400 Bad Request响应,表示验证失败是由请求数据不正确引起的。

字段级别的验证

为序列化器添加validate_<field_name>()方法可以直接对指定的字段进行验证,该方法

  • 验证失败时应该抛出serializers.ValidationError异常,并注明异常原因
  • 最后返回经过验证的值

例如下面的例子验证手机号必须是11位:

from test_framework import serializers

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=32)
    cellphone = serializers.CharField(max_length=11)
    
    def validate_cellphone(self, val: str):
        if len(val) != 11:
            raise serializers.ValidationError("cellphone must be 11 bits")
        return val

对于可选的字段(required=False)只有在序列化数据包含该字段时才会触发验证。

对象级别的验证

如果需要联合几个字段一起验证,应该在对象级别进行验证,重写validate()方法可实现在对象级别进行验证:

class UserSerializer(serializers.Serializer):
    username = serializers.CharField(max_length=32)
    cellphone = serializers.CharField(max_length=11)
    
    def validate(self, data: Dict):
        ...

入参 data 是一个数据字典,验证失败抛出 ValidationError,验证成功返回数据字典。

函数验证器

除了添加实例方法validate_<field_name>()对字段进行验证外,还可以在定义字段时指定一系列的函数验证器:

class GameRecord(serializers.Serializer):
    score = IntegerField(validators=[multiple_of_ten])
    ...

验证函数直接以待验证的值作为参数,验证失败抛出 ValidationError,并且验证成功可以不需要返回值

def multiple_of_ten(value):
    if value % 10 != 0:
        raise serializers.ValidationError('Not a multiple of ten')

可复用的验证器

DRF 提供了可复用的验证器:

from rest_framework import validators

高级用法,此处不表,可参考文档:https://www.django-rest-framework.org/api-guide/validators/#validators

References

0

评论区