Django FormWizard with dynamic forms
Asked Answered
H

5

6

I want to implement a simple 2 part FormWizard. Form 1 will by dynamically generated something like this:

class BuyAppleForm(forms.Form):
   creditcard = forms.ChoiceField(widget = forms.RadioSelect)
   type = forms.ChoiceField(widget = forms.RadioSelect)
   def __init__(self,*args, **kwargs):
        user = kwargs['user']
        del kwargs['user']

        super(BuyAppleForm, self).__init__(*args, **kwargs)

        credit_cards = get_credit_cards(user)
        self.fields['creditcard'].choices = [(card.id,str(card)) for card in credit_cards]

        apple_types= get_types_packages()
        self.fields['type'].choices = [(type.id,str(type)) for type in apple_types]

This will dynamically create a form with lists of available choices.

My second form, I actually want no input. I just want to display a confirmation screen containing the credit card info, apple info, and money amounts (total, tax, shipping). Once user clicks OK, I want the apple purchase to commence.

I was able to implement the single form way by passing in the request.user object in the kwargs. However, with the FormWizard, I cannot figure this out.

Am I approaching the problem wrong and is the FormWizard not the proper way to do this? If it is, how can the Form __init__ method access the user object from the HTTP request?

Hejaz answered 11/12, 2008 at 4:13 Comment(0)
H
0

I don't know if answering one's own question is an acceptable behaviour on StackOverflow, here is my solution to my own problem.

First, ditch FormWizard.

I have one form. Two views: buy_apples and buy_apples_confirm

First view only handles GET. It prints out the unbound form, with an action to go to the URL of the second view.

The second view checks for the presence of a POST parameter named "confirm". If it is not present (as it is not when the view is loaded the first time) it:

  1. Adjusts the widget on all the fields to be HiddenInput
  2. Writes out template which gives an order summary. This template also sets a hidden field called "confirm" to 1 (even though this field does not exist on the Form)

When the user clicks to buy the apples, the form is submitted back and the buy_apples_confirm view is invoked one more time. This time, a POST parameter called "confirm" is present, so we actually process the purchase transaction and the user gets his apples.

I welcome any critiques on this method or better ways of handling the situation. I am new to Django and find that there are many different ways of approaching a problem. I want to learn from the best though.

Hejaz answered 11/12, 2008 at 6:18 Comment(0)
C
4

I haven't used it, but for the situation you describe, it seems like you may want to try the FormPreview instead of the FormWizard. From the documentation it sounds like what you're after.

Coricoriaceous answered 11/12, 2008 at 12:14 Comment(2)
Thank you for pointing out FormPreview. However, in my case, part of the problem is passing in an additional **kwargs value (request.user as 'user') to the init of the Form constructor (needed for dynamic form generation), and I do not see how that is possible with the FormPreview.Hejaz
I'm not sure whether it'd help (with FormPreview), but something you could do in your code above is not to put that code in the init method. e.g. x = BuyAppleForm(), then do x.set_choices_for_user(request.user) before it's displayed.Coricoriaceous
D
4

When I was trying to figure out FormWizard, I searched all over and found responses such as most of these that just say don't use it. FormPreview would work fine since OP is only interested in a one-level form, but the question is still valid in how to use FormWizard.

Even though this question is so old, I think it is valuable to answer here because this question is asked on so many sites and I see no cohesive response to it, nor a clear solution in the docs.

I think in terms of the OPs question, overriding process_step is the way to go. The trick is in creating the form (or view) within this method that will receive the data from the first form.

I added this form_setup to my forms.py as a utility wrapper (think constructor):

def form_setup(**kwargs):
    def makeform(data, prefix=None, initial=None):
        form = FormLev2(data, prefix, initial)
        for k, v in kwargs.items():
            if k == 'some_list':
                form.fields['some_list'].choices = v
            ...
        return form
    return makeform

Then override process_step as follows:

def process_step(self, request, process, step):
    if step == 1
        if form.is_valid():  #form from step 1
            objs = Table.objects.filter(...) #based on last form 
            self.form_list[1] = form_setup(some_list=[(o.id,o.name) for o in objs])  #(*)
    ...

That way, you are able to dynamically modify form_list(*), in the sense that you modify the form_list in the FormWizard instance, rather than the form definitions themselves. The wrapper function is essential for this functionality, as it returns a function that will instantiate a new Form object, which is then used within FormWizard to be called with the data for the next form, and allows you to use the data from the previous one.

Edit: for Erik's comment, and to clarify the last part.

Also note that process_step will be called with step [0,n] after step n.

Dakar answered 1/2, 2011 at 1:39 Comment(1)
The for k, v in kwargs line needs to be for k, v in kwargs.items(). Other than that, great solution :)Vercingetorix
H
0

I don't know if answering one's own question is an acceptable behaviour on StackOverflow, here is my solution to my own problem.

First, ditch FormWizard.

I have one form. Two views: buy_apples and buy_apples_confirm

First view only handles GET. It prints out the unbound form, with an action to go to the URL of the second view.

The second view checks for the presence of a POST parameter named "confirm". If it is not present (as it is not when the view is loaded the first time) it:

  1. Adjusts the widget on all the fields to be HiddenInput
  2. Writes out template which gives an order summary. This template also sets a hidden field called "confirm" to 1 (even though this field does not exist on the Form)

When the user clicks to buy the apples, the form is submitted back and the buy_apples_confirm view is invoked one more time. This time, a POST parameter called "confirm" is present, so we actually process the purchase transaction and the user gets his apples.

I welcome any critiques on this method or better ways of handling the situation. I am new to Django and find that there are many different ways of approaching a problem. I want to learn from the best though.

Hejaz answered 11/12, 2008 at 6:18 Comment(0)
F
0

Thank you krys for answering to your own question. Helped me, but I still got some remarks.

FormPreview is not the way to go since it as far as I know does not support dynamic forms. It relies on a fixed form class to generate the from from there. But we are generating dynamically here with a function. Maybe FormPreview will support this one day (or already does and I dont know how).

Krys solution seems to do the same as FormPreview. Only the hash is left out, so user may change data in the hidden fields or do you check it again?. If you check it again, that would not be following DRY because you duplicate the check (okay, could be in a reusable method, so only tiny repetition).

What I was wondering, how do you adjust the widget? Do you duplicate the form with the new widgets or is there a way to change that dynamically?

Flickertail answered 12/3, 2009 at 10:43 Comment(0)
G
0

How about changing the call method to take an extra parameter?

something similar to this: http://d-w.me/blog/2010/3/18/15/

Graybill answered 3/5, 2010 at 2:38 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.