Django装饰器与中间件问题:process_view无法获取required_feature属性
问题原因
当你用method_decorator装饰ViewSet的list方法时,required_feature属性被添加到了类的list方法上。但Django在处理ViewSet路由时,会将方法包装成一个全新的视图函数,中间件的process_view拿到的是这个包装后的函数,而非原本被装饰的list方法,因此无法通过getattr获取到required_feature属性。
解决方案
方案1:修改中间件,从ViewSet原始方法中获取属性
在中间件的process_view方法中,通过视图函数的view_class和action属性定位到原始的ViewSet方法,再提取required_feature:
class EntitlementMiddleware(MiddlewareMixin): def __init__(self, get_response) -> None: self.get_response = get_response def __call__(self, request) -> Any: if request.user.is_authenticated: request.user.features = get_user_features(request.user) else: request.user.features = [] return self.get_response(request) def process_view(self, request, view_func, view_args, view_kwargs): print(f"process_view - view_func: {view_func}") # Debugging required_feature = None # 检查当前视图是否来自ViewSet if hasattr(view_func, 'view_class') and hasattr(view_func, 'action'): view_class = view_func.view_class action_name = view_func.action # 获取对应的类方法 view_method = getattr(view_class, action_name, None) if view_method: # 取出被method_decorator包装的原始方法 original_method = getattr(view_method, '__wrapped__', view_method) required_feature = getattr(original_method, 'required_feature', None) else: # 处理普通视图函数的情况 required_feature = getattr(view_func, 'required_feature', None) if required_feature and not request.user.features.filter(name=required_feature): raise PermissionDenied("You do not have access to this feature") return None def process_response(self, request, response): return response
方案2:调整装饰器使用方式,直接装饰路由生成的视图
在注册ViewSet路由时,直接给对应的HTTP方法绑定装饰后的方法,确保属性被添加到最终的视图函数上:
from django.urls import include from rest_framework.routers import DefaultRouter from django.utils.decorators import method_decorator router = DefaultRouter() # 先装饰list方法,再注册路由 decorated_list = method_decorator(require_feature("Basic"))(MyViewSet.list) router.register(r'my', MyViewSet.as_view({'get': decorated_list}), basename='my') urlpatterns = [ path('', include(router.urls)), ]
方案3:修改装饰器,确保属性被双向传递
调整装饰器逻辑,同时给原始方法和包装后的方法添加属性,保证属性能被中间件捕获:
from functools import wraps def require_feature(feature_name): def decorator(view_func): print(f"process_view - view_func: {view_func}") # Debugging @wraps(view_func) def _wrapped_view(request, *args, **kwargs): return view_func(request, *args, **kwargs) # 同时给原始方法和包装后的方法添加属性 view_func.required_feature = feature_name _wrapped_view.required_feature = feature_name return _wrapped_view return decorator
注意:该方案会给原始类方法添加属性,可能影响其他场景对方法的使用,优先推荐方案1或方案2。
内容的提问来源于stack exchange,提问作者Jean
相关产品推荐
相关产品推荐

