[Django]-Skipping step x to step y and validate step x data

6👍

I don’t think get_context_data is the correct method to do this in; FormWizard is a very specific class that restricts where you can perform different functions.

The typical way to specify when FormWizard skips a step is to use a condition_dictionary. Django uses the structure to only include the form for a step when the conditions (set as callables) return True. If not, then that step’s form doesn’t force form.is_valid() to be called, bypassing the validation of that step. This also assures that all hidden management info for the form is created for each step.

Here’s a example of how this can work:

# I always specify index values for steps so that all functions can share them
STEP_ONE = u'0'
STEP_TWO = u'1'
STEP_THREE = u'2'


def YourFormWizard(SessionWizardView):
    # Your form wizard itself; will not be called directly by urls.py, but rather wrapped in a function that provide the condition_dictionary
    _condition_dict = { # a dictionary with key=step, value=callable function that return True to show step and False to not
        STEP_ONE: return_true, # callable function that says to always show this step
        STEP_TWO: check_step_two, # conditional callable for verifying whether to show step two
        STEP_THREE: return_true, # callable function that says to always show this step
    }
    _form_list = [ # a list of forms used per step
        (STEP_ONE,your_forms.StepOneForm),
        (STEP_TWO, your_forms.StepTwoForm),
        (STEP_THREE, your_forms.StepThreeForm),
    ]
    ...


def return_true(wizard): #  callable function called in _condition_dict
    return True # a condition that is always True, for when you always want form seen

def check_step_two(wizard): #  callable function called in _condition_dict
    step_1_info = wizard.get_cleaned_data_for_step(STEP_ONE)
    # do something with info; can retrieve for any prior steps
    if step_1_info == some_condition:
        return True # show step 2
    else: return False # or don't

''' urls.py '''

your_form_wizard = YourFormWizard.as_view(YourFormWizard._form_list,condition_dict= YourFormWizard._condition_dict)

urlpatterns = patterns('',
    ...
    url(r'^form_wizard_url/$', your_form_wizard, name='my-form-wizard',) 
)

0👍

Based on great Ian Price answer, I tried to make some improvements because I noticed structure could help over time, especially if you try to change order of your steps:

  • I used a dataclass to centralize data about a step wizard, then generating required data for urls later.
  • I used lambdas for returning True and giving callable is optional
  • I extracted STEP_X in a const.py files to be re-usable
  • I tried as possible to gather data within the class view itself rather than in functions

Here is the code (Python 3.7+):

const.py

STEP_0 = '0'
STEP_1 = '1'
STEP_2 = '2'

views.py

from dataclasses import dataclass
from typing import Optional, Callable, Sequence


@dataclass
class WizardStepData:
    step: str
    form_class: any
    trigger_condition: Optional[Callable] = None

    def __init__(self, step, form_class, trigger_condition=None):
        """ if trigger_condition is not provided, we return a Callable that returns True """
        self.step = step
        self.form_class = form_class
        self.trigger_condition = trigger_condition if trigger_condition else lambda _: True


def YourFormWizard(SessionWizardView):
    @staticmethod
    def check_step_one(wizard) -> bool:
        pass  # ...

    @classmethod
    def get_wizard_data_list(cls) -> Sequence:
        return [
            WizardStepData(step=STEP_0,
                           form_class=StepZeroForm),
            WizardStepData(step=STEP_1,
                           form_class=StepOneForm,
                           trigger_condition=cls.check_step_one),
            WizardStepData(step=STEP_2,
                           form_class=StepTwoForm),
        ]

    @classmethod
    def _condition_dict(cls) -> dict:
        return {data.step: data.trigger_condition for data in cls.get_wizard_data_list()}

    @classmethod
    def _form_list(cls) -> list:
        return [(data.step, data.form_class) for data in cls.get_wizard_data_list()]

urls.py

# ...
your_form_wizard = YourFormWizard.as_view(form_list=YourFormWizard._form_list(),
                                          condition_dict=YourFormWizard._condition_dict())

Leave a comment