当前位置: 首页 > 工具软件 > REST Superman > 使用案例 >

Django Rest Framework Serializers API指南

顾兴昌
2023-12-01

官方文档链接

Serializers 序列化器

前言:

序列化器允许诸如查询集和模型实例复杂的数据转换为原生的Python数据类型,然后可以很容易地呈现为JSONXML或其他内容类型。序列化器还提供反序列化功能,允许在首先验证输入数据之后将解析的数据转换回复杂类型。

REST框架中的序列化器的工作方式与Django FormModelForm类非常相似。我们提供了一个Serializer类,该类为您提供了一种强大的通用方法来控制响应的输出,还ModelSerializer提供了一个类,该类为创建用于处理模型实例和查询集的序列化器提供了有用的快捷方式。

声明序列化器

让我们从创建一个简单的对象开始,我们可以将其用于示例目的:

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对象相对应的数据。

声明序列化器看起来与声明表单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'}
type(serializer.data)
# <class 'rest_framework.utils.serializer_helpers.ReturnDict'>

至此,我们已经将一个comment实例转换为Python原生数据类型。为了完成序列化过程,我们将数据渲染到中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"}'
type(json)
# <class 'bytes'>

反序列化对象

反序列化是相似的。首先,我们将流解析为Python原生数据类型

import io
from rest_framework.parsers import JSONParser

stream = io.BytesIO(json)
# <_io.BytesIO object at 0x000001BAD7B29678>
data = JSONParser().parse(stream)
# {'email': 'leila@example.com', 'content': 'foo bar', 'created': '2019-12-19T10:15:08.513248Z'}
type(data)
# <class 'dict'>

然后我们将这些本机数据类型还原为经过验证的数据字典

注意:依然是调用CommentSerializer类的构造函数,但是给data参数传递数据,而不是第一位置参数,这表示反序列化过程。其次,数据有一个验证过程is_valid()。上面给第一位置参数传参,表示的是序列化

serializer = CommentSerializer(data=data)
serializer.is_valid()
# True
serializer.validated_data
# OrderedDict([('email', 'leila@example.com'), ('content', 'foo bar'), ('created', datetime.datetime(2019, 12, 19, 10, 15, 8, 513248, tzi
nfo=<UTC>))])
type(serializer.validated_data)
# <class 'collections.OrderedDict'>

这个步骤做完,只是从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

上面的两个方法在其继承关系中的父类里定义了具体的参数形式,instancevalidated_data都是由继承体系里定义了的,分别表示要返回的Comment实例对象和经过验证的数据。

如果您的对象实例与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

相比普通的Python的Comment类,对Django模型的保存多了一些ORM操作,比如save()方法。

现在,在对数据进行反序列化时,我们可以.save()基于已验证的数据调用以返回对象实例。

comment = serializer.save()

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

# .save()将创建一个新的实例
serializer = CommentSerializer(data=data)

# .save() 将更新现有的' 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里面自己取。

验证方式

反序列化数据时,您始终需要先调用,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默认返回响应。

# 如果数据无效,则返回400响应.
serializer.is_valid(raise_exception=True)

字段级别验证

你可以通过向你的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参数,并且未给该字段提供参数,那么这个验证步骤不会执行


对象级别验证

要执行需要访问多个字段的任何其他验证,请添加一个.validate()方法到你的Serializer子类中。这个方法使用包含各个字段值的字典作为单个参数,错误情况下应该抛出一个 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

验证器参数

还可以通过在字段上声明验证器参数的方式为字段设置指定的验证器,例如:

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

注意上面给参数提供了一个列表值,这说明可以同时使用多个验证器。

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

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

    class Meta:
        # Each room only has one event per day.
        validators = UniqueTogetherValidator(
            queryset=Event.objects.all(),
            fields=['room_number', 'date']
        )

有关更多信息,请参见原文验证程序文档

访问初始数据和实例

将初始化对象或者查询集传递给序列化实例时,可以通过.instance访问原始对象。如果没有传递初始化对象,那么.instance属性值将是None。(正向,即序列化方向)

