Richtige Behandlung mehrerer Formulare auf einer Seite in Django


200

Ich habe eine Vorlagenseite, die zwei Formulare erwartet. Wenn ich nur ein Formular verwende, sind die Dinge in Ordnung wie in diesem typischen Beispiel:

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

Wenn ich jedoch mit mehreren Formularen arbeiten möchte, wie kann ich der Ansicht mitteilen, dass ich nur eines der Formulare und nicht das andere sende (dh es ist immer noch request.POST, aber ich möchte nur das Formular verarbeiten, für das die Übermittlung erfolgt passierte)?


Dies ist die Lösung, die auf der Antwort basiert, wobei Expectedphrase und Bannedphrase die Namen der Senden- Schaltflächen für die verschiedenen Formulare sind und Expectedphraseform und Bannedphraseform die Formulare sind.

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

2
Gibt es keinen logischen Fehler bei Ihrer Lösung? Wenn Sie "verbotene Phrase" posten, wird die erwartete Phrasenform nicht ausgefüllt.
Ztyx

2
Dies wird nur eine Form zu einem Zeitpunkt behandelt, ist die Frage über die vielfältigen Formen zugleich Handhabung
glänzend

Antworten:


140

Sie haben einige Möglichkeiten:

  1. Fügen Sie für die beiden Formulare unterschiedliche URLs in die Aktion ein. Dann haben Sie zwei verschiedene Ansichtsfunktionen, um mit den zwei verschiedenen Formen umzugehen.

  2. Lesen Sie die Werte der Senden-Schaltfläche aus den POST-Daten. Sie können erkennen, auf welche Schaltfläche zum Senden geklickt wurde: Wie kann ich ein Django-Formular für mehrere Senden-Schaltflächen erstellen?


5
3) Bestimmen Sie, welches Formular aus Feldnamen in POST-Daten gesendet wird. Fügen Sie einige versteckte Eingaben hinzu, wenn Ihre Froms keine eindeutigen Felder haben und alle möglichen Werte nicht leer sind.
Denis Otkidach

13
4) Fügen Sie ein verstecktes Feld hinzu, das das Formular identifiziert, und überprüfen Sie den Wert dieses Felds in Ihrer Ansicht.
Soviut

Ich würde mich nach Möglichkeit davon fernhalten, die POST-Daten zu verschmutzen. Ich empfehle stattdessen, der Formularaktions-URL einen GET-Parameter hinzuzufügen.
Pygeek

6
# 1 ist hier wirklich die beste Wahl. Sie möchten Ihren POST nicht mit ausgeblendeten Feldern verschmutzen und Ihre Ansicht auch nicht an Ihre Vorlage und / oder Ihr Formular binden.
Meteorainer

5
@meteorainer Wenn Sie Nummer eins verwenden, gibt es eine Möglichkeit, die Fehler an die Formulare in der übergeordneten Ansicht zurückzugeben, die diese instanziieren, ohne das Nachrichtenframework oder Abfragezeichenfolgen zu verwenden? Diese Antwort scheint am nächsten zu sein, aber hier ist es immer noch nur eine Ansicht, die beide Formulare verarbeitet: stackoverflow.com/a/21271659/2532070
YPCrumble

45

Eine Methode für zukünftige Referenz ist so etwas. Die verbotene Phrasenform ist die erste Form und die erwartete Phrasenform ist die zweite. Wenn der erste getroffen wird, wird der zweite übersprungen (was in diesem Fall eine vernünftige Annahme ist):

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

7
Die Verwendung von Präfix = ist in der Tat der 'richtige Weg'
Rich

prefix-kwarg hat den Job gemacht, nett!
Stephan Hoyer

1
Tolle Idee mit diesen Präfixen, wir haben sie jetzt verwendet und sie funktionieren wie ein Zauber. Wir mussten jedoch noch ein verstecktes Feld einfügen, um festzustellen, welches Formular gesendet wurde, da sich beide Formulare in einem Leuchtkasten befinden (jeweils in einem separaten). Da wir den richtigen Leuchtkasten erneut öffnen müssen, müssen wir genau wissen, welches Formular gesendet wurde. Wenn das erste Formular Validierungsfehler aufweist, gewinnt das zweite automatisch und das erste Formular wird zurückgesetzt, obwohl die Fehler aus dem noch angezeigt werden müssen erste Form.
Ich

Wäre es nicht klobig, dieses Muster auf drei Formen auszudehnen? Zum Beispiel mit der Überprüfung von is_valid () aus dem ersten Formular, dann den ersten beiden usw. Vielleicht haben Sie nur ein handled = False, das aktualisiert wird, Truewenn ein kompatibles Formular gefunden wird?
Binki

14

Die klassenbasierten Ansichten von Django bieten eine generische FormView, die jedoch in jeder Hinsicht nur für ein Formular ausgelegt ist.

