Динамічне додавання форми до набору форматів Джанго за допомогою Ajax


260

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


Я просто здогадуюсь у вашому випадку використання, чи є це щось на зразок функції "Приєднати ще один файл" в gmail, де користувачеві подається поле для завантаження файлу, а нові поля додаються до DOM на лету, коли користувач клацає до кнопки "Прикріпити ще один файл" плюс?
— prairiedogg

Це над чим я збирався працювати незабаром, тому мені також будуть цікаві будь-які відповіді.
— Van Gale

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 що є номером форми, починаючи з0typecloneMore функція дивиться на скільки формах там в даний час, і проходить через кожен вхід і наклейку всередині нової форми , що замінює все імена полех / ідентифікатори від чого - то , як id_clients-(N)-nameна id_clients-(N+1)-nameі так далі. Після його закінчення оновить TOTAL_FORMSполе для відображення нової форми і додає його до кінця набору.

Ця функція особливо корисна для мене, оскільки спосіб її налаштування дозволяє мені використовувати її у всьому додатку, коли я хочу надати більше форм у наборі форматів, і не змушує мене мати приховану форму "шаблон" для дублювання доки я передаю йому ім'я набору форм і формат, у якому викладаються форми. Сподіваюся, це допомагає.


У IE клон з клонованого елемента представлений як <не визначений> при виборі в JS, чому?
— panchicore

Я виявив, що в Django 1.1 вам потрібно буде призначити значення prefixчлену об'єкта Formset Це має бути таким же значенням, як typeаргумент для cloneMoreфункції.
— Дерек Рейнольдс

3
Я змінив це, щоб взяти селектор без: останнього та використаного var total = $ (selector) .length; щоб отримати загальну суму, оскільки оновлення сторінки видалить мої набори форматів, але залишить ВСІЧЕ збільшення, що призведе до збереження неправильного числа. Потім я додав: останнє до селектора за потребою. Дякую за це.
— Грег

2
Я виявив, що для цього використовується $ (this) .attr ({'name': ім'я, 'id': id}). Val (''). RemoveAttr ('перевірено'); Для очищення введення буде зіпсовано прапорці. Встановлення val ('') надає прапорці атрибут порожнього значення. А оскільки прапорці не використовують атрибут значення, це ніколи не буде оновлено - незалежно від того, скільки разів ви натискаєте його. Але здається, що значення має більший пріоритет, ніж "перевірений" атрибут прапорців. Що означатиме, що ви завжди розміщуватимете не встановлені прапорці.
— niklasdstrom

будь ласка Паоло ви можете перевірити мою проблему 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

Блискучий - дякую. Прекрасно використовує наявні помічники Джанго (як empty_form), які я ціную.
— BigglesZX

@BigglesZX - я адаптував рішення, і нові рядки порожніх форм формуються. Однак поля вибору генерують список FK (доступних) варіантів замість спадів, які інакше створюються для оригінального набору форм. Чи повідомлялося про якесь подібне питання?
— користувач12379095

@Dave ви могли б оновити відповідь на більш пізні версії, тобто 3.x? це просто і зрозуміло, але це не працює для мене
— Poula Adel

1
@PoulaAdel Що не працює? Я просто спробував це на Django 3.0.5, і він все ще працює для мене. Дивно після 8 років, але я думаю, що Django та jQuery мають хорошу відсталу сумісність зі старим кодом.
— Дейв


18

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

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

Приклад:

1) Користувач додає дві нові форми до набору форматів за допомогою кнопки "додати більше"

2) Користувач заповнює форми та подає набір форм

3) Користувач натискає кнопку "Назад" у браузері

4) Набір форм тепер зменшується до початкової форми, усіх динамічно доданих форм немає

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

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

Хтось має гарну пропозицію для вирішення цього питання?

Дякую!


2
Якщо ви переспрямовуєтесь після успішного надсилання, кнопка повернення не є проблемою. Якщо ви заповнюєте форми з БД під час наступного відвідування, всі форми з’являються спочатку. Якщо ви невдалі форми через недійсний ввід, всі вони повинні бути там, на повторному відображенні з помилками. Якщо я не розумію ваших тверджень .... Це перенаправлення після публікації дійсно важливо в хорошому робочому додатку, який багато кодерів просто не отримує, грунтуючись на кількості недобре діючих програм, з якими я стикаюся в Інтернеті.
— boatcoder

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

13

Ознайомтеся з такими рішеннями динамічних форм джанго:

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

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

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


11

Моделюйте та наслідуйте:

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

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


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

6

Для цього є плагін jquery , я використовував його з набором inline_form в Django 1.3, і він відмінно працює, включаючи prepopulation, додавання, видалення форми клієнта на стороні клієнта та кілька inline_formsets.


Хоча пов’язана публікація в блозі все ще існує, посилання для завантаження там порушені. Мабуть, плагін створив @ elo80ka, відповідь якого вказує на (попередню?) Версію сценарію.
— lfurini

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

4

Одним із варіантів було б створити набір форматів з усіма можливими формами, але спочатку встановити непотрібні форми на приховані, тобто 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);
}

чи можете ви допомогти мені 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
  • Зберігайте початкові дані

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

2

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

Динамічні набори формат Django

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

Django Formset Documentation

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


1

@Paolo Bergantino

щоб клонувати всі додані обробники просто змінити рядок

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

для

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

щоб запобігти цій проблемі.


чи можете ви допомогти мені 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 та відключити дублювання ідентифікатора та імені. Оскільки введені імена повинні бути унікальними в тамтешній формі, все ототожнення проводиться саме з ними, а не з id. Ви також повинні замінити form, typeі контейнер з formset. (У прикладі вище choices)


чи можете ви допомогти мені stackoverflow.com/questions/62285767/… , я спробував багато, але не отримав відповідь! я дуже ціную вас
— art_cs
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.