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

如何将Django MPTT TreeNodeMultipleChoiceField渲染为树形结构

嘿,这事儿我熟!你不想用mptt_tags的recursetree,还要基于Form而非Model来做树形表单,用jQuery的Fancytree或者Bootstrap的树形组件完全能搞定,我给你一步步拆解:

第一步:准备Django端的树形数据

首先,我们需要把你的RSMDriveModel数据转换成前端组件能识别的树形JSON结构。你可以在视图里写个递归函数来生成这个结构:

# 在你的视图文件里
def build_tree_nodes(queryset):
    """递归生成树形节点结构"""
    nodes = []
    # 先遍历所有根节点(parent为null的)
    for item in queryset.filter(parent__isnull=True):
        node = {
            "title": item.name,  # 替换成你模型里实际显示的字段,比如name/label
            "key": str(item.pk),  # 用主键作为节点唯一标识
            "children": build_tree_nodes(item.get_children())  # MPTT自带的获取子节点方法
        }
        nodes.append(node)
    return nodes

def drive_form_view(request):
    form = RSMDriveForm()
    # 生成树形数据并传给模板
    tree_data = build_tree_nodes(RSMDriveModel.objects.all())
    return render(request, 'your_template.html', {
        'form': form,
        'tree_data': tree_data,
    })
第二步:用Fancytree渲染树形选择器

接下来在模板里,我们把原生的TreeNodeMultipleChoiceField控件隐藏,用Fancytree来替代它:

<!-- 先引入Fancytree的CSS和JS(你可以下载本地资源或者用CDN) -->
<link rel="stylesheet" href="path/to/fancytree/skin-win8/ui.fancytree.css">
<script src="path/to/jquery.fancytree.min.js"></script>

<!-- 隐藏原生的表单控件,我们用Fancytree来替代它的选择功能 -->
{{ form.choices.as_hidden }}

<!-- Fancytree的容器 -->
<div id="tree-container"></div>

<script>
$(document).ready(function() {
    // 初始化Fancytree
    $("#tree-container").fancytree({
        source: {{ tree_data|safe }},  // 把Django传的树形数据转成JS对象
        checkbox: true,  // 启用多选框
        selectMode: 3,  // 允许任意节点多选(1是单选,2是父子联动多选)
        // 当选中状态变化时,同步更新隐藏的表单字段
        select: function(event, data) {
            // 获取所有选中节点的key(也就是模型主键)
            var selectedIds = data.tree.getSelectedNodes().map(node => node.key);
            // 更新隐藏字段的值,用逗号分隔
            $('input[name="choices"]').val(selectedIds.join(','));
        }
    });

    // 如果表单有初始选中值,初始化Fancytree的选中状态
    {% if form.choices.value %}
        var initialIds = "{{ form.choices.value }}".split(',');
        $("#tree-container").fancytree("getTree").visit(function(node) {
            if (initialIds.includes(node.key)) {
                node.setSelected(true);
            }
        });
    {% endif %}
});
</script>
第三步:Bootstrap Treeview替代方案

如果你更倾向于用Bootstrap生态的组件,Bootstrap Treeview也是个不错的选择,逻辑和上面类似:

<!-- 引入Bootstrap Treeview的CSS和JS -->
<link rel="stylesheet" href="path/to/bootstrap-treeview.css">
<script src="path/to/bootstrap-treeview.min.js"></script>

<!-- 隐藏原生控件 -->
{{ form.choices.as_hidden }}

<!-- Treeview容器 -->
<div id="bootstrap-tree"></div>

<script>
$(document).ready(function() {
    // 初始化Bootstrap Treeview
    $('#bootstrap-tree').treeview({
        data: {{ tree_data|safe }},
        showCheckbox: true,
        // 节点选中/取消时更新表单字段
        onNodeChecked: updateSelectedValues,
        onNodeUnchecked: updateSelectedValues
    });

    // 同步选中值到表单字段的函数
    function updateSelectedValues() {
        // 获取所有选中的节点(包括子节点)
        var selectedNodes = $('#bootstrap-tree').treeview('getChecked', {includeAll: true});
        var selectedIds = selectedNodes.map(node => node.key);
        $('input[name="choices"]').val(selectedIds.join(','));
    }

    // 初始化初始选中状态
    {% if form.choices.value %}
        var initialIds = "{{ form.choices.value }}".split(',');
        $('#bootstrap-tree').treeview('checkNode', initialIds);
    {% endif %}
});
</script>
第四步:表单提交的处理

不用担心提交的问题,因为我们已经把选中的节点主键用逗号分隔存到了原生的choices字段里,而TreeNodeMultipleChoiceField本身就支持接收逗号分隔的ID列表作为输入,所以Django会自动把这些ID转换成对应的模型实例,你在视图里直接用form.cleaned_data['choices']就能拿到选中的对象集合了。

小提示: 如果你的模型显示字段不是name,记得把build_tree_nodes里的item.name改成你实际的字段,比如item.title之类的。

内容的提问来源于stack exchange,提问作者Ava Trimble

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.25 07:58:16