How to Process Multi Step Forms in Django?

2,607 views
Skip to first unread message

Ken Nguyen

unread,
May 1, 2015, 8:58:28 PM5/1/15
to django...@googlegroups.com

I've made some attempted to use formwizard but there wasn't much documentation about it so I decided to stay with the basic. I've successfully obtained and display the data from the first form to the second form and added some checkbox next to the data to allow user to choose whether to overwrite or ignore the duplicate data found in the backend process. The problem I have is the second form doesn't know how retrieve the data of the first form after hitting "Confirm" button. The form2.html template invalidated the data completely since it called itself again by the form action after submitting the data. Is there a way to solve this or a better approach to this?

forms.py

class NameForm (forms.Form):
first_name = forms.CharField (required = False)
last_name
= forms.CharField (required = False)

class CheckBox (forms.Form):
overwrite = forms.BooleanField (required = False)

views.py

def form1 (request):

   
NameFormSet = formset_factory (NameForm, formset = BaseNodeFormSet, extra = 2, max_num = 5)

   
if request.method == 'POST':

        name_formset
= NameFormSet (request.POST, prefix = 'nameform')

       
if name_formset.is_valid ():
            data
= name_formset.cleaned_data

            context
= {'data': data}
           
return render (request, 'nameform/form2.html', context)
       
else:
            name_formset
= NameFormSet (prefix = 'nameform')

     context
= {......}

     
return render (request, 'nameform/form1.html', context)

def form2 (request):

   
CheckBoxFormSet = formset_factory (CheckBox, extra = 2, max_num = 5)

   
if request.method == 'POST':

        checkbox_formset
= CheckBoxFormSet (request.POST, prefix = 'checkbox')

       
if checkbox_formset.is_valid ():
            data
= checkbox_formset.cleaned_data

            context
= {'data': data}
           
return render (request, 'nameform/success.html', context)

       
else:
            checkbox_formset
= CheckBoxFormSet (prefix = 'checkbox')

     
return HttpResponse ('No overwrite data.')



form2.html


<!DOCTYPE html>
<html>
<head lang="en">
   
<meta charset="UTF-8">
    {% load staticfiles %}
   
<link rel="stylesheet" type="text/css" href="{% static 'nodeform/style.css' %}" >
   
<title>User Information</title>
</head>
<body>
   
<h1>User Information:</h1>
   
<form action="form2" method="POST">
   
<div id="tablefont">
   
<table id="table01">
       
<tr>
           
<th>First Name</th>
           
<th>Last Name</th>
           
<th class="center">Overwrite</th>
       
</tr>
        {% for info in data %}
       
<tr>
           
<td>{{ info.first_name }}</td>
           
<td>{{ info.last_address }}</td>
           
<td class="center"><input type="checkbox" name='overwrite' value="1"></td>
       
</tr>
        {% endfor %}
   
</table>
   
</div>
   
<br>
   
<p><input type="submit" value="Confirm">
   
<a href="{% url 'form1' %}">
       
<button type="button">Cancel</button></a></p>
   
</form>
</body>
</html>

Bernardo Brik

unread,
May 2, 2015, 4:24:22 PM5/2/15
to django...@googlegroups.com
You should add the same fields (first_name and address) to form2 and render them with hidden inputs.
You can try to make form2 inherit from form1 and just add the checkbox.

Ken Nguyen

unread,
May 4, 2015, 2:50:04 PM5/4/15
to django...@googlegroups.com

Thank you for the input.  I've already tried what you've suggested but still the same result, "Management Form Data is Missing."  It's not going to know the "first_name" and "last_name" the second time around since I have the variable

{{ info.first_name }} and {{ info.last_name }}

My form2.html currently looking like so:

{% for info in data %}
       
<tr>

           
<input id="id_nameform-{{ forloop.counter0 }}-first_name" name="nameform-{{ forloop.counter0 }}-first_name" type="hidden" value="{{ info.first_name }}" />
       
<input id="id_nameform-{{ forloop.counter0 }}-last_name" name="nameform-{{ forloop.counter0 }}-last_name" type="hidden" value="{{ info.last_name }}" />
           
