|
1 """ |
|
2 Formtools Preview application. |
|
3 """ |
|
4 |
|
5 import cPickle as pickle |
|
6 |
|
7 from django.conf import settings |
|
8 from django.http import Http404 |
|
9 from django.shortcuts import render_to_response |
|
10 from django.template.context import RequestContext |
|
11 from django.utils.hashcompat import md5_constructor |
|
12 from django.contrib.formtools.utils import security_hash |
|
13 |
|
14 AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter. |
|
15 |
|
16 class FormPreview(object): |
|
17 preview_template = 'formtools/preview.html' |
|
18 form_template = 'formtools/form.html' |
|
19 |
|
20 # METHODS SUBCLASSES SHOULDN'T OVERRIDE ################################### |
|
21 |
|
22 def __init__(self, form): |
|
23 # form should be a Form class, not an instance. |
|
24 self.form, self.state = form, {} |
|
25 |
|
26 def __call__(self, request, *args, **kwargs): |
|
27 stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview') |
|
28 self.parse_params(*args, **kwargs) |
|
29 try: |
|
30 method = getattr(self, stage + '_' + request.method.lower()) |
|
31 except AttributeError: |
|
32 raise Http404 |
|
33 return method(request) |
|
34 |
|
35 def unused_name(self, name): |
|
36 """ |
|
37 Given a first-choice name, adds an underscore to the name until it |
|
38 reaches a name that isn't claimed by any field in the form. |
|
39 |
|
40 This is calculated rather than being hard-coded so that no field names |
|
41 are off-limits for use in the form. |
|
42 """ |
|
43 while 1: |
|
44 try: |
|
45 f = self.form.base_fields[name] |
|
46 except KeyError: |
|
47 break # This field name isn't being used by the form. |
|
48 name += '_' |
|
49 return name |
|
50 |
|
51 def preview_get(self, request): |
|
52 "Displays the form" |
|
53 f = self.form(auto_id=AUTO_ID) |
|
54 return render_to_response(self.form_template, |
|
55 {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}, |
|
56 context_instance=RequestContext(request)) |
|
57 |
|
58 def preview_post(self, request): |
|
59 "Validates the POST data. If valid, displays the preview page. Else, redisplays form." |
|
60 f = self.form(request.POST, auto_id=AUTO_ID) |
|
61 context = {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state} |
|
62 if f.is_valid(): |
|
63 context['hash_field'] = self.unused_name('hash') |
|
64 context['hash_value'] = self.security_hash(request, f) |
|
65 return render_to_response(self.preview_template, context, context_instance=RequestContext(request)) |
|
66 else: |
|
67 return render_to_response(self.form_template, context, context_instance=RequestContext(request)) |
|
68 |
|
69 def post_post(self, request): |
|
70 "Validates the POST data. If valid, calls done(). Else, redisplays form." |
|
71 f = self.form(request.POST, auto_id=AUTO_ID) |
|
72 if f.is_valid(): |
|
73 if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')): |
|
74 return self.failed_hash(request) # Security hash failed. |
|
75 return self.done(request, f.cleaned_data) |
|
76 else: |
|
77 return render_to_response(self.form_template, |
|
78 {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}, |
|
79 context_instance=RequestContext(request)) |
|
80 |
|
81 # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ######################## |
|
82 |
|
83 def parse_params(self, *args, **kwargs): |
|
84 """ |
|
85 Given captured args and kwargs from the URLconf, saves something in |
|
86 self.state and/or raises Http404 if necessary. |
|
87 |
|
88 For example, this URLconf captures a user_id variable: |
|
89 |
|
90 (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)), |
|
91 |
|
92 In this case, the kwargs variable in parse_params would be |
|
93 {'user_id': 32} for a request to '/contact/32/'. You can use that |
|
94 user_id to make sure it's a valid user and/or save it for later, for |
|
95 use in done(). |
|
96 """ |
|
97 pass |
|
98 |
|
99 def security_hash(self, request, form): |
|
100 """ |
|
101 Calculates the security hash for the given HttpRequest and Form instances. |
|
102 |
|
103 Subclasses may want to take into account request-specific information, |
|
104 such as the IP address. |
|
105 """ |
|
106 return security_hash(request, form) |
|
107 |
|
108 def failed_hash(self, request): |
|
109 "Returns an HttpResponse in the case of an invalid security hash." |
|
110 return self.preview_post(request) |
|
111 |
|
112 # METHODS SUBCLASSES MUST OVERRIDE ######################################## |
|
113 |
|
114 def done(self, request, cleaned_data): |
|
115 """ |
|
116 Does something with the cleaned_data and returns an |
|
117 HttpResponseRedirect. |
|
118 """ |
|
119 raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__) |