如何在TabularInline的TreeNodeChoiceField中按gruppo_id过滤选项
问题描述
如何根据gruppo_id的值修改conto字段的选项列表?conto是指向model.ContoCOGE(MPTTModel)所定义的树分支的ForeignKey。每个gruppo对应一棵有特定深度的树,如何在TabularInline的TreeNodeChoiceField中过滤所有树,仅显示对应gruppo_id的树?
调试时,重写的formfield_for_foreignkey(self, db_field, request, **kwargs)方法返回了正确的树,但浏览器渲染页面时,选择字段仍显示所有树。
相关代码
model.py
from mptt.models import MPTTModel, TreeForeignKey from multigroup.models import Gruppo class ContoCOGE (MPTTModel): gruppo = models.ForeignKey(Gruppo, on_delete=models.CASCADE,related_name='contoCOCGE_gruppo') parent = TreeForeignKey('self', on_delete=models.CASCADE, null=True, blank=True, related_name='children') class PncGen (models.Model): gruppo = models.ForeignKey(Gruppo, on_delete=models.CASCADE) #other stuff class PncRighe (models.Model): gruppo = models.ForeignKey(Gruppo, on_delete=models.CASCADE) idPnc = models.ForeignKey(PncGen,on_delete=models.CASCADE) #puntatore alla testata conto = models.ForeignKey(ContoCOGE,on_delete=models.CASCADE,blank=True, null=True) #other stuff
admin.py
from django_mptt_admin.admin import DjangoMpttAdmin class PncRigheInline(admin.TabularInline): model = PncRighe autocomplete_fields = ['partitarioRiga','conto'] extra = 1 def filter_tree_queryset(self, queryset,request): qs = queryset.filter(gruppo_id=int(request.session['gruppo_utente'])) return qs def get_changeform_initial_data(self, request): return {'gruppo': int(request.session['gruppo_utente'])} def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'gruppo' or db_field.name == 'idRiga' or db_field.name == 'idPnc': kwargs['widget'] = forms.HiddenInput() if db_field.name == 'conto': db = kwargs.get("using") qs = self.get_field_queryset(db, db_field, request) kwargs['queryset']=qs.filter(gruppo_id = request.session['gruppo_utente'] ) @admin.register(PncGen) class PncGenAdmin(admin.ModelAdmin): #... inlines = (PncRigheInline,)
解决方案
问题核心在于你给conto字段设置了autocomplete_fields,这会触发Django Admin用AJAX异步加载选项,而非直接使用formfield_for_foreignkey中过滤后的queryset。同时原代码的formfield_for_foreignkey方法缺少返回值,也是导致过滤不生效的直接原因。以下是几种可行解决思路:
方案1:重写ContoCOGE的Admin查询集(保留自动补全)
给ContoCOGE注册Admin,基于session中的gruppo_utente过滤AJAX请求返回的选项:
# admin.py from django.contrib import admin from .models import ContoCOGE @admin.register(ContoCOGE) class ContoCOGEAdmin(admin.ModelAdmin): def get_queryset(self, request): qs = super().get_queryset(request) if 'gruppo_utente' in request.session: return qs.filter(gruppo_id=request.session['gruppo_utente']) return qs
这样autocomplete的AJAX请求会自动返回当前gruppo对应的树节点。
方案2:移除自动补全,修复formfield_for_foreignkey
如果不需要自动补全功能,移除autocomplete_fields中的conto,同时修复formfield_for_foreignkey的返回值问题:
# admin.py from django_mptt_admin.fields import TreeNodeChoiceField class PncRigheInline(admin.TabularInline): model = PncRighe autocomplete_fields = ['partitarioRiga'] # 移除conto extra = 1 # 其他方法保持不变 def formfield_for_foreignkey(self, db_field, request, **kwargs): if db_field.name == 'gruppo' or db_field.name == 'idRiga' or db_field.name == 'idPnc': kwargs['widget'] = forms.HiddenInput() if db_field.name == 'conto': db = kwargs.get("using") qs = self.get_field_queryset(db, db_field, request) kwargs['queryset'] = qs.filter(gruppo_id=request.session['gruppo_utente']) kwargs['form_class'] = TreeNodeChoiceField # 指定树选择字段 # 必须返回处理后的字段 return super().formfield_for_foreignkey(db_field, request, **kwargs)
关键注意点
- 原代码的
formfield_for_foreignkey方法没有返回值,导致Django忽略你的配置,使用默认字段,这是核心bug之一。 - 使用
autocomplete_fields时,选项由独立AJAX请求获取,必须在对应ModelAdmin中过滤查询集,否则会返回全量数据。
内容的提问来源于stack exchange,提问作者bleish
相关产品推荐
相关产品推荐

