DRF从3.7.7升级到3.8.2后动态@detail_route失效,求替代方案
解决DRF 3.8+动态添加detail_route/action不生成URL的问题
我之前处理过DRF版本升级后类似的路由问题,你遇到的情况是因为DRF 3.8版本对路由扫描逻辑做了优化:旧版本(3.7.x)在路由器注册视图集时会实时扫描类的所有方法,识别带@detail_route装饰的方法;而3.8+版本会在类定义完成阶段就提前收集所有带路由装饰器的方法信息,存储在视图集的_actions类属性中,后续路由器注册时直接读取这个属性,不会再重新扫描动态添加的方法。
加上@detail_route在DRF 3.8已经被标记为废弃,推荐使用@action(detail=True)替代,下面给你几个可行的解决方案:
方案一:手动注册路由到视图集的_actions属性
这个方案最贴合你现有的代码结构,只需要在动态添加方法后,手动把路由信息注册到视图集的_actions属性中:
from rest_framework.decorators import action class SiteTreeViewSet(viewsets.ReadOnlyModelViewSet): # 原类代码保持不变 def generic_model_rest(model_, field, serfield): # 用@action替代@detail_route,指定detail=True表示是详情页路由 @action(detail=True, methods=['get']) def _f(self, request, pk=None): itm = SiteTree.objects.get(pk=int(pk)) # 优化:用f-string更清晰 serializer_cls = globals()[f'Settings{serfield}Serializer'] method_name = f'get_sett_{serfield.lower()}' data_source = getattr(itm, method_name)() if serfield in ('WWW', 'Oth',): serializer = serializer_cls(data_source, many=True) else: serializer = serializer_cls(data_source) return Response(serializer.data) # 生成动态方法名 dynamic_method_name = f'settings_{serfield.lower()}' # 把方法绑定到视图集类 setattr(SiteTreeViewSet, dynamic_method_name, _f) # 手动将路由信息注册到视图集的_actions属性 if not hasattr(SiteTreeViewSet, '_actions'): SiteTreeViewSet._actions = {} # 从装饰后的方法中获取DRF的action配置 SiteTreeViewSet._actions[dynamic_method_name] = _f.drf_action # 调用动态生成方法的函数 generic_model_rest(SettingsApartment, 'apartment', 'Apt') generic_model_rest(SettingsSwitch, 'switch', 'Swt') generic_model_rest(SettingsWeblinks, 'weblinks', 'WWW') generic_model_rest(SettingsWeblinks, 'weblinks1', 'WW1') generic_model_rest(SettingsOther, 'other', 'Oth') # 正常注册视图集到路由器 router.register(r'rest/sitetree', SiteTreeViewSet, 'SiteTree')
方案二:手动添加路由到路由器
如果不想修改动态方法的注册逻辑,也可以在路由器注册视图集后,手动添加每个动态方法的URL路由:
# 先注册基础视图集 router.register(r'rest/sitetree', SiteTreeViewSet, 'SiteTree') # 手动添加每个动态方法的路由 # 格式:path(URL路径, 视图集的as_view配置, 路由名称) router.urls.extend([ path('rest/sitetree/<pk>/settings_apt/', SiteTreeViewSet.as_view({'get': 'settings_apt'}), name='sitetree-settings-apt'), path('rest/sitetree/<pk>/settings_swt/', SiteTreeViewSet.as_view({'get': 'settings_swt'}), name='sitetree-settings-swt'), # 其他动态方法同理添加 ])
这个方案的缺点是需要手动维护每个动态方法的路由,不够灵活,但适合临时快速解决问题。
方案三:利用元类在类定义时动态生成方法
如果希望更“原生”地让DRF识别动态方法,可以用元类在视图集类定义阶段就生成这些方法,这样DRF的路由扫描逻辑就能正常捕获:
from rest_framework.decorators import action class SiteTreeMeta(type): def __new__(cls, name, bases, attrs): # 动态生成方法的逻辑放在元类里 def generic_model_rest(model_, field, serfield): @action(detail=True, methods=['get']) def _f(self, request, pk=None): itm = SiteTree.objects.get(pk=int(pk)) serializer_cls = globals()[f'Settings{serfield}Serializer'] method_name = f'get_sett_{serfield.lower()}' data_source = getattr(itm, method_name)() if serfield in ('WWW', 'Oth',): serializer = serializer_cls(data_source, many=True) else: serializer = serializer_cls(data_source) return Response(serializer.data) return _f # 添加所有需要的动态方法 attrs['settings_apt'] = generic_model_rest(SettingsApartment, 'apartment', 'Apt') attrs['settings_swt'] = generic_model_rest(SettingsSwitch, 'switch', 'Swt') attrs['settings_www'] = generic_model_rest(SettingsWeblinks, 'weblinks', 'WWW') attrs['settings_ww1'] = generic_model_rest(SettingsWeblinks, 'weblinks1', 'WW1') attrs['settings_oth'] = generic_model_rest(SettingsOther, 'other', 'Oth') return super().__new__(cls, name, bases, attrs) # 让视图集继承这个元类 class SiteTreeViewSet(viewsets.ReadOnlyModelViewSet, metaclass=SiteTreeMeta): # 原类代码保持不变 # 正常注册视图集 router.register(r'rest/sitetree', SiteTreeViewSet, 'SiteTree')
这个方案让动态方法在类定义阶段就存在,DRF的路由扫描逻辑会自动识别,不需要额外操作,但代码结构会有变化。
关键注意点
- 不要再使用
@detail_route,DRF 3.8+已经废弃,官方推荐用@action(detail=True)替代,两者功能一致,但action更灵活(可以指定methods、url_path等参数)。 - 动态添加方法后,必须确保DRF能读取到路由配置,要么手动注册到
_actions,要么在类定义阶段就生成方法。
内容的提问来源于stack exchange,提问作者velis
相关产品推荐
相关产品推荐

