在DRF中嵌套ViewSet路由

10

我创建了两个ModelViewSets,如下所示(为演示简化):

class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = SomeEventSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        return SomeObjects.objects.filter(owner=self.request.user)

    def get_serializer_context(self):
        context = super(SomeBaseViewSet, self).get_serializer_context()
        context.update({
            "user": self.request.user,
            "some_context_key": False
        })
        return context

class AdminViewSet(SomeBaseViewSet):
    # Added in the HasAdminPermission
    permission_classes = (permissions.IsAuthenticated, HasAdminPermission)

    # Different queryset filter (overriding `get_queryset` vs setting queryset for standardization)
    def get_queryset(self):
        return SomeObjects.objects.all()

    # The context should have `some_context_key=True`, and `user=request.user`
    def get_serializer_context(self):
        context = super(AdminViewSet, self).get_serializer_context()
        context.update({
            "some_context_key": True
        })
        return context

我的路由器/URL配置如下

router = DefaultRouter()

router.register(r'some_view', SomeBaseViewSet, base_name="some_view")

urlpatterns += [
    url(r'^api/', include(router.urls)),
]

如果我想将/api/some_view/admin路由到AdminViewSet,最好的方法是什么?
我尝试过以下几种方法:
- 在SomeBaseViewSet上使用@list_route,但无法弄清楚如何将其与我的AdminViewSet连接起来; - 将url(r'^api/some_view/admin$', AdminViewSet.as_view({"get": "list"}))添加到我的urlpatterns中(这样做有点奇怪而且手动),虽然它有点起作用,但是会削弱ViewSet的功能; - 为some_view viewset创建一个专门的DefaultRouter,然后将其挂载在url(r'^api/some_view/')上,但这也是一种繁琐的方式,如果路由数量很大,就不太实用了。
是否有“正确”的方法来完成我想要实现的目标,或者我应该寻找解决此问题的其他方法(例如筛选器)?
我看到过像https://github.com/alanjds/drf-nested-routers这样的库,但对于我的(相当简单的)需求来说似乎有些过度设计。
4个回答

4

不确定是否有遗漏,但我刚刚测试过,这个方法完美地运作了(顺序很重要):

router = DefaultRouter()
# this will overrides routes from the line below
router.register(r'some_view/admin', AdminViewSet) 
router.register(r'some_view', SomeBaseViewSet)

@Fitblip 嘿,这应该是被接受的答案,简单而正确的方法,只需注意顺序即可。 - Oğulcan Çelik

2

使用列表路由定义您的管理员视图集。这些参数将允许您执行一个带有指定权限(已经身份验证并具有管理员权限)的get请求,扩展此类。 例如/someview/adminsomeotherview/admin

from rest_framework.decorators import list_route
class AdminViewSet(viewset.Viewsets):

    @list_route(methods=['get'], 
                  permissions=[permissions.IsAuthenticated, HasAdminPermission],
                  url_path='admin'
     )
     def admin(self, request):
          # All your custom logic in regards to querysets and serializing
          return serialized.data

然后,您可以扩展任何需要管理员操作路由的视图集。

class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet, AdminViewset):
    serializer_class = SomeEventSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        return SomeObjects.objects.filter(owner=self.request.user)

    def get_serializer_context(self):
        context = super(SomeBaseViewSet, self).get_serializer_context()
        context.update({
            "user": self.request.user,
            "some_context_key": False
        })
        return context

在进行路由规划时,应当谨慎处理参数。一般情况下,基础路由之后的参数,例如 /someview/{param}/,用于ID引用。确保您的ID引用不会与详细路由发生冲突。


1

"@list_route" 对于我的使用情况更加合适(如我在上面的回答中所概述的),因为它不需要输入 PK,也不需要。感谢您的建议! - Fitblip
@Fitblip,你说得完全正确——detail_route确实需要一个PK,所以在这里没有用。感谢指正。 - matias elgart
感谢您的回复!这个问题有点冷清。 - Fitblip

1
我想我已经找到了自己问题的答案,但我在这上面放了一个+50 rep的赏金,以防有人想参与(@tom-christie也许?)。
无论如何,我解决我的用例的方式是使用@list_route和AdminViewSet的.as_view()函数。
像这样的东西就足够了:
class SomeBaseViewSet(viewsets.ReadOnlyModelViewSet):
    serializer_class = SomeEventSerializer
    permission_classes = (permissions.IsAuthenticated,)

    def get_queryset(self):
        return SomeObjects.objects.filter(owner=self.request.user)

    def get_serializer_context(self):
        context = super(SomeBaseViewSet, self).get_serializer_context()
        context.update({
            "user": self.request.user,
            "some_context_key": False
        })
        return context

    @list_route()
    def admin(self, request):
        return AdminViewSet.as_view({"get": "list"})(request)

class AdminViewSet(SomeBaseViewSet):
    # Added in the HasAdminPermission
    permission_classes = (permissions.IsAuthenticated, HasAdminPermission)

    # Different queryset filter (overriding `get_queryset` vs setting queryset for standardization)
    def get_queryset(self):
        return SomeObjects.objects.all()

    # The context should have `some_context_key=True`, and `user=request.user`
    def get_serializer_context(self):
        context = super(AdminViewSet, self).get_serializer_context()
        context.update({
            "some_context_key": True
        })
        return context

并且将允许根据函数名称相应地路由url,并强制执行所需的任何额外内容。

您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Dap
我猜as_view的评估应该移动到admin列表路由之外,但除此之外,这对我来说似乎不是一个坏主意。 - dhill

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接