djangodjango-formwizard

Passing information from view to form with FormWizard


I'm trying to pass data from my view to my form class using WizardView.

Without WizardView, I do this using get_forms_kwargs() like the below:

def get_form_kwargs(self):
    kwargs = super(MenuAddWizard, self).get_form_kwargs()
    kwargs.update({'month': self.kwargs['month']})
    return kwargs

And in my form class I use:

def __init__(self, *args, **kwargs):
    self.month = kwargs.pop('month', None)

All good - I'm able to use 'month' for validation in e.g. clean().

When I'm using WizardView though, I'm specifying the step in get_forms_kwargs() as follows, per the docs:

def get_form_kwargs(self, step=0):
    kwargs = super(MenuAddWizard, self).get_form_kwargs()
    kwargs.update({'month': self.kwargs['month']})
    return kwargs

My get_form() doesn't like this:

  File "python312\Lib\site-packages\formtools\wizard\views.py", line 311, in post
    return self.render_next_step(form)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "python312\Lib\site-packages\formtools\wizard\views.py", line 322, in render_next_step
    new_form = self.get_form(
               ^^^^^^^^^^^^^^
  File "myproject\views.py", line 1614, in get_form
    form = super().get_form(step, data, files)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "python312\Lib\site-packages\formtools\wizard\views.py", line 432, in get_form
    return form_class(**kwargs)
           ^^^^^^^^^^^^^^^^^^^^
TypeError: BaseFormSet.__init__() got an unexpected keyword argument 'month'

Any idea how to properly pass kwargs (or any other way) to a form with Django Form Wizard?

For reference, the full view is below (without get_forms_kwargs()):

class MenuAddWizard(LoginRequiredMixin, SessionWizardView):

    # Either provide form_list as part of .as_view() in urls, or here.
    form_list = [MenuInputForm, DayMenuFormSet]
    template_name = "menu_add.html"

    def get_context_data(self, form, **kwargs):
        context = super(MenuAddWizard, self).get_context_data(form=form, **kwargs)
        month = self.kwargs['month']
        context['month'] = month
        return context

    def get_form_kwargs(self):
        kwargs = super(MenuAddWizard, self).get_form_kwargs()
        kwargs.update({'month': self.kwargs['month']})
        return kwargs

    def get_form(self, step=None, data=None, files=None):
        form = super().get_form(step, data, files)

        if step is None:
            step = self.steps.current

        if step == '1':

            step_0_cleaned_data = self.get_cleaned_data_for_step('0')
            # Using step 0 data, I generate the list to be used for initial; removing logic for brevity and using two example items as test

            DayMenuFormSet = formset_factory(form=DayMenuForm, extra=0)
            formset = DayMenuFormSet(initial=[{'menu': 'Some example text'}, {'menu': 'This is another test...'}],
                                     data=data)

            form = formset

        return form

My forms are all inheriting from the standard forms.Form.


Solution

  • You seem to use a FormSet, probably made out of your form. Then you need to pass the values through the form_kwargs=… parameter [Django-doc], so:

    def get_form_kwargs(self, *args, **kwargs):
        result = super().get_form_kwargs(*args, **kwargs)
        result.update(form_kwargs={'month': self.kwargs['month']})
        return result

    or perhaps simpler:

    def get_form_kwargs(self, *args, **kwargs):
        return {
            **super().get_form_kwargs(*args, **kwargs),
            'form_kwargs': {'month': self.kwargs['month']},
        }

    EDIT: the problem seems to be that you use a FormSet for a certain step, and a (simple) Form for other steps.

    You should pass the value as month if it is for a (simple) form, and through form_kwargs for a FormSet, so:

    class MenuAddWizard(LoginRequiredMixin, SessionWizardView):
        # …
    
        def get_form_kwargs(self, step=None, *args, **kwargs):
            result = super().get_form_kwargs(step=step, *args, **kwargs)
            if str(step) == '0':
                result.update(month=self.kwargs['month'])
            else:
                # if used for a forms in a FormSet, probably not
                # result.update(form_kwargs={'month': self.kwargs['month']})
                pass
            return result
    
        # …

    Note: Since PEP-3135 [pep], you don't need to call super(…) with parameters if the first parameter is the class in which you define the method, and the second is the first parameter (usually self) of the function.