|
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 self.process_preview(request, f, context) |
|
64 context['hash_field'] = self.unused_name('hash') |
|
65 context['hash_value'] = self.security_hash(request, f) |
|
66 return render_to_response(self.preview_template, context, context_instance=RequestContext(request)) |
|
67 else: |
|
68 return render_to_response(self.form_template, context, context_instance=RequestContext(request)) |
|
69 |
|
70 def post_post(self, request): |
|
71 "Validates the POST data. If valid, calls done(). Else, redisplays form." |
|
72 f = self.form(request.POST, auto_id=AUTO_ID) |
|
73 if f.is_valid(): |
|
74 if self.security_hash(request, f) != request.POST.get(self.unused_name('hash')): |
|
75 return self.failed_hash(request) # Security hash failed. |
|
76 return self.done(request, f.cleaned_data) |
|
77 else: |
|
78 return render_to_response(self.form_template, |
|
79 {'form': f, 'stage_field': self.unused_name('stage'), 'state': self.state}, |
|
80 context_instance=RequestContext(request)) |
|
81 |
|
82 # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ######################## |
|
83 |
|
84 def parse_params(self, *args, **kwargs): |
|
85 """ |
|
86 Given captured args and kwargs from the URLconf, saves something in |
|
87 self.state and/or raises Http404 if necessary. |
|
88 |
|
89 For example, this URLconf captures a user_id variable: |
|
90 |
|
91 (r'^contact/(?P<user_id>\d{1,6})/$', MyFormPreview(MyForm)), |
|
92 |
|
93 In this case, the kwargs variable in parse_params would be |
|
94 {'user_id': 32} for a request to '/contact/32/'. You can use that |
|
95 user_id to make sure it's a valid user and/or save it for later, for |
|
96 use in done(). |
|
97 """ |
|
98 pass |
|
99 |
|
100 def process_preview(self, request, form, context): |
|
101 """ |
|
102 Given a validated form, performs any extra processing before displaying |
|
103 the preview page, and saves any extra data in context. |
|
104 """ |
|
105 pass |
|
106 |
|
107 def security_hash(self, request, form): |
|
108 """ |
|
109 Calculates the security hash for the given HttpRequest and Form instances. |
|
110 |
|
111 Subclasses may want to take into account request-specific information, |
|
112 such as the IP address. |
|
113 """ |
|
114 return security_hash(request, form) |
|
115 |
|
116 def failed_hash(self, request): |
|
117 "Returns an HttpResponse in the case of an invalid security hash." |
|
118 return self.preview_post(request) |
|
119 |
|
120 # METHODS SUBCLASSES MUST OVERRIDE ######################################## |
|
121 |
|
122 def done(self, request, cleaned_data): |
|
123 """ |
|
124 Does something with the cleaned_data and returns an |
|
125 HttpResponseRedirect. |
|
126 """ |
|
127 raise NotImplementedError('You must define a done() method on your %s subclass.' % self.__class__.__name__) |