将数据传递给序列化器实例时,未修改的数据可以通过.initial_data获取。如果没有传递data关键字参数,那么.initial_data属性不存在。(反向,即反序列化方向

部分更新

默认情况下,序列化器必须传递所有必填字段的值,否则就会引发验证错误。但是我们可以将 partial参数指定为True,来允许部分更新

# 用部分数据更新“comment”
serializer = CommentSerializer(comment, data={'content': 'foo bar'}, partial=True)

处理关系对象

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

Serializer类本身也是一种Field,并且可以用来表示一个对象嵌套在另一个对象中的关系。也就是处理Django模型中的关系类型,一对一、一对多、多对多的字段。

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

关系字段数据验证

当我们在处理包含关系字段的序列化过程中,如果关联字段的值不合法,同样会被检测出来,并且将错误信息保存在相应的位置,如下例,CommentSerializer中的user字段关联到authUser表,但是提供了一个不合法的邮箱地址,sorry,你不能成功反序列化,DRF给出了明确的错误信息。类似的,.validated_data 属性也将包括关系字段的数据结构。

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.']}

为关系字段编写.create()方法

对于关系型字段,我们需要编写.create().update()处理保存多个对象的方法。

下面的示例演示如何处理创建一个具有关联的概要信息对象的用户。

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

为关系字段定义.update()方法

对于更新,你需要仔细考虑如何处理关联字段的更新。 例如,如果关联字段的值是None,或者没有提供,那么会发生下面哪种情况?

  • 在数据库中将关联字段设置成NULL
  • 删除关联的实例。
  • 忽略数据并保留这个实例。
  • 抛出验证错误。

下面是我们之前UserSerializer类中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 follow 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 framework从3.x版本后要求你始终明确的定义这些方法。并且,默认的ModelSerializer 类的.create().update()方法不包括对关联字段的支持,需要你自己实现,或者需求第三方模块的支持。

但是,有第三方软件包,例如DRF Writable Nested,它们支持自动可写嵌套表示。

使用模型管理类保存关联对象

在序列化器中保存多个相关实例的另一种方法是编写处理创建正确实例的自定义模型管理器类,也就是Django原生的models.Manager

例如,假设我们想确保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

现在,该管理器类使得用户实例和用户信息实例总是在同一时间创建。我们在序列化器类上的.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文档,以及有关使用模型和管理器类的博客

处理多个对象

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

同时序列化多个对象

为了能够序列化一个查询集或者一个对象列表而不是一个单独的对象,需要在实例化序列化器类的时候传一个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'}
# ]

反序列化多个对象

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

附加额外的上下文

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

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


ModelSerializer

通常,您会需要与Django模型定义紧密映射的序列化程序类。

ModelSerializer类提供了一个快捷方式,可以让你自动创建一个具有模型中相应字段的Serializer

ModelSerializer类直接继承了Serializer,不同之处在于

  • 它将根据模型自动为您生成一组字段。
  • 它将自动为序列化器生成验证器,例如unique_together验证器。
  • 它包括简单的默认实现.create().update()

声明ModelSerializer类的方法如下所示:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']

默认情况下,该类上的所有模型字段都将映射到相应的序列化器字段。

任何关系(例如模型上的外键)都将映射到PrimaryKeyRelatedField。默认情况下不包括反向关系,除非按照序列化器关系文档中的指定明确包含反向关系。

如果你想确定ModelSerializer自动创建了哪些字段和验证器,可以打开Django shell。

为此,请使用打开Django shell,python manage.py shell然后导入序列化程序类,实例化它,并打印对象表示形式…

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

指定要包括的字段

如果你希望在模型序列化器中只使用默认字段的一部分,你可以使用fieldsexclude选项来执行此操作,就像使用ModelForm一样。强烈建议你使用fields属性显式的设置要序列化的字段。这样就不太可能因为你修改了模型而无意中暴露了数据。

例如:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']

您也可以将fields属性设置为特殊值,'__all__'以指示应使用模型中的所有字段。

例如:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = '__all__'

您可以将exclude属性设置为要从序列化器中排除的字段列表。

例如:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        exclude = ['users']

在上面的例子中,如果Account模型具有3个字段account_nameuserscreated,这将只有字段account_namecreated被序列化。

fieldsexclude属性中的名称通常会映射到模型类上的模型字段。

另外,fields选项中的名称可以映射到属性或方法,这些属性或方法不包含模型类上存在的参数。

从3.3.0版开始,必须提供属性fieldsexclude

关系字段的序列化深度

ModelSerializer默认使用主键进行对象关联,但是你也可以使用depth选项轻松生成嵌套关联:

class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ['id', 'account_name', 'users', 'created']
        depth = 1

depth选项应该设置一个整数值,表明应该遍历的关联深度。

如果要自定义序列化的方式你需要自定义该字段。

明确指定字段

觉得全自动的字段不满足需求,也可以轻度定制,这就向基础的Serializer类靠拢了一点。

可以通过在ModelSerializer类上显式声明字段来增加额外的字段或者重写默认的字段,就和在Serializer类一样的。

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

此选项应该是字段名称的列表或元组,并且声明如下:

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选项中。


注意: 有一种特殊情况,其中一个只读字段是模型级别unique_together约束的一部分。在这种情况下,序列化器需要该字段的值才能验证约束,但也是不能由用户编辑的。

处理此问题的正确方法是在序列化器上显式指定该字段,同时提供read_only=Truedefault=…关键字参数。

这种情况的一个例子就是对于一个和其他标识符unique_together的当前认证的User是只读的。 在这种情况下你可以像下面这样声明user字段:

user = serializers.PrimaryKeyRelatedField(read_only=True, default=serializers.CurrentUserDefault())

请查看验证程序文档以获取有关UniqueTogetherValidatorCurrentUserDefault类的详细信息。


附加关键字参数

还可以通过使用extra_kwargs选项快捷地在字段上指定任意附加的关键字参数。这个选项是一个将具体字段名称当作键值的字典。例如:

class CreateUserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ['email', 'username', 'password']
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = User(
            email=validated_data['email'],
            username=validated_data['username']
        )
        user.set_password(validated_data['password'])
        user.save()
        return user

请记住,如果已在序列化程序类上显式声明了该字段,则该extra_kwargs选项将被忽略。

关系字段

在序列化模型实例的时候,你可以选择多种不同的方式来表示关联关系。对于ModelSerializer默认是使用相关实例的主键,也就是PrimaryKeyRelatedField。

替代的其他方法包括使用超链接序列化、序列化完整的嵌套表示或者使用自定义表示的序列化。

更多详细信息请查阅序列化器关系章节

自定义字段映射

ModelSerializer类还暴露了一个可以覆盖的API,以便在实例化序列化器时改变序列化器字段的自动确定方式。

通常情况下,如果ModelSerializer没有生成默认情况下你需要的字段,那么你应该将它们显式地添加到类中,或者直接使用常规的Serializer类。但是在某些情况下,你可能需要创建一个新的基类,定义任意模型创建序列化字段的方式。

.serializer_field_mapping

将Django model的字段类型映射到REST framework serializer的字段类型。你可以覆写这个映射

用于关联对象的字段类型

对于ModelSerializer此属性默认为PrimaryKeyRelatedField

对于HyperlinkedModelSerializer此属性默认为serializers.HyperlinkedRelatedField

serializer_url_field

url类型的字段类。默认是 serializers.HyperlinkedIdentityField。

serializer_choice_field

选择类型的字段类。默认是serializers.ChoiceField。

field_class和field_kwargs API

调用下面的方法来确定应该自动包含在序列化器类中每个字段的类和关键字参数。这些方法都应该返回 (field_class, field_kwargs)元组。

.build_standard_field(self, field_name, model_field)

调用以生成映射到标准模型字段的序列化器字段。

默认实现基于serializer_field_mapping属性返回一个序列化器类。

.build_relational_field(self, field_name, relation_info)

调用以生成映射到关系模型字段的序列化器字段。

默认实现基于serializer_related_field属性返回一个序列化器类。

relation_info参数是一个命名元组,包含model_fieldrelated_modelto_manyhas_through_model属性。

.build_nested_field(self, field_name, relation_info, nested_depth)

depth选项被设置时,被调用后生成一个对应到关联模型字段的序列化器字段。。

默认实现基于ModelSerializer或动态创建关系的序列化器类HyperlinkedModelSerializer

nested_depthde值是depth的值减一。

relation_info参数是一个命名元组,包含model_fieldrelated_modelto_manyhas_through_model属性。

.build_property_field(self, field_name, model_class)

被调用后生成一个对应到模型类中属性或无参数方法的序列化器字段。

默认实现返回一个ReadOnlyField类。

.build_url_field(self, field_name, model_class)

被调用后为序列化器自己的url字段生成一个序列化器字段。默认实现是返回一个HyperlinkedIdentityField类。

.build_unknown_field(self, field_name, model_class)

当字段名称未映射到任何模型字段或模型属性时调用。尽管子类可以自定义此行为,但默认实现会引发错误。


超链接模型序列化器

HyperlinkedModelSerializer类直接继承ModelSerializer类,不同之处在于它使用超链接来表示关联关系而不是主键。

默认情况下,HyperlinkedModelSerializer序列化器将包含一个url字段而不是主键字段。

url字段将使用HyperlinkedIdentityField字段来表示,模型的任何关联都将使用HyperlinkedRelatedField字段来表示。

你可以通过将主键添加到fields选项中来显式的包含主键字段,例如下面的id

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ['url', 'id', 'account_name', 'users', 'created']

这个类的源代码非常简单:

class HyperlinkedModelSerializer(ModelSerializer):
    # 下面这句是核心
    serializer_related_field = HyperlinkedRelatedField

    def get_default_field_names(self, declared_fields, model_info):
        # 覆写了ModelSerializer中的方法,在第一个变量处发生了变化,使用了url名字。
        return (
            [self.url_field_name] +
            list(declared_fields) +
            list(model_info.fields) +
            list(model_info.forward_relations)
        )

    def build_nested_field(self, field_name, relation_info, nested_depth):
        # 覆写了ModelSerializer中的方法,嵌套的子类依然继承的是HyperlinkedModelSerializer
        class NestedSerializer(HyperlinkedModelSerializer):
            class Meta:
                model = relation_info.related_model
                depth = nested_depth - 1
                fields = '__all__'

        field_class = NestedSerializer
        field_kwargs = get_nested_relation_kwargs(relation_info)

        return field_class, field_kwargs

绝对和相对URL

当实例化一个HyperlinkedModelSerializer时,你必须在序列化器的上下文中包含当前的request值,例如:

serializer = AccountSerializer(queryset, context={'request': request})

这样做将确保超链接可以包含适当的主机名,以使生成的表示形式使用完全限定的URL,例如:

http://api.example.com/accounts/1/

而不是相对URL,例如:

/accounts/1/

如果你真的要使用相对URL,你应该明确的在序列化器上下文中传递一个{'request': None}参数,而不是忽略不写。

如何确定超链接视图

我们为什么要使用超链接的序列化器?因为默认的主键字段只是冰冷的数字id,也就是1,2,3等等,在前端你无法知道它具体的含义。使用超链接则返回的是对应对象的url访问地址,是可以点击跳转直达的,更加形象。

既然是点击跳转可以直达,那么就需要确定哪些视图能应用超链接到模型实例的方法,否则没有对应的视图来处理这些链接的请求,就会404了。

默认情况下,超链接期望对应到一个样式能匹配'{model_name}-detail'的视图,并通过pk关键字参数查找实例。

那么可以通过在extra_kwargs中设置view_namelookup_field中的一个或两个来重写URL字段视图名称和查询字段。如下所示:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    class Meta:
        model = Account
        fields = ['account_url', 'account_name', 'users', 'created']
        extra_kwargs = {
            'url': {'view_name': 'accounts', 'lookup_field': 'account_name'},
            'users': {'lookup_field': 'username'}
        }

或者你可以显式的设置序列化器上的字段。例如:

class AccountSerializer(serializers.HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='accounts',
        lookup_field='slug'
    )
    users = serializers.HyperlinkedRelatedField(
        view_name='user-detail',
        lookup_field='username',
        many=True,
        read_only=True
    )

    class Meta:
        model = Account
        fields = ['url', 'account_name', 'users', 'created']

提示:正确匹配超链接表示和你的URL配置有时可能会有点困难。打印一个HyperlinkedModelSerializer实例的repr是一个特别有用的方式来检查关联关系映射的那些视图名称和查询字段。


更改URL字段名称

URL字段的名称默认为'url'。你可以在settings.py中修改URL_FIELD_NAME配置项进行全局性修改。


ListSerializer

ListSerializer继承的是BaseSerializer,属于Serializer的兄弟

ListSerializer类能够一次性序列化和验证多个对象。我们通常不要直接使用ListSerializer,而是应该在实例化一个序列化器时简单地传递一个many=True参数。

当一个序列化器在带有many=True选项被序列化时,实际将创建一个ListSerializer类的实例。该序列化器类将成为ListSerializer类的子类。

可以传递一个allow_empty参数给ListSerializer序列化器。这个参数的默认值是True,但是如果你不想把空列表当作有效输入的话可以把它设置成False

自定义ListSerializer行为

碰到下面的情况,你可能需要自定制ListSerializer的一些行为:

  • 希望提供列表的特定验证,例如检查一个元素是否与列表中的另外一个元素冲突。
  • 想自定义多个对象的创建或更新行为。

对于这些情况,当你可以通过使用序列化器类的Meta类下面的list_serializer_class选项来修改当many=True时正在使用的类。

也就是说这个时候你必须写一个继承了serializers.ListSerializer类的子类,然后在需要用它的序列化器类的Meta中添加list_serializer_class条目。

例如:

class CustomListSerializer(serializers.ListSerializer):
    ...

class CustomSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = CustomListSerializer

自定义多个对象的创建

默认情况下,多个对象的创建是简单的对列表中每个对象调用.create()方法。如果要自定义实现,那么你需要自定义当被传递many=True参数时使用的ListSerializer类中的.create()方法。

例如:

class BookListSerializer(serializers.ListSerializer):
    def create(self, validated_data):
        books = [Book(**item) for item in validated_data]
        return Book.objects.bulk_create(books)

class BookSerializer(serializers.Serializer):
    ...
    class Meta:
        list_serializer_class = BookListSerializer

自定义多个更新

默认情况下,ListSerializer该类不支持多个更新。这是因为插入和删除应预期的行为是模棱两可的。

要支持多个更新,您需要明确地这样做。在编写多个更新代码时,请确保牢记以下几点:

  • 您如何确定应该为数据列表中的每个项目更新哪个实例?
  • 应该如何处理插入?它们是无效的还是创建新对象?
  • 应如何处理清除?它们是否意味着对象删除或删除关系?应该默默地忽略它们还是无效?
  • 排序如何处理?更改两个项目的位置是否意味着状态发生任何变化或被忽略?

你需要向实例序列化器中显式添加一个id字段。默认隐式生成的id字段是read_only。这就导致它在更新时被删除。一旦你明确地声明它,它将在列表序列化器的update方法中可用。

下面是一个多对象更新的示例:

class BookListSerializer(serializers.ListSerializer):
    def update(self, instance, validated_data):
        # Maps for id->instance and id->data item.
        book_mapping = {book.id: book for book in instance}
        data_mapping = {item['id']: item for item in validated_data}

        # Perform creations and updates.
        ret = []
        for book_id, data in data_mapping.items():
            book = book_mapping.get(book_id, None)
            if book is None:
                ret.append(self.child.create(data))
            else:
                ret.append(self.child.update(book, data))

        # Perform deletions.
        for book_id, book in book_mapping.items():
            if book_id not in data_mapping:
                book.delete()

        return ret

class BookSerializer(serializers.Serializer):
    # We need to identify elements in the list using their primary key,
    # so use a writable field here, rather than the default which would be read-only.
    id = serializers.IntegerField()
    ...

    class Meta:
        list_serializer_class = BookListSerializer

3.1发行版中可能会包含第三方软件包,该软件包为多种更新操作提供了一些自动支持,类似于allow_add_removeREST框架2中的行为。

自定义ListSerializer初始化

当带有many=True参数的序列化器被实例化时,我们需要确定哪些参数和关键字参数应该被传递给子Serializer类和父类ListSerializer.__init__()方法。

默认实现是将所有参数都传递给两个类,除了validators和自定义的关键字参数,这些参数都假定用于子序列化器类。

有时候,你可能需要明确指定当被传递many=True参数时,子类和父类应该如何实例化。你可以使用many_init类方法来执行此操作。

    @classmethod
    def many_init(cls, *args, **kwargs):
        # Instantiate the child serializer.
        kwargs['child'] = cls()
        # Instantiate the parent list serializer.
        return CustomListSerializer(*args, **kwargs)

BaseSerializer

BaseSerializer 可以简单的用来替代序列化和反序列化的样式。

Serializer类直接继承了BaseSerializer类,所以两者具有基本相同的API:

  • .data - 返回传出的原始数据。
  • .is_valid() - 反序列化并验证传入的数据。
  • .validated_data - 返回经过验证后的传入数据。
  • .errors - 返回验证期间的错误。
  • .save() - 将验证的数据保留到对象实例中。

它还有可以覆写的四种方法,具体取决于你想要序列化类支持的功能:

  • .to_representation() - 重写此方法来改变读取操作的序列化结果。
  • .to_internal_value() - 重写此方法来改变写入操作的序列化结果。
  • .create() 和 .update() - 重写其中一个或两个来改变保存实例时的动作。

因为此类提供与Serializer类相同的接口,所以你可以将它与现有的基于类的通用视图一起使用,就像使用常规SerializerModelSerializer一样。

你需要注意到的唯一区别是BaseSerializer类并不会在可浏览的API页面中生成HTML表单。

  • 只读的 BaseSerializer 

要使用BaseSerializer类实现只读的序列化器,我们只需要覆写.to_representation()方法。让我们看一个简单的Django模型的示例:

class HighScore(models.Model):
    created = models.DateTimeField(auto_now_add=True)
    player_name = models.CharField(max_length=10)
    score = models.IntegerField()

创建一个只读的序列化器将HighScore实例转换为原始数据类型很简单。

class HighScoreSerializer(serializers.BaseSerializer):
    def to_representation(self, instance):
        return {
            'score': instance.score,
            'player_name': instance.player_name
        }

现在,我们可以使用此类来序列化单个HighScore实例:

@api_view(['GET'])
def high_score(request, pk):
    instance = HighScore.objects.get(pk=pk)
    serializer = HighScoreSerializer(instance)
    return Response(serializer.data)

或使用它来序列化多个实例:

@api_view(['GET'])
def all_high_scores(request):
    queryset = HighScore.objects.order_by('-score')
    serializer = HighScoreSerializer(queryset, many=True)
    return Response(serializer.data)

可读写的BaseSerializer

要创建一个读写都支持的序列化器,我们首先需要实现.to_internal_value()方法。这个方法返回用来构造对象实例的经过验证的值,如果提供的数据格式不正确,则可能引发ValidationError

一旦你实现了.to_internal_value()方法,那些基础的验证API都会在序列化对象上可用了,你就可以使用.is_valid().validated_data 和 .errors 方法。

如果你还想支持.save(),你还需要实现.create().update()方法中的一个或两个。

下面是支持读、写操作的 HighScoreSerializer 完整示例:

class HighScoreSerializer(serializers.BaseSerializer):
    def to_internal_value(self, data):
        score = data.get('score')
        player_name = data.get('player_name')

        # Perform the data validation.
        if not score:
            raise serializers.ValidationError({
                'score': 'This field is required.'
            })
        if not player_name:
            raise serializers.ValidationError({
                'player_name': 'This field is required.'
            })
        if len(player_name) > 10:
            raise serializers.ValidationError({
                'player_name': 'May not be more than 10 characters.'
            })

        # Return the validated values. This will be available as
        # the `.validated_data` property.
        return {
            'score': int(score),
            'player_name': player_name
        }

    def to_representation(self, instance):
        return {
            'score': instance.score,
            'player_name': instance.player_name
        }

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

创建新的基类

BaseSerializer类还可以用来创建新的通用序列化基类来处理特定的序列化样式或者用来整合备用存储后端。

下面这个类是一个可以将任意对象强制转换为基本表示的通用序列化类的示例。

class ObjectSerializer(serializers.BaseSerializer):
    """
    A read-only serializer that coerces arbitrary complex objects
    into primitive representations.
    """
    def to_representation(self, instance):
        output = {}
        for attribute_name in dir(instance):
            attribute = getattr(instance, attribute_name)
            if attribute_name.startswith('_'):
                # Ignore private attributes.
                pass
            elif hasattr(attribute, '__call__'):
                # Ignore methods and other callables.
                pass
            elif isinstance(attribute, (str, int, bool, float, type(None))):
                # Primitive types can be passed through unmodified.
                output[attribute_name] = attribute
            elif isinstance(attribute, list):
                # Recursively deal with items in lists.
                output[attribute_name] = [
                    self.to_representation(item) for item in attribute
                ]
            elif isinstance(attribute, dict):
                # Recursively deal with items in dictionaries.
                output[attribute_name] = {
                    str(key): self.to_representation(value)
                    for key, value in attribute.items()
                }
            else:
                # Force anything else to its string representation.
                output[attribute_name] = str(attribute)
        return output

序列化器serializer高级用法

重写序列化和反序列化行为

如果你需要自定义序列化类的序列化、反序列化或验证过程的行为,可以通过重写.to_representation().to_internal_value()方法来完成。

这么做的一些原因包括......

  • 为新的序列化基类添加新行为。
  • 对现有的类稍作修改。
  • 提高频繁访问返回大量数据的API端点的序列化性能。

这些方法的签名如下:

  • .to_representation(self, instance)

接收一个需要被序列化的对象实例并且返回一个序列化之后的表示。通常,这意味着返回内置Python数据类型的结构。可以处理的确切类型取决于你为API配置的渲染类。

可以重写以修改表示样式。例如:

def to_representation(self, instance):
    """Convert `username` to lowercase."""
    ret = super().to_representation(instance)
    ret['username'] = ret['username'].lower()
    return ret
  • .to_internal_value(self, data)

将未经验证的传入数据作为输入,返回可以通过serializer.validated_data来访问的已验证的数据。如果在序列化类上调用.save(),则该返回值也将传递给.create().update()方法。这是反向的过程,由前端数据往后端保存。

如果任何验证条件失败,那么该方法会引发一个serializers.ValidationError(errors)。通常,此处的errors参数将是错误消息字典的一个映射字段,或者是settings.NON_FIELD_ERRORS_KEY设置的值。

传递给此方法的data参数通常是request.data的值,因此它提供的数据类型将取决于你为API配置的解析器类。

序列化器Serializer的继承

与Django表单类似,你可以通过继承扩展和重用序列化类。这允许你在父类上声明一组公共字段或方法,然后可以在许多序列化器中使用它们。举个例子:

class MyBaseSerializer(Serializer):
    my_field = serializers.CharField()

    def validate_my_field(self, value):
        ...

class MySerializer(MyBaseSerializer):
    ...

像Django的ModelModelForm类一样,Meta序列化程序上的内部类不会隐式继承其父级的内部Meta类。如果要让Meta该类从父类继承,则必须明确地这样做。例如:

class AccountSerializer(MyBaseSerializer):
    class Meta(MyBaseSerializer.Meta):
        model = Account

通常,我们建议不要在内部Meta类上使用继承,而应显式声明所有选项。

此外,以下警告适用于序列化程序继承过程:

  • 正常的Python名称解析规则适用。如果您有多个声明Meta内部类的基类,则仅使用第一个基类。这意味着孩子的Meta(如果存在),否则就是第一个父类的Meta等。
  • 通过在子类上将名称设置为None,可以声明性地删除从父类继承的Field

class MyBaseSerializer(ModelSerializer):
    my_field = serializers.CharField()

class MySerializer(MyBaseSerializer):
    my_field = None

但是,你只能使用此黑科技去掉父类显式声明定义的字段;它不会阻止ModelSerializer生成的默认字段。

动态修改字段

初始化序列化程序后,可以使用.fields属性访问序列化程序上设置的字段的字典。访问和修改此属性使您可以动态修改序列化程序。

访问或修改序列化器的fileds属性可以动态地修改字段。这可以实现一些有趣的操作,比如在运行过程中修改某些字段的参数。

例如,如果您希望能够在初始化时设置序列化程序应使用的字段,则可以创建一个序列化程序类,如下所示:

class DynamicFieldsModelSerializer(serializers.ModelSerializer):
    """
    A ModelSerializer that takes an additional `fields` argument that
    controls which fields should be displayed.
    """

    def __init__(self, *args, **kwargs):
        # Don't pass the 'fields' arg up to the superclass
        fields = kwargs.pop('fields', None)

        # Instantiate the superclass normally
        super(DynamicFieldsModelSerializer, self).__init__(*args, **kwargs)

        if fields is not None:
            # Drop any fields that are not specified in the `fields` argument.
            allowed = set(fields)
            existing = set(self.fields)
            for field_name in existing - allowed:
                self.fields.pop(field_name)

然后,您可以执行以下操作:

>>> class UserSerializer(DynamicFieldsModelSerializer):
>>>     class Meta:
>>>         model = User
>>>         fields = ['id', 'username', 'email']
>>>
>>> print(UserSerializer(user))
{'id': 2, 'username': 'jonwatts', 'email': 'jon@example.com'}
>>>
>>> print(UserSerializer(user, fields=('id', 'email')))
{'id': 2, 'email': 'jon@example.com'}

自定义默认字段

REST Framework2提供了一个API,允许开发人员重写ModelSerializer类如何自动生成默认字段集的方式。

该API包括.get_field().get_pk_field()和其他方法。

由于串行器已经从3.0进行了重新设计,因此该API不再存在。您仍然可以修改创建的字段,但是需要参考源代码,并且要注意,如果您所做的更改是针对API的私有位的,则它们可能会发生变化。

 

 类似资料: