Django REST Framework跨路由调用ViewSet方法的实现问题
问题场景
使用Django REST Framework从数据表获取条目返回序列化JSON时,访问如下地址可正常运行:localhost/api/mixs?id=12
对应ViewSet源码如下:
class MixViewSet(viewsets.ModelViewSet): serializer_class = MixSerializer filter_backends = [django_filters.rest_framework.DjangoFilterBackend] filter_fields = ["id","user"] filterset_fields = ['id'] search_fields = ['id'] def list(self,request,*args,**kwargs): queryset = self.filter_queryset(self.get_queryset()) serializer = self.get_serializer(queryset, many=True) custom_data = { 'items': serializer.data } custom_data.update({ 'meta':{"api":"Mix"} }) return Response(custom_data) def get_queryset(self): queryset = Mix.objects.all() ids = self.request.query_params.get('ids') if ids is not None: id_arr = ids.split(',') if len(id_arr) > 0: queryset = queryset.filter(id__in=id_arr) u_key = self.request.query_params.get('u_key') if u_key is not None: queryset = queryset.filter(u_key=u_key) return queryset
现在需要在另一个接口中复用该ViewSet的返回逻辑,目标是新建Mix记录后,返回和访问localhost/api/mixs?id=新插入记录ID完全一致的结果。当前实现代码如下,触发了AssertionError: You passed a Serializer instance as data, but probably meant to pass serialized .dataor.error. representation报错:
def createMix(request): # 执行逻辑创建Mix实例 m = Mix(detail={},user=1) m.save() print(m.id) ### 示例输出为13 # 期望实现和访问`localhost/api/mixs?id=13`完全等价的返回 obj = Mix.objects.get(id=m.id) response = MixSerializer(obj) print(response) return Response(response)
调用localhost/myapp/createsong时,程序会先向Mix表插入数据,之后需要返回对应id的标准接口结构。
解答
这个需求完全可以实现,当前实现有两个核心问题:
- 报错的直接原因是DRF的
Response类不接收序列化器实例作为参数,必须传入序列化后生成的原生Python数据结构,也就是序列化器实例的.data属性。 - 就算修正上述问题,直接序列化单条对象返回的结构也和
MixViewSet的输出不一致:MixViewSet的list方法返回的是包裹了items数组、meta字段的自定义结构,直接调用序列化器无法复用这层包装逻辑,也不会走ViewSet内置的过滤流程。
推荐实现方案(最高复用性)
直接调用MixViewSet的现有逻辑,不需要重复编写序列化、结构包装代码,后续ViewSet逻辑调整时该接口会自动同步,不需要额外维护:
from rest_framework.decorators import api_view from rest_framework import status @api_view(['POST']) # 按接口实际支持的请求方法调整 def createMix(request): # 原有创建Mix的业务逻辑 m = Mix(detail={}, user=1) m.save() # 替换当前请求的查询参数,指定查询新建记录的id # DRF原生的query_params是不可变对象,需要先拷贝为可变字典 new_query_params = request.query_params.copy() new_query_params['id'] = m.id request._request.GET = new_query_params # 实例化ViewSet,初始化请求上下文后直接调用list方法获取标准响应 mix_viewset = MixViewSet() mix_viewset.setup(request) mix_viewset.format_kwarg = None return mix_viewset.list(request, status=status.HTTP_201_CREATED)
轻量替代方案
如果不想修改原请求对象,也可以直接按照ViewSet的逻辑手动组装返回结构,适合逻辑简单、后续不会频繁调整ViewSet返回规则的场景:
from rest_framework.decorators import api_view from rest_framework import status @api_view(['POST']) def createMix(request): # 原有创建Mix的业务逻辑 m = Mix(detail={}, user=1) m.save() # 和MixViewSet.list逻辑保持一致组装返回 queryset = Mix.objects.filter(id=m.id) serializer = MixSerializer(queryset, many=True) resp_data = { "items": serializer.data, "meta": {"api": "Mix"} } return Response(resp_data, status=status.HTTP_201_CREATED)
注意:如果
MixViewSet后续新增了权限校验、过滤规则、返回字段调整,第一种方案不需要修改任何代码就能自动同步变更,第二种方案需要同步修改对应逻辑,维护成本更高。
内容的提问来源于stack exchange,提问作者whitebear
相关产品推荐
相关产品推荐

