Właściwy sposób obsługi wielu formularzy na jednej stronie w Django

200

Mam stronę szablonu oczekującą dwóch formularzy. Jeśli użyję tylko jednego formularza, wszystko będzie w porządku, jak w tym typowym przykładzie:

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

Jeśli jednak chcę pracować z wieloma formularzami, w jaki sposób mogę poinformować widok, że przesyłam tylko jeden formularz, a nie drugi (tj. Nadal jest to request.POST, ale chcę przetworzyć tylko formularz, dla którego przesłano stało się)?


Jest to rozwiązanie oparte na odpowiedzi, w której oczekiwane wyrażenia i zbanowane wyrażenia to nazwy przycisków wysyłania dla różnych formularzy, a oczekiwane wyrażenia i zbanowane wyrażenia to formularze.

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
źródło
2
Czy nie ma logicznego błędu w twoim rozwiązaniu? Jeśli opublikujesz „zbanowane wyrażenie”, oczekiwane wyrażenie nie zostanie wypełnione.
Ztyx
2
Będzie to obsługiwać tylko jeden formularz na raz, pytanie dotyczy obsługi wielu formularzy jednocześnie
świeci

Odpowiedzi:

140

Masz kilka opcji:

  1. Umieść różne adresy URL w akcji dla dwóch formularzy. Następnie będziesz mieć dwie różne funkcje widoku do obsługi dwóch różnych form.

  2. Odczytaj wartości przycisku przesyłania z danych POST. Możesz powiedzieć, który przycisk przesłania został kliknięty: Jak zbudować wiele formularzy django dla przycisków przesyłania?

Ned Batchelder
źródło
5
3) Ustal, który formularz jest przesyłany z nazw pól w danych POST. Dołącz niektóre ukryte dane wejściowe, jeśli twoje fromy nie mają unikalnych pól, a wszystkie możliwe wartości nie są puste.
Denis Otkidach
13
4) Dodaj ukryte pole identyfikujące formularz i sprawdź wartość tego pola w swoim widoku.
Soviut
Trzymałbym się z dala od zanieczyszczenia danych POST, jeśli to możliwe. Zamiast tego polecam dodanie parametru GET do adresu URL akcji formularza.
pygeek
6
# 1 to naprawdę najlepszy wybór tutaj. Nie chcesz zanieczyszczać swojego POST ukrytymi polami i nie chcesz uwięzić swojego widoku na swoim szablonie i / lub formularzu.
meteorainer
5
@meteorainer, jeśli używasz numeru jeden, czy istnieje sposób, aby przekazać błędy z powrotem do formularzy w widoku nadrzędnym, który je tworzy, bez użycia struktury komunikatów lub ciągów zapytań? Ta odpowiedź wydaje się najbliższa, ale tutaj wciąż jest tylko jeden widok obsługujący obie formy: stackoverflow.com/a/21271659/2532070
YPCrumble
45

Metodą do wykorzystania w przyszłości jest coś takiego. zbanowana fraza jest pierwszą formą, a oczekiwana fraza jest drugą. Jeśli pierwszy zostanie trafiony, drugi zostanie pominięty (co w tym przypadku jest rozsądnym założeniem):

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
źródło
7
użycie przedrostka = jest rzeczywiście „właściwą drogą”
Rich
prefix-kwarg wykonał robotę, miło!
Stephan Hoyer
1
Świetny pomysł z tymi prefiksami, użyliśmy ich teraz i działają jak urok. Ale nadal musieliśmy wstawić ukryte pole, aby wykryć, który formularz został przesłany, ponieważ oba formularze znajdują się w lightbox (każdy w osobnym). Ponieważ musimy ponownie otworzyć właściwy lightbox, musimy dokładnie wiedzieć, który formularz został przesłany, a następnie, jeśli pierwszy formularz zawiera błędy sprawdzania poprawności, drugi automatycznie wygrywa, a pierwszy formularz jest resetowany, chociaż nadal musimy wyświetlać błędy z pierwsza forma. Pomyślałem, że powinieneś wiedzieć
Enduriel
Czy nie byłoby niezręczne rozciągnięcie tego wzoru na przypadek trzech form? Podobnie jak w przypadku sprawdzania is_valid () w pierwszej formie, a następnie w pierwszych dwóch itd. Może po prostu handled = Falseaktualizuje się, Truegdy zostanie znaleziony zgodny formularz?
binki
14