<td>{{ info.first_name }}</td>
           
<td>{{ info.last_name }}</td>

           
<td class="center"><input type="checkbox" name='overwrite' value="1"></td>
       
</tr>
       
{% endfor %}


When you say inherit from form1 and just add the checkboxes, can you elaborate that?  How do I inherit it?  Do you mean just replicate the first form and add checkboxes to it?

Thanks,

Ken

Bernardo Brik

unread,
May 4, 2015, 3:13:25 PM5/4/15
to django...@googlegroups.com
Hi Ken,
You are getting this error because you are missing this line in your template: {{ formset.management_form }}

You need to carry the information to your second view. If you don’t include first_name and last_address as fields in the second form, the information won’t get to your second view, when you post. That’s why I suggested rendering them as hidden inputs. Your Checkbox form also need to have the fields.

This is what I mean:
class NameForm (forms.Form):
first_name = forms.CharField (required = False)
last_name
= forms.CharField (required = False)

class CheckBox (NameForm):

overwrite = forms.BooleanField (required = False)


And on the template:

        <tr>
            
<td>{{ info.first_name }}</td>
            
<td>{{ info.last_address }}</td>
            
<td class="center"><input type="checkbox" name='overwrite' value="1"></td>
        
</tr>
        <input type=“hidden” name=“first_name” value=“{{ info.first_name }}”>
        <input type=“hidden” name=“last_address” value=“{{ info.last_address }}”>

Let me know if this helps.
cheers,

--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/jnDIOT4dNqQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.
To view this discussion on the web visit https://groups.google.com/d/msgid/django-users/797bdc71-28e4-4277-b645-7c0be9593b27%40googlegroups.com.
For more options, visit https://groups.google.com/d/optout.



--
Bernardo Brik

Ken Nguyen

unread,
May 4, 2015, 6:40:08 PM5/4/15
to django...@googlegroups.com
Hi Bernardo,

Using your hidden method works.  I can see the "hidden" value from Form2 but how do I use those hidden information in form2 so it can redisplay correctly?  First time around, it will use the variable from form1.  The second time around it should use the hidden information without the variable.  How do I achieve that?

You are correct!  I cannot carry the information to the second method in view having it the way it is so I've added the session under the cleaned_data of form1 and retrieved it at the beginning of form2.

data = name_formset.cleaned_data
request.session ['location'] = location
            request.session ['data'] = data

Step 1:  Form1 - Display First Name, Last Name, and location Input Field
Step 2:  Once user submit the information, render the data to form2 and add the checkbox next to it.
Step 3:  User choose to select the overwrite checkbox and then click "Confirm".
Step 4:  Form2 in view checked for 'POST' method and process cleaned data or return empty form2.  This stage created a "Management Form is Missing Error" because of the variables that I used in form1 to create the form2 template are no longer there.  The {{ checkbox_formset.management_form }} is already in the template.

Here you can see that the names are displaying correctly.  By the way, I made an error in my original post.  It was supposed to be "last_name" not "last_address".


form2.html

form2.html
<!DOCTYPE html>
<html>
<head lang="en">
   
<meta charset="UTF-8">
   
{% load staticfiles %}
   
<link rel="stylesheet" type="text/css" href="{% static 'nodeform/style.css' %}" >
   
<title>User Information</title>
</
head>
<body>
   
<h1>User Information:</h1>
   
<form action="form2" method="POST">

   
{{ checkbox_formset.management_form }}

   
<div id="tablefont">
   
<table id="table01">
       
<tr>
           
<th>First Name</th>
            <th>Last Name</
th>
           
<th class="center">Overwrite</th>
        </
tr>
       
{% for info in data %}
       
<tr>

           
<input id="id_nameform-{{ forloop.counter0 }}-first_name" name="nameform-{{ forloop.counter0 }}-first_name" type="hidden" value="{{ info.first_name }}" />
       
<input id="id_nameform-{{ forloop.counter0 }}-last_name" name="nameform-{{ forloop.counter0 }}-last_name" type="hidden" value="{{ info.last_name }}" />
           
<td>{{ info.first_name }}</td>
           
