Динамическое добавление формы в набор форм Django с помощью Ajax

260

Я хочу автоматически добавлять новые формы в набор форм Django, используя Ajax, так что, когда пользователь нажимает кнопку «добавить», он запускает JavaScript, который добавляет новую форму (которая является частью набора форм) на страницу.

Чип Тол
источник
Я просто догадываюсь, что в вашем случае использования это что-то вроде функции «Присоединить другой файл» в gmail, где пользователю предоставляется поле для загрузки файла, а новые поля добавляются в DOM на лету, когда пользователь нажимает «Прикрепить другой файл» плюс?
Prairiedogg
Это то, над чем я собираюсь поработать в ближайшее время, поэтому мне также будут интересны любые ответы.
Ван Гейл
2
Этот вопрос немного нечеткий, в заголовке, описании и тегах упоминается «Ajax». Тем не менее, ни один из ответов не использует Ajax, он все еще требует отправки формы.
Антуан Пинсард

Ответы:

219

Вот как я это делаю, используя 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)-nameto id_clients-(N+1)-nameи так далее. После завершения обновляет TOTAL_FORMSполе, чтобы отразить новую форму, и добавляет его в конец набора.

Эта функция особенно полезна для меня, потому что способ ее настройки позволяет использовать ее в приложении, когда я хочу предоставить больше форм в наборе форм, и не требует наличия скрытой формы «шаблона» для дублирования. пока я передаю его имя набора форм и формат, в котором выложены формы. Надеюсь, поможет.

Паоло Бергантино
источник
В IE клон из клонированного элемента представлен как <undefined> при выборе в JS, почему?
Panchicore
Я обнаружил, что в Django 1.1 вам нужно присвоить значение prefixчлену объекта Formset. Это должно соответствовать значению typeаргумента cloneMoreфункции.
Дерек Рейнольдс
3
Я изменил это, чтобы взять селектор без: last и used var total = $ (selector) .length; чтобы получить мой итог, потому что обновление страницы удалит мои наборы форм, но оставит ВСЕГО увеличение, приводящее к сохранению неправильного числа. Затем я добавил: последний в селектор по мере необходимости. Спасибо за это.
Грег
2
Я обнаружил, что это с помощью $ (this) .attr ({'name': name, 'id': id}). Val (''). RemoveAttr ('checked'); Для очистки ввода будут испорчены флажки. Установка val ('') дает флажкам пустой атрибут значения. А поскольку флажки не используют атрибут value, он никогда не будет обновляться - независимо от того, сколько раз вы щелкнете по нему. Но кажется, что значение имеет более высокий приоритет, чем атрибут «флажок», установленный для флажков. Это будет означать, что вы всегда будете ставить не отмеченные флажки.
Никласдстрем
пожалуйста, Паоло, вы можете проверить мою проблему stackoverflow.com/questions/62252867/…
art_cs
109

Упрощенная версия ответа Паоло с использованием 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)я получаю только одну форму, в чистом методе. не могли бы вы объяснить, как с этим бороться во взглядах?
AJ
Бриллиант - спасибо. Прекрасно использует доступных помощников Django (как empty_form), что я ценю.
BigglesZX
@BigglesZX - я адаптировал решение, и генерируются новые строки пустых форм. Однако блоки выбора генерируют список вариантов FK (доступных) вместо раскрывающихся списков, которые в противном случае генерируются для исходного набора форм. Сообщалось ли о какой-либо проблеме такого рода?
user12379095
@ Дэйв, не могли бы вы обновить ответ для более поздних версий, например 3.x? это просто и понятно, но это не работает для меня
Poula Adel
1
@PoulaAdel Что не работает? Я только что попробовал это на Django 3.0.5, и он все еще работает для меня. Удивительно после 8 лет, но я думаю, что Django и jQuery имеют хорошую обратную совместимость со старым кодом.
Дейв
18

Предложение Паоло прекрасно работает с одним предупреждением - кнопками браузера назад / вперед.

Динамические элементы, созданные с помощью скрипта Паоло, не будут отображаться, если пользователь вернется в набор форм с помощью кнопки «назад / вперед». Проблема, которая может быть нарушителем соглашения для некоторых.

Пример:

1) Пользователь добавляет две новые формы в форму с помощью кнопки «Добавить»

2) Пользователь заполняет формы и отправляет форму

3) Пользователь нажимает кнопку возврата в браузере

4) Formset теперь приведен к исходной форме, все динамически добавленные формы отсутствуют

Это не является дефектом сценария Паоло вообще; но факт жизни с манипуляциями домом и кешем браузера.

