Saya ingin secara otomatis menambahkan formulir baru ke Django formset menggunakan Ajax, sehingga ketika pengguna mengklik tombol "add" itu menjalankan JavaScript yang menambahkan formulir baru (yang merupakan bagian dari formset) ke halaman.
Saya ingin secara otomatis menambahkan formulir baru ke Django formset menggunakan Ajax, sehingga ketika pengguna mengklik tombol "add" itu menjalankan JavaScript yang menambahkan formulir baru (yang merupakan bagian dari formset) ke halaman.
Jawaban:
Ini adalah bagaimana saya melakukannya, menggunakan jQuery :
Templat saya:
<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>
Dalam file 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);
}
Apa fungsinya:
cloneMore
menerima selector
sebagai argumen pertama, dan type
formset sebagai argumen ke-2. Apa yang selector
harus dilakukan adalah menyampaikannya apa yang harus digandakan. Dalam hal ini, saya meneruskannya div.table:last
sehingga jQuery mencari tabel terakhir dengan kelas table
. Bagian :last
itu penting karena selector
juga digunakan untuk menentukan apa bentuk baru yang akan dimasukkan setelah. Kemungkinan besar Anda menginginkannya di akhir formulir lainnya. The type
argumen adalah agar kita dapat memperbarui management_form
lapangan, terutama TOTAL_FORMS
, serta kolom formulir yang sebenarnya. Jika Anda memiliki formset penuh dengan, misalnya, Client
model, bidang manajemen akan memiliki ID id_clients-TOTAL_FORMS
dan id_clients-INITIAL_FORMS
, sedangkan bidang formulir akan dalam format id_clients-N-fieldname
denganN
menjadi nomor formulir, dimulai dengan 0
. Jadi dengan type
argumen cloneMore
fungsi melihat berapa banyak bentuk yang ada saat ini, dan melewati setiap input dan label di dalam formulir baru menggantikan semua nama bidang / id dari sesuatu seperti id_clients-(N)-name
ke id_clients-(N+1)-name
dan seterusnya. Setelah selesai, itu memperbarui TOTAL_FORMS
bidang untuk mencerminkan formulir baru dan menambahkannya ke akhir set.
Fungsi ini sangat membantu saya karena cara pengaturannya memungkinkan saya untuk menggunakannya di seluruh aplikasi ketika saya ingin memberikan lebih banyak formulir dalam sebuah formset, dan tidak membuat saya perlu memiliki formulir "templat" tersembunyi untuk menduplikasi selama saya berikan nama format dan format di mana formulir diletakkan. Semoga ini bisa membantu.
prefix
anggota Objek Formset. Nilai ini harus sama dengan type
argumen untuk cloneMore
fungsi.
Versi sederhana dari jawaban Paolo menggunakan empty_form
sebagai templat.
<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 Hanya mendapatkan satu formulir, di dalam metode bersih. dapatkah Anda menjelaskan bagaimana menangani ini dalam pandangan?
empty_form
), yang saya hargai.
Saya telah memposting cuplikan dari aplikasi yang saya kerjakan beberapa waktu lalu. Mirip dengan Paolo, tetapi juga memungkinkan Anda menghapus formulir.
Saran Paolo bekerja dengan indah dengan satu peringatan - tombol kembali / maju browser.
Elemen dinamis yang dibuat dengan skrip Paolo tidak akan diberikan jika pengguna kembali ke formset menggunakan tombol kembali / maju. Masalah yang mungkin menjadi pemecah kesepakatan bagi sebagian orang.
Contoh:
1) Pengguna menambahkan dua formulir baru ke formset menggunakan tombol "tambah lagi"
2) Pengguna mengisi formulir dan mengirimkan formset
3) Pengguna mengklik tombol kembali di browser
4) Formset sekarang dikurangi menjadi bentuk asli, semua bentuk yang ditambahkan secara dinamis tidak ada
Ini sama sekali bukan cacat pada naskah Paolo; tetapi fakta kehidupan dengan manipulasi dom dan cache browser.
Saya kira orang bisa menyimpan nilai-nilai formulir di sesi dan memiliki beberapa ajax magic ketika formset memuat untuk membuat elemen lagi dan memuat ulang nilai-nilai dari sesi; tetapi tergantung pada seberapa anal Anda ingin tentang pengguna yang sama dan beberapa contoh formulir ini mungkin menjadi sangat rumit.
Adakah yang punya saran bagus untuk menangani ini?
Terima kasih!
Lihat solusi berikut untuk formulir Django dinamis:
http://code.google.com/p/django-dynamic-formset/
https://github.com/javisantana/django-dinamyc-form/tree/master/frm
Keduanya menggunakan jQuery dan khusus Django. Yang pertama tampaknya sedikit lebih halus dan menawarkan unduhan yang disertai dengan demo yang luar biasa.
Simulasikan dan tiru:
<input>
bidang.<input>
ladang berubah.Meskipun saya tahu format menggunakan <input>
bidang tersembunyi khusus dan tahu kira-kira apa yang harus dilakukan skrip, saya tidak mengingat detail dari atas kepala saya. Apa yang saya jelaskan di atas adalah apa yang akan saya lakukan dalam situasi Anda.
Ada plugin jquery untuk ini , saya menggunakannya dengan set inline_form di Django 1.3, dan berfungsi dengan baik, termasuk prepopulasi, penambahan sisi klien, penghapusan, dan beberapa inline_formsets.
Salah satu opsi adalah membuat formset dengan setiap form yang mungkin, tetapi pada awalnya mengatur form yang tidak diminta menjadi tersembunyi - yaitu display: none;
,. Bila perlu untuk menampilkan formulir, atur tampilan css ke block
atau apa pun yang sesuai.
Tanpa tahu lebih detail tentang apa yang "Ajax" Anda lakukan, sulit untuk memberikan respons yang lebih detail.
Versi cloneMore lainnya, yang memungkinkan sanitasi bidang selektif. Gunakan saat Anda perlu mencegah beberapa bidang terhapus.
$('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);
}
Ada masalah kecil dengan fungsi cloneMore. Karena itu juga membersihkan nilai dari bidang tersembunyi yang dihasilkan secara otomatis Django, hal itu menyebabkan Django mengeluh jika Anda mencoba menyimpan suatu formset dengan lebih dari satu formulir kosong.
Ini adalah perbaikan:
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);
}
Saya pikir ini adalah solusi yang jauh lebih baik.
Bagaimana Anda membuat format dinamis di Django?
Apakah hal-hal yang dikloning tidak:
Untuk coders di luar sana yang mencari sumber daya untuk memahami solusi di atas sedikit lebih baik:
Setelah membaca tautan di atas, dokumentasi Django dan solusi sebelumnya seharusnya lebih masuk akal.
Sebagai ringkasan singkat tentang apa yang membuat saya bingung: Formulir Manajemen berisi tinjauan umum tentang formulir-formulir di dalamnya. Anda harus menjaga agar informasi itu akurat agar Django mengetahui formulir yang Anda tambahkan. (Komunitas, tolong beri saya saran jika beberapa kata-kata saya tidak ada di sini. Saya baru mengenal Django.)
@ Paolo Bergantino
untuk mengkloning semua handler yang terpasang, cukup modifikasi jalurnya
var newElement = $(selector).clone();
untuk
var newElement = $(selector).clone(true);
untuk mencegah masalah ini.
Ya, saya juga merekomendasikan untuk menyerahkannya di html jika Anda memiliki jumlah entri yang terbatas. (Jika tidak, Anda harus menggunakan metode lain).
Anda dapat menyembunyikannya seperti ini:
{% for form in spokenLanguageFormset %}
<fieldset class="languages-{{forloop.counter0 }} {% if spokenLanguageFormset.initial_forms|length < forloop.counter and forloop.counter != 1 %}hidden-form{% endif %}">
Maka js sangat sederhana:
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();
};
};
}
Karena semua jawaban di atas menggunakan jQuery dan membuat beberapa hal agak rumit saya menulis skrip berikut:
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
}
};
Pertama, Anda harus mengatur auto_id ke false sehingga menonaktifkan duplikasi id dan nama. Karena nama input harus unik dalam bentuk di sana, semua identifikasi dilakukan dengan mereka dan bukan dengan id. Anda juga harus mengganti form
, type
dan wadah formset. (Dalam contoh di atas choices
)