Ajout dynamique d'une forme à un formset Django avec Ajax

je veux ajouter automatiquement de nouveaux formulaires à un formset Django en utilisant Ajax, de sorte que lorsque l'utilisateur clique sur un bouton" Ajouter " il exécute JavaScript qui ajoute un nouveau formulaire (qui fait partie du formset) à la page.

232
demandé sur Dmitriy 2009-02-02 01:26:27

15 réponses

C'est comme ça que je le fais, en utilisant jQuery :

mon modèle:

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

dans un fichier 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);
}

Ce qu'il fait:

cloneMore accepte selector comme premier argument, et le type de formset comme deuxième. Ce que le selector devrait faire, c'est lui passer ce qu'il devrait dupliquer. Dans ce cas, je passe div.table:last pour que jQuery cherche la dernière table avec une classe de table . La partie :last est importante parce que le selector est également utilisé pour déterminer ce que la nouvelle forme sera insérée après. Il est plus que probable que vous le vouliez à la fin du reste des formulaires. L'argument type permet de mettre à jour le champ management_form , notamment TOTAL_FORMS , ainsi que les champs de formulaires actuels. Si vous avez un formset rempli de, disons, Client modèles, les champs de gestion auront ID de id_clients-TOTAL_FORMS et id_clients-INITIAL_FORMS , tandis que les champs de formulaire seront dans un format de id_clients-N-fieldname avec N étant le numéro de formulaire, en commençant par 0 . Ainsi avec l'argument type la fonction cloneMore regarde combien de formes il y a actuellement, et passe en revue chaque entrée et étiquette à l'intérieur de la nouvelle forme remplaçant tous les noms de champ/ids de quelque chose comme id_clients-(N)-name à id_clients-(N+1)-name et ainsi de suite. Une fois terminé, il met à jour le champ TOTAL_FORMS pour tenir compte de la nouvelle forme et l'ajoute à la fin de la série.

cette fonction est particulièrement utile pour moi parce que la façon dont il est configuré, il me permet de l'utiliser tout au long de l'application quand je veux fournir plus de formulaires dans un formset, et ne me fait pas besoin d'avoir un formulaire caché" modèle " pour dupliquer aussi longtemps que je lui passe le nom de formset et le format dans lequel les formulaires sont disposés. Espérons que cela aide.

203
répondu Paolo Bergantino 2009-09-16 06:42:13

Version Simplifiée de la réponse de Paolo en utilisant empty_form comme modèle.

<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
répondu Dave 2014-03-28 09:42:33

j'ai posté un extrait d'une application sur laquelle j'ai travaillé il y a quelques temps. Similaire à Paolo, mais permet également de supprimer des formulaires.

24
répondu elo80ka 2009-03-22 12:17:17

la suggestion de Paolo fonctionne à merveille avec une mise en garde - les boutons de retour/avant du navigateur.

les éléments dynamiques créés avec le script de Paolo ne seront pas rendus si l'utilisateur retourne au formset en utilisant le bouton back/forward. Une question qui peut être une rupture pour certains.

exemple:

1) l'utilisateur ajoute deux nouveaux formulaires au jeu de formulaires en utilisant le bouton" Ajouter-plus

2) l'utilisateur forme et soumet le jeu de formulaires

3) l'utilisateur clique sur le bouton de retour du navigateur

4) Formset est maintenant réduit à la forme originale, tous les formulaires dynamiquement ajoutés ne sont pas là

ce n'est pas un défaut du tout avec le script de Paolo, mais un fait de la vie avec la manipulation de dom et la cache du navigateur.

je suppose que l'on pourrait stocker les valeurs de la forme dans la session et avoir un peu de magie ajax lorsque le formset charge pour créer les éléments à nouveau et recharger les valeurs de la session; mais en fonction de la façon dont anal vous voulez être sur le même utilisateur et les instances multiples du formulaire, cela peut devenir très compliqué.

Quelqu'un a-t-il une bonne suggestion à faire?

Merci!

18
répondu cethegeek 2009-04-21 12:52:17

découvrez les solutions suivantes aux formes dynamiques de django:

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

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

ils utilisent tous les deux jQuery et sont spécifiques à django. Le premier semble un peu plus poli et offre un téléchargement qui vient w/démos qui sont excellents.

13
répondu Kreychek 2011-11-26 20:41:55

