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

12 minutos de lectura

Avatar de usuario de Adam Nelson
adam nelson

Tengo una página de plantilla que espera dos formularios. 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 múltiples formularios, ¿cómo le hago saber a la vista que estoy enviando solo uno de los formularios y no el otro (es decir, sigue siendo request.POST pero solo quiero procesar el formulario para el cual se envió sucedió)?


esta es la solucion basado en la respuesta donde frase esperada y frase prohibida son los nombres de los botones de envío para los diferentes formularios y forma de frase esperada y forma de frase prohibida son las formas.

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

  • ¿No hay un error lógico con su solución? Si publica ‘frase prohibida’, la forma de frase esperada no se completará.

    – Ztyx

    26 de septiembre de 2011 a las 18:39

  • Esto manejará solo un formulario a la vez, la pregunta es sobre el manejo de múltiples formularios al mismo tiempo

    – brillando

    6 de abril de 2016 a las 6:34

  • Todas estas respuestas son útiles, pero no tienen una solución para un formulario no válido. ¿Alguien tiene alguna idea de cómo devolver un formulario no válido cuando dos formularios pueden no ser válidos?

    – judías verdes

    12 de enero de 2021 a las 17:59

  • Estoy realmente sorprendido de que ninguna de estas respuestas haga referencia a conjuntos de formularios: “Un conjunto de formularios es una capa de abstracción para trabajar con múltiples formularios en la misma página”. Ver documentos aquí docs.djangoproject.com/en/3.2/topics/forms/modelforms/… y aquí docs.djangoproject.com/en/3.2/topics/forms/formsets. Sé que esta es una vieja pregunta, pero los conjuntos de formularios no son nuevos.

    – Malik A. Rumi

    26 de enero de 2022 a las 11:34


Avatar de usuario de Ned Batchelder
Ned Batchelder

Tienes pocas opciones:

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

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

  • 3) Determine qué formulario se envía a partir de los nombres de campo en los datos POST. Incluya algunas entradas ocultas si sus formularios no tienen campos únicos y todos los valores posibles no están vacíos.

    – Denis Otkidach

    24 de octubre de 2009 a las 16:39

  • 4) Agregue un campo oculto que identifique el formulario y verifique el valor de este campo en su vista.

    – Soviético

    14 de junio de 2011 a las 3:40

  • Me mantendría alejado de contaminar los datos POST si es posible. Recomiendo agregar un parámetro GET a la URL de acción del formulario.

    – pygeek

    29 de julio de 2013 a las 17:28


  • # 1 es realmente su mejor apuesta aquí. No desea contaminar su POST con campos ocultos y tampoco desea vincular su vista a su plantilla y/o formulario.

    – meteorito

    17 de diciembre de 2013 a las 0:15

  • @meteorainer si usa el número uno, ¿hay alguna forma de devolver 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

    15/10/2014 a las 10:10

Un método para futuras referencias 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 el 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')

  • usar prefix= es de hecho la ‘manera correcta’

    – Rico

    18 de julio de 2012 a las 5:30

  • prefix-kwarg hizo el trabajo, ¡bien!

    – Stephan Hoyer

    7 de enero de 2013 a las 14:08

  • 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 reabrir el lightbox correcto, 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 necesitamos mostrar los errores del primera forma. Solo pensé que deberías saber

    – Enduriel

    3 mayo 2013 a las 16:12


  • ¿No sería torpe extender este patrón al caso de tres formas? Por ejemplo, al verificar is_valid() desde el primer formulario, luego los dos primeros, etc. Tal vez solo tenga un handled = False que se actualiza a True cuando se encuentra una forma compatible?

    – binki

    13 de enero de 2016 a las 22:31

avatar de usuario de ybendana
ybendana

Necesitaba varios formularios validados de forma independiente en la misma página. Los conceptos clave que me faltaban eran 1) usar el prefijo del formulario para el nombre del botón de envío y 2) un formulario ilimitado 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 el comentario 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>

  • Creo que esto es en realidad una solución limpia. Gracias.

    – Chantyal

    28 de enero de 2016 a las 13:12

  • Me gusta mucho esta solución. Una pregunta: ¿hay alguna razón por la que _get_form() no sea un método de la clase MyView?

    – ataque aéreo

    17 de agosto de 2016 a las 4:13

  • @AndréTerra definitivamente podría serlo, aunque probablemente desee tenerlo en una clase genérica que herede de TemplateView para poder reutilizarlo en otras vistas.

    – ybendana

    17 de agosto de 2016 a las 17:48

  • Esta es una gran solución. Necesitaba cambiar una línea del __get_form para que funcionara: data = request.POST if prefix in next(iter(request.POST.keys())) else None De lo contrario in no funcionó

    – larapsodia

    27 de agosto de 2016 a las 16:27


  • El uso de una sola etiqueta

    como esta significa que los campos obligatorios son obligatorios en todo el mundo cuando se deben realizar según el botón de envío en el que se hizo clic. Dividir en dos etiquetas
    (con la misma acción) funciona.

    – Destello

    21 de noviembre de 2017 a las 13:45


Quería compartir mi solución donde Django Forms no se usa. 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 hice fue introducir una etiqueta de entrada invisible para poder pasar un parámetro a las vistas para comprobar 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>

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

Avatar de usuario de Daniel Sokolowski
daniel sokolowski

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 formularios con la misma URL de acción de destino usando las vistas genéricas de Django es extender ‘TemplateView’ como se muestra a continuación; Utilizo este enfoque con tanta frecuencia que lo convertí 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>

...

  • 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 redirigir a una vista de plantilla común. El punto es hacer que la vista de plantilla sea responsable de obtener el contenido y las vistas de formulario del guardado. Sin embargo, la validación es un problema. Se me pasó por la cabeza guardar los formularios en la sesión… Sigo buscando una solución limpia.

    –Daniel Bernardini

    18 de julio de 2014 a las 16:58


avatar de usuario de e-nouri
e-nutrir

Esto es un poco tarde, pero esta es la mejor solución que encontré. Usted hace un diccionario de búsqueda para el nombre del formulario y su clase, también debe agregar un atributo para identificar el formulario, y en sus vistas debe 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.

  • 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 redirigir a una vista de plantilla común. El punto es hacer que la vista de plantilla sea responsable de obtener el contenido y las vistas de formulario del guardado. Sin embargo, la validación es un problema. Se me pasó por la cabeza guardar los formularios en la sesión… Sigo buscando una solución limpia.

    –Daniel Bernardini

    18 de julio de 2014 a las 16:58


vista:

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

plantilla:

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

  • ¿Podría por favor explicar su respuesta? Ayudaría a otros con un problema similar y podría ayudar a depurar su código o el de los interrogadores …

    – CreyD

    9 de enero de 2018 a las 9:33

¿Ha sido útil esta solución?