django-mptt如何实现admin后台选中父节点时自动勾选对应子节点
解决方案
1 修正表单基础配置错误
你现有表单的Meta类中关联模型配置错误,原本要绑定Deviation模型,错写成了Orgunit,先修正这部分:
from django.forms import ModelForm, widgets from mptt.forms import TreeNodeMultipleChoiceField from orgunits.models import Orgunit from deviations.models import Deviation class MyForm(ModelForm): orgunits = TreeNodeMultipleChoiceField(queryset=Orgunit.objects.all(), widget=widgets.SelectMultiple()) class Meta: model = Deviation # 这里修正为Deviation fields = '__all__'
2 后端自动过滤仅保存STORE类型叶子节点
重写表单的clean_orgunits方法,对用户提交的选中节点做处理:遍历所有选中节点,递归获取其所有后代节点,过滤出类型为STORE的叶子节点后返回,保证最终存入多对多关联的只有符合要求的节点:
class MyForm(ModelForm): orgunits = TreeNodeMultipleChoiceField(queryset=Orgunit.objects.all(), widget=widgets.SelectMultiple()) class Meta: model = Deviation fields = '__all__' def clean_orgunits(self): selected_nodes = self.cleaned_data.get('orgunits', []) store_nodes = set() for node in selected_nodes: # 获取当前节点的所有后代节点(包含自身) all_descendants = node.get_descendants(include_self=True) # 过滤出STORE类型的叶子节点 store_nodes.update(all_descendants.filter(type='STORE', rght=models.F('lft')+1)) return list(store_nodes)
3 前端实现选中父节点自动勾选子节点
在DeviationAdmin中注入自定义JS,实现交互逻辑:选中父节点时自动勾选其所有子节点,取消选中父节点时也同步取消子节点选中状态:
修改Admin代码
from django.contrib import admin from deviations.forms import MyForm from deviations.models import Deviation @admin.register(Deviation) class DeviationAdmin(admin.ModelAdmin): form = MyForm class Media: js = ('js/deviation_admin.js',) # 你需要在静态文件目录下创建这个JS文件
编写deviation_admin.js代码
document.addEventListener('DOMContentLoaded', function() { const selectEl = document.querySelector('#id_orgunits'); const options = Array.from(selectEl.options); // 构建节点ID到层级、子节点的映射 const nodeMap = {}; options.forEach(opt => { const nodeId = parseInt(opt.value); const level = (opt.textContent.match(/^[\u00A0]+/) || [''])[0].length / 2; // mptt默认用2个空格缩进计算层级 nodeMap[nodeId] = { element: opt, level: level, children: [] }; }); // 构建父子节点关系 const sortedNodes = Object.values(nodeMap).sort((a,b) => a.level - b.level); sortedNodes.forEach(node => { if (node.level === 0) return; // 匹配最近的层级更小的节点作为父节点 for (let i = sortedNodes.indexOf(node) -1; i >=0; i--) { const prevNode = sortedNodes[i]; if (prevNode.level < node.level) { prevNode.children.push(node); break; } } }); // 监听选中变化同步子节点状态 selectEl.addEventListener('change', function(e) { const changedOpts = Array.from(e.target.selectedOptions); changedOpts.forEach(changedOpt => { const nodeId = parseInt(changedOpt.value); const node = nodeMap[nodeId]; const isChecked = changedOpt.selected; // 递归更新所有子节点选中状态 function setChildrenChecked(parentNode, checked) { parentNode.children.forEach(child => { child.element.selected = checked; setChildrenChecked(child, checked); }); } setChildrenChecked(node, isChecked); }) }); });
内容的提问来源于stack exchange,提问作者Альберт Александров
相关产品推荐
相关产品推荐

