Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Proper way to handle multiple forms on one page in Django

Tags:

django

People also ask

How do I stop multiple form submissions in Django?

Multiple form submission happens because when page refreshes that same url hits, which call that same view again and again and hence multiple entries saved in database. To prevent this, we are required to redirect the response to the new url/view, so that next time page refreshes it will hit that new url/view.

Can you have multiple forms within the same HTML document?

Of course it's possible.


You have a few options:

  1. Put different URLs in the action for the two forms. Then you'll have two different view functions to deal with the two different forms.

  2. Read the submit button values from the POST data. You can tell which submit button was clicked: How can I build multiple submit buttons django form?


A method for future reference is something like this. bannedphraseform is the first form and expectedphraseform is the second. If the first one is hit, the second one is skipped (which is a reasonable assumption in this case):

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

I needed multiple forms that are independently validated on the same page. The key concepts I was missing were 1) using the form prefix for the submit button name and 2) an unbounded form does not trigger validation. If it helps anyone else, here is my simplified example of two forms AForm and BForm using TemplateView based on the answers by @adam-nelson and @daniel-sokolowski and comment by @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>

Django's class based views provide a generic FormView but for all intents and purposes it is designed to only handle one form.

One way to handle multiple forms with same target action url using Django's generic views is to extend the 'TemplateView' as shown below; I use this approach often enough that I have made it into an Eclipse IDE template.

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)

The html template is to the following effect:

...

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

...

Wanted to share my solution where Django Forms are not being used. I have multiple form elements on a single page and I want to use a single view to manage all the POST requests from all the forms.

What I've done is I have introduced an invisible input tag so that I can pass a parameter to the views to check which form has been submitted.

<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