Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft: restore original form list generation and support dynamic form lists Fixes #220 #240

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 32 additions & 14 deletions formtools/wizard/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -165,8 +165,6 @@ def get_initkwargs(cls, form_list=None, initial_dict=None, instance_dict=None,

computed_form_list = OrderedDict()

assert len(form_list) > 0, 'at least one form is needed'

# walk through the passed form list
for i, form in enumerate(form_list):
if isinstance(form, (list, tuple)):
Expand Down Expand Up @@ -401,18 +399,32 @@ def get_form_kwargs(self, step=None):
"""
return {}

def get_form(self, step=None, data=None, files=None):
def get_form_class(self, step):
"""
Returns the form class for step.

If self.form_list is not empty then it is assumed the wizard has been
implemented according to the original form list generation strategy and the form
class is taken from there. If self.form_list is empty, however, then get the
form class from the dynamically generated list provided by get_form_list().
"""
return self.form_list[step] if self.form_list else self.get_form_list()[step]

def get_form(self, step=None, data=None, files=None, form_cls=None):
"""
Constructs the form for a given `step`. If no `step` is defined, the
current step will be determined automatically.

The form will be initialized using the `data` argument to prefill the
new form. If needed, instance or queryset (for `ModelForm` or
`ModelFormSet`) will be added too.

If form_cls is provided, this class will be instantiated rather than trying to
retrieve the class from the form list.
"""
if step is None:
step = self.steps.current
form_class = self.get_form_list()[step]
form_class = form_cls or self.get_form_class(step)
# prepare the kwargs for the form instance.
kwargs = self.get_form_kwargs(step)
kwargs.update({
Expand Down Expand Up @@ -490,21 +502,27 @@ def get_all_cleaned_data(self):
cleaned_data.update(form_obj.cleaned_data)
return cleaned_data

def get_cleaned_data_for_step(self, step):
def get_cleaned_data_for_step(self, step, form_cls=None):
"""
Returns the cleaned data for a given `step`. Before returning the
cleaned data, the stored values are revalidated through the form.
If the data doesn't validate, None will be returned.

A form_cls can be provided to avoid having to query the class by calling
get_form_list(). This is useful when overriding get_form_list() to create a
dynamic form list but data from other steps is required.
"""
if step in self.form_list:
form_obj = self.get_form(
step=step,
data=self.storage.get_step_data(step),
files=self.storage.get_step_files(step),
)
if form_obj.is_valid():
return form_obj.cleaned_data
return None
if self.form_list and step not in self.form_list:
return None
form_obj = self.get_form(
step=step,
data=self.storage.get_step_data(step),
files=self.storage.get_step_files(step),
form_cls=form_cls,
)
if not form_obj.is_valid():
return None
return form_obj.cleaned_data

def get_next_step(self, step=None):
"""
Expand Down
48 changes: 38 additions & 10 deletions tests/wizard/test_forms.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import sys
from collections import OrderedDict
from importlib import import_module

from django import forms, http
Expand Down Expand Up @@ -93,11 +94,11 @@ def done(self, form_list, **kwargs):


class TestWizardWithCustomGetFormList(TestWizard):

form_list = [Step1]

def get_form_list(self):
return {'start': Step1, 'step2': Step2}
form_list = OrderedDict([('start', Step1), ('step2', Step2)])
self.get_cleaned_data_for_step("step2", form_cls=Step2)
form_list["step3"] = Step3
return form_list


class FormTests(TestCase):
Expand Down Expand Up @@ -159,21 +160,41 @@ def test_form_condition(self):
response, instance = testform(request)
self.assertEqual(instance.get_next_step(), 'step2')

def test_form_condition_avoid_recursion(self):
def test_form_condition_can_check_prior_step_data(self):
def step_check(wizard):
wizard.get_cleaned_data_for_step('start')
return False

testform = TestWizard.as_view(
[('start', Step1), ('step2', Step2), ('step3', Step3)],
condition_dict={'step2': step_check}
)
request = get_request()
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(80)
try:
response, instance = testform(request)
self.assertEqual(instance.get_next_step(), 'step3')
except RecursionError:
self.fail("RecursionError happened during wizard test.")
finally:
sys.setrecursionlimit(old_limit)

def test_form_condition_future_can_check_future_step_data(self):
def subsequent_step_check(wizard):
data = wizard.get_cleaned_data_for_step('step3') or {}
return data.get('foo')

testform = TestWizard.as_view(
[('start', Step1), ('step2', Step2), ('step3', Step3)],
condition_dict={'step3': subsequent_step_check}
condition_dict={'step2': subsequent_step_check}
)
request = get_request()
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(80)
try:
response, instance = testform(request)
self.assertEqual(instance.get_next_step(), 'step2')
self.assertEqual(instance.get_next_step(), 'step3')
except RecursionError:
self.fail("RecursionError happened during wizard test.")
finally:
Expand Down Expand Up @@ -298,11 +319,18 @@ def test_get_form_list_default(self):

def test_get_form_list_custom(self):
request = get_request()
testform = TestWizardWithCustomGetFormList.as_view([('start', Step1)])
testform = TestWizardWithCustomGetFormList.as_view()
response, instance = testform(request)

form_list = instance.get_form_list()
self.assertEqual(form_list, {'start': Step1, 'step2': Step2})
old_limit = sys.getrecursionlimit()
sys.setrecursionlimit(80)
try:
form_list = instance.get_form_list()
except RecursionError:
self.fail("RecursionError happened during wizard test.")
finally:
sys.setrecursionlimit(old_limit)
self.assertEqual(form_list, {'start': Step1, 'step2': Step2, 'step3': Step3})
self.assertIsInstance(instance.get_form('step2'), Step2)


Expand Down