How to pass previous form data to the constructor of a DynamicForm in FormWizard
Asked Answered
M

4

13

I have a FormWizard where I need data from the first form to pass to the constructor of the second form so I can build a dynamic form.

I can get the first form's data via the process_step of the FormWizard.

I create the fields of the second form with a database call of the list of fields.

class ConditionWizardDynamicQuestions(forms.Form):

    def __init__(self, DynamicQuestions=None, *args, **kwargs):
       super(ConditionWizardDynamicQuestions, self).__init__(*args, **kwargs)
       questions = Question.objects.filter(MYDATA = DATA_FROM_1STFORM)
       for q in questions:
            dynField = FieldFactory(q)
            self.fields[q.label] = dynField

How can I pass over the DATA_FROM_1STFORM ?


my resultant code: I abandoned the init of the form, and switched it to the CreateQuestions def. Then used the wizard's get_form override to alter the form after creation.

class ConditionWizard(SessionFormWizard):
    def get_form(self, request, storage, step=None, data=None, files=None):
        form = super(ConditionWizard, self).get_form(request, storage, step, data, files)
        stepIndex = self.get_step_index(request, storage, step)
        if stepIndex == 1:
            form.CreateQuestions(request.session["WizardConditionId"])
        if stepIndex == 3:
            form.fields['hiddenConditionId'].initial = request.session["WizardConditionId"]
            form.fields['medicationName'].queryset = Medication.objects.filter(condition = request.session["WizardConditionId"])
        return form
Monge answered 15/10, 2010 at 7:1 Comment(0)
D
5

FormWizard already passes the data from each previous form to the next form. If you want to get that data in order to instantiate a class (for example, if a form has special keyword arguments that it requires), one way of doing it is to grab the querydict by overriding get_form in your form wizard class. For example:

class SomeFormWizard(FormWizard):
    def get_form(self, step, data=None):
        if step == 1 and data: # change this to whatever step requires
                               # the extra data
            extra_data = data.get('key_from_querydict')
            if extra_data:
                return self.form_list[step](data,
                                            keyword_argument=extra_data,
                                            prefix=self.prefix_for_step(step),                                                                                                                                            
                                            initial=self.initial.get(step, None))
        # Fallback for the other forms.
        return self.form_list[step](data,
                                    prefix=self.prefix_for_step(step),                                                                                                                                            
                                    initial=self.initial.get(step, None))

Note that you can also override parse_params(self, request, *args, **kwargs) in FormWizard to access the url/request data, just like you would in a view, so if you have request data (request.user, for instance) that is going to be needed for all of the forms, it might be better to get the data from there.

Hope this helps.

Douse answered 11/11, 2010 at 17:29 Comment(3)
I'm running into a problem passing data from step 1 to step 3, but your solution is helpful and insightful.Monge
What sort of problem are you running into? AFAIK it should let you access all of the data from all of the previous forms - that is, step 3 should have the data from step 1 and step 2.Douse
my problem was I was attempting to use initial from the wizard and overriding the form's init. I switched to using the get_form and the world is getting clearer for me :)Monge
T
8

I solved this by overriding get_form_kwargs for the WizardView. It normally just returns an empty dictionary that get_form populates, so by overriding it to return a dictionary with the data you need prepopulated, you can pass kwargs to your form init.

def get_form_kwargs(self, step=None):
    kwargs = {}
    if step == '1':
        your_data = self.get_cleaned_data_for_step('0')['your_data']
        kwargs.update({'your_data': your_data,})
    return kwargs

Then, in your form init method you can just pop the kwarg off before calling super:

self.your_data = kwargs.pop('client', None)
Tendency answered 22/10, 2012 at 15:4 Comment(0)
D
5

FormWizard already passes the data from each previous form to the next form. If you want to get that data in order to instantiate a class (for example, if a form has special keyword arguments that it requires), one way of doing it is to grab the querydict by overriding get_form in your form wizard class. For example:

class SomeFormWizard(FormWizard):
    def get_form(self, step, data=None):
        if step == 1 and data: # change this to whatever step requires
                               # the extra data
            extra_data = data.get('key_from_querydict')
            if extra_data:
                return self.form_list[step](data,
                                            keyword_argument=extra_data,
                                            prefix=self.prefix_for_step(step),                                                                                                                                            
                                            initial=self.initial.get(step, None))
        # Fallback for the other forms.
        return self.form_list[step](data,
                                    prefix=self.prefix_for_step(step),                                                                                                                                            
                                    initial=self.initial.get(step, None))

Note that you can also override parse_params(self, request, *args, **kwargs) in FormWizard to access the url/request data, just like you would in a view, so if you have request data (request.user, for instance) that is going to be needed for all of the forms, it might be better to get the data from there.

Hope this helps.

Douse answered 11/11, 2010 at 17:29 Comment(3)
I'm running into a problem passing data from step 1 to step 3, but your solution is helpful and insightful.Monge
What sort of problem are you running into? AFAIK it should let you access all of the data from all of the previous forms - that is, step 3 should have the data from step 1 and step 2.Douse
my problem was I was attempting to use initial from the wizard and overriding the form's init. I switched to using the get_form and the world is getting clearer for me :)Monge
C
3

Override the get_form_kwargs method of your form wizard in views

view.py

class FormWizard(SessionWizardView):
    def get_form_kwargs(self, step=None):
        kwargs = {}
        if step == '1':
            step0_form_field = self.get_cleaned_data_for_step('0')['previous_form_field_data']
            kwargs.update({'step0_form_field': step0_form_field})
        return kwargs 

Override the init of your form by popping up the data you got from the previous field to create a dynamic field.

forms.py

class MyForm(forms.Form):
    #some fields

class MyForm1(forms.Form):
    def __init__(self, *args, **kwargs):
        extra = kwargs.pop('step0_form_field')
        super(MyForm1, self).__init__(*args, **kwargs)

        for i in range(extra):
            self.fields['name_%s' % i] = forms.CharField()
Cytolysis answered 28/8, 2013 at 8:29 Comment(0)
M
0

I was recently working with django form wizard, and i was solving the similar issue. I don't think you can pass data to init, however, what you can do, is override the init constructor:

next_form = self.form_list[1]

# let's change the __init__
# function which will set the choices :P
def __init__(self, *args, **kw):
    super(next_form, self).__init__(*args, **kw)
    self.fields['availability'].choices = ...
next_form.__init__ = __init__

It's quite annoying that in python you can't declare and assign a function in one go and have to put it in the namespace (unless you use lambdas), but oh well.

Multiflorous answered 16/10, 2010 at 23:52 Comment(4)
I'm not sure I follow. Is this code overriding the init of the subclassed FormWizard? How does this allow me to get the data from step1 to step2?Monge
no, it overrides the init of the next form. Notice how it says self.fields['availability'].choices = ..., now instead of ... you can put any data you want to passMultiflorous
I already have the "next_form" init overrided, so I still don't see how your passing data from the previous form of the wizard.Monge
Oh, this code has to be inside process_step method of wizard - that way you can get self.form_list[0].cleaned_data, and base the new init on thatMultiflorous

© 2022 - 2024 — McMap. All rights reserved.