<td>{{ info.last_name }}</td>
           
<td class="center"><input type="checkbox" name='overwrite' value="1"></td>
       
<
/tr>
        {% endfor %}
    </
table>

Ken Nguyen

unread,
May 4, 2015, 7:52:03 PM5/4/15
to django...@googlegroups.com
Nevermind what I said on my last post, something is internally wrong with my second form.  It can't even render when I hard coded the first_name, last_name and the checkboxes.  Still getting Management Form Data is Missing.

<!DOCTYPE html>
<html>
<head lang="en">
   
<meta charset="UTF-8">
   
{% load staticfiles %}
   
<link rel="stylesheet" type="text/css" href="{% static 'nodeform/style.css' %}" >
   
<title>User Information</title>
</
head>
<body>
   
<h1>User Information:</h1>
   
<form action="form2" method="POST">

   
{{ checkbox_formset.management_form }}

   
<div id="tablefont">
   
<table id="table01">
       
<tr>
           
<th>First Name</th>
           
<th>Last Name</
th>
           
<th class="center">Overwrite</th>
       
</
tr>

       
<tr>
           
<td>John</td>
           
<td>Doe</td>
           
<td class="center"><td class="center"><label for="id_checkbox-0-overwrite"></label>
               
<input id="id_checkbox-0-overwrite" name="checkbox-0-overwrite" type="checkbox" /></td>

       
</tr>
        <tr>
           
<td>John</td>
           
<td>Smith</td>
           
<td class="center"><td class="center"><label for="id_checkbox-1-overwrite"></label>
               
<input id="id_checkbox-1-overwrite" name="checkbox-1-overwrite" type="checkbox" /></td>

       
</tr>

   
</
table>

Ken Nguyen

unread,
May 5, 2015, 4:02:43 AM5/5/15
to django...@googlegroups.com
I've finally figured it out after weeks of troubleshooting and here's how I do it.

In the original post, my views takes in the request from form1 and renders it to form2.  I would get the dreadful "Management Form Data is Missing" error no matter what I did and it led to asking a bunch of questions on here.  I decided to isolate the 2 forms to see if that rendered.  It took a few tries before getting both forms and templates to work separately.  What I noticed was form1.html's action was equal to "none" which was what I wanted.  It would render and posted the form with the errors to itself without redirecting to another page.  I then discovered that the address was still hooked to form1 instead of form2 after it had rendered.  In another word when form1 rendered the data to form2, the address was still on userinfo/form1.html when it should have been userinfo/form2.html in order for action to call the correct form in views.  The magic that I used to fix the issue was HttpResponseRedirect.  I saved the data retrieved from form 1 in a session and recovered it from beginning of form 2.  I used session to render data from form 2 instead of rendering directly from form 1.

Changes to form1 in views.py
  • Save the data in a session for access in different part of views
  • Re-direct to form2 instead of render data from form1
def form1 (request):
...
if formset.is_valid ():
            location = request.POST ['site']
            data
= formset.cleaned_data

            request
.session ['names'] = data

           
return HttpResponseRedirect ('form2')
...

Changes to form2 in views.py
  • Retrieve data from session
  • Do something with data from form2.   For my case, I merged both dictionary into one.

def form2 (request):

data = request.session ['names']
...
if checkbox_formset.is_valid ():
           
for i, form in enumerate (checkbox_formset.cleaned_data):
                data
[i].update (form)  # This will give me a list of name dictionary with the checkbox appended
 
  context
= {'data': data}
            return render (request, 'userinfo/success.html', context)

else:
        checkbox_formset
= CheckBoxFormSet (prefix = 'checkbox')

     context = {'checkbox_formset': checkbox_formset, 'data': data, 'location': location}
     
return render (request, 'userinfo/form2.html', context)


form2.html

<!DOCTYPE html>
<html>
<head lang="en">
   
<meta charset="UTF-8">

    {% load staticfiles %}
   
<link rel="stylesheet" type="text/css" href="{% static 'userinfo/style.css' %}" >
   
<title>Confirmation</title>
</head>
<body>
   
<h2>Submitted Entries:</h2>
   
