Rest-framework专栏讲解(十一):Serializers (1)

2,929 阅读6分钟

MedusaSorcerer的博客


点击跳转到 Rest-Framework 专栏目录

序列化对象是对检索数据或者请求数据判断的最好方式, 省略了繁琐的请求数据的判断以及返回响应数据的遍历。

Serializer

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from datetime import datetime

from rest_framework import serializers, settings


class UserSerializer(serializers.Serializer):
    username = serializers.CharField()
    blog = serializers.URLField(max_length=200)
    created = serializers.DateTimeField()


class User:
    def __init__(self, username, blog, created=None):
        self.username = username
        self.blog = blog
        self.created = created or datetime.now()


if __name__ == '__main__':
    settings.settings.configure()

    user = User(username='MedusaSorcerer', blog='https://juejin.cn/user/2805609406139950')
    serializer = UserSerializer(user)
    print(serializer.data)

在上面的代码块中定义了一个用户序列化类, 此时又定义了一个仿造 ORM 的用户类, 并将 User 的实例对象 user 传递给序列化的 data 参数, 将可以生成一个序列化对象, 打印序列化对象的 .data 属性可以获得以下数据:

{'username': 'MedusaSorcerer', 'blog': 'https://juejin.cn/user/2805609406139950', 'created': '2020-08-10T09:58:35.754067'}

你可以继续将数据渲染成 JSON 对象:

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from datetime import datetime

from rest_framework import serializers, settings


class UserSerializer(serializers.Serializer):
    username = serializers.CharField()
    blog = serializers.URLField(max_length=200)
    created = serializers.DateTimeField()


class User:
    def __init__(self, username, blog, created=None):
        self.username = username
        self.blog = blog
        self.created = created or datetime.now()


if __name__ == '__main__':
    settings.settings.configure()
    from rest_framework.renderers import JSONRenderer

    user = User(username='MedusaSorcerer', blog='https://juejin.cn/user/2805609406139950')
    serializer = UserSerializer(user)
    print(JSONRenderer().render(serializer.data))

注意在使用脚本执行的时候调用 settings.settings.configure() 获取配置信息的位置

运行完成你可以获得字符:

b'{"username":"MedusaSorcerer","blog":"https://juejin.cn/user/2805609406139950","created":"2020-08-10T10:03:36.741946"}'

反序列化

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from datetime import datetime
import io

from rest_framework import serializers, settings


class UserSerializer(serializers.Serializer):
    username = serializers.CharField()
    blog = serializers.URLField(max_length=200)
    created = serializers.DateTimeField()


class User:
    def __init__(self, username, blog, created=None):
        self.username = username
        self.blog = blog
        self.created = created or datetime.now()


if __name__ == '__main__':
    settings.settings.configure()
    from rest_framework.parsers import JSONParser

    json = b'{"username":"MedusaSorcerer","blog":"https://juejin.cn/user/2805609406139950","created":"2020-08-10T10:03:36.741946"}'
    stream = io.BytesIO(json)
    data = JSONParser().parse(stream)
    serializer = UserSerializer(data=data)
    if serializer.is_valid():
        print(serializer.validated_data)
    else:
        print(serializer.errors)

将保存的序列化数据反序列化一样可以实现, 注意的是在你反序列的时候需要执行 .is_valid() 来判断数据是否符合序列化类声明的对象约束, 如果返回的是 False 的话说明序列化对象不是符合声明对象的那样, 在返回 True 的时候就可以用 serializer.validated_data 获取序列化的数据:

rderedDict([('username', 'MedusaSorcerer'), ('blog', 'https://juejin.cn/user/2805609406139950'), ('created', datetime.datetime(2020, 8, 10, 10, 3, 36, 741946))])

那不符合约束的情况就可以用 serializer.errors 获取错误信息:

django.core.exceptions.AppRegistryNotReady: The translation infrastructure cannot be initialized before the apps registry is ready. Check that you don't make non-lazy gettext calls at import time.

事实上在开发中返回的错误信息是一个字典对象, key 是出现错误的字段字符串, value 是出现不符合约束的描述列表, 由于没有在 Django 项目中运行, 所以出现了一个报错信息(忽略)。

保存实例

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from datetime import datetime

from rest_framework import serializers


class UserSerializer(serializers.Serializer):
    username = serializers.CharField()
    blog = serializers.URLField(max_length=200)
    created = serializers.DateTimeField()

    def create(self, validated_data):
        return User(**validated_data)

    def update(self, instance, validated_data):
        instance.username = validated_data.get('username', instance.username)
        instance.blog = validated_data.get('blog', instance.blog)
        instance.created = validated_data.get('created', instance.created)
        return instance


class User:
    def __init__(self, username, blog, created=None):
        self.username = username
        self.blog = blog
        self.created = created or datetime.now()

如果你想使用验证后的数据返回一个新的实例对象, 你需要实现 create 或者 update 或者两个方法都实现。

在 Django-ORM 中你可能需要做一个保存数据的操作(ps: 实际开发中, 并不会这样操作, 很多操作都可以省略):

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from datetime import datetime

from rest_framework import serializers


