I'm using a form wizard in django 1.4 to conditionally add instances of up to seven models. Regardless of which steps a user completes, I'd like the last step to show a preview of all the data they entered. It doesn't have to be a form since the user can use the "First step" or "Previous step" buttons to go back and fix any data they messed up. I'd also like to send a confirmation email to the user with all their data and I suspect whatever solution I come up with here will provide the data for that as well.
Here's what I currently have:
# views.py
FORMS = [
('person_application', PersonApplicationForm),
('family_application', FamilyApplicationForm),
('student_application', StudentApplicationForm),
('spouse', SpouseForm),
('child', ChildFormSet),
('roommate', RoommateFormSet),
('preview', Form), # only doing this because I think FormWizard requires a Form subclass for every step, which makes sense
]
TEMPLATES = {
...
'preview': 'preview.html',
}
condition_dict = {
...
'preview': True,
}
class SignupWizard(SessionWizardView):
...
def get_context_data(self, form, **kwargs):
context = super(SignupWizard, self).get_context_data(form=form, **kwargs)
if self.steps.current == 'preview':
context.update({'all_data': self.get_all_cleaned_data()})
return context
# # This is triggering an infinite loop or something because python gets stuck at 100+% cpu and won't stop even when I kill runserver
# def get_form_initial(self, step):
# if step == 'preview':
# return self.get_all_cleaned_data()
# return {}
...
# urls.py
urlpatterns = patterns('app.views',
...
url(r'^start$', SignupWizard.as_view(FORMS, condition_dict=condition_dict, instance_dict=modelform_instances), name='wizard'),
url(r'^thanks$', 'thanks', name='thanks'),
)
As you can see, at some point I thought I'd try to actually use a Form for the preview, so I tried overriding WizardView.get_form_initial. I wanted to use WizardView.get_all_cleaned_data() to provide all the data as the form's initial dict. However, as I mentioned in the comment, this caused python to get stuck and I had to find and kill the process manually to stop it.
So now I'm thinking I'll just override WizardView.get_context_data() to send an extra context variable to the template containing all the data the user has entered (again, using get_all_cleaned_data()). However, this is going to be a bit complicated for a couple reasons. Since any fields from any of my models that have the same name are going to shadow each other, I'll have to go back and namespace all my model field names. That seems unecessary, but whatever. Also, two of my forms are ModelFormSets, so the data from them comes as a list of dictionaries. Not a big deal, but it's going to make parsing in the template more difficult. This question is getting long but it might be helpful to see the data, so here's an example of what is currently returned by get_all_cleaned_data() (as it gets sent to the template):
{'all_data': {'birthdate': datetime.date(1940, 2, 5),
'building_pref_1': u'NGH4',
'building_pref_2': u'K2',
'city': u'Nashville',
'country': u'',
'email': u'[email protected]',
'first_name': u'Johnny',
u'formset-child': [{'birthdate': datetime.date(2013, 2, 3),
'gender': u'F',
'id': None,
'name': u'Rosanne'},
{'birthdate': datetime.date(2013, 2, 1),
'gender': u'F',
'id': None,
'name': u'Cathy'},
{'birthdate': datetime.date(2013, 2, 5),
'gender': u'F',
'id': None,
'name': u'Cindy'},
{'birthdate': datetime.date(2013, 2, 2),
'gender': u'F',
'id': None,
'name': u'Tara'},
{},
{}],
'furnishing': u'F',
'gender': u'F',
'global_id': u'',
'last_name': u'Cash',
'living_situation': u'SC',
'middle_initial': u'',
'move_in_date': None,
'move_out_date': None,
'name': u'Vivian Liberto',
'phone': u'9891111111',
'smoker_status': u'True',
'state_province': u'TN',
'street_1': u'street',
'street_2': u'',
'student_number': None,
'term': <Term: Summer 2013>,
'type': u'F',
'university_status': u'O',
'university_status_other': u'Travelling musician',
'zip_code': u''},
So, my question is, am I on the right track or is there a better way to do this? For example, might I use a FormPreview subclass as the form for my 'preview' step and define FormPreview.done() as
def done(self, request, cleaned_data):
pass
so that the data just gets passed along to the FormWizard's final processing machinery (i.e. WizardView.done())?