<form action="form2" method="POST">{% csrf_token %}
   
<table>
     
<tr>
       
<th>Firstname</th>
       
<th>Lastname</th>
       
<th class="center">Location</th>

       
<th class="center">Overwrite</th>

       
<th class="center">Index</th>

     
</tr>
      {% for info in data %}
         
<tr>
           
<td>{{ info.first_name }}</td>

           
<td>{{ info.last_name }}</td>
           
<td class="center">{{ location }}</td>
            {{ checkbox_formset.management_form }}
           
<td class="center"><label for="id_checkbox-{{ forloop.counter0 }}-overwrite"></label>
               
<input id="id_checkbox-{{ forloop.counter0 }}-overwrite" name="checkbox-{{ forloop.counter0 }}-overwrite" type="checkbox"/></td>
           
<td class="center">{{ forloop.counter0 }}</td>

         
</tr>
      {% endfor %}
   
</table>

   
<p><input type="submit" value="Confirm">

   
<a href="{% url "addname" %}">

       
<button type="button">Cancel</button>
   
</a></p>
   
</form>
</body>
</html>

All there's left to do is create a template for success.html

<!DOCTYPE html>
<html>
<head lang="en">
   
<meta charset="UTF-8">

   
<title>Success</title>
</head>
<body>
   
<h2>Submitted Entries:</h2>
   
<ul>

        {% for info in data %}
            <li>{{ info.first_name }} {{ info.last_name }} {{ info.overwrite }}</li>
        {% endfor %}
   
</ul>
   
<p><a href="{% url "addname" %}">Add more names</a></p>
</body>
</html>

Hope this will help some stragglers.  Nevertheless, thanks to Bernardo for helping me all along.

Bernardo Brik

unread,
May 5, 2015, 11:47:26 AM5/5/15
to django...@googlegroups.com
Hi Ken,
This is a good solution.
I like the part that you are redirecting instead of rendering directly form2 from form1 view, that was a code smell that I missed.
I don’t like that you are using Session to save the data. You could run into problems of having to invalidate that, clean data, when to do it, etc.
You should still redirect, but you could pass the data from form1 to form2 via query string.

--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/jnDIOT4dNqQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.

For more options, visit https://groups.google.com/d/optout.



--
Bernardo Brik

Ken Nguyen

unread,
May 5, 2015, 12:48:23 PM5/5/15
to django...@googlegroups.com
Hi Bernardo,

I appreciate the feedback.  I've heard of many ways to pass data between methods in views such as session, write to a file, or use formwizard.  It is still a problem accessing the session from the "cleaned_data"?  This is my first time hearing about query string, care to explain or giving me an example of how it works?  In the meantime, I'll read up on it.

Thanks,
Ken

P.S. - In some part of my code, you may notice the "location"parameter.  You can ignore it or not include it in my code.  I was cut and paste so I didn't catch it.

Bernardo Brik

unread,
May 5, 2015, 1:21:36 PM5/5/15
to django...@googlegroups.com
Hi Ken,
The problem with using Session is that the data will still be there when you finish your “wizard". You have to manually delete it. It is state, which is not good. Each request to your server should be an decoupled operation. It’s ok to store in Session global state, like the current language or user data, but I would not save something that belongs to a “wizard”. What happens next time the same user runs the wizard? You have to worry about that and makes your code complex.

You can encode the data form form1 view in the query string you redirect to form2. It might be tricky since we are talking about formsets. You can use urllib.urlencode, you pass a dict:
url = reverse('form2')
querystring = urllib.urlencode({ ... })
return HttpResponseRedirect('%s?%s' % (url, querystring))

Then in your form2 view you get the data from request.GET
key1 = request.GET[ ... ]

--
You received this message because you are subscribed to a topic in the Google Groups "Django users" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/django-users/jnDIOT4dNqQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to django-users...@googlegroups.com.
To post to this group, send email to django...@googlegroups.com.
Visit this group at http://groups.google.com/group/django-users.

For more options, visit https://groups.google.com/d/optout.



--
Bernardo Brik
Reply all
Reply to author
Forward
0 new messages