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

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

15 ответов


вот как я это делаю, используя 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 formset как 2-й. Что за selector должен сделать, это передать его, что он должен дублировать. В этом случае я передаю его div.table:last так что jQuery ищет последнюю таблицу с классом table. The :last часть важно, потому что selector также используется для определения того, что новая форма будет вставлена после. Более чем вероятно, что вы захотите его в конце остальных форм. The type аргумент таков, что мы можем обновить на cloneMore функция смотрит, сколько форм в настоящее время есть, и проходит через каждый вход и метку внутри новой формы, заменяя все имена полей/идентификаторы из чего-то вроде id_clients-(N)-name to id_clients-(N+1)-name и так далее. После его завершения он обновляет TOTAL_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, но также позволяет удалять формы.


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

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

пример:

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

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

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

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

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

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

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

спасибо!


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

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

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

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


имитировать и подражать:

  • создайте набор форм, соответствующий ситуации до нажать кнопку "Добавить".
  • загрузите страницу, просмотрите источник и обратите внимание на все <input> поля.
  • измените набор форм в соответствии с ситуацией после нажатие кнопки "Добавить" (изменение количества дополнительных полей).
  • загрузите страницу, просмотрите источник и обратите внимание на то, как <input> поля измененный.
  • создайте некоторый JavaScript, который изменяет DOM подходящим образом, чтобы переместить его из до состояние после государство.
  • присоединить JavaScript к кнопке "Добавить".

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


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


одним из вариантов было бы создать набор форм со всеми возможными формами, но изначально установить не запрошенные формы в hidden-ie,display: none;. Когда необходимо отобразить форму, установите для нее значение 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?

делает вещи клон не делает:

  • добавить форму, когда нет начальной формы
  • обрабатывает javascript в форме лучше, например django-ckeditor
  • сохранить исходные данные

@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 и поэтому отключите дублирование идентификатора и имени. Поскольку входные имена должны быть уникальными в форме there, вся идентификация выполняется с ними, а не с id. Вы также должны заменить form, type и контейнер набора форм. (В приведенном выше примере choices)


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

Динамические Наборы Форм Django

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

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

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