DRF-序列化-Serializers

1 声明序列化程序

假设有以下类和对象

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')

声明一个序列化程序,可用于序列化和反序列化与 Comment 对象对应的数据。

from rest_framework import serializers

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

2 序列化对象

常用于响应 HTTP GET 请求,把数据库中的数据序列化后返回给前端。

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

3 反序列化对象

常用于响应 HTTP POST 请求,把序列化数据之后存到数据库。

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)}

4 保存实例

如果我们希望能够基于验证的数据返回完整的对象实例,我们需要实现.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

如果您的对象实例对应于Django模型,那么您还需要确保这些方法将对象保存到数据库中。例如,如果Comment是Django模型,那么方法可能如下所示:

    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() 来返回一个基于已验证数据的对象实例。

comment = serializer.save()

调用 .save() 将创建新实例或更新现有实例,具体取决于在实例化序列化程序类时是否传递了现有实例:

# .save() will create a new instance.
serializer = CommentSerializer(data=data)

# .save() will update the existing `comment` instance.
serializer = CommentSerializer(comment, data=data)

.create().update() 方法都是可选的。根据序列化程序类的用例,可以实现none、one或both。

4.1 将其他属性传递给.save()

有时,您希望视图代码能够在保存实例时注入额外的数据。这些附加数据可能包括诸如当前用户、当前时间或不属于请求数据的任何其他信息。

您可以通过在调用.save() 时包含额外的关键字参数来实现这一点。例如:

serializer.save(owner=request.user)

调用 .create().update() 时,validate_data参数中将包含任何其他关键字参数。

4.2 直接重写.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.validate_data 属性。

5 验证

在反序列化数据时,在尝试访问已验证的数据或保存对象实例之前,始终需要调用 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 密钥的名称可以使用NON_FIELD_ERRORS_KEY REST框架设置来定制。

当反序列化项目列表时,错误将作为表示每个反序列化项目的字典列表返回。

5.1 对无效数据引发异常

# Return a 400 response if the data was invalid.
serializer.is_valid(raise_exception=True)

5.2 字段级别验证

您可以通过向Serializer子类中添加 .validate_<field_name>方法来指定自定义字段级验证。这些方法类似于Django表单上的 .clean_<field_name>方法。

这些方法采用单个参数,即需要验证的字段值。

validate_<field_name> 方法应该返回已验证的值或引发 serializers.ValidationError

from rest_framework import serializers

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

    def validate_title(self, value):
        """
        Check that the blog post is about Django.
        """
        if 'django' not in value.lower():
            raise serializers.ValidationError("Blog post is not about Django")
        return value

注意:如果您的 <field_name>是在序列化程序上用 required=False 参数声明的,则不会执行此验证步骤。

5.3 对象级别验证

若要执行任何其他需要访问多个字段的验证,请将名为 .validate() 的方法添加到Serializer子类中。
此方法采用单个参数,该参数是字段值的字典。
它应该引发一个 serializers.ValidationError 如有必要)。或者只返回已验证的值。例如:

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 start is before finish.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

5.4 验证器

序列化程序上的各个字段可以包括验证器,方法是在字段实例上声明它们,例如:

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])
    ...

Serializer classes 还可以包括可重复使用的验证器,这些验证器应用于完整的字段数据集。这些验证器是通过在内部Meta类上声明它们来包含的,如下所示:

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

    class Meta:
        # 验证 date 和 room_number 两个字段的组合值唯一性
        validators = [
            UniqueTogetherValidator(
                queryset=Event.objects.all(),
                fields=['room_number', 'date']
            )
        ]

6 访问初始数据和实例

将初始对象或 queryset 传递给序列化程序实例时,该对象将从作为 .instance 提供。如果未传递任何初始对象,则 .instance 属性将为None

将数据传递到序列化程序实例时,未修改的数据将作为 .initil_data 提供。如果未传递数据关键字参数,则 .initial_data 属性将不存在。

7 部分更新

默认情况下,必须向序列化程序传递所有必需字段的值,否则会引发验证错误。您可以使用 partial 参数来允许部分更新。

# Update `comment` with partial data
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)

8 处理嵌套对象

前面的例子适用于处理只有简单数据类型的对象,但有时我们也需要能够表示更复杂的对象,其中对象的一些属性可能不是简单的数据类型,如字符串、日期或整数。

Serializer 类本身就是 Field 的一种类型,可用于表示一种对象类型嵌套在另一种对象中的关系。

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)  # May be an anonymous user.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

类似地,如果嵌套表示应该是项列表,则应将many=True标志传递给嵌套序列化程序。

class CommentSerializer(serializers.Serializer):
    user = UserSerializer(required=False)
    edits = EditItemSerializer(many=True)  # A nested list of 'edit' items.
    content = serializers.CharField(max_length=200)
    created = serializers.DateTimeField()

9 可写嵌套表达

在处理支持反序列化数据的嵌套表示时,嵌套对象的任何错误都将嵌套在嵌套对象的字段名下。

