Django后端开发学习笔记Django REST Framework的序列化器

Posted 梆子井欢喜坨

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Django后端开发学习笔记Django REST Framework的序列化器相关的知识,希望对你有一定的参考价值。

学习参考:
【1】重写DRF的to_representation和to_internal_value方法有什么用途?

【2】Django REST Framework教程(4): 玩转序列化器(Serializer)

【3】DRF官方文档

1. 重写DRF的to_representation和to_internal_value方法

DRF所有序列化器类都继承了BaseSerializer 类, 通过重写该类的 to_representation() 和to_internal_value()方法可以改变序列化和反序列化的行为,比如给序列化后的数据添加额外的数据,或者对客户端API请求携带的数据进行反序列化处理以及用来自定义序列化器字段。

  • to_representation() 允许改变序列化的输出。
  • to_internal_value() 允许改变反序列化的输出。

1.1 to_representation方法

from rest_framework import serializers
from .models import Article

class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'
 
    def to_representation(self, value):
        # 调用父类获取当前序列化数据,value代表每个对象实例obj
        # data是我们要输出的json数据,在python中为dict
        data = super().to_representation(value)
		# do something
        return data

1.2 to_internal_value方法

to_internal_value主要在反序列化时用到,其作用处理API请求携带的数据,对其进行验证并转化为Python的数据类型。

from .models import Article
class ArticleSerializer(serializers.ModelSerializer):
    class Meta:
        model = Article
        fields = '__all__'
 
 	
    def to_internal_value(self, data):
        # 提取所需要的数据,对其进行反序列化,data代表未验证的数据
        article_data = data['article_data']
        value = super().to_internal_value(article_data)
        return value

2. 自定义序列化器类字段

to_representation()to_internal_value()方法的令一个重要用途就是用来自定义序列化类字段。
下面是官方文档中的一个例子,数据库中存的是Color对象,但是序列化时要做一定的包装。

class Color:
    """
    A color represented in the RGB colorspace.
    """
    def __init__(self, red, green, blue):
    	assert(red >= 0 and green >= 0 and blue >= 0)
    	assert(red < 256 and green < 256 and blue < 256)
        self.red, self.green, self.blue = red, green, blue

class ColorField(serializers.Field):
	"""
	Color对象会被序列化为 'rgb(#, #, #)'
	"""
	def to_representation(self, value):
		return "rgb(%d, %d, %d)" % (value.red, value.green, value.blue)
	
	def to_internal_value(self, data):
	 	data = data.strip('rgb(').rstrip(')')
	 	red, green, blue = [int(col) for col in data.split(',')]
	 	return Color(red, green, blue)	

默认情况下,字段值被视为映射到对象上的属性。如果需要自定义字段值的访问和设置方式,则需要重写.get_attribute().get_value()

class ClassNameField(serializers.Field):
    def get_attribute(self, instance):
        # We pass the object instance onto `to_representation`,
        # not just the field attribute.
        return instance

    def to_representation(self, value):
        """
        Serialize the value's class name.
        """
        return value.__class__.__name__

3. 如何修改序列化器控制序列化后响应数据的输出格式?

我们的Article模型和自定义的序列化器ArticleSerializer类分别如下所示。

# blog/models.py

class Article(models.Model):
    """Article Model"""
    STATUS_CHOICES = (
        ('p', 'Published'),
        ('d', 'Draft'),
    )

    title = models.CharField(verbose_name='Title (*)', max_length=90, db_index=True)
    body = models.TextField(verbose_name='Body', blank=True)
    author = models.ForeignKey(User, verbose_name='Author', on_delete=models.CASCADE, related_name='articles')
    status = models.CharField(verbose_name='Status (*)', max_length=1, choices=STATUS_CHOICES, default='s', null=True, blank=True)
    create_date = models.DateTimeField(verbose_name='Create Date', auto_now_add=True)

    def __str__(self):
        return self.title
# blog/serializers.py

class ArticleSerializer(serializers.ModelSerializer):

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id', 'author', 'create_date')

author外键序列化默认输出的是用户id,而不是用户名(username),status字段序列输出的也是我们存的"p"和"d",而不是可读的"Published"和"Draft",这样的输出不能很好地满足需求,因此我们需要修改序列化器控制序列化后响应数据的输出格式。

3.1 指定source

在序列化器中新建两个可读字段author和status字段,用以覆盖原来Article模型默认的字段,其中指定author字段的来源(source)为原单个author对象的username,status字段为get_status_display方法返回的完整状态。


class ArticleSerializer(serializers.ModelSerializer):
    author = serializers.ReadOnlyField(source="author.username")
    status = serializers.ReadOnlyField(source="get_status_display")

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id', 'author', 'create_date')

现在输出的结果符合要求,但进行增删改操作时会出问题。我们定义了一个仅可读的status字段把原来的status字段覆盖了,这样反序列化时用户将不能再对文章发表状态进行修改(原来的status字段是可读可修改的)。一个更好的方式是在ArticleSerializer中新增一个为full_status的可读字段,而不是简单覆盖原本可读可写的字段。(author字段原本就是只读字段,不受影响)

class ArticleSerializer(serializers.ModelSerializer):
    author = serializers.ReadOnlyField(source="author.username")
    full_status = serializers.ReadOnlyField(source="get_status_display")

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id', 'author', 'create_date')

3.2 使用SerializerMethodField自定义方法

如果进一步,想在输出的json数据中新增一些模型中原本没有的字段,可以使用SerializerMethodField,它可用于将任何类型的数据添加到对象的序列化表示中。

