Ticket #16174: cbv-formpreview2.diff
File cbv-formpreview2.diff, 10.5 KB (added by , 13 years ago) |
---|
-
django/contrib/formtools/preview.py
diff --git a/django/contrib/formtools/preview.py b/django/contrib/formtools/preview.py index b4cdeba..8c565f4 100644
a b 1 1 """ 2 2 Formtools Preview application. 3 3 """ 4 5 try:6 import cPickle as pickle7 except ImportError:8 import pickle9 10 4 from django.conf import settings 11 from django.http import Http40412 5 from django.shortcuts import render_to_response 13 from django.template.context import RequestContext14 6 from django.utils.crypto import constant_time_compare 15 7 from django.contrib.formtools.utils import form_hmac 8 from django.views.generic import FormView 16 9 10 PREVIEW_STAGE = 'preview' 11 POST_STAGE = 'post' 17 12 AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter. 13 STAGE_FIELD = 'stage' 14 HASH_FIELD = 'hash' 18 15 19 class FormPreview( object):16 class FormPreview(FormView): 20 17 preview_template = 'formtools/preview.html' 21 18 form_template = 'formtools/form.html' 22 19 23 20 # METHODS SUBCLASSES SHOULDN'T OVERRIDE ################################### 24 21 25 def __init__(self, form): 22 def __init__(self, form_class, *args, **kwargs): 23 super(FormPreview, self).__init__(*args, **kwargs) 26 24 # form should be a Form class, not an instance. 27 self.form, self.state = form, {} 25 self.form_class = form_class 26 self._preview_stage = PREVIEW_STAGE 27 self._post_stage = POST_STAGE 28 self._stages = {'1': self._preview_stage, '2': self._post_stage} 29 # A relic of the past; override get_context_data to pass extra context 30 # to the template. Left in for backwards compatibility. 31 self.state = {} 28 32 29 33 def __call__(self, request, *args, **kwargs): 30 stage = {'1': 'preview', '2': 'post'}.get(request.POST.get(self.unused_name('stage')), 'preview') 34 return self.dispatch(request, *args, **kwargs) 35 36 def dispatch(self, request, *args, **kwargs): 37 posted_stage = request.POST.get(self.unused_name(STAGE_FIELD)) 38 self._stage = self._stages.get(posted_stage, self._preview_stage) 39 40 # For backwards compatiblity 31 41 self.parse_params(*args, **kwargs) 32 try: 33 method = getattr(self, stage + '_' + request.method.lower()) 34 except AttributeError: 35 raise Http404 36 return method(request) 42 43 return super(FormPreview, self).dispatch(request, *args, **kwargs) 37 44 38 45 def unused_name(self, name): 39 46 """ … … class FormPreview(object): 45 52 """ 46 53 while 1: 47 54 try: 48 f = self.form.base_fields[name]55 self.form_class.base_fields[name] 49 56 except KeyError: 50 57 break # This field name isn't being used by the form. 51 58 name += '_' 52 59 return name 53 60 54 def preview_get(self, request): 61 def _get_context_data(self, form): 62 """ For backwards compatiblity. """ 63 context = self.get_context_data(form=form) 64 context.update(self.get_context(self.request, form)) 65 return context 66 67 def get(self, request, *args, **kwargs): 55 68 "Displays the form" 56 f = self.form(auto_id=self.get_auto_id(), initial=self.get_initial(request)) 57 return render_to_response(self.form_template, 58 self.get_context(request, f), 59 context_instance=RequestContext(request)) 69 form_class = self.get_form_class() 70 form = self.get_form(form_class) 71 context = self._get_context_data(form) 72 self.template_name = self.form_template 73 return self.render_to_response(context) 74 75 def _check_security_hash(self, token, form): 76 expected = self.security_hash(self.request, form) 77 return constant_time_compare(token, expected) 60 78 61 79 def preview_post(self, request): 62 "Validates the POST data. If valid, displays the preview page. Else, redisplays form." 63 f = self.form(request.POST, auto_id=self.get_auto_id()) 64 context = self.get_context(request, f) 65 if f.is_valid(): 66 self.process_preview(request, f, context) 67 context['hash_field'] = self.unused_name('hash') 68 context['hash_value'] = self.security_hash(request, f) 69 return render_to_response(self.preview_template, context, context_instance=RequestContext(request)) 80 """ For backwards compatibility. failed_hash calls this method by 81 default. """ 82 self._stage = self._preview_stage 83 return self.post(request) 84 85 def form_valid(self, form): 86 context = self._get_context_data(form) 87 if self._stage == self._preview_stage: 88 self.process_preview(self.request, form, context) 89 context['hash_field'] = self.unused_name(HASH_FIELD) 90 context['hash_value'] = self.security_hash(self.request, form) 91 self.template_name = self.preview_template 92 return self.render_to_response(context) 70 93 else: 71 return render_to_response(self.form_template, context, context_instance=RequestContext(request)) 94 form_hash = self.request.POST.get(self.unused_name(HASH_FIELD), '') 95 if not self._check_security_hash(form_hash, form): 96 return self.failed_hash(self.request) # Security hash failed. 97 return self.done(self.request, form.cleaned_data) 72 98 73 def _check_security_hash(self, token, request, form): 74 expected = self.security_hash(request, form) 75 return constant_time_compare(token, expected) 76 77 def post_post(self, request): 78 "Validates the POST data. If valid, calls done(). Else, redisplays form." 79 f = self.form(request.POST, auto_id=self.get_auto_id()) 80 if f.is_valid(): 81 if not self._check_security_hash(request.POST.get(self.unused_name('hash'), ''), 82 request, f): 83 return self.failed_hash(request) # Security hash failed. 84 return self.done(request, f.cleaned_data) 85 else: 86 return render_to_response(self.form_template, 87 self.get_context(request, f), 88 context_instance=RequestContext(request)) 99 def form_invalid(self, form): 100 context = self._get_context_data(form) 101 self.template_name = self.form_template 102 return render_to_response(context) 89 103 90 104 # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ######################## 91 105 … … class FormPreview(object): 96 110 """ 97 111 return AUTO_ID 98 112 99 def get_initial(self, request ):113 def get_initial(self, request=None): 100 114 """ 101 115 Takes a request argument and returns a dictionary to pass to the form's 102 116 ``initial`` kwarg when the form is being created from an HTTP get. 103 117 """ 104 return {}118 return self.initial 105 119 106 120 def get_context(self, request, form): 107 121 "Context for template rendering." 108 return {'form': form, 'stage_field': self.unused_name('stage'), 'state': self.state} 109 122 context = { 123 'form': form, 124 'stage_field': self.unused_name(STAGE_FIELD), 125 'state': self.state 126 } 127 return context 128 129 def get_form_kwargs(self): 130 """ This is overriden to maintain backward compatibility and pass 131 the request to get_initial. """ 132 kwargs = { 133 'initial': self.get_initial(self.request), 134 'auto_id': self.get_auto_id() 135 } 136 if self.request.method in ('POST', 'PUT'): 137 kwargs.update({ 138 'data': self.request.POST, 139 'files': self.request.FILES, 140 }) 141 return kwargs 110 142 111 143 def parse_params(self, *args, **kwargs): 112 144 """ 113 Given captured args and kwargs from the URLconf, saves something in 114 self.state and/or raises Http404 if necessary. 145 Called in dispatch() prior to delegating the request to get() or post(). 146 Given captured args and kwargs from the URLconf, allows the ability to 147 save something on the instance and/or raises Http404 if necessary. 115 148 116 149 For example, this URLconf captures a user_id variable: 117 150 -
django/contrib/formtools/tests/__init__.py
diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py index 7084386..cae9c9a 100644
a b warnings.filterwarnings('ignore', category=PendingDeprecationWarning, 17 17 18 18 success_string = "Done was called!" 19 19 20 20 21 class TestFormPreview(preview.FormPreview): 21 22 def get_context(self, request, form): 22 23 context = super(TestFormPreview, self).get_context(request, form) 23 24 context.update({'custom_context': True}) 24 25 return context 25 26 27 def get_context_data(self, **kwargs): 28 context = super(TestFormPreview, self).get_context_data(**kwargs) 29 context['more_custom_context'] = True 30 return context 31 26 32 def get_initial(self, request): 27 33 return {'field1': 'Works!'} 28 34 … … class PreviewTests(TestCase): 67 73 stage = self.input % 1 68 74 self.assertContains(response, stage, 1) 69 75 self.assertEqual(response.context['custom_context'], True) 76 self.assertEqual(response.context['is_bound_form'], False) 70 77 self.assertEqual(response.context['form'].initial, {'field1': 'Works!'}) 71 78 72 79 def test_form_preview(self): … … class PreviewTests(TestCase): 86 93 stage = self.input % 2 87 94 self.assertContains(response, stage, 1) 88 95 96 # Check that the correct context was passed to the template 97 self.assertEqual(response.context['custom_context'], True) 98 self.assertEqual(response.context['is_bound_form'], True) 99 89 100 def test_form_submit(self): 90 101 """ 91 102 Test contrib.formtools.preview form submittal. … … class PreviewTests(TestCase): 140 151 response = self.client.post('/preview/', self.test_data) 141 152 self.assertEqual(response.content, success_string) 142 153 143 144 154 def test_form_submit_bad_hash(self): 145 155 """ 146 156 Test contrib.formtools.preview form submittal does not proceed … … class PreviewTests(TestCase): 154 164 self.assertNotEqual(response.content, success_string) 155 165 hash = utils.form_hmac(TestForm(self.test_data)) + "bad" 156 166 self.test_data.update({'hash': hash}) 157 response = self.client.post('/previewpreview/', self.test_data) 167 response = self.client.post('/preview/', self.test_data) 168 self.assertTemplateUsed(response, 'formtools/preview.html') 158 169 self.assertNotEqual(response.content, success_string) 159 170 160 171