[Fixed]-Can you use Form Wizard for Model Forms in django?


Form Wizard is being built into Django 1.4 so is a good way to go about this. It should do what you want, but you may need a couple of tweaks.

Don’t worry about the kwargs in done() at the moment – you’re not going to need them.

form_list is the list of forms that you want to use for your steps – from urls.py

urlpatterns = patterns('',
    (r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),

[ContactForm1, ContactForm2] will be passed to done() as form_list.

What you will need to do is break your ModelForm into separate forms. The easiest way to do this (if you want your model on several forms) is to not use ModelForm but just create your own form. It’s pretty easy:

from django import forms

class ContactForm1(forms.Form):
    subject = forms.CharField(max_length=100)
    sender = forms.EmailField()

class ContactForm2(forms.Form):
    message = forms.CharField(widget=forms.Textarea)

Once your forms reflect the portions of your model, just create the views and patterns as described in the docs and set do_something_with_the_form_data(form_list) to a function that completes your model from the form data and then does a save.

You could use ModelForm but – only if you can persuade it to produce different forms for Form Wizard to use for each step – that’s going to be the tricky part.


Say your model has two fields

class AModel( Model ):
    fieldA = CharField()
    fieldB = CharField()

We want to set each field in a separate step using a FormWizard. So we create two ModelForms, each showing one field:

class Form1( ModelForm ):
    class Meta:
        model = AModel
        fields = ( 'fieldA', )

class Form2( ModelForm ):
    class Meta:
        model = AModel
        fields = ( 'fieldB', )

We call our form wizard AWizard; the url.py entry should look something like

url( r'^$', AWizard.as_view( [ Form1, Form2 ] ) ),

In the implementation of AWizard we need to make sure all the forms write their data to a single instance, which we then save to the database:

class AWizard( SessionWizardView ):
    instance = None

    def get_form_instance( self, step ):
        if self.instance is None:
            self.instance = AModel()
        return self.instance

    def done( self, form_list, **kwargs ):

Notice that we override the method get_form_instance. This method returns the model instance the forms bind to.

You might think (I did), that this method creates an instance for the first request (the first step of the wizard), and then keeps using that same instance for all steps.

Actually, it’s a little more complicated. For each request a new instance of AWizard is created, which in turn creates a new AModel instance. So, the steps don’t share a single instance to start with.

The magic happens when the last form is submitted. At this point all forms are revalidated, each form calls get_form_instance and they end up populating a single AModel instance.

That instance is then saved in done.



The view proposed by @wuerg did not work for me, I had to do this:

class AWizard( SessionWizardView ):
    def dispatch(self, request, *args, **kwargs):
        self.instance = AModel()
        return super(ApplyWizard, self).dispatch(request, *args, **kwargs)

    def get_form_instance( self, step ):
        return self.instance

    def done( self, form_list, **kwargs ):
        return HttpResponseRedirect(reverse(thanks))


I had to alter the solution of @wuerg and @madmen to work in my usecase (saving the Model after every step). The big advantage of this approach is that it always uses the same instance of the AModel instead of creating a new instance for every step:

class AWizard(SessionWizardView):
    instance = AModel()

    def dispatch(self, request, *args, **kwargs):
        return super(AWizard, self).dispatch(request, *args, **kwargs)

    def get_form_instance(self, step):
        return self.instance

    def done(self, form_list, **kwargs):
        return render_to_response('done.html')

Leave a comment