Maneira correta de lidar com múltiplos formulários em uma página no Django

202

Eu tenho uma página de modelo esperando dois formulários. Se eu apenas usar um formulário, tudo ficará bem como neste exemplo típico:

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

No entanto, se eu quiser trabalhar com vários formulários, como faço para que a visualização saiba que estou enviando apenas um e não o outro (ou seja, ainda é request.POST, mas quero apenas processar o formulário para o qual o envio foi enviado) aconteceu)?


Esta é a solução baseada na resposta em que a frase esperada e a frase banida são os nomes dos botões de envio para os diferentes formulários, e a forma esperada e o formulário bannedphrase são os formulários.

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
fonte
2
Não há um erro lógico com sua solução? Se você publicar 'frase banida', o formulário de frase esperado não será preenchido.
Ztyx 26/09/11
2
Isto irá lidar com apenas uma forma de cada vez, a questão é de cerca de manusear as várias formas ao mesmo tempo
de brilho

Respostas:

141

Você tem poucas opções:

  1. Coloque URLs diferentes na ação para os dois formulários. Então você terá duas funções de visualização diferentes para lidar com as duas formas diferentes.

  2. Leia os valores do botão enviar dos dados do POST. Você pode dizer qual botão de envio foi clicado: Como criar vários botões de envio no formulário django?

Ned Batchelder
fonte
5
3) Determine qual formulário é enviado a partir dos nomes dos campos nos dados do POST. Inclua algumas entradas ocultas se suas origens não tiverem campos exclusivos, com todos os valores possíveis não estando vazios.
Denis Otkidach 24/10/09
13
4) Adicione um campo oculto identificando o formulário e verifique o valor desse campo na sua visualização.
Soviut
Eu ficaria longe de poluir os dados do POST, se possível. Eu recomendo adicionar um parâmetro GET ao URL de ação do formulário.
pygeek
6
# 1 é realmente sua melhor aposta aqui. Você não deseja poluir seu POST com campos ocultos e nem deseja vincular sua visualização ao seu modelo e / ou formulário.
Meteorainer
5
@meteorainer se você usa o número um, existe uma maneira de passar os erros de volta aos formulários na visualização pai que os instancia, sem usar a estrutura de mensagens ou as strings de consulta? Esta resposta parece o mais próximo, mas aqui ainda é apenas um ponto de vista lidar com ambas as formas: stackoverflow.com/a/21271659/2532070
YPCrumble
45

Um método para referência futura é algo assim. bannedphraseform é o primeiro formulário e o esperadophraseform é o segundo. Se o primeiro for atingido, o segundo será ignorado (que é uma suposição razoável neste 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')
Adam Nelson
fonte
7
usando prefixo = é realmente o 'caminho certo'
Rich
prefix-kwarg fez o trabalho, legal!
Stephan Hoyer
1
Ótima idéia com esses prefixos, usamos esses agora e eles funcionam como um encanto. Mas ainda era necessário inserir um campo oculto para detectar qual formulário foi enviado, porque os dois estão em uma caixa de luz (cada um em um separado). Como precisamos reabrir a mesa de luz correta, precisamos saber exatamente qual formulário foi enviado e, se o primeiro formulário apresentar algum erro de validação, o segundo vence automaticamente e o primeiro formulário é redefinido, embora ainda seja necessário exibir os erros do primeira forma. Só pensei que você deveria saber
Enduriel
Não seria desajeitado estender esse padrão ao caso de três formas? Tipo, com a verificação de is_valid () do primeiro formulário, depois dos dois primeiros, etc ... Talvez apenas tenha um handled = Falseque seja atualizado para Truequando um formulário compatível for encontrado?
binki
14

As visões baseadas em classe do Django fornecem um FormView genérico, mas para todos os efeitos, ele foi projetado para lidar apenas com um formulário.

Uma maneira de lidar com múltiplos formulários com o mesmo URL de ação de destino usando as visualizações genéricas do Django é estender o 'TemplateView' como mostrado abaixo; Eu uso essa abordagem com bastante frequência para transformá-la em um modelo IDE do 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)

O modelo html tem o seguinte efeito:

...

<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
fonte
1
Estou enfrentando esse mesmo problema e estava tentando encontrar uma maneira de processar cada postagem em uma exibição de formulário separada e, em seguida, redirecionar para uma exibição de modelo comum. O objetivo é tornar a visualização do modelo responsável pelo conteúdo obtido e as visualizações de formulário para salvar. validação é um problema embora. salvar os formulários na sessão passou pela minha cabeça ... Ainda procurando uma solução limpa.
Daniele Bernardini
14

Eu precisava de vários formulários validados independentemente na mesma página. Os principais conceitos que estavam faltando eram 1) usar o prefixo do formulário para o nome do botão enviar e 2) um formulário ilimitado não aciona a validação. Se ajudar alguém, aqui está o meu exemplo simplificado de dois formulários AForm e BForm usando TemplateView com base nas respostas de @ adam-nelson e @ daniel-sokolowski e comentário 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
fonte
Eu acho que essa é realmente uma solução limpa. Obrigado.
chhantyal
Eu realmente gosto desta solução. Uma pergunta: existe uma razão pela qual _get_form () não é um método da classe MyView?
ataque aéreo
1
@ AndréTerra definitivamente poderia ser, embora você provavelmente deseje tê-lo em uma classe genérica que herda de TemplateView para que você possa reutilizá-lo em outros modos de exibição.
Ybendana
1
Esta é uma otima soluçao. Eu precisava alterar uma linha do __get_form para que funcionasse: data = request.POST if prefix in next(iter(request.POST.keys())) else None caso contrário in, não funcionaria.
Larapsodia 27/08/16
Usar uma única tag <form> como esta significa que os campos obrigatórios são obrigatórios globalmente quando devem ser executados, dependendo do botão de envio que foi clicado. Dividir em duas tags <form> (com a mesma ação) funciona.
Flash
3

Queria compartilhar minha solução onde o Django Forms não está sendo usado. Eu tenho vários elementos de formulário em uma única página e quero usar uma única exibição para gerenciar todas as solicitações POST de todos os formulários.

O que fiz foi introduzir uma tag de entrada invisível para que eu possa passar um parâmetro para as visualizações para verificar qual formulário foi enviado.

<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
fonte
Eu acho que essa é uma saída boa e mais fácil
Shedrack
2

É um pouco tarde, mas esta é a melhor solução que encontrei. Você cria um dicionário de pesquisa para o nome do formulário e sua classe, também precisa adicionar um atributo para identificar o formulário e, nas visualizações, deve adicioná-lo como um campo oculto, com o 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)

Espero que isso ajude no futuro.

e-nouri
fonte
2

Se você estiver usando uma abordagem com visualizações baseadas em classe e atributos diferentes de "ação", quero dizer

Coloque URLs diferentes na ação para os dois formulários. Então você terá duas funções de visualização diferentes para lidar com as duas formas diferentes.

Você pode lidar facilmente com erros de diferentes formas usando sobrecarregado get_context_data método , por exemplo:

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

modelo:

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

Visão:

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

modelo:

{% 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
fonte
4
Poderia explicar sua resposta? Ele iria ajudar os outros com um problema simmilar e poderia ajudar a depurar o seu ou o código questionadores ...
creyD
0

Aqui está uma maneira simples de lidar com o exposto acima.

No Template Html, colocamos 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>

Em vista

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



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

In URL Forneça as informações necessárias, como

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