Eine Möglichkeit, mehrere Formulare mit derselben Zielaktions-URL mithilfe der generischen Ansichten von Django zu verarbeiten, besteht darin, die 'TemplateView' wie unten gezeigt zu erweitern. Ich verwende diesen Ansatz oft genug, um ihn zu einer Eclipse-IDE-Vorlage zu machen.

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)

Die HTML-Vorlage hat folgenden Effekt:

...

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

...

1
Ich habe mit demselben Problem zu kämpfen und habe versucht, einen Weg zu finden, um jeden Beitrag in einer separaten Formularansicht zu verarbeiten und dann zu einer gemeinsamen Vorlagenansicht umzuleiten. Der Punkt ist, die Vorlagenansicht für das Abrufen von Inhalten und die Formularansichten für das Speichern verantwortlich zu machen. Validierung ist jedoch ein Problem. Das Speichern der Formulare für die Sitzung kam mir in den Sinn ... Immer noch auf der Suche nach einer sauberen Lösung.
Daniele Bernardini

14

Ich brauchte mehrere Formulare, die unabhängig voneinander auf derselben Seite validiert wurden. Die Schlüsselkonzepte, die mir fehlten, waren 1) die Verwendung des Formularpräfixes für den Namen der Senden-Schaltfläche und 2) ein unbegrenztes Formular löst keine Validierung aus. Wenn es jemand anderem hilft, ist hier mein vereinfachtes Beispiel für zwei Formen AForm und BForm mit TemplateView, basierend auf den Antworten von @ adam-nelson und @ daniel-sokolowski und dem Kommentar von @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>

Ich denke, das ist eigentlich eine saubere Lösung. Vielen Dank.
Chhantyal

Diese Lösung gefällt mir sehr gut. Eine Frage: Gibt es einen Grund, warum _get_form () keine Methode der MyView-Klasse ist?
Luftangriff

1
@ AndréTerra könnte es definitiv sein, obwohl Sie es wahrscheinlich in einer generischen Klasse haben möchten, die von TemplateView erbt, damit Sie es in anderen Ansichten wiederverwenden können.
Ybendana

1
Dies ist eine großartige Lösung. Ich musste eine Zeile des __get_form ändern, damit es funktioniert: data = request.POST if prefix in next(iter(request.POST.keys())) else None Sonst inhat es nicht funktioniert.
Larapsodia

Die Verwendung eines einzelnen <form> -Tags wie dieses bedeutet, dass erforderliche Felder global erforderlich sind, wenn sie pro Formular sein sollen, je nachdem, auf welche Senden-Schaltfläche geklickt wurde. Das Aufteilen in zwei <form> -Tags (mit derselben Aktion) funktioniert.
Flash

3

Wollte meine Lösung teilen, wenn Django Forms nicht verwendet werden. Ich habe mehrere Formularelemente auf einer Seite und möchte eine einzige Ansicht verwenden, um alle POST-Anforderungen aus allen Formularen zu verwalten.

Ich habe ein unsichtbares Eingabe-Tag eingeführt, damit ich einen Parameter an die Ansichten übergeben kann, um zu überprüfen, welches Formular gesendet wurde.

<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

Ich denke, das ist ein guter und einfacher Ausweg
Shedrack vor

2

Das ist etwas spät, aber das ist die beste Lösung, die ich gefunden habe. Sie erstellen ein Suchwörterbuch für den Formularnamen und seine Klasse, Sie müssen auch ein Attribut hinzufügen, um das Formular zu identifizieren, und in Ihren Ansichten müssen Sie es als verstecktes Feld mit dem hinzufügen 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)

Ich hoffe, dass dies in Zukunft helfen wird.


2

Wenn Sie einen Ansatz mit klassenbasierten Ansichten und verschiedenen 'Aktions'-Attributen verwenden, meine ich

Fügen Sie für die beiden Formulare unterschiedliche URLs in die Aktion ein. Dann haben Sie zwei verschiedene Ansichtsfunktionen, um mit den zwei verschiedenen Formen umzugehen.

Mit der überladenen get_context_dataMethode können Sie Fehler aus verschiedenen Formularen problemlos behandeln , z.

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

Vorlage:

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

2

Aussicht:

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")
    ...

Vorlage:

{% 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 %}

4
Könnten Sie bitte Ihre Antwort erklären? Es würde anderen mit einem ähnlichen Problem helfen und könnte helfen, Ihren oder den Fragesteller-Code zu
debuggen

0

Hier ist eine einfache Möglichkeit, mit den oben genannten Problemen umzugehen.

In HTML-Vorlage setzen wir 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>

Im Hinblick auf

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



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

In URL Geben Sie benötigte Informationen wie

urlpatterns = patterns('',
url(r'^addnewroute/$', views.addnewroute, name='addnewroute'),
url(r'^addarea/', include('usermodules.urls')),
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.