使用Ajaxdynamic地将表单添加到Django formset

我想使用Ajax自动将新表单添加到Django formset中,以便当用户单击“添加”button时,它将运行JavaScript,将新的表单(它是formset的一部分)添加到页面。

这是我如何做,使用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 ,以及实际的表单字段。 如果你有一个formset完整的Client模型,pipe理字段的id为id_clients-TOTAL_FORMSid_clients-INITIAL_FORMS ,而表单字段的格式为id_clients-N-fieldname其中N是表格编号,从0开始。 所以通过cloneMore参数, cloneMore函数可以查看当前有多less个表单,并且遍历新表单中的每个input和标签,从id_clients-(N)-nameid_clients-(N+1)-name等。 完成后,它会更新TOTAL_FORMS字段以反映新表单并将其添加到集合的结尾。

这个函数对我来说特别有用,因为它的设置方式允许我在整个应用程序中使用它,当我想在一个formset中提供更多的表单时,并不需要隐藏“模板”表单来复制只要我通过它的forms名称和表格的格式。 希望它有帮助。

保罗的答案使用empty_form作为模板的简化版本。

 <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> 

我已经发布了一段时间我从事的一个应用程序的片段 。 类似Paolo的,也可以让你删除表格。

保罗的build议与一个警告 – 浏览器的后退/前进button精美的作品。

如果用户使用后退/前进button返回到表单集,则不会呈现使用Paolo脚本创build的dynamic元素。 一个问题可能是一些交易断路器。

例:

1)用户使用“add-more”button向formset添加两个新的表单

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

3)用户点击浏览器的后退button

4)Formset现在缩小到原来的forms,所有dynamic添加的表单都不存在

保罗的剧本根本不是一个缺陷; 而是通过dom操作和浏览器caching的生活事实。

我想可以将表单的值存储在会话中,并且在formset加载时重新创build元素并重新加载会话中的值。 但取决于你想要关于同一个用户和多个表单的情况,这可能会变得非常复杂。

任何人都有一个好的build议来处理这个?

谢谢!

看看下面的解决schemedynamicDjango的forms:

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

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

他们都使用jQuery和Django的具体。 第一个似乎有点更加精美,并提供了一个下载,带有很好的演示。

模拟和模仿:

  • 点击“添加”button之前 ,创build一个对应于该情况的表单集。
  • 加载页面,查看源代码并记下所有的<input>字段。
  • 单击“添加”button(更改额外字段的数量) 后,修改表单以符合情况。
  • 加载页面,查看源代码并logging<input>字段如何更改。
  • 创build一些JavaScript,以适当的方式修改DOM,将其从前一状态移动到状态。
  • 将该JavaScript附加到“添加”button。

虽然我知道formset使用了特殊的隐藏的<input>字段,大概知道脚本必须做什么,但我不记得头脑中的细节。 我上面所描述的是我所要做的。

有一个jQuery的插件 ,我用它在Django 1.3中设置的inline_form,它完美的工作,包括预填充,客户端表单添加,删除和多个inline_formsets。

一种select是用各种可能的forms创build一个formset,但是最初将不需要的forms设置为hidden – 即display: none; 。 当需要显示表单时,将其设置为css显示以block或适当的操作。

不知道你的“Ajax”正在做什么的更多细节,很难给出更详细的回应。

另一个克隆更多的版本,它允许select性的领域消毒。 当你需要防止几个字段被删除时使用它。

 $('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); } 

cloneMore函数有一个小问题。 因为它也清理了django自动生成的隐藏字段的值,所以如果您试图用多于一个空表单保存formset,则会导致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); } 

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

你将如何在Django中创build一个dynamic的表单?

克隆的东西不是:

  • 当没有初始表单存在时添加表单
  • 以更好的forms处理javascript,例如django-ckeditor
  • 保留初始数据

@Paolo Bergantino

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

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

对于

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

防止这个问题。

是的,我也build议只是把它们呈现在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(); }; }; } 

因为上面的所有答案都使用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和name的重复。 因为input的名字在表单中必须是唯一的,所有的标识都是用它们来完成的,而不是用id来完成的。 您还必须replaceformset的formtype和容器。 (在上面的例子中)