Я хочу автоматически добавлять новые формы в набор форм Django, используя Ajax, так что, когда пользователь нажимает кнопку «добавить», он запускает JavaScript, который добавляет новую форму (которая является частью набора форм) на страницу.
Я хочу автоматически добавлять новые формы в набор форм Django, используя Ajax, так что, когда пользователь нажимает кнопку «добавить», он запускает JavaScript, который добавляет новую форму (которая является частью набора форм) на страницу.
Ответы:
Вот как я это делаю, используя 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
в качестве первого аргумента, а type
формы - в качестве второго. Что selector
нужно сделать, это передать то, что он должен дублировать. В этом случае я передаю его div.table:last
так, что jQuery ищет последнюю таблицу с классом table
. :last
Часть этого важно , потому что selector
также используется для определения того, что новая форма будет вставлена после. Скорее всего, вы захотите это в конце остальных форм. type
Аргумент , так что мы можем обновить management_form
поле, в частности TOTAL_FORMS
, а также фактические поля формы. Если у вас есть форма, полная, скажем, Client
моделей, поля управления будут иметь идентификаторы id_clients-TOTAL_FORMS
и id_clients-INITIAL_FORMS
, а поля формы будут в формате id_clients-N-fieldname
сN
являющийся номером формы, начиная с 0
. Таким образом, с помощью type
аргумента cloneMore
функция смотрит на то, сколько форм существует в настоящее время, и проходит через каждый ввод и метку внутри новой формы, заменяя все имена / идентификаторы полей чем-то вроде id_clients-(N)-name
to id_clients-(N+1)-name
и так далее. После завершения обновляет TOTAL_FORMS
поле, чтобы отразить новую форму, и добавляет его в конец набора.
Эта функция особенно полезна для меня, потому что способ ее настройки позволяет использовать ее в приложении, когда я хочу предоставить больше форм в наборе форм, и не требует наличия скрытой формы «шаблона» для дублирования. пока я передаю его имя набора форм и формат, в котором выложены формы. Надеюсь, поможет.
prefix
члену объекта Formset. Это должно соответствовать значению type
аргумента cloneMore
функции.
Упрощенная версия ответа Паоло с использованием 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>
CompetitorFormSet = modelformset_factory(ProjectCompetitor, formset=CompetitorFormSets)
ctx['competitor_form_set'] = CompetitorFormSet(request.POST)
я получаю только одну форму, в чистом методе. не могли бы вы объяснить, как с этим бороться во взглядах?
empty_form
), что я ценю.
Я разместил фрагмент приложения, над которым работал некоторое время назад. Похож на Paolo, но также позволяет удалять формы.
Предложение Паоло прекрасно работает с одним предупреждением - кнопками браузера назад / вперед.
Динамические элементы, созданные с помощью скрипта Паоло, не будут отображаться, если пользователь вернется в набор форм с помощью кнопки «назад / вперед». Проблема, которая может быть нарушителем соглашения для некоторых.
Пример:
1) Пользователь добавляет две новые формы в форму с помощью кнопки «Добавить»
2) Пользователь заполняет формы и отправляет форму
3) Пользователь нажимает кнопку возврата в браузере
4) Formset теперь приведен к исходной форме, все динамически добавленные формы отсутствуют
Это не является дефектом сценария Паоло вообще; но факт жизни с манипуляциями домом и кешем браузера.
Я полагаю, что можно сохранить значения формы в сеансе и использовать магию ajax, когда набор форм загружается, чтобы снова создать элементы и перезагрузить значения из сеанса; но в зависимости от того, насколько анальным вы хотите быть с одним и тем же пользователем и несколькими экземплярами формы, это может стать очень сложным.
У кого-нибудь есть хорошее предложение для решения этой проблемы?
Спасибо!
Проверьте следующие решения для динамических форм django:
http://code.google.com/p/django-dynamic-formset/
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
Они оба используют jQuery и являются специфичными для django. Первый кажется немного более отлаженным и предлагает загрузку, которая идет с демо, которые превосходны.
Имитация и подражание:
<input>
поля.<input>
изменились поля.Хотя я знаю, что наборы форм используют специальные скрытые <input>
поля и приблизительно знают, что должен делать скрипт, я не вспоминаю подробности из головы. То, что я описал выше, это то, что я буду делать в вашей ситуации.
Для этого есть плагин jquery , я использовал его с inline_form, установленным в Django 1.3, и он отлично работает, включая предварительное заполнение, добавление, удаление форм на стороне клиента и множественные наборы inline_formsets.
Одним из вариантов было бы создать formset с каждой возможной формы, но сначала установить Ненужные формы для спрятался - то есть display: none;
. Когда необходимо отобразить форму, установите для ее отображения css значение block
или другое подходящее.
Не зная более подробной информации о том, что делает ваш «Аякс», сложно дать более подробный ответ.
Еще одна версия 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);
}
Есть небольшая проблема с функцией 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);
}
Я думаю, что это гораздо лучшее решение.
Как бы вы создали динамический набор форм в Django?
Делает ли что-то клон?
Для программистов, которые ищут ресурсы, чтобы немного лучше понять вышеприведенные решения:
После прочтения вышеупомянутой ссылки, документация Django и предыдущие решения должны иметь намного больше смысла.
В качестве краткого изложения того, что меня смутило: Форма управления содержит обзор форм внутри. Вы должны сохранять эту информацию точной, чтобы Django знал о формах, которые вы добавляете. (Сообщество, пожалуйста, дайте мне предложения, если некоторые из моих формулировок здесь не так. Я новичок в Django.)
@Paolo Bergantino
клонировать все прикрепленные обработчики просто изменить строку
var newElement = $(selector).clone();
для
var newElement = $(selector).clone(true);
чтобы предотвратить эту проблему.
Да, я бы также рекомендовал просто выводить их в 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 и отключить дублирование идентификатора и имени. Поскольку входные имена должны быть уникальными в их форме, вся идентификация выполняется с ними, а не с идентификаторами. Вы также должны заменить form
, type
и контейнер из formset. (В приведенном выше примере choices
)