2 FormWizard class -- implements a multi-page form, validating between each |
2 FormWizard class -- implements a multi-page form, validating between each |
3 step and storing the form's state as HTML hidden fields so that no state is |
3 step and storing the form's state as HTML hidden fields so that no state is |
4 stored on the server side. |
4 stored on the server side. |
5 """ |
5 """ |
6 |
6 |
7 from django import newforms as forms |
7 import cPickle as pickle |
|
8 |
|
9 from django import forms |
8 from django.conf import settings |
10 from django.conf import settings |
9 from django.http import Http404 |
11 from django.http import Http404 |
10 from django.shortcuts import render_to_response |
12 from django.shortcuts import render_to_response |
11 from django.template.context import RequestContext |
13 from django.template.context import RequestContext |
12 import cPickle as pickle |
14 from django.utils.hashcompat import md5_constructor |
13 import md5 |
15 from django.utils.translation import ugettext_lazy as _ |
|
16 from django.contrib.formtools.utils import security_hash |
14 |
17 |
15 class FormWizard(object): |
18 class FormWizard(object): |
16 # Dictionary of extra template context variables. |
19 # Dictionary of extra template context variables. |
17 extra_context = {} |
20 extra_context = {} |
18 |
21 |
88 return self.done(request, final_form_list) |
91 return self.done(request, final_form_list) |
89 |
92 |
90 # Otherwise, move along to the next step. |
93 # Otherwise, move along to the next step. |
91 else: |
94 else: |
92 form = self.get_form(next_step) |
95 form = self.get_form(next_step) |
93 current_step = next_step |
96 self.step = current_step = next_step |
94 |
97 |
95 return self.render(form, request, current_step) |
98 return self.render(form, request, current_step) |
96 |
99 |
97 def render(self, form, request, step, context=None): |
100 def render(self, form, request, step, context=None): |
98 "Renders the given Form object, returning an HttpResponse." |
101 "Renders the given Form object, returning an HttpResponse." |
122 valid. |
125 valid. |
123 |
126 |
124 This default implementation simply renders the form for the given step, |
127 This default implementation simply renders the form for the given step, |
125 but subclasses may want to display an error message, etc. |
128 but subclasses may want to display an error message, etc. |
126 """ |
129 """ |
127 return self.render(self.get_form(step), request, step, context={'wizard_error': 'We apologize, but your form has expired. Please continue filling out the form from this page.'}) |
130 return self.render(self.get_form(step), request, step, context={'wizard_error': _('We apologize, but your form has expired. Please continue filling out the form from this page.')}) |
128 |
131 |
129 def render_revalidation_failure(self, request, step, form): |
132 def render_revalidation_failure(self, request, step, form): |
130 """ |
133 """ |
131 Hook for rendering a template if final revalidation failed. |
134 Hook for rendering a template if final revalidation failed. |
132 |
135 |
137 |
140 |
138 def security_hash(self, request, form): |
141 def security_hash(self, request, form): |
139 """ |
142 """ |
140 Calculates the security hash for the given HttpRequest and Form instances. |
143 Calculates the security hash for the given HttpRequest and Form instances. |
141 |
144 |
142 This creates a list of the form field names/values in a deterministic |
|
143 order, pickles the result with the SECRET_KEY setting and takes an md5 |
|
144 hash of that. |
|
145 |
|
146 Subclasses may want to take into account request-specific information, |
145 Subclasses may want to take into account request-specific information, |
147 such as the IP address. |
146 such as the IP address. |
148 """ |
147 """ |
149 data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY] |
148 return security_hash(request, form) |
150 # Use HIGHEST_PROTOCOL because it's the most efficient. It requires |
|
151 # Python 2.3, but Django requires 2.3 anyway, so that's OK. |
|
152 pickled = pickle.dumps(data, protocol=pickle.HIGHEST_PROTOCOL) |
|
153 return md5.new(pickled).hexdigest() |
|
154 |
149 |
155 def determine_step(self, request, *args, **kwargs): |
150 def determine_step(self, request, *args, **kwargs): |
156 """ |
151 """ |
157 Given the request object and whatever *args and **kwargs were passed to |
152 Given the request object and whatever *args and **kwargs were passed to |
158 __call__(), returns the current step (which is zero-based). |
153 __call__(), returns the current step (which is zero-based). |
207 through the "safe" template filter, to prevent |
202 through the "safe" template filter, to prevent |
208 auto-escaping, because it's raw HTML. |
203 auto-escaping, because it's raw HTML. |
209 """ |
204 """ |
210 context = context or {} |
205 context = context or {} |
211 context.update(self.extra_context) |
206 context.update(self.extra_context) |
212 return render_to_response(self.get_template(self.step), dict(context, |
207 return render_to_response(self.get_template(step), dict(context, |
213 step_field=self.step_field_name, |
208 step_field=self.step_field_name, |
214 step0=step, |
209 step0=step, |
215 step=step + 1, |
210 step=step + 1, |
216 step_count=self.num_steps(), |
211 step_count=self.num_steps(), |
217 form=form, |
212 form=form, |