Django REST Framework API Guide 04

本节大纲

  1、serializers

1、Serializers

Serializers允许复杂的数据,像queryset和模型实例转换成源生的Python数据类型。从而可以更简单的被渲染成JSON,XML或其他内容类型。Serializers也提供了反序列化的功能,允许解析过的数据转化为复杂的类型,在即将到来的数据被验证完之后。

另外的一点就是REST framework跟Django的Form和ModelForm类很相似。这里也提供了2个类,SerializerModelSerializer这两个功能强大的类,用来创建序列化,处理模型实例和queryset。

Declaring Serializers

一开始,让我们创建一个简单对象作为例子。

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

我们将申明一个serializer,它可以用来序列化和反序列化和Comment对象对应的数据。

申明一个serializer看起来跟申明一个form很像。

from rest_framework import serializers

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

Serializing objects

现在使用CommentSerializer来序列化comment或者comment列表。使用Serializer类,跟使用Form类一样。

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

此时,我们已经转换模型实例到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"}'

Deserializing objects

反序列化也很类似。首先我们解析流到Python源生的数据类型。

import io
from rest_framework.parsers import JSONParser

stream = io.BytesIO(json)
data = JSONParser().parse(stream)

然后存储这些数据到一个验证过数据的字典中

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

如果想返回完整的基于验证过的数据的对象实例,需要执行.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的model,这些方法可能看起来像

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

这里就把最前面Comment类的建立抛弃了。完全把它当作一个model。现在当我们要反序列化数据,调用.save()方法返回一个对象实例,基于验证的数据

comment = serializer.save()

调用.save()方法可能会创建一个新的实例,或者更新一个现有的实例,取决于当初始化serializer类时是否已经存在一个实例。

# .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类的使用情况。

Passing additional attributes to .save()

有时你想要你的视图代码可以在保存实例的时候注入新的额外数据。这些额外的数据可能包含当前用户,当前时间或者其他不是request.data一部分的东西。

可以像这样操作

serializer.save(owner=request.user)

当调用.create()或.update()时,任何额外的关键字参数都将包含在validated_data参数中。

Overriding .save() directly

有时候现有的方法不能够满足需求,比如在保存数据的时候,需要发送一封邮件,提醒别人更新的内容

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了。在serializer执行过程中,是先验证后操作(更新,保存),从常理上想其实也是这样,跟Form一样。

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

字典里所有的key都是serializer的字段名,值是与之对应的任何报错信息的字符串列表。有可能会有一个non_field_errors的关键字,它会列出来整体上的验证错误。non_field_errors关键字的名称可以通过使用NON_FIELD_ERRORS_KEY在REST framework的设置里面自定义。

当反序列化一组列表项目时,错误信息将会返回一个包含字典的列表,与反序列化的项目一一对应,即queryset的里面每个实例

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)

在我看来,上面的那种报错,意义不大,对于熟悉form的来说,跟form相似,我们应该更关注的是字段级别的错误信息整体组合错误这两类,现在就来剖析这两点Field-level validation & object-level validation

Field-level validation

你可以自己添加字段级别的验证通过.validate_<field_name>方法在你的Serializer子类里面,这跟form里面的字段验证.clean_<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

如果在serializer申明的<field_name>带有参数required=False,如果不包括此字段,则不会执行此验证步骤。

Object-level validation

