使用Ajax动态地将表单添加到Django formset

时间:2009-02-01 22:26:27

标签: ajax django

我想使用Ajax自动将新表单添加到Django表单集中,这样当用户单击“添加”按钮时,它会运行JavaScript,向页面添加一个新表单(它是表单集的一部分)。

15 个答案:

答案 0 :(得分:209)

我就是这样做的,使用jQuery

我的模板:

<h3>My Services</h3>
{{ serviceFormset.management_form }}
{% for form in serviceFormset.forms %}
    <div class='table'>
    <table class='no_error'>
        {{ form.as_table }}
    </table>
    </div>
{% endfor %}
<input type="button" value="Add More" id="add_more">
<script>
    $('#add_more').click(function() {
        cloneMore('div.table:last', 'service');
    });
</script>

在javascript文件中:

function cloneMore(selector, type) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;
        $(this).attr({'name': name, 'id': id}).val('').removeAttr('checked');
    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}

它的作用:

cloneMore接受selector作为第一个参数,并将formset的type作为第二个参数。 selector应该做的是将它应该复制的内容传递给它。在这种情况下,我传递它div.table:last,以便jQuery查找具有类table的最后一个表。 :last部分很重要,因为selector也用于确定之后插入新表单的内容。很可能你会在其余表格结束时想要它。 type参数是为了让我们可以更新management_form字段,尤其是TOTAL_FORMS字段,以及实际的表单字段。如果您的表单集中包含Client个模型,则管理字段的ID为id_clients-TOTAL_FORMSid_clients-INITIAL_FORMS,而表单字段的格式为id_clients-N-fieldname } N是表单编号,以0开头。因此,使用type参数,cloneMore函数会查看当前有多少表单,并遍历新表单中的每个输入和标签,从id_clients-(N)-name替换所有字段名称/ ID到id_clients-(N+1)-name等等。完成后,它会更新TOTAL_FORMS字段以反映新表单并将其添加到集合的末尾。

此功能对我特别有用,因为它的设置方式允许我在整个应用程序中使用它,当我想在formset中提供更多表单时,并不会让我需要一个隐藏的“模板”只要我传递formset名称和表单的格式,就可以复制表单。希望它有所帮助。

答案 1 :(得分:101)

使用empty_form作为模板的简化版Paolo答案。

<h3>My Services</h3>
{{ serviceFormset.management_form }}
<div id="form_set">
    {% for form in serviceFormset.forms %}
        <table class='no_error'>
            {{ form.as_table }}
        </table>
    {% endfor %}
</div>
<input type="button" value="Add More" id="add_more">
<div id="empty_form" style="display:none">
    <table class='no_error'>
        {{ serviceFormset.empty_form.as_table }}
    </table>
</div>
<script>
    $('#add_more').click(function() {
        var form_idx = $('#id_form-TOTAL_FORMS').val();
        $('#form_set').append($('#empty_form').html().replace(/__prefix__/g, form_idx));
        $('#id_form-TOTAL_FORMS').val(parseInt(form_idx) + 1);
    });
</script>

答案 2 :(得分:24)

我来自我前一段时间工作过的应用posted a snippet。与Paolo相似,但也允许您删除表格。

答案 3 :(得分:18)

Paolo的建议很好地解决了一个问题 - 浏览器的后退/前进按钮。

如果用户使用后退/前进按钮返回到formset,则不会呈现使用Paolo脚本创建的动态元素。对某些人来说可能是一个交易破坏者的问题。

示例:

1)用户使用“添加更多”按钮

向表单集添加两个新表单

2)用户填充表单并提交formset

3)用户点击浏览器中的后退按钮

4)Formset现在缩减为原始形式,所有动态添加的表单都不存在

这根本不是Paolo脚本的缺陷;但这是dom操纵和浏览器缓存的生活现实。

我想有人可以在会话中存储表单的值,并且当formset加载再次创建元素并从会话重新加载值时,会有一些ajax魔法;但是根据你想要关于同一个用户和表单的多个实例的肛门,这可能会变得非常复杂。

任何人都有一个很好的建议来解决这个问题吗?

谢谢!

答案 4 :(得分:13)

查看以下动态django表单的解决方案:

http://code.google.com/p/django-dynamic-formset/

https://github.com/javisantana/django-dinamyc-form/tree/master/frm

他们都使用jQuery并且是特定于django的。第一个似乎更精致,并提供了一个非常好的演示下载。

答案 5 :(得分:11)

模拟和模仿:

  • 创建一个与之前的情况相对应的表单集点击“添加”按钮。
  • 加载页面,查看来源并记下所有<input>字段。
  • 修改formset以对应点击“添加”按钮后的情况(更改额外字段的数量)。
  • 加载页面,查看来源并记下<input>字段的更改方式。
  • 创建一些JavaScript,以适当的方式修改DOM,将其从之前的状态转移到之后的状态。
  • 将该JavaScript附加到“添加”按钮。

虽然我知道formsets使用特殊的隐藏<input>字段并且大致知道脚本必须做什么,但我不记得我头脑中的详细信息。我上面描述的是我在你的情况下会做的事情。

答案 6 :(得分:5)

有一个jquery plugin for this,我在Django 1.3中设置了inline_form,它运行得很好,包括预填充,客户端表单添加,删除和多个inline_formsets。

答案 7 :(得分:4)

一种选择是创建一个包含所有可能形式的formset,但最初将不需要的表单设置为隐藏 - 即display: none;。当需要显示表单时,将其css显示设置为block或任何适当的。