class UserSerializer(serializers.Serializer):
    username = serializers.CharField()
    blog = serializers.URLField(max_length=200)
    created = serializers.DateTimeField()

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

    def update(self, instance, validated_data):
        instance.username = validated_data.get('username', instance.username)
        instance.blog = validated_data.get('blog', instance.blog)
        instance.created = validated_data.get('created', instance.created)
        instance.save()
        return instance

保存实例或者更新实例的 save() 方法, 主要是看你是否在创建实例序列化对象是否传递了对象:

# 没有传递实例对象, 所以这是新增
serializer = UserSerializer(data=data)

# 传递了一个 user 对象, 所以这是更新了 user 对象的数据
serializer = UserSerializer(user, data=data)

验证数据

反序列化的时候你可以直接调用序列化实例对象的 .is_valid() 方法进行判断约束数据, 在开发中你也可以使用 .is_valid(raise_exception=True) 将错误信息返回给 API 调用者。

如果对一个字段需要特殊的判断, 你可以使用 validate_<field_name> 来进行逻辑判断, <field_name> 是你在序列化类中声明的字段名称。

ps:你可以将序列化类和 ORM 类联想, 但是概念完全不一样, 逻辑上很相似。

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import serializers

class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()

    def validate_title(self, value):
        """
        对 title 字段进行判断
        """
        if 'medusa' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Medusa")
        return value

同时对多个字段进行逻辑判断:

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import serializers


class BlogPostSerializer(serializers.Serializer):
    title = serializers.CharField(max_length=100)
    content = serializers.CharField()
    start = serializers.IntegerField()
    finish = serializers.IntegerField()

    def validate(self, data):
        """
        判断传递的 start 和 finish 参数是否符合大小逻辑
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

你也可以使用字段约束上直接声明你的判断:

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import serializers


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


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

同时你可以声明完整字段验证器:

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import serializers
from rest_framework.validators import UniqueTogetherValidator


class EventSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
    date = serializers.DateField()

    class Meta:
        validators = [
            # 唯一性验证器
            UniqueTogetherValidator(
                queryset=User.objects.all(),
                fields=['username', 'other']
            )
        ]

访问实例和初始数据

当你将初始对象或查询集传递给序列化实例时, 该对象将变为可使用 .instance 属性获取对象, 如果没有传递初始对象, 则 .instance 属性值为 None

将数据传递给序列化程序实例时, 未修改的数据将以 .initial_data 形式提供, 如果未传递 data 关键字参数, 则该 .initial_data 属性将不存在。

部分更新

默认情况下, 必须为所有声明的序列化字段传递对应的值, 否则序列化器会引发验证错误, 此时你可以使用 partial 参数来允许部分更新。

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import serializers


class UpdateSerializer(serializers.Serializer):
    name = serializers.CharField()
    room_number = serializers.IntegerField(choices=[101, 102, 103, 201])
    date = serializers.DateField()


UpdateSerializer(user, data={'room_number': 101}, partial=True)

序列化嵌套

如果一个序列化对象的属性是另一个序列化对象, 又要怎么处理?

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import serializers


class UserSerializer(serializers.Serializer):
    email = serializers.EmailField()
    username = serializers.CharField(max_length=100)


class CommentSerializer(serializers.Serializer):
    user = UserSerializer()
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

如果嵌套可以选择接受该 None 值,则应将 required=False 传递给嵌套序列化器:

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

如果嵌套序列对象是多条数据构成, 则应将 many=True 传递给嵌套序列化:

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

如果反序列化的数据无法通过约束, 那么会错误信息返回至构造字段中:

serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()  # False, email 和 content 无法通过验证

# 输出错误数据
serializer.errors
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}

嵌套对象处理示例

#!/usr/bin/env python
# _*_ Coding: UTF-8 _*_
from rest_framework import serializers


class UserSerializer(serializers.ModelSerializer):
    profile = ProfileSerializer()

    class Meta:
        model = User
        fields = ['username', 'email', 'profile']

    def create(self, validated_data):
        profile_data = validated_data.pop('profile')
        user = User.objects.create(**validated_data)
        Profile.objects.create(user=user, **profile_data)
        return user

序列化多个对象

queryset = Book.objects.all()
serializer = BookSerializer(queryset, many=True)
serializer.data

序列化 serializer.data 的值:

[
    {'id': 0, 'title': 'The electric kool-aid acid test', 'author': 'Tom Wolfe'},
    {'id': 1, 'title': 'If this is a man', 'author': 'Primo Levi'},
    {'id': 2, 'title': 'The wind-up bird chronicle', 'author': 'Haruki Murakami'}
]

额外上下文链接

在某些情况下, 除了要序列化的对象外, 还需要为序列化器提供额外的上下文。

一种常见的情况是如果您使用的是包含超链接关系的序列化程序, 则要求序列化程序有权访问当前请求, 以便它可以正确生成完全合格的 URL。

您可以在实例化序列化程序时通过传递 context 参数来提供任意其他上下文:

serializer = AccountSerializer(account, context={'request': request})
serializer.data

{
	'id': 6, 
    'owner': 'denvercoder9', 
    'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 
    'details': 'http://example.com/accounts/6/details'
}