Ajax ile Django form kümesine dinamik olarak form ekleme


260

Kullanıcı bir "Ekle" düğmesini tıklattığında sayfaya yeni bir form (form kümesinin bir parçası olan) ekleyen JavaScript çalıştırır böylece otomatik olarak Ajax kullanarak bir Django form kümesine yeni formlar eklemek istiyorum.


Ben sadece burada kullanım durumunuzu tahmin ediyorum, kullanıcı bir dosya yükleme alanı ile sunulan ve kullanıcı tıkladığında anında DOM yeni alanlar eklenir gmail "Başka Dosya Ekle" özelliği gibi bir şey var "Başka Dosya Ekle" artı düğmesine nasıl?
prairiedogg

Bu yakında üzerinde çalışacağım bir şey, bu yüzden herhangi bir cevapla da ilgileneceğim.
Van Gale

2
Bu soru biraz bulanık, başlık, açıklama ve etiketler "Ajax" söz. Ancak, yanıtların hiçbiri Ajax'tan yararlanmıyor, yine de formun gönderilmesini gerektiriyor.
Antoine Pinsard

Yanıtlar:


219

Bu nasıl jQuery kullanarak yapmak :

Şablonum:

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

Bir javascript dosyasında:

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

Bu ne yapar:

cloneMoreselectorilk argüman, typeformset ise 2. argüman olarak kabul edilir . Ne selectoryapmalıyım onu çoğaltmak gereken geçmek olduğunu. Bu durumda, ben div.table:lastjQuery bir sınıf ile son tablo arar böylece onu geçmek table. Bunun bir :lastkısmı önemlidir, çünkü selectoryeni formun sonradan ne ekleneceğini belirlemek için de kullanılır. Muhtemelen formların geri kalanının sonunda olmasını istersiniz. typeBiz güncelleme böylece argümanı management_formözellikle alanını TOTAL_FORMS, hem de fiili form alanları. Eğer, diyelim ki, tam bir formset varsa Clientmodellerinde, yönetim alanları arasında kimliğe sahip olur id_clients-TOTAL_FORMSve id_clients-INITIAL_FORMSform alanları bir biçimde olacaktır ederken, id_clients-N-fieldnameileNile başlayan form numarasıdır 0. Yani birlikte typeargüman cloneMorekaç formları de fonksiyon görünüyor orada şu anda ve böyle bir şey gelen tüm alan adları / kimlikleri yerine yeni bir form içindeki her giriş ve etiket geçer id_clients-(N)-nameetmek id_clients-(N+1)-namevb. Tamamlandıktan sonra, TOTAL_FORMSalanı yeni formu yansıtacak şekilde günceller ve kümenin sonuna ekler.

Bu işlev benim için özellikle yararlıdır çünkü kurulum şekli, bir form kümesinde daha fazla form sağlamak istediğimde uygulama boyunca kullanmamı sağlar ve çoğaltmak için gizli bir "şablon" formuna sahip olmamı gerektirmez form seti adını ve formların düzenlendiği biçimi geçirdiğim sürece. Umarım yardımcı olur.


IE'de, JS'de seçildiğinde klonlanmış bir öğeden gelen bir klon <defined> olarak temsil edilir, neden?
panchicore

Django 1.1'de prefixFormset Nesnesi üyesine bir değer atamanız gerektiğini buldum . Bu type, cloneMoreişlevin bağımsız değişkeniyle aynı değerde olmalıdır .
Derek Reynolds

3
Seçiciyi olmadan almak için bunu değiştirdim: son ve kullanılan var total = $ (selector) .length; sayfamın yenilenmesi form setlerimi kaldıracak ancak TOPLAM artışı yanlış numaraya kaydederek bırakarak toplamımı elde etmek için. Sonra ekledim: gerektiği gibi son seçiciye. Bunun için teşekkürler.
Greg

2
Bunu $ (this) .attr ({'name': name, 'id': id}). Val (''). RemoveAttr ('işaretli') kullanarak buldum; Girişi temizlemek için onay kutularını karıştırır. Val ('') ayarı onay kutularına boş bir değer özelliği verir. Onay kutuları değer niteliğini kullanmadığından, kaç kez tıklasanız da bu hiçbir zaman güncellenmez. Ancak, değerin, onay kutularının "işaretli" özelliğinden daha yüksek önceliğe sahip olduğu görülmektedir. Bu, her zaman kontrol edilmeyen onay kutularını yayınlayacağınız anlamına gelir.
niklasdstrom

