改造REST-Framework,自定义修饰器为ViewSet中每个Action指定单独的Serializer

在 REST-Framework 的 ViewSet 中,一般是通过 ViewSet 类的 serializer_class 属性来指定视图所使用的 Serializer。

但是很多时候我们需要为 ViewSet 中的某个 Action 指定不同的 Serializer。 比如在 UserViewSet中有一个修改密码的 Action:change_password,我们需要单独为它指定一个 ChangePasswordSerializer。

根据官方文档,我们可以这样做,重写 ViewSet 类的 get_serializer_class 方法,自己实现判断逻辑返回需要的 Serializer。

1
2
3
4
def get_serializer_class(self):
if self.action == "change_password":
return ChangePasswordSerializer
return super(UserViewSet, self).get_serializer_class()

这样在 change_password 方法中我们就可以直接通过 self.get_serializer() 得到 ChangePasswordSerializer 对象了。

但是这不是一个优雅的解决方法,我们应该劲量避免代码中出现硬编码!!!

为了优雅的解决这个问题,我们需要实现一个方法装饰器和一个 Mixin 类。

首先是一个装饰器,用于为 Action 指定 Serializer。

1
2
3
4
5
6
def action_serializer(serializer):
"""用于为 Action 指定 Serializer 类的装饰器"""
def decorator(func):
func.serializer_class = serializer
return func
return decorator

接下来是一个 Mixin 类,重写 get_serializer_class 方法,返回使用了 action_serializer 装饰器修饰的 Action 的 Serializer 类即可。

1
2
3
4
5
6
7
class ActionSerializerMixin(object):
"""使用 Action 指定 Serializer 的 Mixin 类"""
def get_serializer_class(self):
action = getattr(self, self.action)
if hasattr(action, "serializer_class"):
return action.serializer_class
return super(ActionSerializerMixin, self).get_serializer_class()

这样在我们的 UserViewSet 中,代码就可以简化成这样了,十分优雅。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

class UserViewSet(ActionSerializerMixin, # ActionSerializerMixin类必须放在多重继承的第一个
mixins.ListModelMixin,
mixins.RetrieveModelMixin,
mixins.UpdateModelMixin,
viewsets.GenericViewSet):

queryset = User.objects.all()
serializer_class = UserSerializer # 其他Action默认的serializer
permission_classes = (permissions.IsAuthenticated, IsOwnerOrReadOnly, )

@action_serializer(ChangePasswordSerializer) # 使用装饰器为change_password指定单独的Serializer
@action(detail=False, methods=["POST"])
def change_password(self, request):
"""修改密码 Action"""
pass

 

其实还有一个更简单的方法

但是我暂时没有仔细看APIView类的源码,不确定在实例化ViewSet以后修改serializer_class属性会不会造成什么问题。

这个方法就是直接在装饰器中修改ViewSet对象的serializer_class属性,这样就只需要向action方法添加一个装饰器就能实现我们想要的功能了。

1
2
3
4
5
6
7
8
9
from functools import wraps
def action_serializer(serializer):
def decorator(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
self.serializer_class = serializer
return func(self, *args, **kwargs)
return wrapper
return decorator

改造REST-Framework,自定义修饰器为ViewSet中每个Action指定单独的Serializer

https://blog.dennic365.com/2019/04/19/rest-framework-viewset-action-serializer/

作者

Dennic

发布于

2019-04-19

更新于

2022-02-24

许可协议

评论