Django: Dynamically set Formset for step in Form Wizard
Asked Answered
S

2

7

As part of a Form Wizard in my Django view I am using a Formset. The wizard's forms for each step are declared like this:

UserFormSet = modelformset_factory(account_models.MyUser,
                                   form=account_forms.MyUserForm, 
                                   extra=5, 
                                   max_num=10, 
                                   can_delete=True)

FORMS = [('userchoice', UserChoiceForm),
         ('user', UserFormSet),]
TEMPLATES = {'userchoice': "account/userchoice.html",
             'user': "account/user.html",}

What I am trying to achieve is this: In UserChoiceForm (first step) the number of required users can be set. I want to use this value to dynamically set the extra attribute on UserFormSet so that only the required number of Forms gets displayed in the second step.

I am trying to do this by overriding the wizard's get_form() method:

class MyUserWizard(SessionWizardView):
 def get_form(self, step=None, data=None, files=None):
     form = super(MyUserWizard, self).get_form(step, data, files)
     
     # Determine the step if not given
     if step is None:
         step = self.steps.current

     if step == 'user':
         # Return number of forms for formset requested 
         # in previous step.
         userchoice = self.get_cleaned_data_for_step('userchoice')
         num_users = userchoice['num_users']
         CoFunderFormSet.extra = num_users
         return CoFunderFormSet
     return form

With this approach I am able to get the right amount of forms displayed for the second step, but when trying to post the Formset I am ending up with this error:

[u'ManagementForm data is missing or has been tampered with']

The POST data has the expected management form fields set, e.g.

form-TOTAL_FORMS    u'1'

but I assume the FormWizard is using the Formset that was set in the initial FORMS list and therefore the management forms do not match.

I was wondering if there is a solution to this and if there is a way to tell the FormWizard to use the dynamically generated Formset on POST instead.

Sleeper answered 13/2, 2014 at 11:41 Comment(0)
S
6

You can override get_form_initial, assume you have already set your form_list like this:

form_list = [
        (FIRST_STEP, forms.FirstForm),
        (SECOND_STEP, modelformset_factory(Second_model, form=forms.SecondForm)),
    ]

def get_form_initial(self, step):
        """
        Set extra parameter for step2, which is from clean data of step1.
        """
        if step == self.SECOND_STEP:
            form_class = self.form_list[step]
            data = self.get_cleaned_data_for_step(self.FIRST_STEP)
            if data is not None:
                extra = get_extra_count(data) # use cleaned data calculate extra
                form_class.extra = extra
        return super(PackageWizard, self).get_form_initial(step)
Straiten answered 13/1, 2015 at 7:15 Comment(1)
This approach works unless you're using multi-threading, as it is not threadsafe. A threadsafe approach might be to override get_form and build your formsets dynamically.Derina
C
0

If you get this error message,

[u'ManagementForm data is missing or has been tampered with']

In your template make sure that you have given {{ wizard.form.management_form }}.

Curley answered 7/11, 2014 at 9:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.