lütfen paolo sorunumu kontrol edebilir misin stackoverflow.com/questions/62252867/…
art_cs

109

Paolo'nun cevabını empty_formşablon olarak kullanarak basitleştirilmiş versiyonu .

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

görünüşte bunu nasıl başarabilirim? i kullandığım zaman CompetitorFormSet = modelformset_factory(ProjectCompetitor, formset=CompetitorFormSets) ctx['competitor_form_set'] = CompetitorFormSet(request.POST)temiz bir yöntemle, sadece bir form olsun. Bunu görüşlerle nasıl başa çıkacağınızı açıklar mısınız?
AJ

Harika - teşekkürler. empty_formBen takdir mevcut Django yardımcıları (gibi ), mükemmel kullanır .
BigglesZX

@BigglesZX - Çözümü uyarladım ve yeni boş form satırları oluşturuluyor. Bununla birlikte, seçim kutuları, orijinal form kümesi için başka şekilde oluşturulan açılır listeler yerine FK (kullanılabilir) seçeneklerinin bir listesini oluşturur. Bu nitelikte herhangi bir sorun bildirildi mi?
user12379095

@Daha sonraki sürümlerin cevabını güncelleyebiliyor musunuz, örneğin 3.x? basit ve net ama benim için çalışmıyor
Poula Adel

1
@PoulaAdel Neler çalışmıyor? Ben sadece Django 3.0.5 üzerinde denedim ve hala benim için çalışıyor. 8 yıl sonra şaşırtıcı, ama sanırım Django ve jQuery eski kod ile iyi geriye dönük uyumluluk var.
Dave


18

Paolo'nun önerisi, tarayıcının geri / ileri düğmeleri olan bir uyarı ile güzel çalışıyor.

Kullanıcı geri / ileri düğmesini kullanarak form kümesine dönerse, Paolo'nun komut dosyasıyla oluşturulan dinamik öğeler oluşturulmaz. Bazıları için bir anlaşma kırıcı olabilecek bir konu.

Misal:

1) Kullanıcı "eklenti" düğmesini kullanarak form kümesine iki yeni form ekler

2) Kullanıcı formları doldurur ve form setini gönderir

3) Kullanıcı tarayıcıdaki geri düğmesini tıklar

4) Formset artık orijinal forma indirgenmiştir, dinamik olarak eklenen tüm formlar orada değildir

Bu Paolo'nun senaryosunda bir kusur değil; ancak dom manipülasyonu ve tarayıcının önbelleği ile hayatın bir gerçeği.

Ben bir oturumda formun değerlerini saklamak ve yeniden öğeleri oluşturmak ve oturum değerleri yeniden yüklemek için form kümesi yüklendiğinde bazı ajax büyü var varsayalım; ancak aynı kullanıcı ve formun birden çok örneği hakkında ne kadar anal olmak istediğinize bağlı olarak bu çok karmaşık hale gelebilir.

Herkes bununla başa çıkmak için iyi bir öneri var mı?

Teşekkürler!


2
Başarılı bir şekilde gönderdikten sonra yönlendirme yaparsanız geri düğmesi sorun olmaz. Bir sonraki ziyaretinizde DB'deki formları doldurursanız, tüm formlar başlangıçta görünür. Geçersiz giriş nedeniyle formlarda başarısız olursanız, hepsinin hatalı yeniden gösterimde olması gerekir. İfadelerinizi anlamadığım sürece .... Bu gönderi yönlendirmesi iyi çalışan bir uygulamada gerçekten önemlidir, kodlayıcıların birçoğu sadece web'de çalıştırdığım kötü davranan uygulamaların sayısına dayanmaz.
boatcoder

bana yardımcı olabilir stackoverflow.com/questions/62285767/… , ben çok denedim ama bir cevap alamadım! seni çok takdir ediyorum
art_cs


11

Simüle edin ve taklit edin:

  • Duruma karşılık gelen bir formset oluşturun önce"Ekle" düğmesini tıklamadan .
  • Sayfayı yükleyin, kaynağı görüntüleyin ve tümünü not edin <input> alanları .
  • Form kümesini, sonraki duruma karşılık gelecek şekilde değiştirin"Ekle" düğmesini tıklattıktan değiştirin (fazladan alan sayısını değiştirin).
  • Sayfayı yükleyin, kaynağı görüntüleyin ve <input> alanların değiştiğini .
  • DOM'u önceki durumdan sonraki duruma taşımak için uygun bir şekilde değiştiren bir JavaScript oluşturun .
  • Bu JavaScript'i "ekle" düğmesine ekleyin.

