Dynamiczne dodawanie formularza do zestawu form Django za pomocą Ajax

Chcę automatycznie dodawać nowe formularze do zestawu formularzy Django za pomocą Ajax, tak aby gdy użytkownik kliknie przycisk "Dodaj" uruchamiał JavaScript, który dodaje nowy formularz (który jest częścią zestawu formularzy) do strony.

 232
Author: Dmitriy, 2009-02-02

15 answers

Tak to robię, używając jQuery :

Mój szablon:

<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>

W pliku 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);
}

Co robi:

cloneMore przyjmuje selector jako pierwszy argument, a type formset jako drugi. {[3] } powinien przekazać to, co powinien powielać. W tym przypadku przekazuję div.table:last, aby jQuery szukał ostatniej tabeli z klasą table. Część :last jest ważna, ponieważ selector jest również używana do określenia, co nowy formularz zostanie wstawiony po. Bardziej niż prawdopodobne, że chcesz go na końcu reszty formularzy. Argument type jest tak, że możemy zaktualizować pole management_form, w szczególności TOTAL_FORMS, jak również rzeczywiste pola formularza. Jeśli masz zestaw formularzy pełen, powiedzmy, modeli Client, pola zarządzania będą miały identyfikatory id_clients-TOTAL_FORMS i id_clients-INITIAL_FORMS, podczas gdy pola formularza będą miały format id_clients-N-fieldname z N jako numerem formularza, zaczynającym się od 0. Więc z argumentem type Funkcja cloneMore patrzy na ile formularze są obecnie i przechodzą przez każde wejście i etykietę wewnątrz nowego formularza, zastępując wszystkie nazwy pól / identyfikatory z czegoś w rodzaju id_clients-(N)-name na id_clients-(N+1)-name i tak dalej. Po zakończeniu aktualizuje pole TOTAL_FORMS, aby odzwierciedlić nowy formularz i dodaje go na końcu zestawu.

Ta funkcja jest dla mnie szczególnie pomocna, ponieważ sposób jej konfiguracji pozwala mi używać jej w całej aplikacji, gdy chcę dostarczyć więcej formularzy w zestawie formularzy i nie sprawia, że muszę mieć ukryty formularz "szablon" do powielenia, o ile podam mu nazwę zestawu formularzy i format, w którym formularze są ułożone. Mam nadzieję, że to pomoże.

 203
Author: Paolo Bergantino,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-09-16 06:42:13

Uproszczona wersja odpowiedzi Paolo za pomocą empty_form jako szablon.

<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>
 87
Author: Dave,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2014-03-28 09:42:33

Wysłałem Fragment z aplikacji, nad którą pracowałem jakiś czas temu. Podobny do Paolo, ale pozwala również na kasowanie formularzy.

 24
Author: elo80ka,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-03-22 12:17:17

Sugestia Paolo działa pięknie z jednym zastrzeżeniem - przyciskami wstecz/do przodu przeglądarki.

Dynamiczne elementy utworzone za pomocą skryptu Paolo nie będą renderowane, jeśli użytkownik powróci do formsetu za pomocą przycisku back/forward. Problem, który może być łamanie umowy dla niektórych.

Przykład:

1) użytkownik dodaje dwa nowe formularze do zestawu formularzy za pomocą przycisku "Dodaj-więcej"

2) użytkownik wypełnia formularze i przesyła zestaw formularzy

3) Użytkownik kliknie przycisk Wstecz w przeglądarce

4) Formset jest teraz zredukowany do oryginalnego formularza, wszystkich dynamicznie dodawanych formularzy nie ma

To nie jest wada skryptu Paolo w ogóle; ale fakt życia z manipulacją dom i cache przeglądarki.

Przypuszczam, że można zapisać wartości formularza w sesji i mieć trochę magii ajax, gdy zestaw formularzy ładuje się ponownie, aby utworzyć elementy i przeładować wartości z sesji; ale w zależności od tego, jak chcesz być o tym samym użytkowniku i wiele wystąpień formularza może stać się bardzo skomplikowane.

Czy ktoś ma dobry pomysł na poradzenie sobie z tym?

Dzięki!

 18
Author: cethegeek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-04-21 12:52:17

Sprawdź następujące rozwiązania dynamicznych form django:

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

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

Oba używają jQuery i są specyficzne dla django. Pierwszy wydaje się nieco bardziej dopracowany i oferuje pobieranie, które pochodzi z/dema, które są doskonałe.

 13
Author: Kreychek,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-11-26 20:41:55

