14

I've recently learned Django forms by subclassing FormView, where the desired form is assigned to the FormView.form_class attribute. When the form validates, the form_valid() method is invoked (for that one form). For example:

from accounts.forms import SignUpForm, UpdateAccountForm, UpdateBillingForm

class SignUpView(FormView):
    form_class = SignUpForm

    def form_valid(self, form):
    # code when form validates...

However, I now have a situation where I need three unique forms on one page (with only one form visible to the user at a time). So, I'd like to handle them all in the same View.

Are multi-form pages possible using FormView? I'm not sure how to handle it, both in terms of passing multiple forms to the View (e.g. the other UpdateAccountForm and UpdateBillingForm), as well as distinguishing which one was submitted/validated? What would be the best way?

pete
  • 2,471
  • 3
  • 25
  • 46
  • Looks like it's not possible with FormView. Per these [answers](http://stackoverflow.com/questions/1395807/proper-way-to-handle-multiple-forms-on-one-page-in-django), one should use a generic View, and add prefixes (?) – pete Jan 21 '14 at 20:33
  • 1
    I just wrote a tool to do this exact thing, only partially documented as of now, but the class MultFormView is only a couple lines of code to handle exactly what you are trying to do https://github.com/dm03514/django-cbv-toolkit – dm03514 Jan 22 '14 at 00:08

2 Answers2

21

Well, for what it's worth here's what ultimately worked for me, using a generic View.

1) I added a hidden input field (named 'action') to each individual form on the page. For example, this is the form for updating user's info, which is pulling in UserForm:

<form action='/account/' method='post'>{% csrf_token %}
   <input type='hidden' name='action' value='edit_user'> 
   {{ user_form.as_p }}
   <input type='submit' value='Update'>
</form>

2) In my View logic, I can distinguish the forms by applying a prefix (per other SO posts and Django docs). Then, depending on the incoming 'action', I only bind the applicable form to the POST request (so validations aren't applied across all of them). In my case, I had two forms defined in forms.py, UserForm and BillingForm:

from django.views.generic.edit import View
from django.shortcuts import render
from django.http import HttpResponse

from accounts.forms import UserForm, BillingForm

class AccountView(View):

    def get(self, request):
        # code for GET request...

    def post(self, request):
        #instantiate all unique forms (using prefix) as unbound
        user_form    = UserForm(prefix='user_form')
        billing_form = BillingForm(prefix='billing_form')

        # determine which form is submitting (based on hidden input called 'action')
        action = self.request.POST['action']

        # bind to POST and process the correct form
        if (action == 'edit_user'):
            user_form = UserForm(request.POST, prefix='user_form')
            if user_form.is_valid():
                # user form validated, code away..

        elif (action == 'edit_billing'):
            billing_form = BillingForm(request.POST, prefix='billing_form')
            if billing_form.is_valid():
                # billing form validated, code away..

        # prep context
        context = {
            'user_form':    user_form,
            'billing_form': billing_form,
        }
        return render(request, 'accounts/account.html', context) 

Seems to work well, hopefully this is the right approach (?)

pete
  • 2,471
  • 3
  • 25
  • 46
  • 1
    Why are the forms instantiated twice, both times with the prefix? – problemofficer Sep 27 '18 at 05:58
  • @problemofficer the only reason I can see is if the if/elif statements don't run, the page is rendered again with the forms. basically as if the form was not valid. – bmeyer71 Jul 24 '19 at 21:56
1

You can write a plain python class mimicking the Form API (at least the useful parts) and wrapping your three forms. Detecting which form has been submitted is just a matter of adding a hidden input with the form's identifier in each form (hint : use prefixes for your forms and use that same prefix as identifier).

The other solution is to use a simple function-based view instead, but even there I'd still use the same "form wrapper" pattern as far as I'm concerned.

bruno desthuilliers
  • 68,994
  • 6
  • 72
  • 93