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

210

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

  • 0
    Я просто догадываюсь, что в вашем случае использования это что-то вроде функции «Присоединить другой файл» в gmail, где пользователю предоставляется поле для загрузки файла, а новые поля добавляются в DOM на лету, когда пользователь нажимает добавить кнопку «Прикрепить другой файл»?
  • 0
    Это то, над чем я собираюсь поработать в ближайшее время, поэтому мне также будут интересны любые ответы.
Показать ещё 1 комментарий
Теги:

14 ответов

194
Лучший ответ

Вот как я это делаю, используя 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, чтобы отобразить новую форму и добавляет ее в конец набора.

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

  • 0
    В IE клон из клонированного элемента представлен как <undefined> при выборе в JS, почему?
  • 0
    Я обнаружил, что в Django 1.1 вам нужно будет присвоить значение prefix члену объекта Formset. Это должно быть то же значение, что и аргумент type для функции cloneMore .
Показать ещё 4 комментария
64

Упрощенная версия ответа Паоло с использованием 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>
  • 0
    как я могу иметь дело с этим в представлении? когда я использую CompetitorFormSet = modelformset_factory(ProjectCompetitor, formset=CompetitorFormSets) ctx['competitor_form_set'] = CompetitorFormSet(request.POST) я получаю только одну форму, в чистом методе. не могли бы вы объяснить, как с этим справляться?
  • 10
    Это должен быть принятый ответ
Показать ещё 2 комментария
24

Я опубликовал фрагмент из приложения, над которым я работал некоторое время назад. Подобно Паоло, но также позволяет удалять формы.

18

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

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

Пример:

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

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

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

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

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

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

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

Спасибо!

  • 2
    Если вы перенаправили после успешной отправки, кнопка «Назад» не является проблемой. Если вы заполняете формы из БД при следующем посещении, все формы появляются изначально. Если вы потерпите неудачу в формах из-за неверного ввода, все они должны быть там на повторном отображении с ошибками. Если только я не понимаю ваши заявления ... Перенаправление после публикации действительно важно в хорошо работающем приложении, которое многие программисты просто не получают, основываясь на количестве плохо работающих приложений, с которыми я сталкиваюсь в Интернете.
13

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

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

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

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

  • 0
    Ссылка на код Google не работает.
11

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

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

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

6

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

  • 0
    Хотя ссылка на блог все еще существует, ссылки для скачивания там не работают. Видимо, плагин был создан @ elo80ka, чей ответ указывает на (предварительную?) Версию скрипта.
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);
}
4

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

Не зная подробностей о том, что делает ваш "Ajax", трудно дать более подробный ответ.

2

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

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

Делает ли клонированные объекты:

  • Добавить форму, если нет начальных форм
  • Обрабатывает javascript в форме лучше, например django-ckeditor
  • Сохранять исходные данные
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);
}
1

Поскольку все ответы выше используют jQuery и делают некоторые вещи немного сложными, я написал следующие script:

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 и контейнер набора форм. (В примере выше choices)

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();
        };
    };
}
1

@Паоло Бергантино

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

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

для

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

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

Ещё вопросы

Сообщество Overcoder
Наверх
Меню