Django DRF嵌套ViewSet路由注册访问异常解决方案
问题根源
DRF内置DefaultRouter/SimpleRouter的路由匹配逻辑按注册顺序优先匹配前缀,你当前代码先注册了前缀为journal的路由,后续访问/journal/win这类路径时,路由器会先匹配到journal前缀,把路径剩余的win当做JournalViewSet详情接口的主键参数,根本不会走到后续注册的WinLogViewSet等视图集,自然会出现返回journal接口Schema、POST方法不被允许的问题。
当你把前缀改成非嵌套的move、win这类和journal平级的路径时,前缀没有包含关系,不会出现匹配错位,功能就正常。
解决方案
根据实际业务场景选对应方案即可:
场景1:子路径不需要依赖父资源ID(即当前场景,
/journal/win这类接口不需要接收journal主键参数,只是路径前缀统一挂在journal下)
两种实现方式:- 调整注册顺序,把所有前缀更长的子路由放在短前缀路由前面注册,路由器会优先匹配到更长的前缀,修改后的代码如下:
from rest_framework import routers from .viewsets import (JournalViewSet, StatisticViewSet, FuelLogViewSet, MoveLogViewSet, RestLogViewSet, WinLogViewSet, GreatfulLogViewSet) router = routers.DefaultRouter() # 优先注册所有带journal/前缀的长路径路由 router.register(r'journal/statistic', StatisticViewSet) router.register(r'journal/fuel', FuelLogViewSet) router.register(r'journal/move', MoveLogViewSet) router.register(r'journal/rest', RestLogViewSet) router.register(r'journal/win', WinLogViewSet) router.register(r'journal/greatful', GreatfulLogViewSet) # 最后注册前缀最短的journal根路由 router.register(r'journal', JournalViewSet) urlpatterns = router.urls- 自定义路由器,自动按前缀长度倒序匹配,不需要手动调整注册顺序,一劳永逸:
from rest_framework import routers class LongPrefixFirstRouter(routers.DefaultRouter): def register(self, prefix, viewset, basename=None): super().register(prefix, viewset, basename) # 每次注册后按前缀长度从长到短排序,保证长前缀优先匹配 self.registry.sort(key=lambda item: -len(item[0]))用这个
LongPrefixFirstRouter替换原来的DefaultRouter即可,注册顺序不影响匹配结果。场景2:需要真正的资源嵌套(比如路径为
/journal/<journal_id>/win/,接口需要拿到父级journal的ID过滤对应数据)
这种场景不要手动拼接路径前缀,使用嵌套路由写法即可:from rest_framework import routers from .viewsets import (JournalViewSet, WinLogViewSet) # 其他视图集省略 router = routers.DefaultRouter() router.register(r'journal', JournalViewSet, basename='journal') # 生成嵌套路由 /journal/{journal_pk}/win/ journal_nested_router = routers.NestedSimpleRouter(router, r'journal', lookup='journal') journal_nested_router.register(r'win', WinLogViewSet, basename='journal-win') # 其余fuel、move等子资源按相同方式注册到journal_nested_router即可 urlpatterns = router.urls + journal_nested_router.urls这种写法下,在子资源的ViewSet中可以通过
self.kwargs['journal_pk']获取路径中传入的父资源ID,做对应的数据过滤、权限校验逻辑。
内容的提问来源于stack exchange,提问作者Dmitriy Lunev
相关产品推荐
相关产品推荐