Widoki oparte na klasach Django zapewniają ogólny FormView, ale do wszystkich celów i celów został zaprojektowany do obsługi tylko jednego formularza.

Jednym ze sposobów obsługi wielu formularzy przy użyciu tego samego adresu URL akcji docelowej przy użyciu ogólnych widoków Django jest rozszerzenie „TemplateView”, jak pokazano poniżej; Używam tego podejścia wystarczająco często, aby przekształcić je w szablon 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)

Szablon HTML ma następujący efekt:

...

<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 Sokołowski
źródło
1
Walczę z tym samym problemem i próbowałem znaleźć sposób na przetworzenie każdego postu w osobnym widoku formularza, a następnie przekierowanie do wspólnego widoku szablonu. Chodzi o to, aby widok szablonu był odpowiedzialny za pobieranie zawartości i widoki formularza za zapisywanie. walidacja jest jednak problemem. zapisanie formularzy na sesję przyszło mi do głowy ... Wciąż szukam czystego rozwiązania.
Daniele Bernardini,
14

Potrzebowałem wielu formularzy, które są niezależnie sprawdzane na tej samej stronie. Kluczowymi pojęciami, których mi brakowało, były 1) użycie prefiksu formularza dla nazwy przycisku przesyłania i 2) niezwiązany formularz nie uruchamia sprawdzania poprawności. Jeśli to pomaga komukolwiek innemu, oto mój uproszczony przykład dwóch form AForm i BForm przy użyciu TemplateView na podstawie odpowiedzi @ adam-nelson i @ daniel-sokolowski oraz komentarza @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
źródło
Myślę, że to właściwie czyste rozwiązanie. Dzięki.
chhantyal
Naprawdę podoba mi się to rozwiązanie. Jedno pytanie: czy istnieje powód, dla którego _get_form () nie jest metodą klasy MyView?
nalot
1
@ AndréTerra na pewno może być, chociaż prawdopodobnie chciałbyś mieć go w klasie ogólnej, która dziedziczy z TemplateView, abyś mógł go ponownie użyć w innych widokach.
ybendana,
1
To świetne rozwiązanie. Musiałem zmienić jeden wiersz __get_form, aby działał: w data = request.POST if prefix in next(iter(request.POST.keys())) else None przeciwnym razie innie działałby .
larapsodia
Użycie pojedynczego znacznika <form> w ten sposób oznacza, że ​​wymagane pola są wymagane globalnie, kiedy powinny być zgodne z formularzem, w zależności od tego, który przycisk przesłania został kliknięty. Działa podział na dwa znaczniki <form> (z tą samą akcją).
Flash
3

Chciałem udostępnić moje rozwiązanie, w którym formularze Django nie są używane. Mam wiele elementów formularza na jednej stronie i chcę użyć jednego widoku do zarządzania wszystkimi żądaniami POST ze wszystkich formularzy.

To, co zrobiłem, to wprowadzenie niewidocznego znacznika wejściowego, dzięki czemu mogę przekazać parametr do widoków, aby sprawdzić, który formularz został przesłany.

<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
źródło
Myślę, że jest to dobre i łatwiejsze wyjście
Shedrack
2

Jest trochę późno, ale to najlepsze rozwiązanie, jakie znalazłem. Tworzysz słownik wyszukiwania dla nazwy formularza i jego klasy, musisz także dodać atrybut, aby zidentyfikować formularz, aw swoich widokach musisz dodać go jako pole ukryte za pomocą 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)

Mam nadzieję, że pomoże to w przyszłości.

e-nouri
źródło
2

Jeśli używasz podejścia z widokami klasowymi i różnymi „działaniami”, mam na myśli

Umieść różne adresy URL w akcji dla dwóch formularzy. Następnie będziesz mieć dwie różne funkcje widoku do obsługi dwóch różnych form.

Możesz łatwo obsługiwać błędy z różnych formularzy za pomocą get_context_datametody przeciążonej , np .:

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

szablon:

<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
źródło
2

widok:

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

szablon:

{% 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
źródło
4
Czy możesz wyjaśnić swoją odpowiedź?
Pomógłby
0

Oto prosty sposób poradzenia sobie z powyższym.

W szablonie HTML umieszczamy 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>

Z uwagi

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



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

W adresie URL podaj potrzebne informacje, takie jak

urlpatterns = patterns('',
url(r'^addnewroute/$', views.addnewroute, name='addnewroute'),
url(r'^addarea/', include('usermodules.urls')),
Abilash Raghu
źródło