Derek Stegelman

Multiple Forms in Django Views

In one of our projects, we maintain a fairly large central view to display a dashboard of user information. Recently, we've begun adding the ability for users to edit parts of that information inline on the dashboard. Due to the nature of the data being disparate, we need to have the ability to build out multiple different forms, and at the same time maintain some sort of structure and organization within the application itself.

I'll go over how we implemented a solution to this problem with a simplified example.

Forms

To accomplish this task we start out creating a Django form for each formset/data that we want the user to be able to edit. We want to keep as much business logic relating to the form inside here as possible which allows us to keep complex business logic out of our views.

class SomeForm(forms.Form):
    first_name = forms.CharField()
    last_name = forms.CharField()
    alternate_email_1 = forms.EmailField(required=False)
    alternate_email_2 = forms.EmailField(required=False)

    def __init__(self, *args, **kwargs):
        """
        Dynamic form manipulation.
        """

    def save(self):
        """
        Call save actions here
        """

Views

The real challenge with adding multiple forms is how to handle the processing in an elegant way. I didn't find a truely good way to do this, but this solution accomplished our goals.

Goals

  • Handle business logic outside of the main view
  • Provide a unique URL for handling each form
  • Ability to validate the form just as you would if it were apart of the main view

To accomplish this, we first create a new view that will only handle a single form submission. We will repeat this for any addtional forms that we need to process.

@login_required
def handle_some_form(request):
    if request.method == 'POST':

        SOME_SUCCESS = 'Success message'
        SOME_FAIL = 'Fail message'

        try:
            del request.session['some_form']
        except KeyError:
            pass

        form = SomeForm(request.POST, prefix='some')

        if form.is_valid():
            try:
                form.save()
                messages.success(request, SOME_SUCCESS)
            except CustomException:
                messages.error(request, SOME_FAIL)
        else:
            request.session['some_form'] = request.POST
    return redirect('home')
# Urls.py
url(r'^forms/someform/$', views.handle_some_form, name='handle_form'),

The first thing we do after confirming that the request is POST, is clear a session variable. This session variable can be set later if the form is not valid.

If the form validates, we save (or do some other form action) and create a success message and redirect the user back to our main view, simple enough.

The difficult part was coming up with a way to handle form validation since we need to load the main view, but with data sent to the second view.

When a form doesn't validate in the second view, we set a session variable with the posted data and then redirect the user back to the main view with this session variable set.

Then in our main view:

try:
    form_post = request.session['some_form']
    form = SomeForm(form_post)
    form.is_valid()

    context['some_form'] = some_form
    del request.session['some_form']
except KeyError:
    pass

We check for the existence of the session variable and attempt again to validate the form. When it fails we are left with the invalid form which now has its errors dict filled out, so we can present that back to the user. We set the form context, delete the session variable so that it doesn't collide with the next request and display the failed form back to the user.

Do overs

If I was able to do this over again I think I'd probably look at making the main view class based which might give us a bit more of an elegant way to handle these types of forms. We could also build out some form view mixins that would allow us to quickly extend and build out form handling views.