Symulować i naśladować:

  • Utwórz zestaw formularzy odpowiadający sytuacji przed kliknięciem przycisku "Dodaj".
  • załaduj stronę, zobacz źródło i zanotuj wszystkie pola <input>.
  • zmodyfikuj zestaw formularzy tak, aby odpowiadał sytuacji po kliknięciu przycisku "Dodaj" (Zmień liczbę dodatkowych pól).
  • załaduj stronę, zobacz źródło i zanotuj, jak zmieniły się pola <input>.
  • Tworzenie JavaScript, który modyfikuje DOM w odpowiedni sposób przenosi go ze stanu przed do stanu po .
  • Dołącz ten JavaScript do przycisku "Dodaj".

Chociaż wiem, że formularze używają specjalnych ukrytych pól <input> i wiem w przybliżeniu, co skrypt musi zrobić, nie przypominam sobie szczegółów z głowy. To, co opisałem powyżej, to to, co bym zrobił w twojej sytuacji.

 11
Author: akaihola,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-09 20:49:11

Istnieje jQuery plugin do tego , użyłem go z zestawem inline_form w Django 1.3 i działa idealnie, włączając w to prepopulację, dodawanie, usuwanie formularzy po stronie klienta i wiele zestawów inline_formsets.

 6
Author: e-satis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-01-04 19:57:20

Jedną z opcji byłoby utworzenie zestawu formularzy z każdym możliwym formularzem, ale początkowo Ustaw nieodwzajemnione formularze na hidden-ie, display: none;. Gdy jest to konieczne, aby wyświetlić formularz, Ustaw wyświetlacz css na block lub cokolwiek jest właściwe.

Bez wiedzy więcej szczegółów na temat tego, co robi twój "Ajax", trudno jest dać bardziej szczegółową odpowiedź.

 4
Author: Daniel Naab,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2009-02-02 00:49:35

Kolejna wersja cloneMore, która pozwala na selektywną sanityzację pól. Użyj go, gdy musisz zapobiec usunięciu kilku pól.

$('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
Author: xaralis,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-11-04 14:00:25

Jest mały problem z funkcją cloneMore. Ponieważ czyści również wartość automatycznie wygenerowanych ukrytych pól django, powoduje, że django będzie narzekać, jeśli spróbujesz zapisać zestaw formularzy z więcej niż jednym pustym formularzem.

Oto poprawka:

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);
}
 2
Author: Cesar Canassa,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2010-06-07 21:22:30

Myślę, że to o wiele lepsze rozwiązanie.

Jak zrobić dynamiczny formset w Django?

Robi rzeczy nie:

  • Dodaj formularz, gdy nie ma form początkowych
  • lepiej obsługuje javascript w formie, na przykład django-ckeditor
  • Zachowaj początkowe dane
 2
Author: Bufke,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 11:47:28

@ Paolo Bergantino

Aby sklonować wszystkie dołączone manipulatory wystarczy zmodyfikować linię

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

Dla

var newElement = $(selector).clone(true);
Aby zapobiec temu problemowi.
 1
Author: panchicore,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-05-23 12:10:44

Tak też polecam po prostu renderowanie ich w html, jeśli masz skończoną liczbę wpisów. (Jeśli nie będziesz musiał użyć innej metody).

Możesz je ukryć w ten sposób:

{% for form in spokenLanguageFormset %}
    <fieldset class="languages-{{forloop.counter0 }} {% if spokenLanguageFormset.initial_forms|length < forloop.counter and forloop.counter != 1 %}hidden-form{% endif %}">

Wtedy js jest naprawdę proste:

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
Author: Bob Spryn,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-08-09 01:05:17

Ponieważ wszystkie powyższe odpowiedzi używają jQuery i sprawiają, że niektóre rzeczy są nieco skomplikowane napisałem następujący skrypt:

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
    }
};

Najpierw należy ustawić auto_id na false i wyłączyć powielanie id i name. Ponieważ nazwy wejściowe muszą być unikalne w tej formie, Cała identyfikacja odbywa się za ich pomocą, a nie za pomocą identyfikatorów. Musisz również wymienić form, type oraz kontener zestawu formularzy. (W powyższym przykładzie choices)

 1
Author: R3turnz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-04-03 16:40:57

Dla programistów, którzy polują na zasoby, aby lepiej zrozumieć powyższe rozwiązania:

Django Dynamic Formsets

Po przeczytaniu powyższego linku dokumentacja Django i poprzednie rozwiązania powinny mieć o wiele więcej sensu.

Dokumentacja Django Formset

Jako krótkie podsumowanie tego, co byłem zdezorientowany: formularz zarządzania zawiera przegląd formularzy wewnątrz. Musisz zachować te informacje w celu aby Django był świadomy form, które dodajesz. (Społeczność, proszę dać mi sugestie, jeśli niektóre z moich sformułowań jest wyłączony tutaj. Jestem nowy w Django.)

 0
Author: Ryan Buchmeier,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2018-03-03 14:53:19