Formetlerin özel gizli <input>alanları kullandığını ve betiğin ne yapması gerektiğini bildiğim halde, başımın üstündeki ayrıntıları hatırlamıyorum. Yukarıda tarif ettiğim, sizin durumunuzda ne yapacağım.


Bana stackoverflow.com/questions/62285767/… yardımcı olabilir , ben çok denedim stackoverflow.com/questions/62285767/… ama cevap alamadım! seni çok takdir ediyorum
art_cs

6

Bunun için bir jquery eklentisi var, Django 1.3'te ayarlanmış inline_form ile kullandım ve ön popülasyon, istemci tarafı form ekleme, kaldırma ve çoklu inline_formsets dahil olmak üzere mükemmel çalışıyor.


Bağlantılı blog gönderisi hala mevcut olsa da, oradaki indirme bağlantıları kopmuştur. Görünüşe göre, eklenti, cevabı komut dosyasının bir (ön?) Sürümüne işaret eden @ elo80ka tarafından oluşturuldu .
lfurini

bana yardımcı olabilir stackoverflow.com/questions/62285767/… , ben çok denedim ama bir cevap alamadım! seni çok takdir ediyorum
art_cs

4

Bir seçenek, olası her formla bir form kümesi oluşturmak, ancak başlangıçta gerekli olmayan formları gizli olarak ayarlamaktır display: none;. Bir form görüntülemek gerektiğinde, css ekranınıblock veya uygun olanı seçin.

"Ajax" cihazınızın ne yaptığına dair daha fazla ayrıntı bilmeden, daha ayrıntılı bir yanıt vermek zor.


4

Alanların seçici olarak dezenfekte edilmesine izin veren daha fazla versiyon. Birkaç alanın silinmesini önlemek istediğinizde kullanın.

$('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);
}

bana yardımcı olabilir stackoverflow.com/questions/62285767/… , ben çok denedim ama bir cevap alamadım! seni çok takdir ediyorum
art_cs

2

CloneMore işleviyle ilgili küçük bir sorun var. Ayrıca, otomatik olarak oluşturulan django gizli alanlarının değerini de temizlediğinden, bir form kümesini birden fazla boş formla kaydetmeye çalıştığınızda django'nun şikayet etmesine neden olur.

İşte bir düzeltme:

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

bana yardımcı olabilir stackoverflow.com/questions/62285767/… , ben çok denedim ama bir cevap alamadım! seni çok takdir ediyorum
art_cs


2

Yukarıdaki çözümleri biraz daha iyi anlamak için kaynakları avlayan kodlayıcılar için:

Django Dinamik Form Setleri

Yukarıdaki bağlantıyı okuduktan sonra, Django belgeleri ve önceki çözümler çok daha mantıklı olmalıdır.

Django Formset Belgeleri

Ne tarafından karıştırıldığını hızlı bir özet olarak: Yönetim Formu içinde formların bir genel bakış içerir. Django'nun eklediğiniz formlardan haberdar olması için bu bilgileri doğru tutmalısınız. (Topluluk, bazı ifadelerim burada değilse lütfen bana önerilerde bulunun. Django'da yeniyim.)



1

Ayrıca, sınırlı sayıda girişiniz varsa, bunları html'de oluşturmanızı da öneririm. (Yapmazsanız başka bir yöntem kullanmanız gerekir).

Bunları şu şekilde saklayabilirsiniz:

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

O zaman js gerçekten basit:

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

bana yardımcı olabilir stackoverflow.com/questions/62285767/… , ben çok denedim ama bir cevap alamadım! seni çok takdir ediyorum
art_cs

1

Yukarıdaki tüm cevaplar jQuery kullanmak ve bazı şeyler biraz karmaşık yapmak için aşağıdaki komut yazdı:

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

Öncelikle auto_id öğesini false olarak ayarlamalı ve böylece kimlik ve adın çoğaltılmasını devre dışı bırakmalısınız. Girdi adları orada formda benzersiz olması gerektiğinden, tüm kimlikler id'lerle değil onlarla yapılır. Ayrıca değiştirmek zorunda form, typeve formset kabı. (Yukarıdaki örnekte choices)


bana yardımcı olabilir stackoverflow.com/questions/62285767/… , ben çok denedim ama bir cevap alamadım! seni çok takdir ediyorum
art_cs
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.