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

Django+Javascript动态生成ModelForm问题:JS无法识别Python变量

解决Django模板中JS动态添加表单失效的问题

问题场景

想通过点击按钮动态添加project_members_form表单,但点击后功能完全无反应。原模板和视图代码如下:

模板代码

{% extends 'base.html' %}

{% block content %}

<form method='POST'>

<script>
    function addMemberFnc() {
        document.getElementById("teamMemberDiv").innerHTML+={{ project_members_form }}
    }
</script>

    {{ project_form }}
    <p id="demo" onclick="addMemberFnc()">Add member.</p>
    <div style="border-style: dotted" id="teamMemberDiv">
{#        {{ project_members_form }}#}
    </div>
    <button type="submit" class="btn btn-primary my-5">Submit</button>
</form>

{% endblock %}

视图代码

def project_generation(request):
    if request.method == 'POST':
        pass
    else:
        project_form = ProjectForm(prefix='Project')
        project_role_form = ProjectRoleForm(prefix='ProjectRole')
        project_members_form = ProjectMemberForm(prefix='ProjectMember')

        return render(request,
                      "project_xml_generation.html",
                      {
                          "project_form": project_form,
                          "project_members_form": project_members_form
                       },
                      )

错误原因

Django渲染{{ project_members_form }}时会直接输出原始HTML代码,把这段HTML拼到JS的innerHTML里会破坏JS语法结构——比如HTML里的双引号、换行符会导致JS解析报错(浏览器控制台会出现类似Unexpected token '<'的错误),最终函数无法执行。

解决方案

1. 转义表单HTML为合法JS字符串

用Django的escapejs过滤器,把表单内容转义成JS能识别的字符串格式,避免语法错误。

2. 动态生成表单唯一前缀

多个同前缀的表单提交时会互相覆盖数据,所以每次新增表单要修改前缀,生成唯一标识(比如加数字后缀),确保后端能正确识别每个表单的提交数据。

修改后的代码

模板代码

{% extends 'base.html' %}

{% block content %}
<form method='POST'>
    {% csrf_token %} <!-- 必须添加,否则POST提交会被Django的CSRF保护拦截 -->

    <script>
        // 提前把表单HTML转义成合法JS字符串
        const baseMemberForm = "{{ project_members_form|escapejs }}";
        let memberIndex = 1; // 用来生成唯一前缀

        function addMemberFnc() {
            // 替换表单中的前缀,生成唯一标识
            const newForm = baseMemberForm.replace(/ProjectMember/g, `ProjectMember_${memberIndex}`);
            document.getElementById("teamMemberDiv").innerHTML += newForm;
            memberIndex++;
        }
    </script>

    {{ project_form }}
    <p id="demo" onclick="addMemberFnc()">添加成员</p>
    <div style="border-style: dotted" id="teamMemberDiv">
    </div>
    <button type="submit" class="btn btn-primary my-5">提交</button>
</form>
{% endblock %}

视图代码(处理POST请求)

def project_generation(request):
    if request.method == 'POST':
        project_form = ProjectForm(request.POST, prefix='Project')
        member_forms = []
        current_index = 0

        # 遍历所有提交的成员表单
        while f'ProjectMember_{current_index}-id' in request.POST:
            form = ProjectMemberForm(request.POST, prefix=f'ProjectMember_{current_index}')
            member_forms.append(form)
            current_index += 1

        # 验证所有表单并保存
        if project_form.is_valid() and all(form.is_valid() for form in member_forms):
            project = project_form.save()
            for form in member_forms:
                member = form.save(commit=False)
                member.project = project  # 关联到当前项目
                member.save()
            # 保存成功后重定向到列表页或成功页面
            return redirect('project_list')
    else:
        project_form = ProjectForm(prefix='Project')
        project_role_form = ProjectRoleForm(prefix='ProjectRole')
        project_members_form = ProjectMemberForm(prefix='ProjectMember')

        return render(request,
                      "project_xml_generation.html",
                      {
                          "project_form": project_form,
                          "project_members_form": project_members_form
                       },
                      )

关键说明

  • escapejs过滤器会自动转义HTML中的特殊字符(比如双引号、换行符),让表单HTML能作为合法的JS字符串使用。
  • 修改前缀是Django处理多个同类型表单的标准方式,确保每个表单的数据在提交时不会被覆盖,后端能正确解析每个表单的字段。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.19 19:38:09