Introduction
Form wizards support conditional steps, pre-populated fields, and custom context data. These features let you build adaptive multi-step workflows that respond to user input at each step.
Key Concepts
- condition_dict: A dictionary that controls which wizard steps are shown based on previous answers.
- get_form_initial(): Provides initial data for each step, often from a previous step's answers.
- get_context_data(): Adds custom data to the template context for any step.
Real World Context
A loan application wizard might skip the business info step for personal accounts. An e-commerce checkout might skip the shipping step for digital products. Conditional steps prevent users from filling out irrelevant forms.
Deep Dive
Conditional Steps
Use condition_dict to show or skip steps based on previous data:
pythonfrom formtools.wizard.views import SessionWizardView def needs_business_info(wizard): """Show business step only for business accounts.""" data = wizard.get_cleaned_data_for_step('account') or {} return data.get('account_type') == 'business' def needs_shipping(wizard): """Show shipping step only for physical products.""" data = wizard.get_cleaned_data_for_step('product') or {} return data.get('is_physical', True) class CheckoutWizard(SessionWizardView): form_list = [ ('account', AccountForm), ('business', BusinessInfoForm), ('product', ProductForm), ('shipping', ShippingForm), ('payment', PaymentForm), ] condition_dict = { 'business': needs_business_info, 'shipping': needs_shipping, } def done(self, form_list, form_dict, **kwargs): all_data = {} for form in form_list: all_data.update(form.cleaned_data) Order.objects.create(**all_data) return redirect('order_confirmation')
The condition functions receive the wizard instance and return True to show the step or False to skip it. Steps with no condition always appear.
Pre-Populating Steps with get_form_initial()
Carry data forward from earlier steps:
pythonclass RegistrationWizard(SessionWizardView): form_list = [ ('credentials', CredentialsForm), ('profile', ProfileForm), ('preferences', PreferencesForm), ] def get_form_initial(self, step): initial = super().get_form_initial(step) if step == 'profile': cred_data = self.get_cleaned_data_for_step('credentials') if cred_data: # Pre-fill name from email email = cred_data.get('email', '') initial['display_name'] = email.split('@')[0] return initial
This creates a smoother experience by suggesting reasonable defaults based on what the user already entered.
Adding Context with get_context_data()
Provide extra data to the wizard template:
pythonclass OnboardingWizard(SessionWizardView): template_name = 'onboarding/wizard.html' form_list = [ ('basics', BasicsForm), ('skills', SkillsForm), ('goals', GoalsForm), ] def get_context_data(self, form, **kwargs): context = super().get_context_data(form, **kwargs) # Add step descriptions step_info = { 'basics': { 'title': 'Tell us about yourself', 'description': 'Basic profile information', }, 'skills': { 'title': 'Your skills', 'description': 'Select technologies you know', }, 'goals': { 'title': 'Learning goals', 'description': 'What do you want to achieve?', }, } current = self.steps.current context['step_info'] = step_info.get(current, {}) # Add summary of previous data context['all_previous'] = {} for step in self.steps.all: data = self.get_cleaned_data_for_step(step) if data: context['all_previous'][step] = data return context
This lets the template display a sidebar summary of previously entered data alongside the current step.
Custom Step Templates
Use get_template_names() for per-step templates:
pythonclass MultiTemplateWizard(SessionWizardView): def get_template_names(self): return [f'wizard/{self.steps.current}.html']
Each step renders with its own template (wizard/account.html, wizard/payment.html, etc.).
Common Pitfalls
- Condition functions returning None — If get_cleaned_data_for_step() returns None (step not yet visited), your condition must handle that gracefully. Always default to True or False explicitly.
- Template context not updating — get_context_data() is called on every render but cached step data only exists after validation. Check for None before accessing cleaned data.
Best Practices
- Use named steps — Define form_list as a list of (name, FormClass) tuples rather than bare classes. Named steps make condition_dict and get_cleaned_data_for_step() more readable.
- Show progress indicators — Use wizard.steps.step1, wizard.steps.count, and wizard.steps.all in your template to build a visual progress bar.
Summary
- condition_dict controls which steps are shown based on previous answers.
- get_form_initial() carries data forward from earlier steps.
- get_context_data() provides extra template data like step descriptions and progress info.
- get_template_names() allows per-step custom templates.