如果不了解您的“Ajax”正在做什么的更多细节,很难给出更详细的回复。

答案 8 :(得分:4)

另一个cloneMore版本,允许对字段进行选择性清理。当您需要防止删除多个字段时使用它。

$('table tr.add-row a').click(function() {
    toSanitize = new Array('id', 'product', 'price', 'type', 'valid_from', 'valid_until');
    cloneMore('div.formtable table tr.form-row:last', 'form', toSanitize);
});

function cloneMore(selector, type, sanitize) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var namePure = $(this).attr('name').replace(type + '-' + (total-1) + '-', '');
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;
        $(this).attr({'name': name, 'id': id}).removeAttr('checked');

        if ($.inArray(namePure, sanitize) != -1) {
            $(this).val('');
        }

    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}

答案 9 :(得分:2)

cloneMore函数存在一个小问题。由于它还清除了django自动生成的隐藏字段的值,如果您尝试使用多个空表单保存表单集,则会导致django抱怨。

这是一个修复:

function cloneMore(selector, type) {
    var newElement = $(selector).clone(true);
    var total = $('#id_' + type + '-TOTAL_FORMS').val();
    newElement.find(':input').each(function() {
        var name = $(this).attr('name').replace('-' + (total-1) + '-','-' + total + '-');
        var id = 'id_' + name;

        if ($(this).attr('type') != 'hidden') {
            $(this).val('');
        }
        $(this).attr({'name': name, 'id': id}).removeAttr('checked');
    });
    newElement.find('label').each(function() {
        var newFor = $(this).attr('for').replace('-' + (total-1) + '-','-' + total + '-');
        $(this).attr('for', newFor);
    });
    total++;
    $('#id_' + type + '-TOTAL_FORMS').val(total);
    $(selector).after(newElement);
}

答案 10 :(得分:2)

我认为这是一个更好的解决方案。

How would you make a dynamic formset in Django?

克隆没有:

  • 当没有初始表格时添加表格
  • 更好地处理表单中的javascript,例如django-ckeditor
  • 保留初始数据

答案 11 :(得分:1)

@Paolo Bergantino

克隆所有附加的处理程序只需修改行

var newElement = $(selector).clone();

代表

var newElement = $(selector).clone(true);

阻止this problem.

答案 12 :(得分:1)

是的,如果您的条目数量有限,我还建议您只在html中渲染它们。 (如果不这样做,则必须使用其他方法)。

你可以像这样隐藏它们:

{% for form in spokenLanguageFormset %}
    <fieldset class="languages-{{forloop.counter0 }} {% if spokenLanguageFormset.initial_forms|length < forloop.counter and forloop.counter != 1 %}hidden-form{% endif %}">

然后js非常简单:

addItem: function(e){
    e.preventDefault();
    var maxForms = parseInt($(this).closest("fieldset").find("[name*='MAX_NUM_FORMS']").val(), 10);
    var initialForms = parseInt($(this).closest("fieldset").find("[name*='INITIAL_FORMS']").val(), 10);
    // check if we can add
    if (initialForms < maxForms) {
        $(this).closest("fieldset").find("fieldset:hidden").first().show();
        if ($(this).closest("fieldset").find("fieldset:visible").length == maxForms ){
            // here I'm just hiding my 'add' link
            $(this).closest(".control-group").hide();
        };
    };
}

答案 13 :(得分:1)

因为上面的所有答案都使用jQuery并且使一些事情变得有点复杂,所以我写了下面的脚本:

function $(selector, element) {
    if (!element) {
        element = document
    }
    return element.querySelector(selector)
}

function $$(selector, element) {
    if (!element) {
        element = document
    }
    return element.querySelectorAll(selector)
}

function hasReachedMaxNum(type, form) {
    var total = parseInt(form.elements[type + "-TOTAL_FORMS"].value);
    var max = parseInt(form.elements[type + "-MAX_NUM_FORMS"].value);
    return total >= max
}

function cloneMore(element, type, form) {
    var totalElement = form.elements[type + "-TOTAL_FORMS"];
    total = parseInt(totalElement.value);
    newElement = element.cloneNode(true);
    for (var input of $$("input", newElement)) {
        input.name = input.name.replace("-" + (total - 1) + "-", "-" + total + "-");
        input.value = null
    }
    total++;
    element.parentNode.insertBefore(newElement, element.nextSibling);
    totalElement.value = total;
    return newElement
}
var addChoiceButton = $("#add-choice");
addChoiceButton.onclick = function() {
    var choices = $("#choices");
    var createForm = $("#create");
    cloneMore(choices.lastElementChild, "choice_set", createForm);
    if (hasReachedMaxNum("choice_set", createForm)) {
        this.disabled = true
    }
};

首先,您应将auto_id设置为false,以便禁用重复的ID和名称。因为输入名称必须在表格中是唯一的,所有标识都是用它们完成的,而不是用id来完成的。 您还必须替换formtype和formset的容器​​。 (在上面的例子中choices

答案 14 :(得分:0)

对于那些正在寻找资源以便更好地理解上述解决方案的编码员:

Consuming Code

阅读完上述链接后,Django文档和以前的解决方案应该更有意义。

Django Dynamic Formsets

作为我对此感到困惑的快速摘要:管理表格包含对其中表格的概述。您必须保持该信息的准确性,以便Django了解您添加的表单。 (社区,如果我的一些措辞不在这里,请给我建议。我是Django的新手。)