Manera adecuada de manejar múltiples formularios en una página en Django

204

Tengo una página de plantilla que espera dos formas. Si solo uso un formulario, las cosas están bien como en este ejemplo típico:

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

Sin embargo, si quiero trabajar con varios formularios, ¿cómo hago para que la vista sepa que estoy enviando solo uno de los formularios y no el otro (es decir, todavía es una solicitud. sucedió)?


Esta es la solución basada en la respuesta donde la frase esperada y la frase prohibida son los nombres de los botones de envío para los diferentes formularios y la forma esperada y la frase prohibida son los formularios.

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
fuente
2
¿No hay un error lógico con su solución? Si publica 'frase prohibida', la forma de frase esperada no se completará.
Ztyx
2
Esto manejará solo un formulario a la vez, la pregunta es sobre el manejo de los formularios múltiples al mismo tiempo
brilla el

Respuestas:

142

Tienes pocas opciones:

  1. Ponga diferentes URL en la acción para los dos formularios. Entonces tendrá dos funciones de vista diferentes para lidiar con las dos formas diferentes.

  2. Lea los valores del botón de envío de los datos POST. Puede saber en qué botón de envío se hizo clic: ¿Cómo puedo construir varios botones de envío con el formulario django?

Ned Batchelder
fuente
55
3) Determine qué formulario se envía a partir de los nombres de campo en los datos POST. Incluya algunas entradas ocultas si sus froms no tienen campos únicos con todos los valores posibles que no estén vacíos.
Denis Otkidach
13
4) Agregue un campo oculto que identifique el formulario y verifique el valor de este campo en su vista.
Soviut
Me alejaría de contaminar los datos POST si es posible. En su lugar, recomiendo agregar un parámetro GET a la url de acción del formulario.
pygeek
66
# 1 es realmente tu mejor apuesta aquí. No desea contaminar su POST con campos ocultos ni desea atar su vista a su plantilla y / o formulario.
meteorainer
55
@meteorainer si usa el número uno, ¿hay alguna forma de pasar los errores a los formularios en la vista principal que los instancia, sin usar el marco de mensajes o las cadenas de consulta? Esta respuesta parece la más cercana, pero aquí sigue siendo solo una vista que maneja ambos formularios: stackoverflow.com/a/21271659/2532070
YPCrumble
45

Un método para referencia futura es algo como esto. La forma de frase prohibida es la primera forma y la forma de frase esperada es la segunda. Si se golpea al primero, se omite el segundo (lo cual es una suposición razonable en este 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
fuente
77
usando prefix = es de hecho la 'forma correcta'
Rich
prefix-kwarg hizo el trabajo, ¡bien!
Stephan Hoyer
1
Gran idea con esos prefijos, los usamos ahora y funcionan de maravilla. Pero aún teníamos que insertar un campo oculto para detectar qué formulario se envió, porque ambos formularios están en una caja de luz (cada uno en uno separado). Debido a que necesitamos volver a abrir la caja de luz correcta, necesitamos saber exactamente qué formulario se envió, y luego, si el primer formulario tiene algún error de validación, el segundo gana automáticamente y el primer formulario se reinicia, aunque todavía tenemos que mostrar los errores del primera forma. Solo pensé que deberías saberlo
Enduriel
¿No sería torpe extender este patrón al caso de tres formas? Al igual que, con la comprobación is_valid () desde el primer formulario, luego los dos primeros, etc. ¿Tal vez solo handled = Falsese actualiza Truecuando se encuentra un formulario compatible?
binki
14

Las vistas basadas en clases de Django proporcionan un FormView genérico, pero para todos los efectos, está diseñado para manejar solo un formulario.

Una forma de manejar múltiples formas con la misma URL de acción de destino usando las vistas genéricas de Django es extender la 'Plantilla Vista' como se muestra a continuación; Utilizo este enfoque con la frecuencia suficiente para convertirlo en una plantilla IDE de 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)

La plantilla html tiene el siguiente efecto:

...

<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
fuente
1
Estoy luchando con este mismo problema y estaba tratando de encontrar una manera de procesar cada publicación en una vista de formulario separada y luego redirigirla a una vista de plantilla común. El objetivo es hacer que la vista de plantilla sea responsable del contenido obtenido y las vistas de formulario para guardar. Sin embargo, la validación es un problema. Guardar los formularios en la sesión me pasó por la mente ... Todavía estoy buscando una solución limpia.
Daniele Bernardini
14

Necesitaba múltiples formularios que se validan independientemente en la misma página. Los conceptos clave que me faltaban eran 1) usar el prefijo de formulario para el nombre del botón de envío y 2) un formulario sin límites no activa la validación. Si ayuda a alguien más, aquí está mi ejemplo simplificado de dos formularios AForm y BForm usando TemplateView basado en las respuestas de @ adam-nelson y @ daniel-sokolowski y comentarios 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
fuente
Creo que esta es realmente una solución limpia. Gracias.
chhantyal
Realmente me gusta esta solución. Una pregunta: ¿hay alguna razón por la cual _get_form () no es un método de la clase MyView?
ataque aéreo
1
@ AndréTerra definitivamente podría ser, aunque probablemente desee tenerlo en una clase genérica que herede de TemplateView para que pueda reutilizarlo en otras vistas.
ybendana
1
Esta es una gran solución. Necesitaba cambiar una línea de __get_form para que funcionara: de lo data = request.POST if prefix in next(iter(request.POST.keys())) else None contrario inno funcionó.
larapsodia
El uso de una sola etiqueta <form> como esta significa que los campos obligatorios son obligatorios globalmente cuando deben ser por formulario, dependiendo del botón de envío en el que se hizo clic. La división en dos etiquetas <form> (con la misma acción) funciona.
Flash
3

Quería compartir mi solución donde no se utilizan formularios Django. Tengo varios elementos de formulario en una sola página y quiero usar una sola vista para administrar todas las solicitudes POST de todos los formularios.

Lo que he hecho es que he introducido una etiqueta de entrada invisible para poder pasar un parámetro a las vistas para verificar qué formulario se ha 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
fuente
Creo que esta es una buena y más fácil salida
Shedrack
2

Esto es un poco tarde, pero esta es la mejor solución que encontré. Haces un diccionario de búsqueda para el nombre del formulario y su clase, también debes agregar un atributo para identificar el formulario, y en tus vistas debes agregarlo como un campo oculto, con el 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 esto ayude en el futuro.

e-nouri
fuente
2

Si está utilizando un enfoque con vistas basadas en clases y diferentes atributos de 'acción', quiero decir

Ponga diferentes URL en la acción para los dos formularios. Entonces tendrá dos funciones de vista diferentes para lidiar con las dos formas diferentes.

Puede manejar fácilmente errores de diferentes formularios utilizando el get_context_datamétodo sobrecargado , por ejemplo:

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

ver:

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
fuente
44
¿Podría por favor explicar su respuesta? Sería de gran ayuda a otros con un problema simmilar y podría ayudar a la depuración de la o el código de interrogadores ...
creyD
0

Aquí hay una manera simple de manejar lo anterior.

En Plantilla HTML ponemos 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 vista

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



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

En URL Dar información necesaria como

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