Ticket #6209: patch-#6209-preview_wizard.diff

File patch-#6209-preview_wizard.diff, 7.8 KB (added by Rajesh Dhawan, 16 years ago)

Revised patch to share security hash implementation between preview and wizard

  • django/contrib/formtools/wizard.py

     
    1212from django.shortcuts import render_to_response
    1313from django.template.context import RequestContext
    1414from django.utils.hashcompat import md5_constructor
     15from django.contrib.formtools.utils import security_hash
    1516
    1617class FormWizard(object):
    1718    # Dictionary of extra template context variables.
     
    140141        """
    141142        Calculates the security hash for the given HttpRequest and Form instances.
    142143
    143         This creates a list of the form field names/values in a deterministic
    144         order, pickles the result with the SECRET_KEY setting and takes an md5
    145         hash of that.
    146 
    147144        Subclasses may want to take into account request-specific information,
    148145        such as the IP address.
    149146        """
    150         data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY]
    151         # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
    152         # Python 2.3, but Django requires 2.3 anyway, so that's OK.
    153         pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
    154         return md5_constructor(pickled).hexdigest()
     147        return security_hash(request, form)
    155148
    156149    def determine_step(self, request, *args, **kwargs):
    157150        """
  • django/contrib/formtools/utils.py

     
     1import cPickle as pickle
     2
     3from django.conf import settings
     4from django.utils.hashcompat import md5_constructor
     5from django.forms import BooleanField
     6
     7def security_hash(request, form):
     8        """
     9        Calculates the security hash for the given Form instance.
     10
     11        This creates a list of the form field names/values in a deterministic
     12        order, pickles the result with the SECRET_KEY setting and takes an md5
     13        hash of that.
     14        """
     15        # Ensure that the hash does not change when a BooleanField's bound
     16        # data is a string `False' or a boolean False.
     17        # DRY: Rather than re-coding this special behaviour here, we
     18        # create a dummy BooleanField and call its clean method to get a
     19        # boolean True or False verdict that is consistent with
     20        # BooleanField.clean()
     21        dummy_bool = BooleanField(required=False)
     22        def _cleaned_data(bf):
     23            if isinstance(bf.field, BooleanField):
     24                return dummy_bool.clean(bf.data)
     25            return bf.data
     26        data = [(bf.name, _cleaned_data(bf) or '') for bf in form] + [settings.SECRET_KEY]
     27        # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
     28        # Python 2.3, but Django requires 2.3 anyway, so that's OK.
     29        pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
     30        return md5_constructor(pickled).hexdigest()
     31
  • django/contrib/formtools/preview.py

     
    99from django.shortcuts import render_to_response
    1010from django.template.context import RequestContext
    1111from django.utils.hashcompat import md5_constructor
     12from django.contrib.formtools.utils import security_hash
    1213
    1314AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
    1415
     
    9798
    9899    def security_hash(self, request, form):
    99100        """
    100         Calculates the security hash for the given Form instance.
     101        Calculates the security hash for the given HttpRequest and Form instances.
    101102
    102         This creates a list of the form field names/values in a deterministic
    103         order, pickles the result with the SECRET_KEY setting and takes an md5
    104         hash of that.
    105 
    106         Subclasses may want to take into account request-specific information
     103        Subclasses may want to take into account request-specific information,
    107104        such as the IP address.
    108105        """
    109         data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY]
    110         # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
    111         # Python 2.3, but Django requires 2.3 anyway, so that's OK.
    112         pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
    113         return md5_constructor(pickled).hexdigest()
     106        return security_hash(request, form)
    114107
    115108    def failed_hash(self, request):
    116109        "Returns an HttpResponse in the case of an invalid security hash."
  • django/contrib/formtools/tests.py

     
    44from django.test import TestCase
    55
    66success_string = "Done was called!"
    7 test_data = {'field1': u'foo',
    8              'field1_': u'asdf'}
    97
    108
    119class TestFormPreview(preview.FormPreview):
     
    1715class TestForm(forms.Form):
    1816    field1 = forms.CharField()
    1917    field1_ = forms.CharField()
     18    bool1 = forms.BooleanField(required=False)
    2019
    2120
    2221class PreviewTests(TestCase):
     
    2726        self.preview = preview.FormPreview(TestForm)
    2827        input_template = '<input type="hidden" name="%s" value="%s" />'
    2928        self.input = input_template % (self.preview.unused_name('stage'), "%d")
     29        self.test_data = {'field1':u'foo', 'field1_':u'asdf'}
    3030
    3131    def test_unused_name(self):
    3232        """
     
    5959        """
    6060        # Pass strings for form submittal and add stage variable to
    6161        # show we previously saw first stage of the form.
    62         test_data.update({'stage': 1})
    63         response = self.client.post('/test1/', test_data)
     62        self.test_data.update({'stage': 1})
     63        response = self.client.post('/test1/', self.test_data)
    6464        # Check to confirm stage is set to 2 in output form.
    6565        stage = self.input % 2
    6666        self.assertContains(response, stage, 1)
     
    7777        """
    7878        # Pass strings for form submittal and add stage variable to
    7979        # show we previously saw first stage of the form.
    80         test_data.update({'stage': 2})
    81         response = self.client.post('/test1/', test_data)
     80        self.test_data.update({'stage':2})
     81        response = self.client.post('/test1/', self.test_data)
    8282        self.failIfEqual(response.content, success_string)
    83         hash = self.preview.security_hash(None, TestForm(test_data))
    84         test_data.update({'hash': hash})
    85         response = self.client.post('/test1/', test_data)
     83        hash = self.preview.security_hash(None, TestForm(self.test_data))
     84        self.test_data.update({'hash': hash})
     85        response = self.client.post('/test1/', self.test_data)
    8686        self.assertEqual(response.content, success_string)
    8787
     88    def test_bool_submit(self):
     89        """
     90        Test contrib.formtools.preview form submittal when form contains:
     91        BooleanField(required=False)
     92
     93        Ticket: #6209 - When an unchecked BooleanField is previewed, the preview
     94        form's hash would be computed with no value for ``bool1``. However, when
     95        the preview form is rendered, the unchecked hidden BooleanField would be
     96        rendered with the string value 'False'. So when the preview form is
     97        resubmitted, the hash would be computed with the value 'False' for
     98        ``bool1``. We need to make sure the hashes are the same in both cases.
     99
     100        """
     101        self.test_data.update({'stage':2})
     102        hash = self.preview.security_hash(None, TestForm(self.test_data))
     103        self.test_data.update({'hash':hash, 'bool1':u'False'})
     104        response = self.client.post('/test1/', self.test_data)
     105        self.assertEqual(response.content, success_string)
     106
Back to Top