Modo corretto di gestire più moduli su una pagina in Django


204

Ho una pagina modello che prevede due moduli. Se uso solo un modulo, le cose vanno bene come in questo tipico esempio:

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

Se voglio lavorare con più moduli, tuttavia, come faccio a sapere alla vista che sto inviando solo uno dei moduli e non l'altro (cioè è ancora richiesta.POST ma voglio solo elaborare il modulo per il quale l'invio è accaduto)?


Questa è la soluzione basata sulla risposta in cui la frase prevista e la frase vietata sono i nomi dei pulsanti di invio per le diverse forme e la forma prevista e la frase vietata sono le forme.

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
Non c'è un errore logico con la tua soluzione? Se pubblichi "bannedphrase", la forma prevista non verrà popolata.
Ztyx,

2
Questo gestirà solo un modulo alla volta, la domanda riguarda la gestione dei moduli multipli allo stesso tempo
splendente

Risposte:


142

Hai alcune opzioni:

  1. Inserisci URL diversi nell'azione per i due moduli. Quindi avrai due diverse funzioni di visualizzazione per gestire le due diverse forme.

  2. Leggi i valori del pulsante di invio dai dati POST. Puoi dire a quale pulsante di invio è stato fatto clic: Come posso creare più moduli di invio django pulsanti?


5
3) Determinare quale modulo viene inviato dai nomi dei campi nei dati POST. Includi alcuni input nascosti se i tuoi from non hanno campi univoci con tutti i possibili valori che non sono vuoti.
Denis Otkidach,

13
4) Aggiungi un campo nascosto che identifica il modulo e controlla il valore di questo campo nella tua vista.
Soviet

Se possibile, starei lontano dall'inquinamento dei dati POST. Consiglio invece di aggiungere un parametro GET all'URL dell'azione del modulo.
pygeek,

6
Il numero 1 è davvero la soluzione migliore qui. Non vuoi inquinare il tuo POST con campi nascosti e non vuoi collegare la tua vista al tuo modello e / o modulo.
meteorainer

5
@meteorainer se usi il numero uno, c'è un modo per restituire gli errori ai moduli nella vista padre che li istanzia, senza usare il framework dei messaggi o le stringhe di query? Questa risposta sembra la più vicina, ma qui è ancora solo una gestione di entrambe le forme vista: stackoverflow.com/a/21271659/2532070
YPCrumble

45

Un metodo per riferimento futuro è qualcosa di simile. bannedphraseform è il primo modulo e prevedephraseform è il secondo. Se il primo viene colpito, il secondo viene ignorato (il che è un presupposto ragionevole in questo caso):

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
usare il prefisso = è davvero il "modo corretto"
Rich

prefix-kwarg ha fatto il lavoro, bello!
Stephan Hoyer,

1
Ottima idea con quei prefissi, li abbiamo usati ora e funzionano come un fascino. Ma dovevamo ancora inserire un campo nascosto per rilevare quale modulo era stato inviato, poiché entrambi i moduli sono in una lightbox (ognuno in uno separato). Poiché è necessario riaprire la lightbox corretta, è necessario sapere esattamente quale modulo è stato inviato, quindi se il primo modulo presenta errori di convalida, il secondo vince automaticamente e il primo modulo viene ripristinato, sebbene sia ancora necessario visualizzare gli errori dal prima forma. Pensavo solo che dovresti saperlo
Enduriel,

Non sarebbe ingombrante estendere questo schema al caso di tre forme? Ad esempio, con il controllo di is_valid () dal primo modulo, quindi i primi due, ecc ... Forse hai appena un handled = Falseche viene aggiornato Truequando viene trovato un modulo compatibile?
binki,

14

Le viste basate sulla classe di Django forniscono un FormView generico ma a tutti gli effetti è progettato per gestire solo un modulo.

Un modo per gestire più moduli con lo stesso URL di azione target usando le viste generiche di Django è estendere il 'TemplateView' come mostrato di seguito; Uso questo approccio abbastanza spesso da averlo trasformato in un modello 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)

Il modello html ha il seguente effetto:

...

<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
Sto lottando con questo stesso problema e stavo cercando di trovare un modo per elaborare ciascun post in una vista modulo separata e quindi reindirizzare a una vista modello comune. Il punto è rendere responsabile la visualizzazione del modello per ottenere il contenuto e le visualizzazioni del modulo per il salvataggio. la convalida è comunque un problema. il salvataggio dei moduli nella sessione mi è passato per la testa ... Sto ancora cercando una soluzione pulita.
Daniele Bernardini,

14

Avevo bisogno di più moduli convalidati in modo indipendente sulla stessa pagina. I concetti chiave che mi mancavano erano 1) utilizzando il prefisso del modulo per il nome del pulsante di invio e 2) un modulo illimitato non attiva la convalida. Se aiuta qualcun altro, ecco il mio esempio semplificato di due moduli AForm e BForm che utilizzano TemplateView basato sulle risposte di @ adam-nelson e @ daniel-sokolowski e sul commento di @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>

Penso che questa sia in realtà una soluzione pulita. Grazie.
chhantyal,

Mi piace molto questa soluzione. Una domanda: c'è un motivo per cui _get_form () non è un metodo della classe MyView?
attacco aereo

1
@ AndréTerra potrebbe sicuramente essere, anche se probabilmente vorresti averlo in una classe generica che eredita da TemplateView in modo da poterlo riutilizzare in altre viste.
Ybendana,

1
Questa è un'ottima soluzione Avevo bisogno di cambiare una riga di __get_form in modo che funzionasse: data = request.POST if prefix in next(iter(request.POST.keys())) else None altrimenti innon funzionava.
larapsodia,

L'uso di un singolo tag <form> in questo modo significa che i campi obbligatori sono obbligatori a livello globale quando devono essere per modulo in base al pulsante di invio selezionato. La divisione in due tag <form> (con la stessa azione) funziona.
Flash

3

Volevo condividere la mia soluzione in cui Django Forms non viene utilizzato. Ho più elementi del modulo in una singola pagina e desidero utilizzare una vista singola per gestire tutte le richieste POST da tutti i moduli.

Quello che ho fatto è che ho introdotto un tag di input invisibile in modo da poter passare un parametro alle viste per verificare quale modulo è stato inviato.

<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

Penso che sia una buona e facile via d'uscita
Shedrack, il

2

È un po 'tardi, ma questa è la soluzione migliore che ho trovato. Fai un dizionario di ricerca per il nome del modulo e la sua classe, devi anche aggiungere un attributo per identificare il modulo e nelle tue viste devi aggiungerlo come campo nascosto, con il 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)

Spero che questo possa aiutare in futuro.


2

Se stai usando un approccio con viste basate sulla classe e attirano diverse "azioni", intendo

Inserisci URL diversi nell'azione per i due moduli. Quindi avrai due diverse funzioni di visualizzazione per gestire le due diverse forme.

È possibile gestire facilmente errori da diversi moduli utilizzando il get_context_datametodo sovraccarico , ad esempio:

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

modello:

<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

Visualizza:

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

modello:

{% 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
Potresti spiegare la tua risposta, per favore?
Aiuterebbe gli

0

Ecco un modo semplice per gestire quanto sopra.

Nel modello HTML inseriamo 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>

In vista

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



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

Nell'URL Fornisci le informazioni necessarie come

urlpatterns = patterns('',
url(r'^addnewroute/$', views.addnewroute, name='addnewroute'),
url(r'^addarea/', include('usermodules.urls')),
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.