You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.20 17:37:46