Chcę automatycznie dodawać nowe formularze do zestawu formularzy Django za pomocą Ajax, aby gdy użytkownik kliknie przycisk „dodaj”, uruchomi JavaScript, który dodaje nowy formularz (który jest częścią zestawu formularzy) do strony.
Chcę automatycznie dodawać nowe formularze do zestawu formularzy Django za pomocą Ajax, aby gdy użytkownik kliknie przycisk „dodaj”, uruchomi JavaScript, który dodaje nowy formularz (który jest częścią zestawu formularzy) do strony.
Odpowiedzi:
Tak to robię za pomocą 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 to robi:
cloneMore
przyjmuje selector
jako pierwszy argument, a type
formset jako drugi. To, co selector
należy zrobić, to przekazać to, co powinno się powielić. W tym przypadku przekazuję go div.table:last
, aby jQuery szukał ostatniej tabeli z klasą table
. Ta :last
część jest ważna, ponieważ selector
służy również do określenia, po czym zostanie wstawiony nowy formularz. Bardziej niż prawdopodobne, że będziesz tego chciał na końcu pozostałych formularzy. type
Argument jest tak, że możemy zaktualizować management_form
pole, zwłaszcza TOTAL_FORMS
, jak również rzeczywistych pól formularza. Jeśli masz zestaw formularzy pełen, powiedzmy, Client
modeli, 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
zN
będący numerem formularza, zaczynając od 0
. Więc z type
argumentu cloneMore
wygląd funkcyjne, jak wiele form Obecnie są i przechodzi przez każdego wejścia i etykiety wewnątrz nowej formie zastępujący wszystkie nazwy pól / numery z czegoś podobnego id_clients-(N)-name
do id_clients-(N+1)-name
i tak dalej. Po zakończeniu aktualizuje TOTAL_FORMS
pole, 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 korzystać z niej w całej aplikacji, gdy chcę udostępnić więcej formularzy w zestawie formularzy, i nie wymaga posiadania ukrytego formularza „szablonu” do kopiowania tak długo, jak go przekażę, nazwa zestawu formularzy i format, w jakim formularze są ułożone. Mam nadzieję, że to pomoże.
prefix
elementu obiektu Formset. Powinna mieć tę samą wartość co type
argument cloneMore
funkcji.
Uproszczona wersja odpowiedzi Paolo wykorzystująca 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>
CompetitorFormSet = modelformset_factory(ProjectCompetitor, formset=CompetitorFormSets)
ctx['competitor_form_set'] = CompetitorFormSet(request.POST)
i otrzymuję tylko jeden formularz, w czystej metodzie. czy możesz wyjaśnić, jak sobie z tym poradzić w widokach?
empty_form
), co doceniam.
Mam napisali fragment z aplikacji pracowałem na jakiś czas temu. Podobne do Paolo, ale umożliwia również usuwanie formularzy.
Sugestia Paolo działa pięknie z jednym zastrzeżeniem - przyciskami Wstecz / Dalej przeglądarki.
Elementy dynamiczne utworzone za pomocą skryptu Paolo nie będą renderowane, jeśli użytkownik powróci do zestawu formularzy za pomocą przycisku Wstecz / Dalej. Problem, który może w niektórych przypadkach stanowić przeszkodę.
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 klika przycisk Wstecz w przeglądarce
4) Zestaw formularzy został teraz zredukowany do pierwotnego formularza, nie ma tam wszystkich dynamicznie dodanych formularzy
Nie jest to wcale wada skryptu Paolo; ale faktem jest manipulacja domem i pamięć podręczna przeglądarki.
Przypuszczam, że można zapisać wartości formularza w sesji i mieć trochę magii ajax, gdy zestaw formularzy ładuje się, aby ponownie utworzyć elementy i ponownie załadować wartości z sesji; ale w zależności od tego, jak analny chcesz być na temat tego samego użytkownika i wielu wystąpień formularza, może to być bardzo skomplikowane.
Czy ktoś ma dobrą sugestię, jak sobie z tym poradzić?
Dzięki!
Sprawdź następujące rozwiązania dla dynamicznych formularzy django:
http://code.google.com/p/django-dynamic-formset/
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
Oba korzystają z jQuery i są specyficzne dla django. Pierwszy wydaje się nieco bardziej dopracowany i oferuje pobieranie, które zawiera w / dema, które są doskonałe.
Symuluj i naśladuj:
<input>
pola.<input>
zmiany w polach.Chociaż wiem, że zestawy formularzy używają specjalnych ukrytych <input>
pól i wiem mniej więcej, co skrypt musi zrobić, nie pamiętam szczegółów z góry głowy. Powyżej opisałem, co zrobiłbym w twojej sytuacji.
Jest do tego wtyczka jquery , użyłem jej z zestawem inline_form w Django 1.3 i działa idealnie, w tym prepopulation, dodawanie, usuwanie i wiele formularzy po stronie klienta.
Jedną z opcji byłoby utworzenie zestawu formularzy z każdą możliwą formą, ale początkowo ustaw niepotrzebne formularze na ukryte - tj display: none;
. Gdy konieczne jest wyświetlenie formularza, ustaw jego wyświetlanie css na block
lub cokolwiek jest odpowiednie.
Nie znając więcej szczegółów na temat tego, co robi Twój „Ajax”, trudno jest podać bardziej szczegółową odpowiedź.
Kolejna wersja cloneMore, która pozwala na selektywną dezynfekcję pól. Użyj go, gdy chcesz zapobiec skasowaniu 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);
}
Istnieje niewielki problem z funkcją cloneMore. Ponieważ usuwa również wartość ukrytych pól automatycznie generowanych przez django, powoduje, że django 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);
}
Myślę, że to znacznie lepsze rozwiązanie.
Jak stworzyłbyś dynamiczny zestaw formularzy w Django?
Czy klonowanie rzeczy nie:
Aby koderzy szukający zasobów mogli lepiej zrozumieć powyższe rozwiązania:
Dynamiczne zestawy formularzy Django
Po przeczytaniu powyższego linku dokumentacja Django i poprzednie rozwiązania powinny mieć o wiele większy sens.
Jako krótkie podsumowanie tego, co mnie pomyliło: Formularz zarządzania zawiera przegląd formularzy w nim zawartych. Musisz zachować dokładność tych informacji, aby Django wiedział o dodawanych formularzach. (Społeczność, proszę o sugestie, jeśli niektóre z moich sformułowań są tutaj. Jestem nowy w Django.)
@Paolo Bergantino
aby sklonować wszystkie dołączone programy obsługi, wystarczy zmodyfikować linię
var newElement = $(selector).clone();
dla
var newElement = $(selector).clone(true);
aby zapobiec temu problemowi.
Tak, polecam również renderowanie ich w pliku HTML, jeśli masz skończoną liczbę wpisów. (Jeśli nie, będziesz musiał użyć innej metody).
Możesz je ukryć w następujący 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 %}">
Zatem js jest naprawdę prosty:
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();
};
};
}
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 powinieneś ustawić auto_id na false, a zatem wyłączyć powielanie identyfikatora i nazwy. Ponieważ nazwy wejściowe muszą być tam unikalne, cała identyfikacja odbywa się za ich pomocą, a nie za pomocą identyfikatorów. Trzeba też wymienić form
, type
a pojemnik z formset. (W powyższym przykładzie choices
)