Django Admin非超级用户场景下排除指定fieldsets与inline的实现问题
Django Admin动态排除fieldsets及隐藏Inline的实现方案
现有代码核心问题
- 你在
get_form方法中修改完form对象后,没有返回修改后的form,反而再次调用了super().get_form(),前面的修改全部被覆盖失效 - 仅修改字段的
is_hidden属性只会将字段转为隐藏输入框,不会移除整个字段组,达不到隐藏字段组的效果 - 直接修改
self.fieldsets会存在线程安全问题,Django Admin实例是单例模式,多请求场景下会互相干扰 - 隐藏Inline需要单独重写对应方法,无法通过修改表单字段实现
最优实现方案
1. 动态返回fieldsets
用官方推荐的get_fieldsets方法,直接根据用户权限过滤要返回的字段组,从根源上移除Cooperation Partner Settings字段组,不需要修改表单字段。
2. 动态控制Inline显示
重写对应Inline加载方法,非超级用户时移除email_user对应的Inline配置即可。
3. 修正get_form逻辑
如果需要额外修改表单字段,修改后直接返回修改后的form对象即可。
完整可运行代码
@admin.register(CooperationPartner, site=admin_site) class CooperationPartnerAdmin(model.Admin): inline_type = 'stacked' # 定义基础inline配置,不要直接修改类属性 base_inline_reverse = [( 'email_user', {'fields': [ 'salutation', 'title', 'first_name', 'last_name', 'email', ]}, )] reverse_delete_user = True form = CooperationPartnerAdminForm fieldsets_add = ( (_('Personal Data'), { 'fields': ( 'birthday', 'private_address_street', 'private_address_house_n', 'private_address_extra', 'private_address_postcode', 'private_address_city', ), }), (_('Cooperation Partner Settings'), { 'fields': ( 'pool', 'custom_policy_type', 'custom_database_structure', 'custom_attachments', ) }), (_('Company Data'), { 'fields': ( 'company', 'legal_form', 'business_address_street', 'business_address_house_n', 'business_address_extra', 'business_address_postcode', 'business_address_city', 'international_prefix', 'phone_number', 'career_position', 'loan_intermediary_permission', 'agreed_provision', 'bank_name', 'iban', 'bic', 'account_holder', 'sepa_direct_debit_mandate', 'status_decision_feedback', ), }), ) fieldsets_change = ( (None, { 'fields': ( 'cooperation_partner_id', 'account_state', ), }), ) + fieldsets_add def get_fieldsets(self, request, obj=None): # 先获取基础的fieldsets base_fieldsets = self.fieldsets_change if obj else self.fieldsets_add # 非超级用户过滤掉Cooperation Partner Settings字段组 if not request.user.is_superuser: base_fieldsets = tuple( fieldset for fieldset in base_fieldsets if fieldset[0] != _('Cooperation Partner Settings') ) return base_fieldsets # 如果你使用的基类通过inline_reverse加载inline,重写该方法即可 def get_inline_reverse(self, request, obj=None): base_inlines = self.base_inline_reverse.copy() if not request.user.is_superuser: # 移除email_user对应的inline base_inlines = [item for item in base_inlines if item[0] != 'email_user'] return base_inlines # 如果使用标准Django Inline,替换上面的方法为下面这个即可 # def get_inlines(self, request, obj=None): # inlines = super().get_inlines(request, obj) # if not request.user.is_superuser: # inlines = [inline for inline in inlines if inline != EmailUserInline] # return inlines def get_form(self, request, obj=None, change=False, **kwargs): form = super().get_form(request, obj, change, **kwargs) # 如有额外需要隐藏的单独字段,可以在这里处理 if not request.user.is_superuser: list_of_fields_to_hide = ['pool', 'custom_policy_type', 'birthday'] for field_name in list_of_fields_to_hide: if field_name in form.base_fields: del form.base_fields[field_name] return form
注意事项
- 不要直接修改
self.fieldsets、self.inlines这类类属性,单例模式下会影响所有用户的请求 - 权限判断条件可根据业务需求调整,示例中
not request.user.is_superuser代表非超级用户执行隐藏逻辑 - 如果
inline_reverse是第三方扩展提供的属性,可确认扩展是否提供动态获取方法,没有的话可以在请求方法中临时赋值使用,避免污染类属性。
内容的提问来源于stack exchange,提问作者Lutaaya Huzaifah Idris
相关产品推荐
相关产品推荐