在序列化器中新建cn_status字段,格式为SerializerMethodField,然后再自定义一个get_cn_status方法输出文章中文发表状态即可。

class ArticleSerializer(serializers.ModelSerializer):
    author = serializers.ReadOnlyField(source="author.username")
    status = serializers.ReadOnlyField(source="get_status_display")
    cn_status = serializers.SerializerMethodField()

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id', 'author', 'create_date')

    def get_cn_status(self, obj):
        if obj.status == 'p':
            return "已发表"
        elif obj.status == 'd':
            return "草稿"
        else:
            return ''

SerializerMethodField通常用于显示模型中原本不存在的字段,类似可读字段,你不能通过反序列化对其直接进行修改。

3.3 使用嵌套序列化器

文章中的author字段实际上对应的是一个User模型实例化后的对象,既不是一个整数id,也不是用户名这样一个简单字符串,我们怎样显示更多用户对象信息呢? 其中一种解决方法是使用嵌套序列化器。

class UserSerializer(serializers.ModelSerializer):

    class Meta:
        model = User
        fields = ('id', 'username', 'email')


class ArticleSerializer(serializers.ModelSerializer):
    author = UserSerializer(read_only=True)

    class Meta:
        model = Article
        fields = '__all__'
        read_only_fields = ('id', 'author', 'create_date')

展示效果如下所示:

上述三种方法可以和重写to_representation和to_internal_value方法配合使用,以更好得控制序列化输出。

4. 如何在反序列化时对客户端提供过来的数据进行验证(validation)?

在反序列化数据时,在尝试访问经过验证的数据或保存对象实例之前,总是需要调用 is_valid()方法。如果发生任何验证错误,.errors 属性将包含表示结果错误消息的字典,如下所示:

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

字典中的每个键都是字段名称,值是与该字段对应的任何错误消息的字符串列表。non_field_errors 键也可能存在,并将列出任何常规验证错误。可以使用 REST framework 设置中的 NON_FIELD_ERRORS_KEY 来自定义 non_field_errors 键的名称。

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

4.1 引发无效数据的异常 (Raising an exception on invalid data)

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

4.2 字段级别验证 (Field-level validation)

您可以通过向您的 Serializer 子类中添加 .validate_<field_name> 方法来指定自定义字段级的验证。这些类似于 Django 表单中的 .clean_<field_name> 方法。这些方法采用单个参数,即需要验证的字段值。

您的 validate_<field_name> 方法应该返回已验证的值或抛出 serializers.ValidationError 异常。例如:

from rest_framework import serializers

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

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

注意:如果在您的序列化器上声明了 <field_name> 的参数为 required=False,那么如果不包含该字段,则此验证步骤不会发生。

4.3 对象级别验证 (Object-level validation)

要执行需要访问多个字段的任何其他验证,请添加名为 .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 the start is before the stop.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

4.4 验证器 (Validators)

序列化器上的各个字段都可以包含验证器,通过在字段实例上声明,例如:

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

DRF还提供了很多可重用的验证器,比如UniqueValidator,UniqueTogetherValidator等等。通过在内部 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']
        )

4.5 通过重写to_internal_value方法实现

仍然以上面的ColorField类举例, .fail()方法是引发serializers.ValidationError的快捷方式,它从错误消息字典中获取消息字符串。

default_error_messages = {
    'incorrect_type': 'Incorrect type. Expected a string, but got {input_type}',
    'incorrect_format': 'Incorrect format. Expected `rgb(#,#,#)`.',
    'out_of_range': 'Value out of range. Must be between 0 and 255.'
}

def to_internal_value(self, data):
    if not isinstance(data, str):
        self.fail('incorrect_type', input_type=type(data).__name__)

    if not re.match(r'^rgb\\([0-9]+,[0-9]+,[0-9]+\\)$', data):
        self.fail('incorrect_format')

    data = data.strip('rgb(').rstrip(')')
    red, green, blue = [int(col) for col in data.split(',')]

    if any([col > 255 or col < 0 for col in (red, green, blue)]):
        self.fail('out_of_range')

    return Color(red, green, blue)

5. 重写序列化器类自带的的create和update方法

当我们反序列化数据的时候,基于验证过的数据我们可以调用.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()方法定义了在调用serializer.save()时如何创建和修改完整的实例。

假设我们有个Profile模型与User模型是一对一的关系,当用户注册时我们希望把用户提交的数据分别存入User和Profile模型,这时我们就不得不重写序列化器自带的create方法了。
下例演示了如何通过一个序列化器创建两个模型对象。


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方法。


def update(self, instance, validated_data):
        profile_data = validated_data.pop('profile')
        # 除非应用程序正确地强制始终设置该字段,否则就应该抛出一个需要处理的`DoesNotExist`。
        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 要求你始终显式的编写这些方法。默认的 ModelSerializer .create().update() 方法不包括对可写嵌套表示的支持,所以我们总是需要对create和update方法进行重写。

以上是关于Django后端开发学习笔记Django REST Framework的序列化器的主要内容,如果未能解决你的问题,请参考以下文章

Django后端开发学习笔记Django REST Framework基于类的视图

Django后端开发学习笔记Django REST Framework基于类的视图

Django后端开发学习笔记Django REST Framework的序列化器

Django后端开发学习笔记Django REST Framework的序列化器

Django后端开发学习笔记Django REST Framework的序列化器

Django后端开发学习笔记Django基本概念