关于需要访问多字段的验证,需要添加一个.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 the start is before the stop.
        """
        if data['start'] > data['finish']:
            raise serializers.ValidationError("finish must occur after start")
        return data

值得一提的是form验证的几个阶段在serializer里面也都有,这里再提一个字段基础验证。

Validators

单个字段在serializer可以包含验证器通过在字典实例上申明

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类也可以包含重用的验证器来验证全局的字段数据。这些验证器被包含在内部的Meta类里面

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

    class Meta:
        #验证一个房间每天只能有一个事件
        validators = UniqueTogetherValidator(
            queryset=Event.objects.all(),
            fields=['room_number', 'date']
        )

Partial Updates

默认,serializer必须通过所有需要的字段值,否则讲引发验证错误。但是你可以使用partial参数来允许部分更新。

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

Dealing with nested objects

之前的示例都是利用serializer处理简单的数据类型,但有时我们也需要表现更加复杂的对象,而不仅仅是字符串,数字,日期等等。比如现在要举例的就是对于嵌套的对象的处理。

下面的serializer类本身有一个字段用来标识跟另一个对象嵌套的关系

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

Writable nested representations

当处理支持反序列化数据的嵌套表示,嵌套对象的任何错误讲嵌套在该嵌套对象的字段名下

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

同样的验证过的数据.validated_data属性也是这样的嵌套结构

writing .create() methods for nested representations

如果支持可写的嵌套表示,需要写.create()或者.update()方法来处理保存多个对象。下面的示例,展示了如何处理创建user的嵌套对象

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

Writing .update() methods for nested representations

处理更新需要小心点,比如数据的关系是None,或者没有提供,该怎么做?

  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 framework3需要你总是清楚的写下这些方法。默认ModelSerializer .create()和.update()方法不包含对可嵌套表现的支持。

但是,第三方包是可以做到的,比如DRF Writable Nested支持自动可写的嵌套表示

Handling saving related instances in model manager classes

serialize中保存多个相关实例的替代方法是编写处理创建正确实例的自定义模型管理器类。

假设我们要确保User实例跟Profile实例总是一起成对创建,可以创建一个自定义manager类

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

这个管理器类现在更精细地封装了用户实例和配置文件实例总是同时创建的。现在,我们在serializer类里的.create()方法可以被重写使用新的manager方法

class User(models.Model):
    objects=UserManager()
    .......
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']
    )

关于model managers详细。。

Dealing with multiple objects

序列化对象列表

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文档

Including extra context

额外的内容添加到序列化数据内,在初始化serializer类的时候传入context参数

serializer = AccountSerializer(account, context={'request': request})
serializer.data
# {'id': 6, 'owner': u'denvercoder9', 'created': datetime.datetime(2013, 2, 12, 09, 44, 56, 678870), 'details': 'http://example.com/accounts/6/details'}

内容字典context可以被使用在任何serializer字典逻辑内,比如.to_representation()方法,通过self.contenxt属性

2、ModelSerializer

让serializer跟Django内定义的model的映射关系更紧密。

ModelSerializer类提供一个捷径在你创建serializer类并让字段跟Model的字段自动一一对应。ModelSerializer跟正常的Serializer类几乎一样,除了:

1、自动生成了一系列的字典,基于model模型
2、自动为serializer生成验证器,比如unique_together验证器
3、默认包含简单的.create().update()执行方法
class AccountSerializer(serializers.ModelSerializer):
    class Meta:
        model = Account
        fields = ('id', 'account_name', 'users', 'created')

导入ModelSerializer

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

Specifying which fields to include

方便快捷的自定义哪些字段是需要的,fields为'__all__'代表所有, exclude表示需要排除的,即model内其他的都需要

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

Specifying nested serialization

默认ModelSerializer在关系中使用主键,但是你可以很轻易的生成嵌套表示,使用depth

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

depth选项应该设置为整数值,该整数值指示在恢复到平面表示之前应该遍历的关系的深度。如果你需要客制化的完成序列化,你讲需要自己去定义这个字段

Specify fields explicitly

你可以添加额外的字段到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

额外的字段可以对应任何属性或在模型上是可调用的。

Specifying read only fields

也许你想要让多个字段变成read-only,你可以添加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',)

如果Model字段内有editable=False的设定,自动拉去的字段就会设置read-only属性,而不再需要添加read_only_fields选项

注意,有个特殊的案例,当只读字段是model级别联合唯一unique_together约束中的一部分。在这个案例里面这个字段是serializer类所需要的为了验证约束,但是它同时也是用户不可编辑的

正确的处理方式是清楚的在serializer里面标注出来这个字段,提供read_only=Truedefault=..两个关键字参数。一个示例对于这种只读关系,当前被验证的User跟另一个标识符是联合唯一unique_together

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

Additional keyword arguments

还有一个捷径允许制定任意额外的关键字在字段上,使用extra_kwargs选项。比如这个案例里面的read_only_fields,它标识你不需要清楚的定义这个字段在serializer上。这个选项是一个字段映射字段名称到一个关键字参数字典

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

3、HyperlinkedModelSerializer

HyperlinkedModelSerializer类和ModelSerializer类很像,除了相比较主键它使用超链接来标识关系,serializer默认会包括一个url字段代替主键字段。url字段使用HyperlinkedModelSerializer serializer字段来表示。

url字段会被用HyperlinkedIdentityField serializer字段表示,模型上的任何关系将被用HyperlinkedRelatedField serializer字段表示。你可以直接把主键添加到fields选项

class UserSerializer(HyperlinkedModelSerializer):

    class Meta:
        model = PersonResource
        fields = ('id', 'name', 'age', 'job', 'url', 'classes')

说到这里大家估计已经对示例乱了,这边重新给一份

 1 # serializers.py
 2 class PersonModelSerializer(ModelSerializer):
 3 
 4     class Meta:
 5         model = PersonResource
 6         fields = ('id', 'name', 'age', 'get_gender', 'get_job', 'modify_time')
 7 
 8 class UserSerializer(HyperlinkedModelSerializer):
 9 
10     class Meta:
11         model = PersonResource
12         fields = ('id', 'name', 'age', 'job', 'url', 'classes')
13 
14 class ClassesSerializer(ModelSerializer):
15 
16     class Meta:
17         model = Classes
18         fields = ('id', 'name', 'get_lesson', 'days')
19 
20 
21 # views.py
22 class UserHyperlinkView(ListAPIView):
23     queryset = PersonResource.objects.filter(job=1)
24     serializer_class = UserSerializer
25 
26 class UserDetailView(RetrieveAPIView):
27     queryset = PersonResource.objects.filter(job=1)
28     serializer_class = PersonModelSerializer
29 
30 class ClassDetailView(RetrieveAPIView):
31     queryset = Classes.objects.all()
32     serializer_class = ClassesSerializer
33 
34 
35 # models.py
36 class PersonResource(models.Model):
37     name = models.CharField(max_length=128)
38     age = models.PositiveSmallIntegerField()
39     gender_type = ((1, ''), (2, ''))
40     gender = models.PositiveSmallIntegerField(choices=gender_type, default=1)
41     job_type = ((1, '学生'), (2, '教师'))
42     job = models.PositiveSmallIntegerField(choices=job_type, default=1)
43     classes = models.ForeignKey('Classes', on_delete=models.CASCADE)
44     modify_time = models.DateTimeField(auto_now=True)
45 
46     def __str__(self):
47         return '%s - %s' % (self.name, self.get_job_display())
48 
49     def get_gender(self):
50         return self.get_gender_display()
51 
52     def get_job(self):
53         return self.get_job_display()
54 
55 class Classes(models.Model):
56     name = models.CharField(max_length=128)
57     lesson_type = ((0, 'Python'), (1, 'linux'), (2, 'GO'), (3, 'R'))
58     lesson = models.PositiveSmallIntegerField(choices=lesson_type)
59     days = models.PositiveSmallIntegerField()
60 
61     def __str__(self):
62         return '%s - %s' % (self.name, self.get_lesson_display())
63 
64     def get_lesson(self):
65         return self.get_lesson_display()
View Code

这个HyperlinkedModelSerializer里面有两个字段选项参数:

url     ==>    指向的是 本身,作用是抓取本身的具体数据,所以这里需要指向一个RetrieveAPIView
classes  ==>    外键,指向的另外的一个关联的model,所以这里也需要一个独立的显示另外一个model详细信息的RetrieveAPIView。

所以文件内的内容就很好理解了,我们需要三个V视图,一个服务于HyperlinkedModelSerializer,另外两个就是上面的,用来服务于,RetrieveAPIView。

现在还有一个问题,既然知道需要的url的行为是什么了,在一整个项目的urls.py文件中,如何判定用哪个url呢?

这里HyperlinkedModelSerializer里面的默认是使用{model_name}-detail去匹配抓取的,上面的文件内一直没有把最后的路由关系呈现给大家。

urlpatterns = [
    path('student/', UserHyperlinkView.as_view()),
    path('student/<int:pk>/', UserDetailView.as_view(), name='personresource-detail'),
    path('classes/<int:pk>/', ClassDetailView.as_view(), name='classes-detail'),
]

现在对于简单的HyperLinkedModelSerializer应该一目了然。

Absolute and relative URLS

当初始化一个HyperlinkedModelSerializer,你必须包含整个请求在serializer内容中

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

这样做会让你的超链接有一个合适的域名,一遍结果的显示是使用整个合格的URL,比如

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

而不是相对的url

/accounts/1/

如果你想要用相对的rl,你应该明确的传入{'request': None}参数在serializer context里面。但是上面的实例里面,明显我们并没有传入这些参数,依楼主猜测,在使用View层视图的时候,这一层已经被包装在内部了,当上面一段是废话就好了,反正我觉得用处不大,应该出来的都是绝对路径。

How hyperlinked views are determined

默认超链接期望和满足'{model_name}-detail'风格的视图名称相对应,并通过pk关键字参数超着实例。你可以重写URL字段的视图名和查找字段通过extra_kwarg设置里的使用view_namelookup_field选项:

class UserSerializer(HyperlinkedModelSerializer):

    class Meta:
        model = PersonResource
        fields = ('id', 'name', 'age', 'job')
        extra_kwargs = {
            'url': {'view_name': 'aaa', 'lookup_field': 'name'},
            'classes': {'lookup_field': 'name'}
        }

所以熟悉一点rest ramework,再看完上面示例,应该很灵性的知道这里的设置是怎么一回事了。首先url层,需要修改一下user detail作用的name改成aaa,然后url参数名都改成name并在 view层添加lookup_field

# urls.py
urlpatterns = [
    path('student/', UserHyperlinkView.as_view()),
    path('student/<str:name>/', UserDetailView.as_view(), name='aaa'),
    path('classes/<str:name>/', ClassDetailView.as_view(), name='classes-detail'),
]

# view.py
class UserDetailView(RetrieveAPIView):
    queryset = PersonResource.objects.filter(job=1)
    serializer_class = PersonModelSerializer
    lookup_field = ('name')


class ClassDetailView(RetrieveAPIView):
    queryset = Classes.objects.all()
    serializer_class = ClassesSerializer
    lookup_field = ('name')

你也可以设置此字段在serializer上。

class UserSerializer(HyperlinkedModelSerializer):
    url = serializers.HyperlinkedIdentityField(
        view_name='aaa',
        lookup_field='name'
    )
    classes = serializers.HyperlinkedRelatedField(
        lookup_field='name',
        view_name='classes-detail',
        read_only=True, 
        many=True,  # 如果对应对个对象需要加many=True
    )

    class Meta:
        model = PersonResource
        fields = ('id', 'name', 'age', 'job', 'url', 'classes')

注意

  1、url对应的是HyperlinkedIdentityField,而classes对应的是HyperlinkedRelatedField

  2、如果按照上面这种写法的话view_name, lookup_field就是必填的了,而且外键必须添加read_only=True, 至于many=True就更好理解了。

  3、还有一点细节就是这两种方法里面,注意一下Meta fields选项里面这两个参数,如果是在serializer上面申明的必须fields里面也填写这个参数,如果是extra_kwargs,直接在里面设定好就行。理解一下也很好记忆   

如果想要修改URL字段名,即修改'url',你可以通过使用全局设置URL_FIELD_NAME,看起来意义不大

 4、ListSerializer

ListSerializer类同时提供的序列化和验证多对象的行为。通常你不需要直接使用ListSerializer,而仅仅是在初始化serializer的时候传入many=True参数。当serializer被实例化并传入many=True参数是,ListSerializer实例就会被创建。序列化的类就会变成ListSerializer的子类。

allow_empty参数也可以被传入ListSerializer或者带有many=True参数的serializer。默认是True,但是也可以设置为False,如果你不允许空的list作为合法的输入。

Customizing ListSerializer behavior

当你想客制化ListSerializer的行为时有一些使用案例

1、你想对列表提供特殊的验证,比如检查某个元素和列表中的另一个元素不矛盾。

2、你想要客制化的对多个对象进行创建或者更新行为

对于这些案例你可以修改这些传入many=True参数的类,通过使用serializer的Meta类上的list_serializer_class选项。

class CustomListSerializer(serializers.ListSerializer):
    ...

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

Customizing multiple create

默认对于多对象创建的执行是简单的为每一个列表中的对象调用.create()方法. 如果你想自定制这个行为,你可以客制化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

Customizing multiple update

ListSerializer默认不支持多对象更新,因为中间需要考虑到以什么为依据去判定是更新,创建还是删除一个对象。

你需要清楚地添加一个id字段。默认保留生成id字段被标记成只读。所以在更新的时候应该被移除。当你把这些定义清楚,在你的list serializer里的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版本可能提供了一些自动支持对对象更新的操作,跟REST framework2里面的allow_add_remove行为很像。

Customizing ListSerializer initialization

当一个many=True的serializer被初始化的时候,我们需要决定那些字段和关键字参数应该传入子类(serializer)和父类(ListSerializer)的__init__()方法,默认是全部字段传给两个类,除了验证器和其他自定义关键字参数。

你也许要清楚的表明当传入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

这个类实现了基本相同的API类的序列化程序:

.data         - Returns the outgoing primitive representation.
.is_valid()      - Deserializes and validates incoming data.
.validated_data   - Returns the validated incoming data.
.errors        - Returns any errors during validation.
.save()        - Persists the validated data into an object instance

有四个可以被重写的方法,根据你想要实现的serializer类的支持的功能决定

.to_representation() - 重写为了支持序列化,为读操作
.to_internal_value() - 重写为了支持反序列化,为写操作
.create() and .update() - 重写它们中的一个或多个来支持保存实例

因为这个类跟Serializer类提供了一样的接口,你可以使用在基于类的通用视图上,当想要使用常用的Serializer或ModelSerializer类。唯一的不同就是BaseSerializer类不会生产可浏览的HTML表单API。

只读BaseSerializer类

使用BaseSerializer类执行只读serializer,只需要重写.to_representation()方法。通过Django model简单看个例子

class UserBaseSerializer(BaseSerializer):
    def to_representation(self, instance):
        return {
            'name': instance.name,
            'age': instance.age
        }
@api_view(['GET'])
def base_user(request, pk):
    instance = PersonResource.objects.get(pk=pk)
    serializer = UserBaseSerializer(instance)
    return Response(serializer.data)

或者如果多实例:

@api_view(['GET'])
def base_user1(request):
    queryset = PersonResource.objects.order_by('id')
    serializer = UserBaseSerializer(queryset, many=True)
    return Response(serializer.data)

可读写的BaseSerializer类

创建读写的serializer首先不要执行.to_internal_value(), 基础的验证API将会在这个serializer上可用,你可以使用.is_valid(), .validated_data.errors

如果你想要支持.save()方法,你将需要执行.create().update()方法中的一个或者多个

class UserBaseSerializer(BaseSerializer):
    def to_internal_value(self, data):
        name = data.get('name')
        age = int(data.get('age'))

        if not name:
            raise  ValidationError(
                {'name': 'this field is required.'}
            )
        if not age:
            raise ValidationError({
                'age': 'this field is required.'
            })
        if age > 25:
            raise ValidationError({
                'age': 'age may not bigger than 25.'
            })
        return {
            'name': name,
            'age': age
        }

    def to_representation(self, instance):
        return {
            'name': instance.name,
            'age': instance.age,
        }

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

Creating new base classes

在你想要建立新的通用serializer类来处理特殊的序列化格式或者和可选择的存储后台集成时BaseSerializer类也很有用

class ObjectSerializer(serializers.BaseSerializer):
    """
    A read-only serializer that coerces arbitrary complex objects
    into primitive representations.
    """
    def to_representation(self, obj):
        for attribute_name in dir(obj):
            attribute = getattr(obj, attribute_name)
            if attribute_name('_'):
                # 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)

Advanced serializer usage

重写序列化和反序列化行为。如果有需要,你可以重写.to_representation()或者.to_internal_value()方法。

比如,轻轻地继承一下这个方法,并把name转换成小写字符串

.to_representation()

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

.to_internal_value()

带着没有验证的数据作为输入,应该返回验证过的数据并让serializer.validated_data可用.这个返回值也将被传入.create()或者.update()方法,如果.save()方法被在这个serializer类上调用。

如果想做对象级别的验证,推荐你重写.validate()方法

数据通过这个方法正常会变成request.data的值

Serializer Inheritance

跟form一样,你可以通过继承对serializers进行扩展或者重用。它允许你申明一些字段或者方法在父类上可以被其他serializers使用。

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

    def validate_my_field(self, value):
        ...

class MySerializer(MyBaseSerializer):
    ...

跟Django的Model和ModelForm类一样,serializer内部的Meta类不是隐式的继承自它父类的Meta类。你必须明确的继承它的父类。

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

通常,我们不推荐使用继承在内部的Meta类,而是显示地申明所有选项代替。

另外,关于serializer继承的一些警告

1、标准的Python命名解决规则应用。如果你有多个基类定义了内部的Meta类,只有第一个的会被使用。这表示,子类如果存在Meta则是本身的,否则是第一个父类的Meta

2、可以申明性地移除字段从继承的父类,通过设定名称为None在子类上。

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

class MySerializer(MyBaseSerializer):
    my_field = None

Dynamically modify fields

通过.fields属性对字段为所欲为。

class DynamicFieldsModelSerializer(serializers.ModelSerializer):

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

上面的实例就只能显示fields里面定义的字段了。

原文地址:https://www.cnblogs.com/wuzdandz/p/9631032.html