创建Django Rest Framework的方法序列化视图

Django Rest Framework(DRF)提供的框架极大的方便了REST API的开发工作。其中的generics模块提供了很多通用场景下的视图。但是在使用的过程中发现在模型序列化ModelSerializer方面总有些不便。

例如下面的情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# models.py
import uuid

from django.db import models


class Organization(models.Model):
org_uuid = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False)
name = models.CharField(max_length=40)


class User(AbstractBaseUser):
user_uuid = models.UUIDField(
primary_key=True, default=uuid.uuid4, editable=False)
# 为了方便简洁,省略其余字段,例如username...
org = models.ForeignKey('Organization', on_delete=models.CASCADE,)

通常在Model中都会遇到外键或者其它关系的字段。在创建User(无须连级创建Organization)或者更新其org字段时我们希望提供其所属的Organization主键org_uuid即可,而在获取一个User时,仅仅返回一个org_uuid并不能告诉调用者任何有意义的Organization信息。因此用于创建和获取User的序列化类也不尽相同。

事实上我更倾向于从请求的方法Method角度来决定使用什么样的序列化类。根据上面的情况,我通常会至少创建两种模型序列化类ModelSerializer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# serializers.py
from rest_framework import serializers

from myapp import models

class OrganizationListViewSerializer(serializers.ModelSerializer):
class Meta:
model = models.Organization
fields = '__all__'


class UserListViewSerializer(serializers.ModelSerializer):
org = OrganizationListViewSerializer()

class Meta:
model = models.User
fields = '__all__'


class UserCreateUpdateSerializer(serializers.ModelSerializer):
org = serializers.PrimaryKeyRelatedField(
queryset=models.Organization.objects.all())

class Meta:
model = models.User
fields = '__all__

利用嵌套的序列化类UserListViewSerializer能自动扩展Organization的信息。而在修改或者创建User时,只需要提供Organization的主键即可。

现在回到generics模块提供的通用视图上,根据REST的原则,通常使用的是generics.ListCreateAPIViewgenerics.RetrieveUpdateAPIView, 这也是根据接口和请求方法区分出的通用视图,在视图这点上框架做得很好。但是针对序列化类还不够灵活,默认一个APIView只能定义一个serializer_class。而我需要的是根据不同请求方法区分出使用不同的序列化类。实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
# views.py
from rest_framework import exceptions
from rest_framework import generics

from myapp import models
from myapp import serializers as ser


class MethodSerializerView(object):
'''
Utility class for get different serializer class by method.
For example:
method_serializer_classes = {
('GET'): MyModelListViewSerializer,
('PUT', 'PATCH'): MyModelCreateUpdateSerializer
}
'''
method_serializer_classes = None

def get_serializer_class(self):
assert self.method_serializer_classes is not None, (
'Expected view %s should contain method_serializer_classes '
'to get right serializer class.' %
(self.__class__.__name__, )
)
for methods, serializer_cls in self.method_serializer_classes.items():
if self.request.method in methods:
return serializer_cls

raise exceptions.MethodNotAllowed(self.request.method)


class UsersListCreateView(MethodSerializerView, generics.ListCreateAPIView):
'''
API: /users
Method: GET/POST
'''
queryset = models.User.objects.all()
method_serializer_classes = {
('GET'): ser.UserListViewSerializer,
('POST'): ser.UserCreateUpdateSerializer
}


class UsersDetailView(MethodSerializerView, generics.RetrieveUpdateAPIView):
'''
API: /user/:user_uuid
Method: GET/PUT/PATCH
'''
queryset = models.User.objects.all()
method_serializer_classes = {
('GET'): ser.UserListViewSerializer,
('PUT', 'PATCH'): ser.UserCreateUpdateSerializer
}

注意最后两个View的类继承顺序,MethodSerializerView要在前,才能使得它的get_serializer_class被调用。

如此一来可以根据不同的方法采取不同序列化策略,😉