serializer = CommentSerializer(data={'user': {'email': 'foobar', 'username': 'doe'}, 'content': 'baz'})
serializer.is_valid()
# False
serializer.errors
# {'user': {'email': ['Enter a valid e-mail address.']}, 'created': ['This field is required.']}

类似地,.validated_data 属性将包括嵌套的数据结构。

9.1 为嵌套表示编写 .create()方法

如果您支持可写嵌套表示,则需要编写 .create().update() 方法来处理保存多个对象。
下面的示例演示了如何使用嵌套的 profile 对象创建用户

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
class Server(models.Model):
    hostname=...
    ip = ...
    
class Memory(models.Model):
    size = ...
    solt = ...
    server = model.ForeignKey(
        to=Server, 
        on_delete=models.CASCADE,
        related_name='memory')

class ServerSerializer(serializers.ModelSerializer):
    class Meta:
        model = Server
        fields = "__all__"
        
class MemorySerializer(serializers.ModelSerializer):
    server= ServerSerializer()

    class Meta:
        model = Memory
        fields = '__all__'

    def create(self, validated_data):
        server_data = validated_data.pop('server')
        server = Memory.objects.get(
                hostname=server_data.get('hostname'))
        memory = Memory.objects.create(server=server, **validated_data)
        return memory

9.2 为嵌套表示编写 .update()方法

对于更新,您需要仔细考虑如何处理关系的更新。例如,如果关系的数据为 None 或未提供,则应发生以下哪种情况?

  • 在数据库中将关系设置为NULL。
  • 删除关联的实例。
  • 忽略数据并保持实例原样。
  • 引发验证错误。

下面是上一个 MemorySerializer 类上的 .update() 方法的示例

    def update(self, instance, validated_data):
        profile_data = validated_data.pop('profile')
        # Unless the application properly enforces that this field is
        # always set, the following could raise a `DoesNotExist`, which
        # would need to be handled.
        profile = instance.profile

        instance.username = validated_data.get('username', instance.username)
        instance.email = validated_data.get('email', instance.email)
        instance.save()

        profile.is_premium_member = profile_data.get(
            'is_premium_member',
            profile.is_premium_member
        )
        profile.has_support_contract = profile_data.get(
            'has_support_contract',
            profile.has_support_contract
         )
        profile.save()

        return instance

因为嵌套创建和更新的行为可能是不明确的,并且可能需要相关模型之间的复杂依赖关系,所以REST框架 3 要求您始终显式地编写这些方法。
默认的 ModelSerializer.create().update() 方法不支持可写嵌套表示。

然而,也有第三方包可用,如DRF可写嵌套,支持自动可写嵌套表示。

9.3 在模型管理器类中处理与保存相关的实例

在序列化程序中保存多个相关实例的另一种方法是编写自定义模型管理器类,以处理创建正确的实例。

例如,假设我们希望确保 User 实例和 Profile 实例始终作为一对一起创建。我们可以编写一个自定义管理器类,看起来像这样:

class UserManager(models.Manager):
    ...

    def create(self, username, email, is_premium_member=False, has_support_contract=False):
        user = User(username=username, email=email)
        user.save()
        profile = Profile(
            user=user,
            is_premium_member=is_premium_member,
            has_support_contract=has_support_contract
        )
        profile.save()
        return user

这个管理器类现在更完美地封装了 user 实例和 profile 实例总是同时创建的。序列化程序类上的 .create() 方法现在可以重写为使用新的管理器方法。

def create(self, validated_data):
    return User.objects.create(
        username=validated_data['username'],
        email=validated_data['email'],
        is_premium_member=validated_data['profile']['is_premium_member'],
        has_support_contract=validated_data['profile']['has_support_contract']
    )

有关这种方法的更多详细信息,请参阅关于模型管理器的Django文档,以及这篇关于使用模型和管理器类的博客文章。

10 处理多个对象

Serializer类还可以处理对象列表的序列化或反序列化。

10.1 序列化多个对象

若要序列化查询集或对象列表而不是单个对象实例,应在实例化序列化程序时传递 many=True 标志。然后,您可以传递要序列化的查询集或对象列表。

queryset = Book.objects.all()
serializer = BookSerializer(queryset, many=True)
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'}
# ]

10.2 反序列化多个对象

反序列化多个对象的默认行为是支持创建多个对象,但不支持更新多个对象。有关如何支持或自定义这两种情况的更多信息,请参阅下面的ListSerializer文档。

11 包括额外的上下文

在某些情况下,除了要序列化的对象之外,还需要向序列化程序提供额外的上下文。一种常见的情况是,如果您使用的序列化程序包含超链接关系,这要求序列化程序能够访问当前请求,以便正确生成完全限定的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'}

通过访问 self.context 属性,上下文字典可以在任何序列化程序字段逻辑中使用,例如自定义 .to_presentation() 方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

shark_西瓜甜

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值