Динамічне додавання форми до набору форматів Джанго за допомогою 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.