Django Form Wizard

Sometimes, when dealing with large forms, we get the feeling of overwhelming complexity. Try putting yourself in the end user’s position, before you throw an endless number of fields onto a single page.

Form wizard is a simple solution that solves this complexity, and as a result leading to a better user experience.

As django developers we’re lucky to have a built-in form wizard application, although, I was bit surprised at how many developers either don’t use it (when they should) or know very little about it.

If you’re new to django form wizard, you can get started by visiting Django official docs.

Here are a few tips that I’ve rounded up over the course of a few projects for dealing with the form wizard.

Tip #1. When creating your wizard class, try to keep the urls.py clean. The official docs example doesn’t really follow this. Let’s fix it.

# views.py
TRANSFER_FORMS = [
    ("step1", Form1),
    ("step2", Form2),
]
TRANSFER_TEMPLATES = { 
    "step1": "template/path/step1.html",
    "step2": "template/path/step2.html"
}


class MyFormWizard(SessionWizardView):
    # my awesome code

my_form_wizard_view = MyFormWizard.as_view(TRANSFER_FORMS)

# urls.py
url(r"^wizard/$", "my_form_wizard_view", name="my_form_wizard_view"),

As you see, we can build our view instance directly under wizard class, then assign it to our custom view name and use it in the urls.py thus keeping the urls.py nice and clean. No need for wizard related imports in urls.py!

Tip #2. Any time you upload the file in any step, Django will make a copy of it into the temporary path, but it won’t delete it for you, so you need to take care of it yourself, usually inside of the done method.

# views.py
class MyFormWizard(SessionWizardView):
    location=os.path.join(settings.MEDIA_ROOT, 'temp', 'files')
    file_storage = FileSystemStorage(location)
        
    def done(self, form_list, **kwargs):
        upload_file = form_list[0].cleaned_data['my_file']
        # ...
        self.file_storage.delete(upload_file.name)

 

Tip #3. Django wizard is a CBV, which means you can use mixins to easily extend its functionality. For this purpose I found django-braces very helpful.

# views.py
from braces.views import UserPassesTestMixin

class MyFormWizard(UserPassesTestMixin, SessionWizardView):
    def test_func(self, user):
    return user.is_authenticated() and user.is_staffmember()

 

Tip #4. There are cases when creating more complicated and dynamic wizard flow that you may have the need of accessing submitted wizard data from outside views by hooking up ajax call. Here’s an example of how to access data and files from certain step form.

# views.py
my_wizard_view = MyFormWizard.as_view([ ("step1", Form1), ])

def ajax_view(request, pk):
    storage = SessionStorage(
        prefix='my_form_wizard',
        request=request,
        file_storage=FileSystemStorage(
            location=os.path.join(settings.MEDIA_ROOT, 'temp', 'files')
        )
    )
 
    form = Form1(
        data=storage.get_step_data('step1'),
        files=storage.get_step_files('step1'),
        prefix='step1'
    )

    if form.is_valid(): 
        # we’re good, run my awesome code! 

 

Hopefully these few snippets will make your django development process easier and more fun. I hightly recommend for you to take a look at this great django addition to create better and more user friendly form wizards.