Django Admin自定义list_display时SalaryAdmin无request属性报错
问题核心原因
Django的ModelAdmin类是全局单例实例,和单次请求的生命周期完全解绑,类本身默认不会挂载当前请求对应的request对象,直接在自定义列表方法里访问self.request必然会触发属性不存在的报错。
可落地的两种解决实现
方案1:最小改动,重写changelist_view绑定request
这是最省事的改法,只需要在进入列表页视图的时候把当前request临时存在admin实例上,后续list_display调用自定义方法时就能直接取到:
@admin.register(Salary) class SalaryAdmin(admin.ModelAdmin): list_display = ['user_name', 'action'] def changelist_view(self, request, extra_context=None): # 将当前请求对象临时挂载到实例属性 self.request = request return super().changelist_view(request, extra_context) def action(self, obj): current_user = self.request.user # 超级用户直接放行 if current_user.is_superuser: return True # 非超管的角色判断逻辑 if (obj.hr_state == 'request-change-approved' and current_user.user_role.position.code == 'HRM'): return True return False
注意:这种写法仅适用于常规Django同步请求场景,异步并发环境下因为ModelAdmin是全局共享的,挂载在self上的request可能出现请求串数据的问题,优先选方案2。
方案2:无全局属性污染,更安全的闭包方案
通过重写get_list_display方法,在方法内部通过闭包捕获当前请求对象,完全不修改全局admin实例的属性,不管同步异步环境都不会出问题:
@admin.register(Salary) class SalaryAdmin(admin.ModelAdmin): # 基础列不需要写死在list_display里,统一在get_list_display里返回 list_display = ['user_name'] def get_list_display(self, request): # 闭包捕获当前request,不需要依赖self存储 def render_action_status(obj): if request.user.is_superuser: return True if (obj.hr_state == 'request-change-approved' and request.user.user_role.position.code == 'HRM'): return True return False # 给自定义方法设置列名,避免admin显示乱码 render_action_status.short_description = '操作权限状态' # 拼接基础列和自定义列返回 return ['user_name', render_action_status]
常见踩坑提醒
- 不要试图在
ModelAdmin的__init__方法里获取并存储request,类初始化时请求对象还没生成,根本拿不到。 - 如果自定义方法后续要返回HTML格式的按钮、链接,不要直接拼字符串,用Django自带的
format_html做转义处理,避免XSS风险。 - 如果返回值是布尔类型,Django Admin会自动在列表列渲染成对勾/叉号的默认展示效果,不需要额外写前端逻辑。
内容的提问来源于stack exchange,提问作者MecaTheclau
相关产品推荐
相关产品推荐

