Sharing Our Passion for Technology
& continuous learning
〈  Back to Blog

Multi-Step Forms in Django

Forms in Django seem to be a relatively advanced topic if you want to do anything outside of models.  Case in point: I had to develop a multi-step form in django that would spit out a certain result.  There were only two forms that the user needed to fill out, but the second form had to change what fields it had based on the input from the first form.  Also, the form did not save anything to the database; it just created a file for the end user to download.  To solve this problem, I did research.  A lot of research.  And like any good technical topic, the information I needed was all over the place on the Internet.

The first solution I looked at was using Django’s built-in wizard system.  Essentially, you create several form classes in your app’s forms.py file, and each form class translates to each step in the wizard.  Then, in your project’s urls.py file your create a URL match and associate it with WizardForm.as_view([MyFormStep1, MyFormStep2, …]).  I spent most of my time trying to make this solution work, but the wizard objects in Django don’t do so well passing information from one form to the next, let alone modifying subsequent forms on the fly.

The second solution– the one I took in the end– was to basically make URLs that point to each step in the form (two URLs in my case) and handle passing POST data to subsequent forms myself.  The forms still resided in the forms.py file for my app, but the logic for passing information from the first form to the second was handled in the views.py file.  Each view function in views.py accepts an HTTPRequest object which has POST and GET members that store form information, if any, from the request.  The second view function passed the POST data into the constructor of the second form, which could then add or modify its own form fields on the fly.  Each view function used render_to_response() from django.shortcuts to put the forms on templates and present them to the user.

Example Code

forms.py

from django import forms

class MyFormStep1:
  car_brand = forms.ChoiceField(
    label="Select a car brand",
    choices=(('chevrolet', 'Chevrolet'),
      ('ford', 'Ford'), ('toyota', 'Toyota'))
  )

class MyFormStep2:
  def __init__(self, data_from_post = None):
    if data_form_post is not None:
      _post = data_from_post
      brand = _post.get('car_brand', None)
      list_of_cars = get_cars_by_brand(brand)

      for carname in list_of_cars:
         self.fields['car_%s' % carname] = forms.CharField(label=carname)

views.py

from myapp.forms import MyFormStep1, MyFormStep2
from django.shortcuts import render_to_response

def select_car_brand_view(request):
  form = MyFormStep1()
  return render_to_response('myform_1_template.html', {'form': form})

def list_cars_for_brand_view(request):
  form = MyFormStep2()
  return render_to_response('myform_2_template.html', {'form': form})

Conclusions

Is it an elegant solution? Probably not, since I didn’t use WizardForms from Django, but the amount of time I spent getting nowhere trying to mangle up a WizardForm implementation to get it to work definitely wasn’t any better.

If anyone has a better solution, I would love to hear from you in the comments section below.  I’m a Django newbie.

Resources

〈  Back to Blog