Index: django/contrib/formtools/wizard.py
===================================================================
--- django/contrib/formtools/wizard.py	(revision 8448)
+++ django/contrib/formtools/wizard.py	(working copy)
@@ -12,6 +12,7 @@
 from django.shortcuts import render_to_response
 from django.template.context import RequestContext
 from django.utils.hashcompat import md5_constructor
+from django.contrib.formtools.utils import security_hash
 
 class FormWizard(object):
     # Dictionary of extra template context variables.
@@ -140,18 +141,10 @@
         """
         Calculates the security hash for the given HttpRequest and Form instances.
 
-        This creates a list of the form field names/values in a deterministic
-        order, pickles the result with the SECRET_KEY setting and takes an md5
-        hash of that.
-
         Subclasses may want to take into account request-specific information,
         such as the IP address.
         """
-        data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY]
-        # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
-        # Python 2.3, but Django requires 2.3 anyway, so that's OK.
-        pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
-        return md5_constructor(pickled).hexdigest()
+        return security_hash(request, form)
 
     def determine_step(self, request, *args, **kwargs):
         """
Index: django/contrib/formtools/utils.py
===================================================================
--- django/contrib/formtools/utils.py	(revision 0)
+++ django/contrib/formtools/utils.py	(revision 0)
@@ -0,0 +1,31 @@
+import cPickle as pickle
+
+from django.conf import settings
+from django.utils.hashcompat import md5_constructor
+from django.forms import BooleanField
+
+def security_hash(request, form):
+        """
+        Calculates the security hash for the given Form instance.
+
+        This creates a list of the form field names/values in a deterministic
+        order, pickles the result with the SECRET_KEY setting and takes an md5
+        hash of that.
+        """
+        # Ensure that the hash does not change when a BooleanField's bound 
+        # data is a string `False' or a boolean False.
+        # DRY: Rather than re-coding this special behaviour here, we 
+        # create a dummy BooleanField and call its clean method to get a 
+        # boolean True or False verdict that is consistent with 
+        # BooleanField.clean()
+        dummy_bool = BooleanField(required=False)
+        def _cleaned_data(bf):
+            if isinstance(bf.field, BooleanField):
+                return dummy_bool.clean(bf.data)
+            return bf.data
+        data = [(bf.name, _cleaned_data(bf) or '') for bf in form] + [settings.SECRET_KEY]
+        # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
+        # Python 2.3, but Django requires 2.3 anyway, so that's OK.
+        pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
+        return md5_constructor(pickled).hexdigest()
+
Index: django/contrib/formtools/preview.py
===================================================================
--- django/contrib/formtools/preview.py	(revision 8448)
+++ django/contrib/formtools/preview.py	(working copy)
@@ -9,6 +9,7 @@
 from django.shortcuts import render_to_response
 from django.template.context import RequestContext
 from django.utils.hashcompat import md5_constructor
+from django.contrib.formtools.utils import security_hash
 
 AUTO_ID = 'formtools_%s' # Each form here uses this as its auto_id parameter.
 
@@ -97,20 +98,12 @@
 
     def security_hash(self, request, form):
         """
-        Calculates the security hash for the given Form instance.
+        Calculates the security hash for the given HttpRequest and Form instances.
 
-        This creates a list of the form field names/values in a deterministic
-        order, pickles the result with the SECRET_KEY setting and takes an md5
-        hash of that.
-
-        Subclasses may want to take into account request-specific information
+        Subclasses may want to take into account request-specific information,
         such as the IP address.
         """
-        data = [(bf.name, bf.data or '') for bf in form] + [settings.SECRET_KEY]
-        # Use HIGHEST_PROTOCOL because it's the most efficient. It requires
-        # Python 2.3, but Django requires 2.3 anyway, so that's OK.
-        pickled = pickle.dumps(data, pickle.HIGHEST_PROTOCOL)
-        return md5_constructor(pickled).hexdigest()
+        return security_hash(request, form)
 
     def failed_hash(self, request):
         "Returns an HttpResponse in the case of an invalid security hash."
Index: django/contrib/formtools/tests.py
===================================================================
--- django/contrib/formtools/tests.py	(revision 8448)
+++ django/contrib/formtools/tests.py	(working copy)
@@ -4,8 +4,6 @@
 from django.test import TestCase
 
 success_string = "Done was called!"
-test_data = {'field1': u'foo',
-             'field1_': u'asdf'}
 
 
 class TestFormPreview(preview.FormPreview):
@@ -17,6 +15,7 @@
 class TestForm(forms.Form):
     field1 = forms.CharField()
     field1_ = forms.CharField()
+    bool1 = forms.BooleanField(required=False)
 
 
 class PreviewTests(TestCase):
@@ -27,6 +26,7 @@
         self.preview = preview.FormPreview(TestForm)
         input_template = '<input type="hidden" name="%s" value="%s" />'
         self.input = input_template % (self.preview.unused_name('stage'), "%d")
+        self.test_data = {'field1':u'foo', 'field1_':u'asdf'}
 
     def test_unused_name(self):
         """
@@ -59,8 +59,8 @@
         """
         # Pass strings for form submittal and add stage variable to
         # show we previously saw first stage of the form.
-        test_data.update({'stage': 1})
-        response = self.client.post('/test1/', test_data)
+        self.test_data.update({'stage': 1})
+        response = self.client.post('/test1/', self.test_data)
         # Check to confirm stage is set to 2 in output form.
         stage = self.input % 2
         self.assertContains(response, stage, 1)
@@ -77,11 +77,30 @@
         """
         # Pass strings for form submittal and add stage variable to
         # show we previously saw first stage of the form.
-        test_data.update({'stage': 2})
-        response = self.client.post('/test1/', test_data)
+        self.test_data.update({'stage':2})
+        response = self.client.post('/test1/', self.test_data)
         self.failIfEqual(response.content, success_string)
-        hash = self.preview.security_hash(None, TestForm(test_data))
-        test_data.update({'hash': hash})
-        response = self.client.post('/test1/', test_data)
+        hash = self.preview.security_hash(None, TestForm(self.test_data))
+        self.test_data.update({'hash': hash})
+        response = self.client.post('/test1/', self.test_data)
         self.assertEqual(response.content, success_string)
 
+    def test_bool_submit(self):
+        """
+        Test contrib.formtools.preview form submittal when form contains:
+        BooleanField(required=False)
+
+        Ticket: #6209 - When an unchecked BooleanField is previewed, the preview
+        form's hash would be computed with no value for ``bool1``. However, when
+        the preview form is rendered, the unchecked hidden BooleanField would be 
+        rendered with the string value 'False'. So when the preview form is
+        resubmitted, the hash would be computed with the value 'False' for
+        ``bool1``. We need to make sure the hashes are the same in both cases.
+
+        """
+        self.test_data.update({'stage':2})
+        hash = self.preview.security_hash(None, TestForm(self.test_data))
+        self.test_data.update({'hash':hash, 'bool1':u'False'})
+        response = self.client.post('/test1/', self.test_data)
+        self.assertEqual(response.content, success_string)
+
