Ticket #16174: cbv-formpreview2.diff

File cbv-formpreview2.diff, 10.5 KB (added by Ryan Kaskel, 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  
    11"""
    22Formtools Preview application.
    33"""
    4 
    5 try:
    6     import cPickle as pickle
    7 except ImportError:
    8     import pickle
    9 
    104from django.conf import settings
    11 from django.http import Http404
    125from django.shortcuts import render_to_response
    13 from django.template.context import RequestContext
    146from django.utils.crypto import constant_time_compare
    157from django.contrib.formtools.utils import form_hmac
     8from django.views.generic import FormView
    169
     10PREVIEW_STAGE = 'preview'
     11POST_STAGE = 'post'
    1712AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
     13STAGE_FIELD = 'stage'
     14HASH_FIELD = 'hash'
    1815
    19 class FormPreview(object):
     16class FormPreview(FormView):
    2017    preview_template = 'formtools/preview.html'
    2118    form_template = 'formtools/form.html'
    2219
    2320    # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
    2421
    25     def __init__(self, form):
     22    def __init__(self, form_class, *args, **kwargs):
     23        super(FormPreview, self).__init__(*args, **kwargs)
    2624        # 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 = {}
    2832
    2933    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
    3141        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)
    3744
    3845    def unused_name(self, name):
    3946        """
    class FormPreview(object):  
    4552        """
    4653        while 1:
    4754            try:
    48                 f = self.form.base_fields[name]
     55                self.form_class.base_fields[name]
    4956            except KeyError:
    5057                break # This field name isn't being used by the form.
    5158            name += '_'
    5259        return name
    5360
    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):
    5568        "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)
    6078
    6179    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)
    7093        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)
    7298
    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)
    89103
    90104    # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
    91105
    class FormPreview(object):  
    96110        """
    97111        return AUTO_ID
    98112
    99     def get_initial(self, request):
     113    def get_initial(self, request=None):
    100114        """
    101115        Takes a request argument and returns a dictionary to pass to the form's
    102116        ``initial`` kwarg when the form is being created from an HTTP get.
    103117        """
    104         return {}
     118        return self.initial
    105119
    106120    def get_context(self, request, form):
    107121        "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
    110142
    111143    def parse_params(self, *args, **kwargs):
    112144        """
    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.
    115148
    116149        For example, this URLconf captures a user_id variable:
    117150
  • 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,  
    1717
    1818success_string = "Done was called!"
    1919
     20
    2021class TestFormPreview(preview.FormPreview):
    2122    def get_context(self, request, form):
    2223        context = super(TestFormPreview, self).get_context(request, form)
    2324        context.update({'custom_context': True})
    2425        return context
    2526
     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
    2632    def get_initial(self, request):
    2733        return {'field1': 'Works!'}
    2834
    class PreviewTests(TestCase):  
    6773        stage = self.input % 1
    6874        self.assertContains(response, stage, 1)
    6975        self.assertEqual(response.context['custom_context'], True)
     76        self.assertEqual(response.context['is_bound_form'], False)
    7077        self.assertEqual(response.context['form'].initial, {'field1': 'Works!'})
    7178
    7279    def test_form_preview(self):
    class PreviewTests(TestCase):  
    8693        stage = self.input % 2
    8794        self.assertContains(response, stage, 1)
    8895
     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
    89100    def test_form_submit(self):
    90101        """
    91102        Test contrib.formtools.preview form submittal.
    class PreviewTests(TestCase):  
    140151        response = self.client.post('/preview/', self.test_data)
    141152        self.assertEqual(response.content, success_string)
    142153
    143 
    144154    def test_form_submit_bad_hash(self):
    145155        """
    146156        Test contrib.formtools.preview form submittal does not proceed
    class PreviewTests(TestCase):  
    154164        self.assertNotEqual(response.content, success_string)
    155165        hash = utils.form_hmac(TestForm(self.test_data)) + "bad"
    156166        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')
    158169        self.assertNotEqual(response.content, success_string)
    159170
    160171
Back to Top