Django form wizard - choices depending on first form step

4.7k views Asked by At

I have created a 2 step form with the FormWizard as follows:

  • 1st step: asking user for location
  • 2nd step: show several search results depending on the user location, and display it as radioButtons

Now the second form depends on the input of the first form. Several blogs or stackoverflow posts cover similar topics, and I followed the instructions. However, the variable which is supposed to be saved during the process_step is not available for the next _init_.

How do I communicate the variable location from the process_step to _init_?

class reMapStart(forms.Form):
    location = forms.CharField()
    CHOICES = [(x, x) for x in ("cars", "bikes")]
    technology = forms.ChoiceField(choices=CHOICES)

class reMapLocationConfirmation(forms.Form):

   def __init__(self, user, *args, **kwargs):
       super(reMapLocationConfirmation, self).__init__(*args, **kwargs)
       self.fields['locations'] = forms.ChoiceField(widget=RadioSelect(), choices=[(x, x)  for x in location])

class reMapData(forms.Form):
   capacity = forms.IntegerField()

class reMapWizard(FormWizard):
   def process_step(self, request, form, step):
       if step == 1:
          self.extra_context['location'] = form.cleaned_data['location']

   def done(self, request, form_list):
       # Send an email or save to the database, or whatever you want with
       # form parameters in form_list
       return HttpResponseRedirect('/contact/thanks/')

Any help is absolutely appreciated.

Thanks, H

PS: posting was updated with newer code.

3

There are 3 answers

1
Hannes On

The working code, after solving the problem with Yuji's help (Thank you) is:

class reMapWizard(FormWizard):

    def render_template(self, request, form, previous_fields, step, context=None):
        if step == 1:
            location = request.POST.get('0-location')
            address, lat, lng, country = getLocation(location)
            form.fields['locations'] = forms.ChoiceField(widget=RadioSelect(), choices = [])
            form.fields['locations'].choices = [(x, x) for x in address]
        return super(reMapWizard, self).render_template(request, form, previous_fields, step, context)
0
Yuji 'Tomita' Tomita On

I figured you could just access the POST dictionary directly in your __init__ method because it looks like the wizard passes POST into each form instance via get_form, but I don't see the data for some reason.

Instead of dwelling on that too long the alternative I've come up with is using the render_template hook.

class ContactWizard(FormWizard):
    def done(selef, request, form_list):
        return http.HttpResponse([form.cleaned_data for form in form_list])

    def render_template(self, request, form, previous_fields, step, context=None):
        """
        The class itself is using hidden fields to pass its state, so
        manually grab the location from the hidden fields (step-fieldname)
        """
        if step == 2: 
            locations = Location.objects.filter(location=request.POST.get('1-location'))
            form.fields['locations'].choices = [(x, x) for x in locations]
        return super(ContactWizard, self).render_template(request, form, previous_fields, step, context)
0
Guy Bowden On

You can get the data from any step by using the storage object:

self.storage.get_step_data('0')

this will return the data dict saved in the storage backend for that particular step.

In my example below, the first form contains an 'Activity' drop down select. Then the second form contains a location select widget which only shows locations that are available for that activity.

This works when you go forward or backward through the wizard - the above answers don't work if you press 'prev' as they rely on the wizard going forward only (i.e. the POST dict won't contain step 0's data if you press prev on step 3!)

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

    form = super(EnquiryWizard, self).get_form(step, data, files)
    #print self['forms']['0'].cleaned_data

    step = step or self.steps.current

    if step == '1':
        step_0_data = self.storage.get_step_data('0')
        activity = Activity.objects.get(pk=step_0_data.get('0-activity'))
        locations = Location.objects.filter(activities=activity)
        form.fields['locations'].widget = forms.CheckboxSelectMultiple(choices=[(x.pk,x.name) for x in locations])

    return form