pythondjangodjango-formsdjango-formwizarddjango-formtools

Why does django wizard form not submit when one form is passed through get_form function?


I'm creating an app containing 3 multiple choice questions. The second multiple choice options are dependent on the response to the first question. To accomplish this, I added a "choices" argument to the second form and called that argument in the wizard's get_form function.

While everything appears to work normally, the problem occurs when trying to submit the form on the last step. The done() function is never called and neither is the clean() function in forms.py. The page just gets refreshed without any errors appearing and the existing selected options get wiped.

From testing, I've concluded that removing the custom init from forms.py and the get_form from views.py fixes the issue--but obviously defeats the purpose of this. I've been trying for a very long time to solve this issue but just can't seem to, any help is greatly apperciated!

views.py

class FormWizardView(SessionWizardView):
    form_list = [FormReflectionOne, FormReflectionTwo]
    template_name = 'reflection/form.html'

    def done(self, form_list, **kwargs):
        form_data = [form.cleaned_data for form in form_list]
        # Save form_data to Reflection Model
        return redirect('dashboard')
    
    def get_form(self, step=None, data=None, files=None):
        if step is None:
            step = self.steps.current

        if step == '1':
            firststep_data = self.get_cleaned_data_for_step('0')
            choices_list = AdjectiveChoice.objects.filter(feeling=firststep_data['feeling'])
            choices = [(x.order, x.adjective) for x in choices_list]
            form = FormReflectionTwo(choices)
        else:
            return super(FormWizardView, self).get_form(step, data, files)
        return form

forms.py

class FormReflectionTwo(forms.Form):
    def __init__(self, choices, *args, **kwargs):
        super(FormReflectionTwo, self).__init__(*args, **kwargs)
        self.fields['adjective'].choices = choices
    
    def clean(self):
        cleaned_data = super(FormReflectionTwo, self).clean()

        adjective = cleaned_data.get('adjective')
        reason = cleaned_data.get('reason')

        if adjective and reason:
            if len(adjective) > 3:
                raise ValidationError({'adjective': "Pick 1 to 3 adjectives"})
            if len(reason) > 3:
                raise ValidationError({'reason': "Pick 1 to 3 reasons"})

        return cleaned_data

    adjective = forms.MultipleChoiceField(choices=ADJECTIVE_CHOICES, widget=forms.CheckboxSelectMultiple, required=True)
    reason = forms.MultipleChoiceField(choices=REASON_CHOICES, widget=forms.CheckboxSelectMultiple, required=True)

    class Meta:
        model = ReflectionEntry
        fields = ['adjective', 'reason']

Small other notes: ADJECTIVE_CHOICES variable is just a complete list of all adjective choices, I know it's not needed but it's there as a fallback. The AdjectiveChoice model holds all the possible options as well as what "question 1" answer they are meant for.

Thanks again!


Solution

  • If you look at the code for the get_form method, there are a lot of set up that is done to prepare the form to work with the form wizard. In your case, I suspect that the form prefixes set up by the get_form_prefix method that are messing the form wizard logic.

    (Method 1)To achieve what you want, you can continue on the same thought of using get_form, and set the choices attribute of the field after you get the form as suggested in the docs :

    def get_form(self, step=None, data=None, files=None):
        form = super().get_form(step, data, files)
    
        # determine the step if not given
        if step is None:
            step = self.steps.current
    
        if step == '1':
            firststep_data = self.get_cleaned_data_for_step('0')
            choices_list = AdjectiveChoice.objects.filter(feeling=firststep_data['feeling'])
            choices = [(x.order, x.adjective) for x in choices_list]
            form.fields['adjective'].choices = choices
        return form 
    

    (Method 2)To achieve what you want, is to override the get_form_kwargs method to pass the coices to your form init:

    def get_form_kwargs(self, step=None):
            """
            Returns the keyword arguments for instantiating the form
            (or formset) on the given step.
            """
            if step == "1":
                firststep_data = self.get_cleaned_data_for_step('0')
                choices_list = AdjectiveChoice.objects.filter(feeling=firststep_data['feeling'])
                choices = [(x.order, x.adjective) for x in choices_list]
                return {"choices": choices}
            return {}
    

    and you'll find them in your form init in the kwargs:

    forms.py

    class FormReflectionTwo(forms.Form):
        def __init__(self, *args, **kwargs):
            super(FormReflectionTwo, self).__init__(*args, **kwargs)
            self.fields['adjective'].choices = kwargs.get("choices")