改造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。

    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。

    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 类即可。

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 中,代码就可以简化成这样了,十分优雅。


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方法添加一个装饰器就能实现我们想要的功能了。

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

发表评论

电子邮件地址不会被公开。