Я полагаю, что можно сохранить значения формы в сеансе и использовать магию ajax, когда набор форм загружается, чтобы снова создать элементы и перезагрузить значения из сеанса; но в зависимости от того, насколько анальным вы хотите быть с одним и тем же пользователем и несколькими экземплярами формы, это может стать очень сложным.

У кого-нибудь есть хорошее предложение для решения этой проблемы?

Спасибо!

cethegeek
источник
2
Если вы перенаправили после успешной отправки, кнопка «Назад» не является проблемой. Если вы заполняете формы из БД при следующем посещении, все формы появляются изначально. Если вы потерпели неудачу в формах из-за неправильного ввода, все они должны быть там на повторном отображении с ошибками. Если я не понимаю ваши заявления ... Перенаправление после публикации действительно важно в хорошо работающем приложении, которое многие программисты просто не получают, основываясь на количестве плохо работающих приложений, с которыми я сталкиваюсь в Интернете.
Боевой кодер
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs
13

Проверьте следующие решения для динамических форм django:

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

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

Они оба используют jQuery и являются специфичными для django. Первый кажется немного более отлаженным и предлагает загрузку, которая идет с демо, которые превосходны.

Kreychek
источник
11

Имитация и подражание:

  • Создайте набор форм, который соответствует ситуации, прежде чем нажимать кнопку «Добавить».
  • Загрузите страницу, просмотрите источник и запишите все <input>поля.
  • Измените форму, чтобы он соответствовал ситуации после нажатия кнопки «Добавить» (измените количество дополнительных полей).
  • Загрузите страницу, просмотрите источник и обратите внимание на то, как <input>изменились поля.
  • Создайте некоторый JavaScript, который изменяет DOM подходящим способом, чтобы переместить его из состояния before в состояние after .
  • Прикрепите этот JavaScript к кнопке «Добавить».

Хотя я знаю, что наборы форм используют специальные скрытые <input>поля и приблизительно знают, что должен делать скрипт, я не вспоминаю подробности из головы. То, что я описал выше, это то, что я буду делать в вашей ситуации.

akaihola
источник
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал stackoverflow.com/questions/62285767/…, но не получил ответа! я очень ценю тебя
art_cs
6

Для этого есть плагин jquery , я использовал его с inline_form, установленным в Django 1.3, и он отлично работает, включая предварительное заполнение, добавление, удаление форм на стороне клиента и множественные наборы inline_formsets.

е-удовлетворяться
источник
Хотя ссылка на блог все еще существует, ссылки для скачивания там не работают. По-видимому, плагин был создан @ elo80ka, чей ответ указывает на (предварительную?) Версию скрипта.
Ифурини
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs
4

Одним из вариантов было бы создать formset с каждой возможной формы, но сначала установить Ненужные формы для спрятался - то есть display: none;. Когда необходимо отобразить форму, установите для ее отображения css значение blockили другое подходящее.

Не зная более подробной информации о том, что делает ваш «Аякс», сложно дать более подробный ответ.

Даниэль Нааб
источник
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);
}
xaralis
источник
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs
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);
}
Сезар Канасса
источник
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs
2

Я думаю, что это гораздо лучшее решение.

Как бы вы создали динамический набор форм в Django?

Делает ли что-то клон?

  • Добавить форму, когда нет начальных форм
  • Лучше обрабатывает javascript в форме, например django-ckeditor
  • Сохранить исходные данные
Bufke
источник
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs
2

Для программистов, которые ищут ресурсы, чтобы немного лучше понять вышеприведенные решения:

Django Dynamic Formsets

После прочтения вышеупомянутой ссылки, документация Django и предыдущие решения должны иметь намного больше смысла.

Документация Django Formset

В качестве краткого изложения того, что меня смутило: Форма управления содержит обзор форм внутри. Вы должны сохранять эту информацию точной, чтобы Django знал о формах, которые вы добавляете. (Сообщество, пожалуйста, дайте мне предложения, если некоторые из моих формулировок здесь не так. Я новичок в Django.)

Райан Бухмайер
источник
1

@Paolo Bergantino

клонировать все прикрепленные обработчики просто изменить строку

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

для

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

чтобы предотвратить эту проблему.

panchicore
источник
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs
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();
        };
    };
}
Боб Сприн
источник
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs
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 и отключить дублирование идентификатора и имени. Поскольку входные имена должны быть уникальными в их форме, вся идентификация выполняется с ними, а не с идентификаторами. Вы также должны заменить form, typeи контейнер из formset. (В приведенном выше примере choices)

R3turnz
источник
Можете ли вы помочь мне stackoverflow.com/questions/62285767/… , я много пробовал, но не получил ответ! я очень ценю тебя
art_cs