我想动态地向Django表单集添加新表单,这样当用户点击“添加”按钮时,它就会运行JavaScript,向页面添加新表单(这是表单集的一部分)。
当前回答
因为上面所有的答案都使用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的复制。因为输入名称在表单中必须是唯一的,所以所有的标识都是用它们来完成的,而不是用id。 您还必须替换表单集的表单、类型和容器。(在上述选择的例子中)
其他回答
这是我如何做到这一点,使用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 accepts selector as the first argument, and the type of formset as the 2nd one. What the selector should do is pass it what it should duplicate. In this case, I pass it div.table:last so that jQuery looks for the last table with a class of table. The :last part of it is important because the selector is also used to determine what the new form will be inserted after. More than likely you'd want it at the end of the rest of the forms. The type argument is so that we can update the management_form field, notably TOTAL_FORMS, as well as the actual form fields. If you have a formset full of, say, Client models, the management fields will have IDs of id_clients-TOTAL_FORMS and id_clients-INITIAL_FORMS, while the form fields will be in a format of id_clients-N-fieldname with N being the form number, starting with 0. So with the type argument the cloneMore function looks at how many forms there currently are, and goes through every input and label inside the new form replacing all the field names/ids from something like id_clients-(N)-name to id_clients-(N+1)-name and so on. After it is finished, it updates the TOTAL_FORMS field to reflect the new form and adds it to the end of the set.
这个功能对我来说特别有帮助,因为它的设置方式允许我在整个应用程序中使用它,当我想在一个格式集中提供更多的表单时,并且不需要我有一个隐藏的“模板”表单来复制,只要我传递它的格式集名称和表单布局的格式。
另一个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);
}
对于那些正在寻找资源以更好地理解上述解决方案的程序员:
Django动态表单
读完上面的链接后,Django文档和以前的解决方案应该更有意义了。
Django Formset文档
作为对我所困惑的内容的快速总结:管理表单包含其中表单的概述。你必须保证这些信息的准确性,这样Django才能知道你添加的表单。(社区,如果我的一些措辞不正确,请给我建议。我刚接触Django。)
Paolo的建议很好用,但有一点需要注意——浏览器的后退/前进按钮。
如果用户使用后退/前进按钮返回到表单集,那么用Paolo的脚本创建的动态元素将不会被呈现。对一些人来说,这个问题可能是交易的破坏者。
例子:
1)用户使用“add-more”按钮向表单集中添加两个新表单
2)用户填充表单并提交表单集
3)用户在浏览器中点击后退按钮
4)形式集现在减少到原来的形式,所有动态添加的形式不存在
这根本不是保罗剧本的缺陷;但是dom操作和浏览器缓存是一个现实。
我认为可以在会话中存储表单的值,并在表单集加载时再次创建元素并从会话中重新加载值;但这取决于你想要的相同用户和表单的多个实例,这可能会变得非常复杂。
有人有好的建议吗?
谢谢!
因为上面所有的答案都使用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的复制。因为输入名称在表单中必须是唯一的,所以所有的标识都是用它们来完成的,而不是用id。 您还必须替换表单集的表单、类型和容器。(在上述选择的例子中)
推荐文章
- django MultiValueDictKeyError错误,我如何处理它
- 我如何在Django中创建一个鼻涕虫?
- 没有名为'django.core.urlresolvers'的模块
- Django - makemigrations -未检测到任何更改
- ">", "<", ">=" 和“< =”不工作”在Django过滤()”
- Django项目工作目录结构的最佳实践
- 如何在Django中表达一对多关系?
- 用Django创建电子邮件模板
- Django的嵌套元类是如何工作的?
- 用Django实现OpenID的最佳解决方案是什么?
- 在使用信号时,你不能在'原子'块的末尾执行查询,但只能在单元测试期间执行
- Django查询- id vs pk
- render(), render_to_response()和direct_to_template()之间有什么区别?
- 如何使用MySQLdb与Python和Django在OSX 10.6?
- 如何在Django中一次添加多个对象到ManyToMany关系?