Moyen approprié de gérer plusieurs formulaires sur une seule page dans Django

204

J'ai une page de modèle qui attend deux formulaires. Si je n'utilise qu'un seul formulaire, les choses vont bien comme dans cet exemple typique:

if request.method == 'POST':
    form = AuthorForm(request.POST,)
    if form.is_valid():
        form.save()
        # do something.
else:
    form = AuthorForm()

Si je veux travailler avec plusieurs formulaires, comment puis-je faire savoir à la vue que je ne soumets qu'un seul des formulaires et pas l'autre (c'est-à-dire qu'il s'agit toujours d'une demande.POST mais je veux uniquement traiter le formulaire pour lequel la soumission arrivé)?


Il s'agit de la solution basée sur la réponse où la phrase attendue et la phrase interdite sont les noms des boutons d' envoi pour les différents formulaires et la forme attendue et la phrase interdite sont les formulaires.

if request.method == 'POST':
    if 'bannedphrase' in request.POST:
        bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
        if bannedphraseform.is_valid():
            bannedphraseform.save()
        expectedphraseform = ExpectedPhraseForm(prefix='expected')
    elif 'expectedphrase' in request.POST:
        expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
        if expectedphraseform.is_valid():
            expectedphraseform.save() 
        bannedphraseform = BannedPhraseForm(prefix='banned')
else:
    bannedphraseform = BannedPhraseForm(prefix='banned')
    expectedphraseform = ExpectedPhraseForm(prefix='expected')
Adam Nelson
la source
2
N'y a-t-il pas une erreur logique avec votre solution? Si vous publiez une «phrase interdite», la forme attendue ne sera pas remplie.
Ztyx
2
Cela ne traitera qu'un seul formulaire à la fois, la question est de gérer les multiples formulaires en même temps
brillant du

Réponses:

142

Vous avez quelques options:

  1. Mettez différentes URL dans l'action pour les deux formulaires. Ensuite, vous aurez deux fonctions d'affichage différentes pour gérer les deux formulaires différents.

  2. Lisez les valeurs du bouton d'envoi à partir des données POST. Vous pouvez dire sur quel bouton de soumission a été cliqué: Comment puis-je créer plusieurs boutons de soumission sous forme django?

Ned Batchelder
la source
5
3) Déterminez quel formulaire est soumis à partir des noms de champ dans les données POST. Incluez des entrées cachées si vos froms n'ont pas de champs uniques avec toutes les valeurs possibles n'étant pas vides.
Denis Otkidach
13
4) Ajoutez un champ caché identifiant le formulaire et vérifiez la valeur de ce champ dans votre vue.
Soviut
Je resterais loin de polluer les données POST si possible. Je recommande plutôt d'ajouter un paramètre GET à l'url d'action du formulaire.
pygeek
6
Le n ° 1 est vraiment votre meilleur pari ici. Vous ne voulez pas polluer votre POST avec des champs cachés et vous ne voulez pas non plus attacher votre vue à votre modèle et / ou formulaire.
météore
5
@meteorainer si vous utilisez le numéro un, existe-t-il un moyen de renvoyer les erreurs aux formulaires dans la vue parent qui les instancie, sans utiliser le cadre de messages ou les chaînes de requête? Cette réponse semble la plus proche, mais ici, il ne s'agit que d'une seule vue gérant les deux formes: stackoverflow.com/a/21271659/2532070
YPCrumble
45

Une méthode de référence future est quelque chose comme ça. bannedphraseform est le premier formulaire et attenduphraseform est le second. Si le premier est touché, le second est ignoré (ce qui est une hypothèse raisonnable dans ce cas):

if request.method == 'POST':
    bannedphraseform = BannedPhraseForm(request.POST, prefix='banned')
    if bannedphraseform.is_valid():
        bannedphraseform.save()
else:
    bannedphraseform = BannedPhraseForm(prefix='banned')

if request.method == 'POST' and not bannedphraseform.is_valid():
    expectedphraseform = ExpectedPhraseForm(request.POST, prefix='expected')
    bannedphraseform = BannedPhraseForm(prefix='banned')
    if expectedphraseform.is_valid():
        expectedphraseform.save()

else:
    expectedphraseform = ExpectedPhraseForm(prefix='expected')