simuler et imiter:

  • créer un jeu de formulaires correspondant à la situation avant en cliquant sur le bouton" Ajouter".
  • charger la page, voir la source et prendre note de tous les champs <input> .
  • modifier le jeu de formulaires pour correspondre à la situation après en cliquant sur le bouton "Ajouter" (changer le nombre de champs supplémentaires).
  • chargez le page, voir la source et noter comment les champs <input> ont changé.
  • crée du JavaScript qui modifie le DOM d'une manière appropriée pour le déplacer de l'état avant à l'état après .
  • joindre ce JavaScript au bouton" Ajouter".

bien que je sache que les forms utilisent des champs spéciaux cachés <input> et savent approximativement ce que le script doit faire, je ne me souviens pas des détails sur le dessus de ma tête. Ce que j'ai décrit ci-dessus est ce que je ferais dans votre situation.

11
répondu akaihola 2009-02-09 20:49:11

il y a un plugin jquery pour ce , Je l'ai utilisé avec inline_form set dans Django 1.3, et il fonctionne parfaitement, y compris la pré-population, le côté client Forme ajoutant, supprimer, et plusieurs inline_formsets.

6
répondu e-satis 2012-01-04 19:57:20

une option serait de créer un formset avec toutes les formes possibles, mais dans un premier temps, définissez les formes non requises à hidden - ie, display: none; . Quand il est nécessaire d'afficher un formulaire, définissez son affichage css à block ou ce qui est approprié.

sans plus de détails sur ce que fait votre" Ajax", il est difficile de donner une réponse plus détaillée.

4
répondu Daniel Naab 2009-02-02 00:49:35

une autre version de cloneMore, qui permet une désinfection sélective des champs. Utilisez-le lorsque vous avez besoin d'empêcher plusieurs champs d'être effacés.

$('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
répondu xaralis 2010-11-04 14:00:25

il y a un petit problème avec la fonction cloneMore. Comme il nettoie également la valeur des champs cachés générés automatiquement par django, il provoque django à se plaindre si vous essayez de sauvegarder un formset avec plus d'un formulaire vide.

voici un fix:

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
répondu Cesar Canassa 2010-06-07 21:22:30

je pense que c'est une bien meilleure solution.

comment feriez-vous un formset dynamique à Django?

"151900920 Fait les choses clone n'est pas:

  • Ajouter un formulaire lorsque aucune forme initiale existe
  • Poignées de javascript dans le formulaire de mieux, par exemple django-ckeditor
  • Conserver les données initiales
2
répondu Bufke 2017-05-23 11:47:28

@Paolo Bergantino

pour cloner tous les maîtres-chiens attachés juste modifier la ligne

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

pour

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

pour prévenir ce problème.

1
répondu panchicore 2017-05-23 12:10:44

Yea je recommande aussi de les rendre simplement dans le html si vous avez un nombre limité d'entrées. (Si vous ne le faites pas, vous devrez utiliser une autre méthode).

vous pouvez les cacher comme ceci:

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

alors le js est vraiment simple:

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
répondu Bob Spryn 2012-08-09 01:05:17

parce que toutes les réponses ci-dessus utilisent jQuery et rendent certaines choses un peu complexes, j'ai écrit le script suivant:

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

vous devez d'abord définir auto_id à false et donc désactiver la duplication d'id et de nom. Comme les noms d'entrée doivent être uniques dans leur forme, toute identification se fait avec eux et non avec des id. Vous devez également remplacer le form , type et le contenant du formset. (Dans l'exemple ci-dessus choices )

1
répondu R3turnz 2017-04-03 16:40:57

pour les codeurs là-bas qui sont des ressources de chasse pour comprendre les solutions ci-dessus un peu mieux:

Django Dynamique Formsets

après avoir lu le lien ci-dessus, la documentation de Django et les solutions précédentes devraient avoir beaucoup plus de sens.

Django Formset Documentation

comme un résumé rapide de ce que je devenais confus par: le Le formulaire de gestion donne un aperçu des formulaires utilisés. Vous devez garder ces informations exactes pour que Django soit au courant des formulaires que vous ajoutez. (Communauté, merci de me donner des idées si certains de mes libellé est ici. Im nouveau à Django.)

0
répondu Ryan Buchmeier 2018-03-03 14:53:19