Я хочу автоматично додати нові форми до набору форматів Джанго за допомогою Ajax, так що коли користувач натискає кнопку "додати", він запускає JavaScript, який додає нову форму (яка є частиною набору форматів) на сторінку.
Я хочу автоматично додати нові форми до набору форматів Джанго за допомогою 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
на 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
), які я ціную.
Я опублікував фрагмент із програми, над якою працював певний час. Подібний до Паоло, але також дозволяє видаляти форми.
Пропозиція Паоло прекрасно працює з одним застереженням - кнопками назад / вперед браузера.
Динамічні елементи, створені за допомогою сценарію Паоло, не будуть надані, якщо користувач повернеться до набору форматів за допомогою кнопки назад / вперед. Проблема, яка може стати перешкодою для деяких угод.
Приклад:
1) Користувач додає дві нові форми до набору форматів за допомогою кнопки "додати більше"
2) Користувач заповнює форми та подає набір форм
3) Користувач натискає кнопку "Назад" у браузері
4) Набір форм тепер зменшується до початкової форми, усіх динамічно доданих форм немає
Це зовсім не дефект сценарію Паоло; але факт життя з маніпуляцією з домом та кешем браузера.
Я припускаю, що можна зберігати значення форми в сеансі і мати якусь магію ajax, коли набір форм завантажується, щоб створити елементи знову і перезавантажити значення з сеансу; але залежно від того, наскільки анальним ви хочете бути приблизно одним і тим же користувачем та кількома екземплярами форми, це може стати дуже складним.
Хтось має гарну пропозицію для вирішення цього питання?
Дякую!
Ознайомтеся з такими рішеннями динамічних форм джанго:
http://code.google.com/p/django-dynamic-formset/
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
Вони обидва використовують jQuery і є специфічними для джанго. Перший здається трохи більш відшліфованим і пропонує завантаження, яке поставляється з відмінними демонстраціями.
Моделюйте та наслідуйте:
<input>
поля.<input>
змінилися поля.Хоча я знаю, що набори форматів використовують спеціальні приховані <input>
поля і приблизно знають, що повинен робити сценарій, я не пригадую деталей у верхній частині голови. Те, що я описав вище, - це я зробив би у вашій ситуації.
Для цього є плагін jquery , я використовував його з набором inline_form в Django 1.3, і він відмінно працює, включаючи prepopulation, додавання, видалення форми клієнта на стороні клієнта та кілька inline_formsets.
Одним із варіантів було б створити набір форматів з усіма можливими формами, але спочатку встановити непотрібні форми на приховані, тобто 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 та відключити дублювання ідентифікатора та імені. Оскільки введені імена повинні бути унікальними в тамтешній формі, все ототожнення проводиться саме з ними, а не з id. Ви також повинні замінити form
, type
і контейнер з formset. (У прикладі вище choices
)