Adam Nelson
la source
7
utiliser le préfixe = est en effet la «bonne façon»
Rich
prefix-kwarg a fait le travail, c'est bien!
Stephan Hoyer
1
Excellente idée avec ces préfixes, nous les avons utilisés maintenant et ils fonctionnent comme un charme. Mais nous avons quand même dû insérer un champ caché pour détecter le formulaire soumis, car les deux formulaires sont dans une lightbox (chacun dans un formulaire séparé). Parce que nous devons rouvrir la lightbox correcte, nous devons savoir exactement quel formulaire a été soumis, puis si le premier formulaire contient des erreurs de validation, le second gagne automatiquement et le premier formulaire est réinitialisé, bien que nous devions toujours afficher les erreurs de la première forme. Je pensais juste que tu devrais savoir
Enduriel
Ne serait-il pas maladroit d'étendre ce schéma au cas de trois formes? Comme, avec le contrôle is_valid () de la première forme, puis les deux premiers, etc ... Peut - être avez juste un handled = Falsequi est mise à jour à Truequand se trouve une forme compatible?
binki
14

Les vues basées sur les classes de Django fournissent un FormView générique mais à toutes fins utiles, il est conçu pour ne gérer qu'un seul formulaire.

Une façon de gérer plusieurs formulaires avec la même URL d'action cible en utilisant les vues génériques de Django consiste à étendre le «TemplateView» comme indiqué ci-dessous; J'utilise cette approche assez souvent pour en faire un modèle IDE Eclipse.

class NegotiationGroupMultifacetedView(TemplateView):
    ### TemplateResponseMixin
    template_name = 'offers/offer_detail.html'

    ### ContextMixin 
    def get_context_data(self, **kwargs):
        """ Adds extra content to our template """
        context = super(NegotiationGroupDetailView, self).get_context_data(**kwargs)

        ...

        context['negotiation_bid_form'] = NegotiationBidForm(
            prefix='NegotiationBidForm', 
            ...
            # Multiple 'submit' button paths should be handled in form's .save()/clean()
            data = self.request.POST if bool(set(['NegotiationBidForm-submit-counter-bid',
                                              'NegotiationBidForm-submit-approve-bid',
                                              'NegotiationBidForm-submit-decline-further-bids']).intersection(
                                                    self.request.POST)) else None,
            )
        context['offer_attachment_form'] = NegotiationAttachmentForm(
            prefix='NegotiationAttachment', 
            ...
            data = self.request.POST if 'NegotiationAttachment-submit' in self.request.POST else None,
            files = self.request.FILES if 'NegotiationAttachment-submit' in self.request.POST else None
            )
        context['offer_contact_form'] = NegotiationContactForm()
        return context

    ### NegotiationGroupDetailView 
    def post(self, request, *args, **kwargs):
        context = self.get_context_data(**kwargs)

        if context['negotiation_bid_form'].is_valid():
            instance = context['negotiation_bid_form'].save()
            messages.success(request, 'Your offer bid #{0} has been submitted.'.format(instance.pk))
        elif context['offer_attachment_form'].is_valid():
            instance = context['offer_attachment_form'].save()
            messages.success(request, 'Your offer attachment #{0} has been submitted.'.format(instance.pk))
                # advise of any errors

        else 
            messages.error('Error(s) encountered during form processing, please review below and re-submit')

        return self.render_to_response(context)

Le modèle html a l'effet suivant:

...

<form id='offer_negotiation_form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
    {% csrf_token %}
    {{ negotiation_bid_form.as_p }}
    ...
    <input type="submit" name="{{ negotiation_bid_form.prefix }}-submit-counter-bid" 
    title="Submit a counter bid"
    value="Counter Bid" />
</form>

...

<form id='offer-attachment-form' class="content-form" action='./' enctype="multipart/form-data" method="post" accept-charset="utf-8">
    {% csrf_token %}
    {{ offer_attachment_form.as_p }}

    <input name="{{ offer_attachment_form.prefix }}-submit" type="submit" value="Submit" />
</form>

...
Daniel Sokolowski
la source
1
Je me bats avec ce même problème et essayais de trouver un moyen de traiter chaque message dans une vue de formulaire séparée, puis de rediriger vers une vue de modèle commune. Le but est de rendre la vue de modèle responsable du contenu obtenu et les vues de formulaire pour l'enregistrement. la validation est cependant un problème. enregistrer les formulaires pour la session m'a traversé l'esprit ... Toujours à la recherche d'une solution propre.
Daniele Bernardini
14

J'avais besoin de plusieurs formulaires validés indépendamment sur la même page. Les concepts clés qui me manquaient étaient 1) l'utilisation du préfixe de formulaire pour le nom du bouton de soumission et 2) un formulaire illimité ne déclenche pas la validation. Si cela aide quelqu'un d'autre, voici mon exemple simplifié de deux formulaires AForm et BForm utilisant TemplateView basé sur les réponses de @ adam-nelson et @ daniel-sokolowski et un commentaire de @zeraien ( https://stackoverflow.com/a/17303480 / 2680349 ):

# views.py
def _get_form(request, formcls, prefix):
    data = request.POST if prefix in request.POST else None
    return formcls(data, prefix=prefix)

class MyView(TemplateView):
    template_name = 'mytemplate.html'

    def get(self, request, *args, **kwargs):
        return self.render_to_response({'aform': AForm(prefix='aform_pre'), 'bform': BForm(prefix='bform_pre')})

    def post(self, request, *args, **kwargs):
        aform = _get_form(request, AForm, 'aform_pre')
        bform = _get_form(request, BForm, 'bform_pre')
        if aform.is_bound and aform.is_valid():
            # Process aform and render response
        elif bform.is_bound and bform.is_valid():
            # Process bform and render response
        return self.render_to_response({'aform': aform, 'bform': bform})

# mytemplate.html
<form action="" method="post">
    {% csrf_token %}
    {{ aform.as_p }}
    <input type="submit" name="{{aform.prefix}}" value="Submit" />
    {{ bform.as_p }}
    <input type="submit" name="{{bform.prefix}}" value="Submit" />
</form>
ybendana
la source
Je pense que c'est en fait une solution propre. Merci.
chhantyal
J'aime vraiment cette solution. Une question: y a-t-il une raison pour laquelle _get_form () n'est pas une méthode de la classe MyView?
frappe aérienne
1
@ AndréTerra, cela pourrait certainement l'être, bien que vous souhaitiez probablement l'avoir dans une classe générique qui hérite de TemplateView afin que vous puissiez la réutiliser dans d'autres vues.
ybendana
1
C'est une excellente solution. Je devais changer une ligne du __get_form pour que ça marche: data = request.POST if prefix in next(iter(request.POST.keys())) else None Sinon ça inne marchait pas.
larapsodia
L'utilisation d'une seule balise <form> comme celle-ci signifie que les champs obligatoires sont requis globalement lorsqu'ils doivent être par formulaire selon le bouton de soumission sur lequel vous avez cliqué. La division en deux balises <form> (avec la même action) fonctionne.
Flash
3

Je voulais partager ma solution là où les formulaires Django ne sont pas utilisés. J'ai plusieurs éléments de formulaire sur une seule page et je souhaite utiliser une seule vue pour gérer toutes les demandes POST de tous les formulaires.

Ce que j'ai fait, c'est que j'ai introduit une balise d'entrée invisible afin que je puisse passer un paramètre aux vues pour vérifier quel formulaire a été soumis.

<form method="post" id="formOne">
    {% csrf_token %}
   <input type="hidden" name="form_type" value="formOne">

    .....
</form>

.....

<form method="post" id="formTwo">
    {% csrf_token %}
    <input type="hidden" name="form_type" value="formTwo">
   ....
</form>

views.py

def handlemultipleforms(request, template="handle/multiple_forms.html"):
    """
    Handle Multiple <form></form> elements
    """
    if request.method == 'POST':
        if request.POST.get("form_type") == 'formOne':
            #Handle Elements from first Form
        elif request.POST.get("form_type") == 'formTwo':
            #Handle Elements from second Form
chatuur
la source
Je pense que c'est une bonne et plus facile sortie
Shedrack
2

C'est un peu tard, mais c'est la meilleure solution que j'ai trouvée. Vous créez un dictionnaire de recherche pour le nom du formulaire et sa classe, vous devez également ajouter un attribut pour identifier le formulaire, et dans vos vues, vous devez l'ajouter en tant que champ masqué, avec le form.formlabel.

# form holder
form_holder = {
    'majeur': {
        'class': FormClass1,
    },
    'majsoft': {
        'class': FormClass2,
    },
    'tiers1': {
        'class': FormClass3,
    },
    'tiers2': {
        'class': FormClass4,
    },
    'tiers3': {
        'class': FormClass5,
    },
    'tiers4': {
        'class': FormClass6,
    },
}

for key in form_holder.keys():
    # If the key is the same as the formlabel, we should use the posted data
    if request.POST.get('formlabel', None) == key:
        # Get the form and initate it with the sent data
        form = form_holder.get(key).get('class')(
            data=request.POST
        )

        # Validate the form
        if form.is_valid():
            # Correct data entries
            messages.info(request, _(u"Configuration validée."))

            if form.save():
                # Save succeeded
                messages.success(
                    request,
                    _(u"Données enregistrées avec succès.")
                )
            else:
                # Save failed
                messages.warning(
                    request,
                    _(u"Un problème est survenu pendant l'enregistrement "
                      u"des données, merci de réessayer plus tard.")
                )
        else:
            # Form is not valid, show feedback to the user
            messages.error(
                request,
                _(u"Merci de corriger les erreurs suivantes.")
            )
    else:
        # Just initiate the form without data
        form = form_holder.get(key).get('class')(key)()

    # Add the attribute for the name
    setattr(form, 'formlabel', key)

    # Append it to the tempalte variable that will hold all the forms
    forms.append(form)

J'espère que cela vous aidera à l'avenir.

e-nouri
la source
2

Si vous utilisez une approche avec des vues basées sur la classe et différentes attrs "action", je veux dire

Mettez différentes URL dans l'action pour les deux formulaires. Ensuite, vous aurez deux fonctions d'affichage différentes pour gérer les deux formulaires différents.

Vous pouvez facilement gérer les erreurs de différents formulaires en utilisant une get_context_dataméthode surchargée , par exemple:

views.py:

class LoginView(FormView):
    form_class = AuthFormEdited
    success_url = '/'
    template_name = 'main/index.html'

    def dispatch(self, request, *args, **kwargs):
        return super(LoginView, self).dispatch(request, *args, **kwargs)

    ....

    def get_context_data(self, **kwargs):
        context = super(LoginView, self).get_context_data(**kwargs)
        context['login_view_in_action'] = True
        return context

class SignInView(FormView):
    form_class = SignInForm
    success_url = '/'
    template_name = 'main/index.html'

    def dispatch(self, request, *args, **kwargs):
        return super(SignInView, self).dispatch(request, *args, **kwargs)

    .....

    def get_context_data(self, **kwargs):
        context = super(SignInView, self).get_context_data(**kwargs)
        context['login_view_in_action'] = False
        return context

modèle:

<div class="login-form">
<form action="/login/" method="post" role="form">
    {% csrf_token %}
    {% if login_view_in_action %}
        {% for e in form.non_field_errors %}
            <div class="alert alert-danger alert-dismissable">
                {{ e }}
                <a class="panel-close close" data-dismiss="alert">×</a>
            </div>
        {% endfor %}
    {% endif %}
    .....
    </form>
</div>

<div class="signin-form">
<form action="/registration/" method="post" role="form">
    {% csrf_token %}
    {% if not login_view_in_action %}
        {% for e in form.non_field_errors %}
            <div class="alert alert-danger alert-dismissable">
                {{ e }}
                <a class="panel-close close" data-dismiss="alert">×</a>
            </div>
        {% endfor %}
    {% endif %}
   ....
  </form>
</div>
NameError
la source
2

vue:

class AddProductView(generic.TemplateView):
template_name = 'manager/add_product.html'

    def get(self, request, *args, **kwargs):
    form = ProductForm(self.request.GET or None, prefix="sch")
    sub_form = ImageForm(self.request.GET or None, prefix="loc")
    context = super(AddProductView, self).get_context_data(**kwargs)
    context['form'] = form
    context['sub_form'] = sub_form
    return self.render_to_response(context)

def post(self, request, *args, **kwargs):
    form = ProductForm(request.POST,  prefix="sch")
    sub_form = ImageForm(request.POST, prefix="loc")
    ...

modèle:

{% block container %}
<div class="container">
    <br/>
    <form action="{% url 'manager:add_product' %}" method="post">
        {% csrf_token %}
        {{ form.as_p }}
        {{ sub_form.as_p }}
        <p>
            <button type="submit">Submit</button>
        </p>
    </form>
</div>
{% endblock %}
Hoang Nhat Nguyen
la source
4
Pourriez-vous expliquer votre réponse? Cela aiderait les autres avec un problème similaire et pourrait aider à déboguer votre code ou celui des interrogateurs ...
creyD
0

Voici un moyen simple de gérer ce qui précède.

Dans le modèle HTML, nous mettons Post

<form action="/useradd/addnewroute/" method="post" id="login-form">{% csrf_token %}

<!-- add details of form here-->
<form>
<form action="/useradd/addarea/" method="post" id="login-form">{% csrf_token %}

<!-- add details of form here-->

<form>

En vue

   def addnewroute(request):
      if request.method == "POST":
         # do something



  def addarea(request):
      if request.method == "POST":
         # do something

Dans l'URL Donnez les informations nécessaires comme

urlpatterns = patterns('',
url(r'^addnewroute/$', views.addnewroute, name='addnewroute'),
url(r'^addarea/', include('usermodules.urls')),
Abilash Raghu
la source