diff --git a/django/contrib/formtools/tests/__init__.py b/django/contrib/formtools/tests/__init__.py
index be0372a..5796852 100644
--- a/django/contrib/formtools/tests/__init__.py
+++ b/django/contrib/formtools/tests/__init__.py
@@ -8,6 +8,11 @@ from django.test import TestCase
 from django.test.utils import get_warnings_state, restore_warnings_state
 from django.utils import unittest
 
+from django.contrib.formtools.wizard.tests import *
+
+warnings.filterwarnings('ignore', category=PendingDeprecationWarning,
+                        module='django.contrib.formtools.wizard')
+
 
 success_string = "Done was called!"
 
diff --git a/django/contrib/formtools/wizard.py b/django/contrib/formtools/wizard.py
deleted file mode 100644
index c19578c..0000000
--- a/django/contrib/formtools/wizard.py
+++ /dev/null
@@ -1,271 +0,0 @@
-"""
-FormWizard class -- implements a multi-page form, validating between each
-step and storing the form's state as HTML hidden fields so that no state is
-stored on the server side.
-"""
-
-try:
-    import cPickle as pickle
-except ImportError:
-    import pickle
-
-from django import forms
-from django.conf import settings
-from django.contrib.formtools.utils import form_hmac
-from django.http import Http404
-from django.shortcuts import render_to_response
-from django.template.context import RequestContext
-from django.utils.crypto import constant_time_compare
-from django.utils.translation import ugettext_lazy as _
-from django.utils.decorators import method_decorator
-from django.views.decorators.csrf import csrf_protect
-
-
-class FormWizard(object):
-    # The HTML (and POST data) field name for the "step" variable.
-    step_field_name="wizard_step"
-
-    # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
-
-    def __init__(self, form_list, initial=None):
-        """
-        Start a new wizard with a list of forms.
-
-        form_list should be a list of Form classes (not instances).
-        """
-        self.form_list = form_list[:]
-        self.initial = initial or {}
-
-        # Dictionary of extra template context variables.
-        self.extra_context = {}
-
-        # A zero-based counter keeping track of which step we're in.
-        self.step = 0
-
-    def __repr__(self):
-        return "step: %d\nform_list: %s\ninitial_data: %s" % (self.step, self.form_list, self.initial)
-
-    def get_form(self, step, data=None):
-        "Helper method that returns the Form instance for the given step."
-        # Sanity check.
-        if step >= self.num_steps():
-            raise Http404('Step %s does not exist' % step)
-        return self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))
-
-    def num_steps(self):
-        "Helper method that returns the number of steps."
-        # You might think we should just set "self.num_steps = len(form_list)"
-        # in __init__(), but this calculation needs to be dynamic, because some
-        # hook methods might alter self.form_list.
-        return len(self.form_list)
-
-    def _check_security_hash(self, token, request, form):
-        expected = self.security_hash(request, form)
-        return constant_time_compare(token, expected)
-
-    @method_decorator(csrf_protect)
-    def __call__(self, request, *args, **kwargs):
-        """
-        Main method that does all the hard work, conforming to the Django view
-        interface.
-        """
-        if 'extra_context' in kwargs:
-            self.extra_context.update(kwargs['extra_context'])
-        current_step = self.determine_step(request, *args, **kwargs)
-        self.parse_params(request, *args, **kwargs)
-
-        # Validate and process all the previous forms before instantiating the
-        # current step's form in case self.process_step makes changes to
-        # self.form_list.
-
-        # If any of them fails validation, that must mean the validator relied
-        # on some other input, such as an external Web site.
-
-        # It is also possible that alidation might fail under certain attack
-        # situations: an attacker might be able to bypass previous stages, and
-        # generate correct security hashes for all the skipped stages by virtue
-        # of:
-        #  1) having filled out an identical form which doesn't have the
-        #     validation (and does something different at the end),
-        #  2) or having filled out a previous version of the same form which
-        #     had some validation missing,
-        #  3) or previously having filled out the form when they had more
-        #     privileges than they do now.
-        #
-        # Since the hashes only take into account values, and not other other
-        # validation the form might do, we must re-do validation now for
-        # security reasons.
-        previous_form_list = []
-        for i in range(current_step):
-            f = self.get_form(i, request.POST)
-            if not self._check_security_hash(request.POST.get("hash_%d" % i, ''),
-                                             request, f):
-                return self.render_hash_failure(request, i)
-
-            if not f.is_valid():
-                return self.render_revalidation_failure(request, i, f)
-            else:
-                self.process_step(request, f, i)
-                previous_form_list.append(f)
-
-        # Process the current step. If it's valid, go to the next step or call
-        # done(), depending on whether any steps remain.
-        if request.method == 'POST':
-            form = self.get_form(current_step, request.POST)
-        else:
-            form = self.get_form(current_step)
-
-        if form.is_valid():
-            self.process_step(request, form, current_step)
-            next_step = current_step + 1
-
-            if next_step == self.num_steps():
-                return self.done(request, previous_form_list + [form])
-            else:
-                form = self.get_form(next_step)
-                self.step = current_step = next_step
-
-        return self.render(form, request, current_step)
-
-    def render(self, form, request, step, context=None):
-        "Renders the given Form object, returning an HttpResponse."
-        old_data = request.POST
-        prev_fields = []
-        if old_data:
-            hidden = forms.HiddenInput()
-            # Collect all data from previous steps and render it as HTML hidden fields.
-            for i in range(step):
-                old_form = self.get_form(i, old_data)
-                hash_name = 'hash_%s' % i
-                prev_fields.extend([bf.as_hidden() for bf in old_form])
-                prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))
-        return self.render_template(request, form, ''.join(prev_fields), step, context)
-
-    # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
-
-    def prefix_for_step(self, step):
-        "Given the step, returns a Form prefix to use."
-        return str(step)
-
-    def render_hash_failure(self, request, step):
-        """
-        Hook for rendering a template if a hash check failed.
-
-        step is the step that failed. Any previous step is guaranteed to be
-        valid.
-
-        This default implementation simply renders the form for the given step,
-        but subclasses may want to display an error message, etc.
-        """
-        return self.render(self.get_form(step), request, step, context={'wizard_error': _('We apologize, but your form has expired. Please continue filling out the form from this page.')})
-
-    def render_revalidation_failure(self, request, step, form):
-        """
-        Hook for rendering a template if final revalidation failed.
-
-        It is highly unlikely that this point would ever be reached, but See
-        the comment in __call__() for an explanation.
-        """
-        return self.render(form, request, step)
-
-    def security_hash(self, request, form):
-        """
-        Calculates the security hash for the given HttpRequest and Form instances.
-
-        Subclasses may want to take into account request-specific information,
-        such as the IP address.
-        """
-        return form_hmac(form)
-
-    def determine_step(self, request, *args, **kwargs):
-        """
-        Given the request object and whatever *args and **kwargs were passed to
-        __call__(), returns the current step (which is zero-based).
-
-        Note that the result should not be trusted. It may even be a completely
-        invalid number. It's not the job of this method to validate it.
-        """
-        if not request.POST:
-            return 0
-        try:
-            step = int(request.POST.get(self.step_field_name, 0))
-        except ValueError:
-            return 0
-        return step
-
-    def parse_params(self, request, *args, **kwargs):
-        """
-        Hook for setting some state, given the request object and whatever
-        *args and **kwargs were passed to __call__(), sets some state.
-
-        This is called at the beginning of __call__().
-        """
-        pass
-
-    def get_template(self, step):
-        """
-        Hook for specifying the name of the template to use for a given step.
-
-        Note that this can return a tuple of template names if you'd like to
-        use the template system's select_template() hook.
-        """
-        return 'forms/wizard.html'
-
-    def render_template(self, request, form, previous_fields, step, context=None):
-        """
-        Renders the template for the given step, returning an HttpResponse object.
-
-        Override this method if you want to add a custom context, return a
-        different MIME type, etc. If you only need to override the template
-        name, use get_template() instead.
-
-        The template will be rendered with the following context:
-            step_field -- The name of the hidden field containing the step.
-            step0      -- The current step (zero-based).
-            step       -- The current step (one-based).
-            step_count -- The total number of steps.
-            form       -- The Form instance for the current step (either empty
-                          or with errors).
-            previous_fields -- A string representing every previous data field,
-                          plus hashes for completed forms, all in the form of
-                          hidden fields. Note that you'll need to run this
-                          through the "safe" template filter, to prevent
-                          auto-escaping, because it's raw HTML.
-        """
-        context = context or {}
-        context.update(self.extra_context)
-        return render_to_response(self.get_template(step), dict(context,
-            step_field=self.step_field_name,
-            step0=step,
-            step=step + 1,
-            step_count=self.num_steps(),
-            form=form,
-            previous_fields=previous_fields
-        ), context_instance=RequestContext(request))
-
-    def process_step(self, request, form, step):
-        """
-        Hook for modifying the FormWizard's internal state, given a fully
-        validated Form object. The Form is guaranteed to have clean, valid
-        data.
-
-        This method should *not* modify any of that data. Rather, it might want
-        to set self.extra_context or dynamically alter self.form_list, based on
-        previously submitted forms.
-
-        Note that this method is called every time a page is rendered for *all*
-        submitted steps.
-        """
-        pass
-
-    # METHODS SUBCLASSES MUST OVERRIDE ########################################
-
-    def done(self, request, form_list):
-        """
-        Hook for doing something with the validated data. This is responsible
-        for the final processing.
-
-        form_list is a list of Form instances, each containing clean, valid
-        data.
-        """
-        raise NotImplementedError("Your %s class has not defined a done() method, which is required." % self.__class__.__name__)
diff --git a/django/contrib/formtools/wizard/__init__.py b/django/contrib/formtools/wizard/__init__.py
new file mode 100644
index 0000000..11645b1
--- /dev/null
+++ b/django/contrib/formtools/wizard/__init__.py
@@ -0,0 +1,270 @@
+"""
+FormWizard class -- implements a multi-page form, validating between each
+step and storing the form's state as HTML hidden fields so that no state is
+stored on the server side.
+"""
+from django.forms import HiddenInput
+from django.contrib.formtools.utils import form_hmac
+from django.http import Http404
+from django.shortcuts import render_to_response
+from django.template.context import RequestContext
+from django.utils.crypto import constant_time_compare
+from django.utils.translation import ugettext_lazy as _
+from django.utils.decorators import method_decorator
+from django.views.decorators.csrf import csrf_protect
+
+
+class FormWizard(object):
+    # The HTML (and POST data) field name for the "step" variable.
+    step_field_name="wizard_step"
+
+    # METHODS SUBCLASSES SHOULDN'T OVERRIDE ###################################
+
+    def __init__(self, form_list, initial=None):
+        """
+        Start a new wizard with a list of forms.
+
+        form_list should be a list of Form classes (not instances).
+        """
+        self.form_list = form_list[:]
+        self.initial = initial or {}
+
+        # Dictionary of extra template context variables.
+        self.extra_context = {}
+
+        # A zero-based counter keeping track of which step we're in.
+        self.step = 0
+
+        import warnings
+        warnings.warn(
+            'Old-style form wizards have been deprecated; use the class-based '
+            'views in django.contrib.formtools.wizard.views instead.',
+            PendingDeprecationWarning)
+
+    def __repr__(self):
+        return "step: %d\nform_list: %s\ninitial_data: %s" % (self.step, self.form_list, self.initial)
+
+    def get_form(self, step, data=None):
+        "Helper method that returns the Form instance for the given step."
+        # Sanity check.
+        if step >= self.num_steps():
+            raise Http404('Step %s does not exist' % step)
+        return self.form_list[step](data, prefix=self.prefix_for_step(step), initial=self.initial.get(step, None))
+
+    def num_steps(self):
+        "Helper method that returns the number of steps."
+        # You might think we should just set "self.num_steps = len(form_list)"
+        # in __init__(), but this calculation needs to be dynamic, because some
+        # hook methods might alter self.form_list.
+        return len(self.form_list)
+
+    def _check_security_hash(self, token, request, form):
+        expected = self.security_hash(request, form)
+        return constant_time_compare(token, expected)
+
+    @method_decorator(csrf_protect)
+    def __call__(self, request, *args, **kwargs):
+        """
+        Main method that does all the hard work, conforming to the Django view
+        interface.
+        """
+        if 'extra_context' in kwargs:
+            self.extra_context.update(kwargs['extra_context'])
+        current_step = self.get_current_or_first_step(request, *args, **kwargs)
+        self.parse_params(request, *args, **kwargs)
+
+        # Validate and process all the previous forms before instantiating the
+        # current step's form in case self.process_step makes changes to
+        # self.form_list.
+
+        # If any of them fails validation, that must mean the validator relied
+        # on some other input, such as an external Web site.
+
+        # It is also possible that alidation might fail under certain attack
+        # situations: an attacker might be able to bypass previous stages, and
+        # generate correct security hashes for all the skipped stages by virtue
+        # of:
+        #  1) having filled out an identical form which doesn't have the
+        #     validation (and does something different at the end),
+        #  2) or having filled out a previous version of the same form which
+        #     had some validation missing,
+        #  3) or previously having filled out the form when they had more
+        #     privileges than they do now.
+        #
+        # Since the hashes only take into account values, and not other other
+        # validation the form might do, we must re-do validation now for
+        # security reasons.
+        previous_form_list = []
+        for i in range(current_step):
+            f = self.get_form(i, request.POST)
+            if not self._check_security_hash(request.POST.get("hash_%d" % i, ''),
+                                             request, f):
+                return self.render_hash_failure(request, i)
+
+            if not f.is_valid():
+                return self.render_revalidation_failure(request, i, f)
+            else:
+                self.process_step(request, f, i)
+                previous_form_list.append(f)
+
+        # Process the current step. If it's valid, go to the next step or call
+        # done(), depending on whether any steps remain.
+        if request.method == 'POST':
+            form = self.get_form(current_step, request.POST)
+        else:
+            form = self.get_form(current_step)
+
+        if form.is_valid():
+            self.process_step(request, form, current_step)
+            next_step = current_step + 1
+
+            if next_step == self.num_steps():
+                return self.done(request, previous_form_list + [form])
+            else:
+                form = self.get_form(next_step)
+                self.step = current_step = next_step
+
+        return self.render(form, request, current_step)
+
+    def render(self, form, request, step, context=None):
+        "Renders the given Form object, returning an HttpResponse."
+        old_data = request.POST
+        prev_fields = []
+        if old_data:
+            hidden = HiddenInput()
+            # Collect all data from previous steps and render it as HTML hidden fields.
+            for i in range(step):
+                old_form = self.get_form(i, old_data)
+                hash_name = 'hash_%s' % i
+                prev_fields.extend([bf.as_hidden() for bf in old_form])
+                prev_fields.append(hidden.render(hash_name, old_data.get(hash_name, self.security_hash(request, old_form))))
+        return self.render_template(request, form, ''.join(prev_fields), step, context)
+
+    # METHODS SUBCLASSES MIGHT OVERRIDE IF APPROPRIATE ########################
+
+    def prefix_for_step(self, step):
+        "Given the step, returns a Form prefix to use."
+        return str(step)
+
+    def render_hash_failure(self, request, step):
+        """
+        Hook for rendering a template if a hash check failed.
+
+        step is the step that failed. Any previous step is guaranteed to be
+        valid.
+
+        This default implementation simply renders the form for the given step,
+        but subclasses may want to display an error message, etc.
+        """
+        return self.render(self.get_form(step), request, step, context={'wizard_error': _('We apologize, but your form has expired. Please continue filling out the form from this page.')})
+
+    def render_revalidation_failure(self, request, step, form):
+        """
+        Hook for rendering a template if final revalidation failed.
+
+        It is highly unlikely that this point would ever be reached, but See
+        the comment in __call__() for an explanation.
+        """
+        return self.render(form, request, step)
+
+    def security_hash(self, request, form):
+        """
+        Calculates the security hash for the given HttpRequest and Form instances.
+
+        Subclasses may want to take into account request-specific information,
+        such as the IP address.
+        """
+        return form_hmac(form)
+
+    def get_current_or_first_step(self, request, *args, **kwargs):
+        """
+        Given the request object and whatever *args and **kwargs were passed to
+        __call__(), returns the current step (which is zero-based).
+
+        Note that the result should not be trusted. It may even be a completely
+        invalid number. It's not the job of this method to validate it.
+        """
+        if not request.POST:
+            return 0
+        try:
+            step = int(request.POST.get(self.step_field_name, 0))
+        except ValueError:
+            return 0
+        return step
+
+    def parse_params(self, request, *args, **kwargs):
+        """
+        Hook for setting some state, given the request object and whatever
+        *args and **kwargs were passed to __call__(), sets some state.
+
+        This is called at the beginning of __call__().
+        """
+        pass
+
+    def get_template(self, step):
+        """
+        Hook for specifying the name of the template to use for a given step.
+
+        Note that this can return a tuple of template names if you'd like to
+        use the template system's select_template() hook.
+        """
+        return 'forms/wizard.html'
+
+    def render_template(self, request, form, previous_fields, step, context=None):
+        """
+        Renders the template for the given step, returning an HttpResponse object.
+
+        Override this method if you want to add a custom context, return a
+        different MIME type, etc. If you only need to override the template
+        name, use get_template() instead.
+
+        The template will be rendered with the following context:
+            step_field -- The name of the hidden field containing the step.
+            step0      -- The current step (zero-based).
+            step       -- The current step (one-based).
+            step_count -- The total number of steps.
+            form       -- The Form instance for the current step (either empty
+                          or with errors).
+            previous_fields -- A string representing every previous data field,
+                          plus hashes for completed forms, all in the form of
+                          hidden fields. Note that you'll need to run this
+                          through the "safe" template filter, to prevent
+                          auto-escaping, because it's raw HTML.
+        """
+        context = context or {}
+        context.update(self.extra_context)
+        return render_to_response(self.get_template(step), dict(context,
+            step_field=self.step_field_name,
+            step0=step,
+            step=step + 1,
+            step_count=self.num_steps(),
+            form=form,
+            previous_fields=previous_fields
+        ), context_instance=RequestContext(request))
+
+    def process_step(self, request, form, step):
+        """
+        Hook for modifying the FormWizard's internal state, given a fully
+        validated Form object. The Form is guaranteed to have clean, valid
+        data.
+
+        This method should *not* modify any of that data. Rather, it might want
+        to set self.extra_context or dynamically alter self.form_list, based on
+        previously submitted forms.
+
+        Note that this method is called every time a page is rendered for *all*
+        submitted steps.
+        """
+        pass
+
+    # METHODS SUBCLASSES MUST OVERRIDE ########################################
+
+    def done(self, request, form_list):
+        """
+        Hook for doing something with the validated data. This is responsible
+        for the final processing.
+
+        form_list is a list of Form instances, each containing clean, valid
+        data.
+        """
+        raise NotImplementedError("Your %s class has not defined a done() method, which is required." % self.__class__.__name__)
diff --git a/django/contrib/formtools/wizard/forms.py b/django/contrib/formtools/wizard/forms.py
new file mode 100644
index 0000000..bf46c5c
--- /dev/null
+++ b/django/contrib/formtools/wizard/forms.py
@@ -0,0 +1,7 @@
+from django import forms
+
+class ManagementForm(forms.Form):
+    """
+    ``ManagementForm`` is used to keep track of the current wizard step.
+    """
+    current_step = forms.CharField(widget=forms.HiddenInput)
diff --git a/django/contrib/formtools/wizard/storage/__init__.py b/django/contrib/formtools/wizard/storage/__init__.py
new file mode 100644
index 0000000..b88ccc7
--- /dev/null
+++ b/django/contrib/formtools/wizard/storage/__init__.py
@@ -0,0 +1,22 @@
+from django.utils.importlib import import_module
+
+from django.contrib.formtools.wizard.storage.base import BaseStorage
+from django.contrib.formtools.wizard.storage.exceptions import (
+    MissingStorageModule, MissingStorageClass, NoFileStorageConfigured)
+
+
+def get_storage(path, *args, **kwargs):
+    i = path.rfind('.')
+    module, attr = path[:i], path[i+1:]
+    try:
+        mod = import_module(module)
+    except ImportError, e:
+        raise MissingStorageModule(
+            'Error loading storage %s: "%s"' % (module, e))
+    try:
+        storage_class = getattr(mod, attr)
+    except AttributeError:
+        raise MissingStorageClass(
+            'Module "%s" does not define a storage named "%s"' % (module, attr))
+    return storage_class(*args, **kwargs)
+
diff --git a/django/contrib/formtools/wizard/storage/base.py b/django/contrib/formtools/wizard/storage/base.py
new file mode 100644
index 0000000..475b39d
--- /dev/null
+++ b/django/contrib/formtools/wizard/storage/base.py
@@ -0,0 +1,93 @@
+from django.core.files.uploadedfile import UploadedFile
+from django.utils.functional import lazy_property
+from django.utils.encoding import smart_str
+
+from django.contrib.formtools.wizard.storage.exceptions import NoFileStorageConfigured
+
+class BaseStorage(object):
+    step_key = 'step'
+    step_data_key = 'step_data'
+    step_files_key = 'step_files'
+    extra_data_key = 'extra_data'
+
+    def __init__(self, prefix, request=None, file_storage=None):
+        self.prefix = 'wizard_%s' % prefix
+        self.request = request
+        self.file_storage = file_storage
+
+    def init_data(self):
+        self.data = {
+            self.step_key: None,
+            self.step_data_key: {},
+            self.step_files_key: {},
+            self.extra_data_key: {},
+        }
+
+    def reset(self):
+        self.init_data()
+
+    def _get_current_step(self):
+        return self.data[self.step_key]
+
+    def _set_current_step(self, step):
+        self.data[self.step_key] = step
+
+    current_step = lazy_property(_get_current_step, _set_current_step)
+
+    def _get_extra_data(self):
+        return self.data[self.extra_data_key] or {}
+
+    def _set_extra_data(self, extra_data):
+        self.data[self.extra_data_key] = extra_data
+
+    extra_data = lazy_property(_get_extra_data, _set_extra_data)
+
+    def get_step_data(self, step):
+        return self.data[self.step_data_key].get(step, None)
+
+    def set_step_data(self, step, cleaned_data):
+        self.data[self.step_data_key][step] = cleaned_data
+
+    @property
+    def current_step_data(self):
+        return self.get_step_data(self.current_step)
+
+    def get_step_files(self, step):
+        wizard_files = self.data[self.step_files_key].get(step, {})
+
+        if wizard_files and not self.file_storage:
+            raise NoFileStorageConfigured
+
+        files = {}
+        for field, field_dict in wizard_files.iteritems():
+            field_dict = dict((smart_str(k), v)
+                              for k, v in field_dict.iteritems())
+            tmp_name = field_dict.pop('tmp_name')
+            files[field] = UploadedFile(
+                file=self.file_storage.open(tmp_name), **field_dict)
+        return files or None
+
+    def set_step_files(self, step, files):
+        if files and not self.file_storage:
+            raise NoFileStorageConfigured
+
+        if step not in self.data[self.step_files_key]:
+            self.data[self.step_files_key][step] = {}
+
+        for field, field_file in (files or {}).iteritems():
+            tmp_filename = self.file_storage.save(field_file.name, field_file)
+            file_dict = {
+                'tmp_name': tmp_filename,
+                'name': field_file.name,
+                'content_type': field_file.content_type,
+                'size': field_file.size,
+                'charset': field_file.charset
+            }
+            self.data[self.step_files_key][step][field] = file_dict
+
+    @property
+    def current_step_files(self):
+        return self.get_step_files(self.current_step)
+
+    def update_response(self, response):
+        pass
diff --git a/django/contrib/formtools/wizard/storage/cookie.py b/django/contrib/formtools/wizard/storage/cookie.py
new file mode 100644
index 0000000..af26e01
--- /dev/null
+++ b/django/contrib/formtools/wizard/storage/cookie.py
@@ -0,0 +1,32 @@
+from django.core.exceptions import SuspiciousOperation
+from django.core.signing import BadSignature
+from django.utils import simplejson as json
+
+from django.contrib.formtools.wizard import storage
+
+
+class CookieStorage(storage.BaseStorage):
+    encoder = json.JSONEncoder(separators=(',', ':'))
+
+    def __init__(self, *args, **kwargs):
+        super(CookieStorage, self).__init__(*args, **kwargs)
+        self.data = self.load_data()
+        if self.data is None:
+            self.init_data()
+
+    def load_data(self):
+        try:
+            data = self.request.get_signed_cookie(self.prefix)
+        except KeyError:
+            data = None
+        except BadSignature:
+            raise SuspiciousOperation('FormWizard cookie manipulated')
+        if data is None:
+            return None
+        return json.loads(data, cls=json.JSONDecoder)
+
+    def update_response(self, response):
+        if self.data:
+            response.set_signed_cookie(self.prefix, self.encoder.encode(self.data))
+        else:
+            response.delete_cookie(self.prefix)
diff --git a/django/contrib/formtools/wizard/storage/exceptions.py b/django/contrib/formtools/wizard/storage/exceptions.py
new file mode 100644
index 0000000..eab9030
--- /dev/null
+++ b/django/contrib/formtools/wizard/storage/exceptions.py
@@ -0,0 +1,10 @@
+from django.core.exceptions import ImproperlyConfigured
+
+class MissingStorageModule(ImproperlyConfigured):
+    pass
+
+class MissingStorageClass(ImproperlyConfigured):
+    pass
+
+class NoFileStorageConfigured(ImproperlyConfigured):
+    pass
diff --git a/django/contrib/formtools/wizard/storage/session.py b/django/contrib/formtools/wizard/storage/session.py
new file mode 100644
index 0000000..84a3848
--- /dev/null
+++ b/django/contrib/formtools/wizard/storage/session.py
@@ -0,0 +1,20 @@
+from django.core.files.uploadedfile import UploadedFile
+from django.contrib.formtools.wizard import storage
+
+
+class SessionStorage(storage.BaseStorage):
+
+    def __init__(self, *args, **kwargs):
+        super(SessionStorage, self).__init__(*args, **kwargs)
+        if self.prefix not in self.request.session:
+            self.init_data()
+
+    def _get_data(self):
+        self.request.session.modified = True
+        return self.request.session[self.prefix]
+
+    def _set_data(self, value):
+        self.request.session[self.prefix] = value
+        self.request.session.modified = True
+
+    data = property(_get_data, _set_data)
diff --git a/django/contrib/formtools/wizard/templates/formtools/wizard/wizard_form.html b/django/contrib/formtools/wizard/templates/formtools/wizard/wizard_form.html
new file mode 100644
index 0000000..b98e58d
--- /dev/null
+++ b/django/contrib/formtools/wizard/templates/formtools/wizard/wizard_form.html
@@ -0,0 +1,17 @@
+{% load i18n %}
+{% csrf_token %}
+{{ wizard.management_form }}
+{% if wizard.form.forms %}
+    {{ wizard.form.management_form }}
+    {% for form in wizard.form.forms %}
+        {{ form.as_p }}
+    {% endfor %}
+{% else %}
+    {{ wizard.form.as_p }}
+{% endif %}
+
+{% if wizard.steps.prev %}
+<button name="wizard_prev_step" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
+<button name="wizard_prev_step" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
+{% endif %}
+<input type="submit" name="submit" value="{% trans "submit" %}" />
diff --git a/django/contrib/formtools/wizard/tests/__init__.py b/django/contrib/formtools/wizard/tests/__init__.py
new file mode 100644
index 0000000..7c66c82
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/__init__.py
@@ -0,0 +1,6 @@
+from django.contrib.formtools.wizard.tests.formtests import *
+from django.contrib.formtools.wizard.tests.sessionstoragetests import *
+from django.contrib.formtools.wizard.tests.cookiestoragetests import *
+from django.contrib.formtools.wizard.tests.loadstoragetests import *
+from django.contrib.formtools.wizard.tests.wizardtests import *
+from django.contrib.formtools.wizard.tests.namedwizardtests import *
diff --git a/django/contrib/formtools/wizard/tests/cookiestoragetests.py b/django/contrib/formtools/wizard/tests/cookiestoragetests.py
new file mode 100644
index 0000000..74c7e82
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/cookiestoragetests.py
@@ -0,0 +1,43 @@
+from django.test import TestCase
+from django.core import signing
+from django.core.exceptions import SuspiciousOperation
+from django.http import HttpResponse
+
+from django.contrib.formtools.wizard.storage.cookie import CookieStorage
+from django.contrib.formtools.wizard.tests.storagetests import get_request, TestStorage
+
+class TestCookieStorage(TestStorage, TestCase):
+    def get_storage(self):
+        return CookieStorage
+
+    def test_manipulated_cookie(self):
+        request = get_request()
+        storage = self.get_storage()('wizard1', request, None)
+
+        cookie_signer = signing.get_cookie_signer(storage.prefix)
+
+        storage.request.COOKIES[storage.prefix] = cookie_signer.sign(
+            storage.encoder.encode({'key1': 'value1'}))
+
+        self.assertEqual(storage.load_data(), {'key1': 'value1'})
+
+        storage.request.COOKIES[storage.prefix] = 'i_am_manipulated'
+        self.assertRaises(SuspiciousOperation, storage.load_data)
+
+    def test_reset_cookie(self):
+        request = get_request()
+        storage = self.get_storage()('wizard1', request, None)
+
+        storage.data = {'key1': 'value1'}
+
+        response = HttpResponse()
+        storage.update_response(response)
+
+        cookie_signer = signing.get_cookie_signer(storage.prefix)
+        signed_cookie_data = cookie_signer.sign(storage.encoder.encode(storage.data))
+        self.assertEqual(response.cookies[storage.prefix].value, signed_cookie_data)
+
+        storage.init_data()
+        storage.update_response(response)
+        unsigned_cookie_data = cookie_signer.unsign(response.cookies[storage.prefix].value)
+        self.assertEqual(unsigned_cookie_data, '{"step_files":{},"step":null,"extra_data":{},"step_data":{}}')
diff --git a/django/contrib/formtools/wizard/tests/formtests.py b/django/contrib/formtools/wizard/tests/formtests.py
new file mode 100644
index 0000000..24fda5e
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/formtests.py
@@ -0,0 +1,195 @@
+from django import forms, http
+from django.conf import settings
+from django.test import TestCase
+from django.template.response import TemplateResponse
+from django.utils.importlib import import_module
+
+from django.contrib.auth.models import User
+
+from django.contrib.formtools.wizard.views import (WizardView,
+                                                   SessionWizardView,
+                                                   CookieWizardView)
+
+
+class DummyRequest(http.HttpRequest):
+    def __init__(self, POST=None):
+        super(DummyRequest, self).__init__()
+        self.method = POST and "POST" or "GET"
+        if POST is not None:
+            self.POST.update(POST)
+        self.session = {}
+        self._dont_enforce_csrf_checks = True
+
+def get_request(*args, **kwargs):
+    request = DummyRequest(*args, **kwargs)
+    engine = import_module(settings.SESSION_ENGINE)
+    request.session = engine.SessionStore(None)
+    return request
+
+class Step1(forms.Form):
+    name = forms.CharField()
+
+class Step2(forms.Form):
+    name = forms.CharField()
+
+class Step3(forms.Form):
+    data = forms.CharField()
+
+class UserForm(forms.ModelForm):
+    class Meta:
+        model = User
+
+UserFormSet = forms.models.modelformset_factory(User, form=UserForm, extra=2)
+
+class TestWizard(WizardView):
+    storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage'
+
+    def dispatch(self, request, *args, **kwargs):
+        response = super(TestWizard, self).dispatch(request, *args, **kwargs)
+        return response, self
+
+class FormTests(TestCase):
+    def test_form_init(self):
+        testform = TestWizard.get_initkwargs([Step1, Step2])
+        self.assertEquals(testform['form_list'], {u'0': Step1, u'1': Step2})
+
+        testform = TestWizard.get_initkwargs([('start', Step1), ('step2', Step2)])
+        self.assertEquals(
+            testform['form_list'], {u'start': Step1, u'step2': Step2})
+
+        testform = TestWizard.get_initkwargs([Step1, Step2, ('finish', Step3)])
+        self.assertEquals(
+            testform['form_list'], {u'0': Step1, u'1': Step2, u'finish': Step3})
+
+    def test_first_step(self):
+        request = get_request()
+
+        testform = TestWizard.as_view([Step1, Step2])
+        response, instance = testform(request)
+        self.assertEquals(instance.steps.current, u'0')
+
+        testform = TestWizard.as_view([('start', Step1), ('step2', Step2)])
+        response, instance = testform(request)
+
+        self.assertEquals(instance.steps.current, 'start')
+
+    def test_persistence(self):
+        testform = TestWizard.as_view([('start', Step1), ('step2', Step2)])
+        request = get_request({'test_wizard-current_step': 'start',
+                               'name': 'data1'})
+        response, instance = testform(request)
+        self.assertEquals(instance.steps.current, 'start')
+
+        instance.storage.current_step = 'step2'
+
+        testform2 = TestWizard.as_view([('start', Step1), ('step2', Step2)])
+        request.POST = {'test_wizard-current_step': 'step2'}
+        response, instance = testform2(request)
+        self.assertEquals(instance.steps.current, 'step2')
+
+    def test_form_condition(self):
+        request = get_request()
+
+        testform = TestWizard.as_view(
+            [('start', Step1), ('step2', Step2), ('step3', Step3)],
+            condition_dict={'step2': True})
+        response, instance = testform(request)
+        self.assertEquals(instance.get_next_step(), 'step2')
+
+        testform = TestWizard.as_view(
+            [('start', Step1), ('step2', Step2), ('step3', Step3)],
+            condition_dict={'step2': False})
+        response, instance = testform(request)
+        self.assertEquals(instance.get_next_step(), 'step3')
+
+    def test_add_extra_context(self):
+        request = get_request({'test_wizard-current_step': 'start'})
+
+        testform = TestWizard.as_view([('start', Step1), ('step2', Step2)])
+        response, instance = testform(
+            request, extra_context={'key1': 'value1'})
+        self.assertEqual(instance.get_extra_data(), {'key1': 'value1'})
+
+        request.method = 'POST'
+        response, instance = testform(
+            request, extra_context={'key1': 'value1'})
+        self.assertEqual(instance.get_extra_data(), {'key1': 'value1'})
+
+    def test_form_prefix(self):
+        request = get_request()
+
+        testform = TestWizard.as_view([('start', Step1), ('step2', Step2)])
+        response, instance = testform(request)
+
+        self.assertEqual(instance.get_form_prefix(), 'start')
+        self.assertEqual(instance.get_form_prefix('another'), 'another')
+
+    def test_form_initial(self):
+        request = get_request()
+
+        testform = TestWizard.as_view([('start', Step1), ('step2', Step2)],
+            initial_dict={'start': {'name': 'value1'}})
+        response, instance = testform(request)
+
+        self.assertEqual(instance.get_form_initial('start'), {'name': 'value1'})
+        self.assertEqual(instance.get_form_initial('step2'), {})
+
+    def test_form_instance(self):
+        request = get_request()
+        the_instance = User()
+        testform = TestWizard.as_view([('start', UserForm), ('step2', Step2)],
+            instance_dict={'start': the_instance})
+        response, instance = testform(request)
+
+        self.assertEqual(
+            instance.get_form_instance('start'),
+            the_instance)
+        self.assertEqual(
+            instance.get_form_instance('non_exist_instance'),
+            None)
+
+    def test_formset_instance(self):
+        request = get_request()
+        the_instance1, created = User.objects.get_or_create(
+            username='testuser1')
+        the_instance2, created = User.objects.get_or_create(
+            username='testuser2')
+        testform = TestWizard.as_view([('start', UserFormSet), ('step2', Step2)],
+            instance_dict={'start': User.objects.filter(username='testuser1')})
+        response, instance = testform(request)
+
+        self.assertEqual(list(instance.get_form_instance('start')), [the_instance1])
+        self.assertEqual(instance.get_form_instance('non_exist_instance'), None)
+
+        self.assertEqual(instance.get_form().initial_form_count(), 1)
+
+    def test_done(self):
+        request = get_request()
+
+        testform = TestWizard.as_view([('start', Step1), ('step2', Step2)])
+        response, instance = testform(request)
+
+        self.assertRaises(NotImplementedError, instance.done, None)
+
+    def test_revalidation(self):
+        request = get_request()
+
+        testform = TestWizard.as_view([('start', Step1), ('step2', Step2)])
+        response, instance = testform(request)
+        instance.render_done(None)
+        self.assertEqual(instance.storage.current_step, 'start')
+
+
+class SessionFormTests(TestCase):
+    def test_init(self):
+        request = get_request()
+        testform = SessionWizardView.as_view([('start', Step1)])
+        self.assertTrue(isinstance(testform(request), TemplateResponse))
+
+
+class CookieFormTests(TestCase):
+    def test_init(self):
+        request = get_request()
+        testform = CookieWizardView.as_view([('start', Step1)])
+        self.assertTrue(isinstance(testform(request), TemplateResponse))
+
diff --git a/django/contrib/formtools/wizard/tests/loadstoragetests.py b/django/contrib/formtools/wizard/tests/loadstoragetests.py
new file mode 100644
index 0000000..267dee0
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/loadstoragetests.py
@@ -0,0 +1,22 @@
+from django.test import TestCase
+
+from django.contrib.formtools.wizard.storage import (get_storage,
+                                                     MissingStorageModule,
+                                                     MissingStorageClass)
+from django.contrib.formtools.wizard.storage.base import BaseStorage
+
+
+class TestLoadStorage(TestCase):
+    def test_load_storage(self):
+        self.assertEqual(
+            type(get_storage('django.contrib.formtools.wizard.storage.base.BaseStorage', 'wizard1')),
+            BaseStorage)
+
+    def test_missing_module(self):
+        self.assertRaises(MissingStorageModule, get_storage,
+            'django.contrib.formtools.wizard.storage.idontexist.IDontExistStorage', 'wizard1')
+
+    def test_missing_class(self):
+        self.assertRaises(MissingStorageClass, get_storage,
+            'django.contrib.formtools.wizard.storage.base.IDontExistStorage', 'wizard1')
+
diff --git a/django/contrib/formtools/wizard/tests/namedwizardtests/__init__.py b/django/contrib/formtools/wizard/tests/namedwizardtests/__init__.py
new file mode 100644
index 0000000..4387356
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/namedwizardtests/__init__.py
@@ -0,0 +1 @@
+from django.contrib.formtools.wizard.tests.namedwizardtests.tests import *
\ No newline at end of file
diff --git a/django/contrib/formtools/wizard/tests/namedwizardtests/forms.py b/django/contrib/formtools/wizard/tests/namedwizardtests/forms.py
new file mode 100644
index 0000000..ae98126
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/namedwizardtests/forms.py
@@ -0,0 +1,42 @@
+from django import forms
+from django.forms.formsets import formset_factory
+from django.http import HttpResponse
+from django.template import Template, Context
+
+from django.contrib.auth.models import User
+
+from django.contrib.formtools.wizard.views import NamedUrlWizardView
+
+class Page1(forms.Form):
+    name = forms.CharField(max_length=100)
+    user = forms.ModelChoiceField(queryset=User.objects.all())
+    thirsty = forms.NullBooleanField()
+
+class Page2(forms.Form):
+    address1 = forms.CharField(max_length=100)
+    address2 = forms.CharField(max_length=100)
+
+class Page3(forms.Form):
+    random_crap = forms.CharField(max_length=100)
+
+Page4 = formset_factory(Page3, extra=2)
+
+class ContactWizard(NamedUrlWizardView):
+    def done(self, form_list, **kwargs):
+        c = Context({
+            'form_list': [x.cleaned_data for x in form_list],
+            'all_cleaned_data': self.get_all_cleaned_data()
+        })
+
+        for form in self.form_list.keys():
+            c[form] = self.get_cleaned_data_for_step(form)
+
+        c['this_will_fail'] = self.get_cleaned_data_for_step('this_will_fail')
+        return HttpResponse(Template('').render(c))
+
+class SessionContactWizard(ContactWizard):
+    storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage'
+
+class CookieContactWizard(ContactWizard):
+    storage_name = 'django.contrib.formtools.wizard.storage.cookie.CookieStorage'
+
diff --git a/django/contrib/formtools/wizard/tests/namedwizardtests/tests.py b/django/contrib/formtools/wizard/tests/namedwizardtests/tests.py
new file mode 100644
index 0000000..f0e9c0c
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/namedwizardtests/tests.py
@@ -0,0 +1,373 @@
+import os
+
+from django.core.urlresolvers import reverse
+from django.http import QueryDict
+from django.test import TestCase
+from django.conf import settings
+
+from django.contrib.auth.models import User
+
+from django.contrib.formtools import wizard
+
+from django.contrib.formtools.wizard.views import (NamedUrlSessionWizardView,
+                                                   NamedUrlCookieWizardView)
+from django.contrib.formtools.wizard.tests.formtests import (get_request,
+                                                             Step1,
+                                                             Step2)
+
+class NamedWizardTests(object):
+    urls = 'django.contrib.formtools.wizard.tests.namedwizardtests.urls'
+
+    def setUp(self):
+        self.testuser, created = User.objects.get_or_create(username='testuser1')
+        self.wizard_step_data[0]['form1-user'] = self.testuser.pk
+
+        wizard_template_dirs = [os.path.join(os.path.dirname(wizard.__file__), 'templates')]
+        settings.TEMPLATE_DIRS = list(settings.TEMPLATE_DIRS) + wizard_template_dirs
+
+    def tearDown(self):
+        del settings.TEMPLATE_DIRS[-1]
+
+    def test_initial_call(self):
+        response = self.client.get(reverse('%s_start' % self.wizard_urlname))
+        self.assertEqual(response.status_code, 302)
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+        wizard = response.context['wizard']
+        self.assertEqual(wizard['steps'].current, 'form1')
+        self.assertEqual(wizard['steps'].step0, 0)
+        self.assertEqual(wizard['steps'].step1, 1)
+        self.assertEqual(wizard['steps'].last, 'form4')
+        self.assertEqual(wizard['steps'].prev, None)
+        self.assertEqual(wizard['steps'].next, 'form2')
+        self.assertEqual(wizard['steps'].count, 4)
+
+    def test_initial_call_with_params(self):
+        get_params = {'getvar1': 'getval1', 'getvar2': 'getval2'}
+        response = self.client.get(reverse('%s_start' % self.wizard_urlname),
+                                   get_params)
+        self.assertEqual(response.status_code, 302)
+
+        # Test for proper redirect GET parameters
+        location = response['Location']
+        self.assertNotEqual(location.find('?'), -1)
+        querydict = QueryDict(location[location.find('?') + 1:])
+        self.assertEqual(dict(querydict.items()), get_params)
+
+    def test_form_post_error(self):
+        response = self.client.post(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}),
+            self.wizard_step_1_data)
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+        self.assertEqual(response.context['wizard']['form'].errors,
+                         {'name': [u'This field is required.'],
+                          'user': [u'This field is required.']})
+
+    def test_form_post_success(self):
+        response = self.client.post(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}),
+            self.wizard_step_data[0])
+        response = self.client.get(response['Location'])
+
+        self.assertEqual(response.status_code, 200)
+        wizard = response.context['wizard']
+        self.assertEqual(wizard['steps'].current, 'form2')
+        self.assertEqual(wizard['steps'].step0, 1)
+        self.assertEqual(wizard['steps'].prev, 'form1')
+        self.assertEqual(wizard['steps'].next, 'form3')
+
+    def test_form_stepback(self):
+        response = self.client.get(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}))
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+        response = self.client.post(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}),
+            self.wizard_step_data[0])
+        response = self.client.get(response['Location'])
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        response = self.client.post(
+            reverse(self.wizard_urlname, kwargs={
+                'step': response.context['wizard']['steps'].current
+            }), {'wizard_prev_step': response.context['wizard']['steps'].prev})
+        response = self.client.get(response['Location'])
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+    def test_form_jump(self):
+        response = self.client.get(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}))
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+        response = self.client.get(
+            reverse(self.wizard_urlname, kwargs={'step': 'form3'}))
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form3')
+
+    def test_form_finish(self):
+        response = self.client.get(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}))
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[0])
+        response = self.client.get(response['Location'])
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[1])
+        response = self.client.get(response['Location'])
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form3')
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[2])
+        response = self.client.get(response['Location'])
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form4')
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[3])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+
+        self.assertEqual(response.context['form_list'], [
+            {'name': u'Pony', 'thirsty': True, 'user': self.testuser},
+            {'address1': u'123 Main St', 'address2': u'Djangoland'},
+            {'random_crap': u'blah blah'},
+            [{'random_crap': u'blah blah'}, {'random_crap': u'blah blah'}]])
+
+    def test_cleaned_data(self):
+        response = self.client.get(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}))
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[0])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[1])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[2])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[3])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+
+        self.assertEqual(
+            response.context['all_cleaned_data'],
+            {'name': u'Pony', 'thirsty': True, 'user': self.testuser,
+             'address1': u'123 Main St', 'address2': u'Djangoland',
+             'random_crap': u'blah blah', 'formset-form4': [
+                 {'random_crap': u'blah blah'},
+                 {'random_crap': u'blah blah'}
+             ]})
+
+    def test_manipulated_data(self):
+        response = self.client.get(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}))
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[0])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[1])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[2])
+        loc = response['Location']
+        response = self.client.get(loc)
+        self.assertEqual(response.status_code, 200, loc)
+
+        self.client.cookies.pop('sessionid', None)
+        self.client.cookies.pop('wizard_cookie_contact_wizard', None)
+
+        response = self.client.post(
+            reverse(self.wizard_urlname,
+                    kwargs={'step': response.context['wizard']['steps'].current}),
+            self.wizard_step_data[3])
+
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+    def test_form_reset(self):
+        response = self.client.post(
+            reverse(self.wizard_urlname, kwargs={'step': 'form1'}),
+            self.wizard_step_data[0])
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        response = self.client.get(
+            '%s?reset=1' % reverse('%s_start' % self.wizard_urlname))
+        self.assertEqual(response.status_code, 302)
+
+        response = self.client.get(response['Location'])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+class NamedSessionWizardTests(NamedWizardTests, TestCase):
+    wizard_urlname = 'nwiz_session'
+    wizard_step_1_data = {
+        'session_contact_wizard-current_step': 'form1',
+    }
+    wizard_step_data = (
+        {
+            'form1-name': 'Pony',
+            'form1-thirsty': '2',
+            'session_contact_wizard-current_step': 'form1',
+        },
+        {
+            'form2-address1': '123 Main St',
+            'form2-address2': 'Djangoland',
+            'session_contact_wizard-current_step': 'form2',
+        },
+        {
+            'form3-random_crap': 'blah blah',
+            'session_contact_wizard-current_step': 'form3',
+        },
+        {
+            'form4-INITIAL_FORMS': '0',
+            'form4-TOTAL_FORMS': '2',
+            'form4-MAX_NUM_FORMS': '0',
+            'form4-0-random_crap': 'blah blah',
+            'form4-1-random_crap': 'blah blah',
+            'session_contact_wizard-current_step': 'form4',
+        }
+    )
+
+class NamedCookieWizardTests(NamedWizardTests, TestCase):
+    wizard_urlname = 'nwiz_cookie'
+    wizard_step_1_data = {
+        'cookie_contact_wizard-current_step': 'form1',
+    }
+    wizard_step_data = (
+        {
+            'form1-name': 'Pony',
+            'form1-thirsty': '2',
+            'cookie_contact_wizard-current_step': 'form1',
+        },
+        {
+            'form2-address1': '123 Main St',
+            'form2-address2': 'Djangoland',
+            'cookie_contact_wizard-current_step': 'form2',
+        },
+        {
+            'form3-random_crap': 'blah blah',
+            'cookie_contact_wizard-current_step': 'form3',
+        },
+        {
+            'form4-INITIAL_FORMS': '0',
+            'form4-TOTAL_FORMS': '2',
+            'form4-MAX_NUM_FORMS': '0',
+            'form4-0-random_crap': 'blah blah',
+            'form4-1-random_crap': 'blah blah',
+            'cookie_contact_wizard-current_step': 'form4',
+        }
+    )
+
+
+class NamedFormTests(object):
+    urls = 'django.contrib.formtools.wizard.tests.namedwizardtests.urls'
+
+    def test_add_extra_context(self):
+        request = get_request()
+
+        testform = self.formwizard_class.as_view(
+            [('start', Step1), ('step2', Step2)],
+            url_name=self.wizard_urlname)
+
+        response, instance = testform(request,
+                                      step='form1',
+                                      extra_context={'key1': 'value1'})
+        self.assertEqual(instance.get_extra_data(), {'key1': 'value1'})
+
+        instance.storage.reset()
+
+        response, instance = testform(request,
+                                      extra_context={'key2': 'value2'})
+        self.assertEqual(instance.get_extra_data(), {'key2': 'value2'})
+
+    def test_revalidation(self):
+        request = get_request()
+
+        testform = self.formwizard_class.as_view(
+            [('start', Step1), ('step2', Step2)],
+            url_name=self.wizard_urlname)
+        response, instance = testform(request, step='done')
+
+        instance.render_done(None)
+        self.assertEqual(instance.storage.current_step, 'start')
+
+class TestNamedUrlSessionFormWizard(NamedUrlSessionWizardView):
+
+    def dispatch(self, request, *args, **kwargs):
+        response = super(TestNamedUrlSessionFormWizard, self).dispatch(request, *args, **kwargs)
+        return response, self
+
+class TestNamedUrlCookieFormWizard(NamedUrlCookieWizardView):
+
+    def dispatch(self, request, *args, **kwargs):
+        response = super(TestNamedUrlCookieFormWizard, self).dispatch(request, *args, **kwargs)
+        return response, self
+
+
+class NamedSessionFormTests(NamedFormTests, TestCase):
+    formwizard_class = TestNamedUrlSessionFormWizard
+    wizard_urlname = 'nwiz_session'
+
+
+class NamedCookieFormTests(NamedFormTests, TestCase):
+    formwizard_class = TestNamedUrlCookieFormWizard
+    wizard_urlname = 'nwiz_cookie'
diff --git a/django/contrib/formtools/wizard/tests/namedwizardtests/urls.py b/django/contrib/formtools/wizard/tests/namedwizardtests/urls.py
new file mode 100644
index 0000000..a97ca98
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/namedwizardtests/urls.py
@@ -0,0 +1,24 @@
+from django.conf.urls.defaults import *
+from django.contrib.formtools.wizard.tests.namedwizardtests.forms import (
+    SessionContactWizard, CookieContactWizard, Page1, Page2, Page3, Page4)
+
+def get_named_session_wizard():
+    return SessionContactWizard.as_view(
+        [('form1', Page1), ('form2', Page2), ('form3', Page3), ('form4', Page4)],
+        url_name='nwiz_session',
+        done_step_name='nwiz_session_done'
+    )
+
+def get_named_cookie_wizard():
+    return CookieContactWizard.as_view(
+        [('form1', Page1), ('form2', Page2), ('form3', Page3), ('form4', Page4)],
+        url_name='nwiz_cookie',
+        done_step_name='nwiz_cookie_done'
+    )
+
+urlpatterns = patterns('',
+    url(r'^nwiz_session/(?P<step>.+)/$', get_named_session_wizard(), name='nwiz_session'),
+    url(r'^nwiz_session/$', get_named_session_wizard(), name='nwiz_session_start'),
+    url(r'^nwiz_cookie/(?P<step>.+)/$', get_named_cookie_wizard(), name='nwiz_cookie'),
+    url(r'^nwiz_cookie/$', get_named_cookie_wizard(), name='nwiz_cookie_start'),
+)
diff --git a/django/contrib/formtools/wizard/tests/sessionstoragetests.py b/django/contrib/formtools/wizard/tests/sessionstoragetests.py
new file mode 100644
index 0000000..c643921
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/sessionstoragetests.py
@@ -0,0 +1,8 @@
+from django.test import TestCase
+
+from django.contrib.formtools.wizard.tests.storagetests import TestStorage
+from django.contrib.formtools.wizard.storage.session import SessionStorage
+
+class TestSessionStorage(TestStorage, TestCase):
+    def get_storage(self):
+        return SessionStorage
diff --git a/django/contrib/formtools/wizard/tests/storagetests.py b/django/contrib/formtools/wizard/tests/storagetests.py
new file mode 100644
index 0000000..fec4fae
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/storagetests.py
@@ -0,0 +1,76 @@
+from datetime import datetime
+
+from django.http import HttpRequest
+from django.conf import settings
+from django.utils.importlib import import_module
+
+from django.contrib.auth.models import User
+
+def get_request():
+    request = HttpRequest()
+    engine = import_module(settings.SESSION_ENGINE)
+    request.session = engine.SessionStore(None)
+    return request
+
+class TestStorage(object):
+    def setUp(self):
+        self.testuser, created = User.objects.get_or_create(username='testuser1')
+
+    def test_current_step(self):
+        request = get_request()
+        storage = self.get_storage()('wizard1', request, None)
+        my_step = 2
+
+        self.assertEqual(storage.current_step, None)
+
+        storage.current_step = my_step
+        self.assertEqual(storage.current_step, my_step)
+
+        storage.reset()
+        self.assertEqual(storage.current_step, None)
+
+        storage.current_step = my_step
+        storage2 = self.get_storage()('wizard2', request, None)
+        self.assertEqual(storage2.current_step, None)
+
+    def test_step_data(self):
+        request = get_request()
+        storage = self.get_storage()('wizard1', request, None)
+        step1 = 'start'
+        step_data1 = {'field1': 'data1',
+                      'field2': 'data2',
+                      'field3': datetime.now(),
+                      'field4': self.testuser}
+
+        self.assertEqual(storage.get_step_data(step1), None)
+
+        storage.set_step_data(step1, step_data1)
+        self.assertEqual(storage.get_step_data(step1), step_data1)
+
+        storage.reset()
+        self.assertEqual(storage.get_step_data(step1), None)
+
+        storage.set_step_data(step1, step_data1)
+        storage2 = self.get_storage()('wizard2', request, None)
+        self.assertEqual(storage2.get_step_data(step1), None)
+
+    def test_extra_context(self):
+        request = get_request()
+        storage = self.get_storage()('wizard1', request, None)
+        extra_context = {'key1': 'data1',
+                         'key2': 'data2',
+                         'key3': datetime.now(),
+                         'key4': self.testuser}
+
+        self.assertEqual(storage.extra_data, {})
+
+        storage.extra_data = extra_context
+        self.assertEqual(storage.extra_data, extra_context)
+
+        storage.reset()
+        self.assertEqual(storage.extra_data, {})
+
+        storage.extra_data = extra_context
+        storage2 = self.get_storage()('wizard2', request, None)
+        self.assertEqual(storage2.extra_data, {})
+
diff --git a/django/contrib/formtools/wizard/tests/wizardtests/__init__.py b/django/contrib/formtools/wizard/tests/wizardtests/__init__.py
new file mode 100644
index 0000000..9173cd8
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/wizardtests/__init__.py
@@ -0,0 +1 @@
+from django.contrib.formtools.wizard.tests.wizardtests.tests import *
\ No newline at end of file
diff --git a/django/contrib/formtools/wizard/tests/wizardtests/forms.py b/django/contrib/formtools/wizard/tests/wizardtests/forms.py
new file mode 100644
index 0000000..726d74a
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/wizardtests/forms.py
@@ -0,0 +1,57 @@
+import tempfile
+
+from django import forms
+from django.core.files.storage import FileSystemStorage
+from django.forms.formsets import formset_factory
+from django.http import HttpResponse
+from django.template import Template, Context
+
+from django.contrib.auth.models import User
+
+from django.contrib.formtools.wizard.views import WizardView
+
+temp_storage_location = tempfile.mkdtemp()
+temp_storage = FileSystemStorage(location=temp_storage_location)
+
+class Page1(forms.Form):
+    name = forms.CharField(max_length=100)
+    user = forms.ModelChoiceField(queryset=User.objects.all())
+    thirsty = forms.NullBooleanField()
+
+class Page2(forms.Form):
+    address1 = forms.CharField(max_length=100)
+    address2 = forms.CharField(max_length=100)
+    file1 = forms.FileField()
+
+class Page3(forms.Form):
+    random_crap = forms.CharField(max_length=100)
+
+Page4 = formset_factory(Page3, extra=2)
+
+class ContactWizard(WizardView):
+    file_storage = temp_storage
+
+    def done(self, form_list, **kwargs):
+        c = Context({
+            'form_list': [x.cleaned_data for x in form_list],
+            'all_cleaned_data': self.get_all_cleaned_data()
+        })
+
+        for form in self.form_list.keys():
+            c[form] = self.get_cleaned_data_for_step(form)
+
+        c['this_will_fail'] = self.get_cleaned_data_for_step('this_will_fail')
+        return HttpResponse(Template('').render(c))
+
+    def get_context_data(self, form, **kwargs):
+        context = super(ContactWizard, self).get_context_data(form, **kwargs)
+        if self.storage.current_step == 'form2':
+            context.update({'another_var': True})
+        return context
+
+class SessionContactWizard(ContactWizard):
+    storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage'
+
+class CookieContactWizard(ContactWizard):
+    storage_name = 'django.contrib.formtools.wizard.storage.cookie.CookieStorage'
+
diff --git a/django/contrib/formtools/wizard/tests/wizardtests/tests.py b/django/contrib/formtools/wizard/tests/wizardtests/tests.py
new file mode 100644
index 0000000..f64b2ba
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/wizardtests/tests.py
@@ -0,0 +1,248 @@
+import os
+
+from django.test import TestCase
+from django.conf import settings
+from django.contrib.auth.models import User
+
+from django.contrib.formtools import wizard
+
+class WizardTests(object):
+    urls = 'django.contrib.formtools.wizard.tests.wizardtests.urls'
+
+    def setUp(self):
+        self.testuser, created = User.objects.get_or_create(username='testuser1')
+        self.wizard_step_data[0]['form1-user'] = self.testuser.pk
+
+        wizard_template_dirs = [os.path.join(os.path.dirname(wizard.__file__), 'templates')]
+        settings.TEMPLATE_DIRS = list(settings.TEMPLATE_DIRS) + wizard_template_dirs
+
+    def tearDown(self):
+        del settings.TEMPLATE_DIRS[-1]
+
+    def test_initial_call(self):
+        response = self.client.get(self.wizard_url)
+        wizard = response.context['wizard']
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(wizard['steps'].current, 'form1')
+        self.assertEqual(wizard['steps'].step0, 0)
+        self.assertEqual(wizard['steps'].step1, 1)
+        self.assertEqual(wizard['steps'].last, 'form4')
+        self.assertEqual(wizard['steps'].prev, None)
+        self.assertEqual(wizard['steps'].next, 'form2')
+        self.assertEqual(wizard['steps'].count, 4)
+
+    def test_form_post_error(self):
+        response = self.client.post(self.wizard_url, self.wizard_step_1_data)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+        self.assertEqual(response.context['wizard']['form'].errors,
+                         {'name': [u'This field is required.'],
+                          'user': [u'This field is required.']})
+
+    def test_form_post_success(self):
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        wizard = response.context['wizard']
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(wizard['steps'].current, 'form2')
+        self.assertEqual(wizard['steps'].step0, 1)
+        self.assertEqual(wizard['steps'].prev, 'form1')
+        self.assertEqual(wizard['steps'].next, 'form3')
+
+    def test_form_stepback(self):
+        response = self.client.get(self.wizard_url)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        response = self.client.post(self.wizard_url, {
+            'wizard_prev_step': response.context['wizard']['steps'].prev})
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+    def test_template_context(self):
+        response = self.client.get(self.wizard_url)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+        self.assertEqual(response.context.get('another_var', None), None)
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+        self.assertEqual(response.context.get('another_var', None), True)
+
+    def test_form_finish(self):
+        response = self.client.get(self.wizard_url)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        post_data = self.wizard_step_data[1]
+        post_data['form2-file1'] = open(__file__)
+        response = self.client.post(self.wizard_url, post_data)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form3')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[2])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form4')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[3])
+        self.assertEqual(response.status_code, 200)
+
+        all_data = response.context['form_list']
+        self.assertEqual(all_data[1]['file1'].read(), open(__file__).read())
+        del all_data[1]['file1']
+        self.assertEqual(all_data, [
+            {'name': u'Pony', 'thirsty': True, 'user': self.testuser},
+            {'address1': u'123 Main St', 'address2': u'Djangoland'},
+            {'random_crap': u'blah blah'},
+            [{'random_crap': u'blah blah'},
+             {'random_crap': u'blah blah'}]])
+
+    def test_cleaned_data(self):
+        response = self.client.get(self.wizard_url)
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+
+        post_data = self.wizard_step_data[1]
+        post_data['form2-file1'] = open(__file__)
+        response = self.client.post(self.wizard_url, post_data)
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[2])
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[3])
+        self.assertEqual(response.status_code, 200)
+
+        all_data = response.context['all_cleaned_data']
+        self.assertEqual(all_data['file1'].read(), open(__file__).read())
+        del all_data['file1']
+        self.assertEqual(all_data, {
+            'name': u'Pony', 'thirsty': True, 'user': self.testuser,
+            'address1': u'123 Main St', 'address2': u'Djangoland',
+            'random_crap': u'blah blah', 'formset-form4': [
+                {'random_crap': u'blah blah'},
+                {'random_crap': u'blah blah'}]})
+
+    def test_manipulated_data(self):
+        response = self.client.get(self.wizard_url)
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+
+        post_data = self.wizard_step_data[1]
+        post_data['form2-file1'] = open(__file__)
+        response = self.client.post(self.wizard_url, post_data)
+        self.assertEqual(response.status_code, 200)
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[2])
+        self.assertEqual(response.status_code, 200)
+        self.client.cookies.pop('sessionid', None)
+        self.client.cookies.pop('wizard_cookie_contact_wizard', None)
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[3])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+    def test_form_refresh(self):
+        response = self.client.get(self.wizard_url)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form1')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        post_data = self.wizard_step_data[1]
+        post_data['form2-file1'] = open(__file__)
+        response = self.client.post(self.wizard_url, post_data)
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form3')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[2])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form4')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[0])
+        self.assertEqual(response.status_code, 200)
+        self.assertEqual(response.context['wizard']['steps'].current, 'form2')
+
+        response = self.client.post(self.wizard_url, self.wizard_step_data[3])
+        self.assertEqual(response.status_code, 200)
+
+
+class SessionWizardTests(WizardTests, TestCase):
+    wizard_url = '/wiz_session/'
+    wizard_step_1_data = {
+        'session_contact_wizard-current_step': 'form1',
+    }
+    wizard_step_data = (
+        {
+            'form1-name': 'Pony',
+            'form1-thirsty': '2',
+            'session_contact_wizard-current_step': 'form1',
+        },
+        {
+            'form2-address1': '123 Main St',
+            'form2-address2': 'Djangoland',
+            'session_contact_wizard-current_step': 'form2',
+        },
+        {
+            'form3-random_crap': 'blah blah',
+            'session_contact_wizard-current_step': 'form3',
+        },
+        {
+            'form4-INITIAL_FORMS': '0',
+            'form4-TOTAL_FORMS': '2',
+            'form4-MAX_NUM_FORMS': '0',
+            'form4-0-random_crap': 'blah blah',
+            'form4-1-random_crap': 'blah blah',
+            'session_contact_wizard-current_step': 'form4',
+        }
+    )
+
+class CookieWizardTests(WizardTests, TestCase):
+    wizard_url = '/wiz_cookie/'
+    wizard_step_1_data = {
+        'cookie_contact_wizard-current_step': 'form1',
+    }
+    wizard_step_data = (
+        {
+            'form1-name': 'Pony',
+            'form1-thirsty': '2',
+            'cookie_contact_wizard-current_step': 'form1',
+        },
+        {
+            'form2-address1': '123 Main St',
+            'form2-address2': 'Djangoland',
+            'cookie_contact_wizard-current_step': 'form2',
+        },
+        {
+            'form3-random_crap': 'blah blah',
+            'cookie_contact_wizard-current_step': 'form3',
+        },
+        {
+            'form4-INITIAL_FORMS': '0',
+            'form4-TOTAL_FORMS': '2',
+            'form4-MAX_NUM_FORMS': '0',
+            'form4-0-random_crap': 'blah blah',
+            'form4-1-random_crap': 'blah blah',
+            'cookie_contact_wizard-current_step': 'form4',
+        }
+    )
+
+
diff --git a/django/contrib/formtools/wizard/tests/wizardtests/urls.py b/django/contrib/formtools/wizard/tests/wizardtests/urls.py
new file mode 100644
index 0000000..e305397
--- /dev/null
+++ b/django/contrib/formtools/wizard/tests/wizardtests/urls.py
@@ -0,0 +1,16 @@
+from django.conf.urls.defaults import *
+from django.contrib.formtools.wizard.tests.wizardtests.forms import (
+    SessionContactWizard, CookieContactWizard, Page1, Page2, Page3, Page4)
+
+urlpatterns = patterns('',
+    url(r'^wiz_session/$', SessionContactWizard.as_view(
+        [('form1', Page1),
+         ('form2', Page2),
+         ('form3', Page3),
+         ('form4', Page4)])),
+    url(r'^wiz_cookie/$', CookieContactWizard.as_view(
+        [('form1', Page1),
+         ('form2', Page2),
+         ('form3', Page3),
+         ('form4', Page4)])),
+)
diff --git a/django/contrib/formtools/wizard/views.py b/django/contrib/formtools/wizard/views.py
new file mode 100644
index 0000000..9cd5261
--- /dev/null
+++ b/django/contrib/formtools/wizard/views.py
@@ -0,0 +1,708 @@
+import copy
+import re
+
+from django import forms
+from django.shortcuts import redirect
+from django.core.urlresolvers import reverse
+from django.forms import formsets, ValidationError
+from django.views.generic import TemplateView
+from django.utils.datastructures import SortedDict
+from django.utils.decorators import classonlymethod
+
+from django.contrib.formtools.wizard.storage import get_storage
+from django.contrib.formtools.wizard.storage.exceptions import NoFileStorageConfigured
+from django.contrib.formtools.wizard.forms import ManagementForm
+
+
+def normalize_name(name):
+    new = re.sub('(((?<=[a-z])[A-Z])|([A-Z](?![A-Z]|$)))', '_\\1', name)
+    return new.lower().strip('_')
+
+class StepsHelper(object):
+
+    def __init__(self, wizard):
+        self._wizard = wizard
+
+    def __dir__(self):
+        return self.all
+
+    def __len__(self):
+        return self.count
+
+    def __repr__(self):
+        return '<StepsHelper for %s (steps: %s)>' % (self._wizard, self.all)
+
+    @property
+    def all(self):
+        "Returns the names of all steps/forms."
+        return self._wizard.get_form_list().keys()
+
+    @property
+    def count(self):
+        "Returns the total number of steps/forms in this the wizard."
+        return len(self.all)
+
+    @property
+    def current(self):
+        """
+        Returns the current step. If no current step is stored in the
+        storage backend, the first step will be returned.
+        """
+        return self._wizard.storage.current_step or self.first
+
+    @property
+    def first(self):
+        "Returns the name of the first step."
+        return self.all[0]
+
+    @property
+    def last(self):
+        "Returns the name of the last step."
+        return self.all[-1]
+
+    @property
+    def next(self):
+        "Returns the next step."
+        return self._wizard.get_next_step()
+
+    @property
+    def prev(self):
+        "Returns the previous step."
+        return self._wizard.get_prev_step()
+
+    @property
+    def index(self):
+        "Returns the index for the current step."
+        return self._wizard.get_step_index()
+
+    @property
+    def step0(self):
+        return int(self.index)
+
+    @property
+    def step1(self):
+        return int(self.index) + 1
+
+
+class WizardView(TemplateView):
+    """
+    The WizardView is used to create multi-page forms and handles all the
+    storage and validation stuff. The wizard is based on Django's generic
+    class based views.
+    """
+    storage_name = None
+    form_list = None
+    initial_dict = None
+    instance_dict = None
+    condition_dict = None
+    template_name = 'formtools/wizard/wizard_form.html'
+
+    def __repr__(self):
+        return '<%s: forms: %s>' % (self.__class__.__name__, self.form_list)
+
+    @classonlymethod
+    def as_view(cls, *args, **kwargs):
+        """
+        This method is used within urls.py to create unique formwizard
+        instances for every request. We need to override this method because
+        we add some kwargs which are needed to make the formwizard usable.
+        """
+        initkwargs = cls.get_initkwargs(*args, **kwargs)
+        return super(WizardView, cls).as_view(**initkwargs)
+
+    @classmethod
+    def get_initkwargs(cls, form_list,
+            initial_dict=None, instance_dict=None, condition_dict=None):
+        """
+        Creates a dict with all needed parameters for the form wizard instances.
+
+        * `form_list` - is a list of forms. The list entries can be single form
+          classes or tuples of (`step_name`, `form_class`). If you pass a list
+          of forms, the formwizard will convert the class list to
+          (`zero_based_counter`, `form_class`). This is needed to access the
+          form for a specific step.
+        * `initial_dict` - contains a dictionary of initial data dictionaries.
+          The key should be equal to the `step_name` in the `form_list` (or
+          the str of the zero based counter - if no step_names added in the
+          `form_list`)
+        * `instance_dict` - contains a dictionary of instance objects. This list
+          is only used when `ModelForm`s are used. The key should be equal to
+          the `step_name` in the `form_list`. Same rules as for `initial_dict`
+          apply.
+        * `condition_dict` - contains a dictionary of boolean values or
+          callables. If the value of for a specific `step_name` is callable it
+          will be called with the formwizard instance as the only argument.
+          If the return value is true, the step's form will be used.
+        """
+        kwargs = {
+            'initial_dict': initial_dict or {},
+            'instance_dict': instance_dict or {},
+            'condition_dict': condition_dict or {},
+        }
+        init_form_list = SortedDict()
+
+        assert len(form_list) > 0, 'at least one form is needed'
+
+        # walk through the passed form list
+        for i, form in enumerate(form_list):
+            if isinstance(form, (list, tuple)):
+                # if the element is a tuple, add the tuple to the new created
+                # sorted dictionary.
+                init_form_list[unicode(form[0])] = form[1]
+            else:
+                # if not, add the form with a zero based counter as unicode
+                init_form_list[unicode(i)] = form
+
+        # walk through the ne created list of forms
+        for form in init_form_list.itervalues():
+            if issubclass(form, formsets.BaseFormSet):
+                # if the element is based on BaseFormSet (FormSet/ModelFormSet)
+                # we need to override the form variable.
+                form = form.form
+            # check if any form contains a FileField, if yes, we need a
+            # file_storage added to the formwizard (by subclassing).
+            for field in form.base_fields.itervalues():
+                if (isinstance(field, forms.FileField) and
+                        not hasattr(cls, 'file_storage')):
+                    raise NoFileStorageConfigured
+
+        # build the kwargs for the formwizard instances
+        kwargs['form_list'] = init_form_list
+        return kwargs
+
+    def get_wizard_name(self):
+        return normalize_name(self.__class__.__name__)
+
+    def get_prefix(self):
+        # TODO: Add some kind of unique id to prefix
+        return self.wizard_name
+
+    def get_form_list(self):
+        """
+        This method returns a form_list based on the initial form list but
+        checks if there is a condition method/value in the condition_list.
+        If an entry exists in the condition list, it will call/read the value
+        and respect the result. (True means add the form, False means ignore
+        the form)
+
+        The form_list is always generated on the fly because condition methods
+        could use data from other (maybe previous forms).
+        """
+        form_list = SortedDict()
+        for form_key, form_class in self.form_list.iteritems():
+            # try to fetch the value from condition list, by default, the form
+            # gets passed to the new list.
+            condition = self.condition_dict.get(form_key, True)
+            if callable(condition):
+                # call the value if needed, passes the current instance.
+                condition = condition(self)
+            if condition:
+                form_list[form_key] = form_class
+        return form_list
+
+    def dispatch(self, request, *args, **kwargs):
+        """
+        This method gets called by the routing engine. The first argument is
+        `request` which contains a `HttpRequest` instance.
+        The request is stored in `self.request` for later use. The storage
+        instance is stored in `self.storage`.
+
+        After processing the request using the `dispatch` method, the
+        response gets updated by the storage engine (for example add cookies).
+        """
+        # add the storage engine to the current formwizard instance
+        self.wizard_name = self.get_wizard_name()
+        self.prefix = self.get_prefix()
+        self.storage = get_storage(self.storage_name, self.prefix, request,
+            getattr(self, 'file_storage', None))
+        self.steps = StepsHelper(self)
+        response = super(WizardView, self).dispatch(request, *args, **kwargs)
+
+        # update the response (e.g. adding cookies)
+        self.storage.update_response(response)
+        return response
+
+    def get(self, request, *args, **kwargs):
+        """
+        This method handles GET requests.
+
+        If a GET request reaches this point, the wizard assumes that the user
+        just starts at the first step or wants to restart the process.
+        The data of the wizard will be resetted before rendering the first step.
+        """
+        self.storage.reset()
+
+        # if there is an extra_context item in the kwargs,
+        # pass the data to the storage engine.
+        self.update_extra_data(kwargs.get('extra_context', {}))
+
+        # reset the current step to the first step.
+        self.storage.current_step = self.steps.first
+        return self.render(self.get_form())
+
+    def post(self, *args, **kwargs):
+        """
+        This method handles POST requests.
+
+        The wizard will render either the current step (if form validation
+        wasn't successful), the next step (if the current step was stored
+        successful) or the done view (if no more steps are available)
+        """
+        # if there is an extra_context item in the kwargs,
+        # pass the data to the storage engine.
+        self.update_extra_data(kwargs.get('extra_context', {}))
+
+        # Look for a wizard_prev_step element in the posted data which
+        # contains a valid step name. If one was found, render the requested
+        # form. (This makes stepping back a lot easier).
+        wizard_prev_step = self.request.POST.get('wizard_prev_step', None)
+        if wizard_prev_step and wizard_prev_step in self.get_form_list():
+            self.storage.current_step = wizard_prev_step
+            form = self.get_form(
+                data=self.storage.get_step_data(self.steps.current),
+                files=self.storage.get_step_files(self.steps.current))
+            return self.render(form)
+
+        # Check if form was refreshed
+        management_form = ManagementForm(self.request.POST, prefix=self.prefix)
+        if not management_form.is_valid():
+            raise ValidationError(
+                'ManagementForm data is missing or has been tampered.')
+
+        form_current_step = management_form.cleaned_data['current_step']
+        if (form_current_step != self.steps.current and
+                self.storage.current_step is not None):
+            # form refreshed, change current step
+            self.storage.current_step = form_current_step
+
+        # get the form for the current step
+        form = self.get_form(data=self.request.POST, files=self.request.FILES)
+
+        # and try to validate
+        if form.is_valid():
+            # if the form is valid, store the cleaned data and files.
+            self.storage.set_step_data(self.steps.current, self.process_step(form))
+            self.storage.set_step_files(self.steps.current, self.process_step_files(form))
+
+            # check if the current step is the last step
+            if self.steps.current == self.steps.last:
+                # no more steps, render done view
+                return self.render_done(form, **kwargs)
+            else:
+                # proceed to the next step
+                return self.render_next_step(form)
+        return self.render(form)
+
+    def render_next_step(self, form, **kwargs):
+        """
+        THis method gets called when the next step/form should be rendered.
+        `form` contains the last/current form.
+        """
+        # get the form instance based on the data from the storage backend
+        # (if available).
+        next_step = self.steps.next
+        new_form = self.get_form(next_step,
+            data=self.storage.get_step_data(next_step),
+            files=self.storage.get_step_files(next_step))
+
+        # change the stored current step
+        self.storage.current_step = next_step
+        return self.render(new_form, **kwargs)
+
+    def render_done(self, form, **kwargs):
+        """
+        This method gets called when all forms passed. The method should also
+        re-validate all steps to prevent manipulation. If any form don't
+        validate, `render_revalidation_failure` should get called.
+        If everything is fine call `done`.
+        """
+        final_form_list = []
+        # walk through the form list and try to validate the data again.
+        for form_key in self.get_form_list():
+            form_obj = self.get_form(step=form_key,
+                data=self.storage.get_step_data(form_key),
+                files=self.storage.get_step_files(form_key))
+            if not form_obj.is_valid():
+                return self.render_revalidation_failure(form_key, form_obj, **kwargs)
+            final_form_list.append(form_obj)
+
+        # render the done view and reset the wizard before returning the
+        # response. This is needed to prevent from rendering done with the
+        # same data twice.
+        done_response = self.done(final_form_list, **kwargs)
+        self.storage.reset()
+        return done_response
+
+    def get_form_prefix(self, step=None, form=None):
+        """
+        Returns the prefix which will be used when calling the actual form for
+        the given step. `step` contains the step-name, `form` the form which
+        will be called with the returned prefix.
+
+        If no step is given, the form_prefix will determine the current step
+        automatically.
+        """
+        if step is None:
+            step = self.steps.current
+        return str(step)
+
+    def get_form_initial(self, step):
+        """
+        Returns a dictionary which will be passed to the form for `step`
+        as `initial`. If no initial data was provied while initializing the
+        form wizard, a empty dictionary will be returned.
+        """
+        return self.initial_dict.get(step, {})
+
+    def get_form_instance(self, step):
+        """
+        Returns a object which will be passed to the form for `step`
+        as `instance`. If no instance object was provied while initializing
+        the form wizard, None be returned.
+        """
+        return self.instance_dict.get(step, None)
+
+    def get_form(self, step=None, data=None, files=None):
+        """
+        Constructs the form for a given `step`. If no `step` is defined, the
+        current step will be determined automatically.
+
+        The form will be initialized using the `data` argument to prefill the
+        new form. If needed, instance or queryset (for `ModelForm` or
+        `ModelFormSet`) will be added too.
+        """
+        if step is None:
+            step = self.steps.current
+        # prepare the kwargs for the form instance.
+        kwargs = {
+            'data': data,
+            'files': files,
+            'prefix': self.get_form_prefix(step, self.form_list[step]),
+            'initial': self.get_form_initial(step),
+        }
+        if issubclass(self.form_list[step], forms.ModelForm):
+            # If the form is based on ModelForm, add instance if available.
+            kwargs.update({'instance': self.get_form_instance(step)})
+        elif issubclass(self.form_list[step], forms.models.BaseModelFormSet):
+            # If the form is based on ModelFormSet, add queryset if available.
+            kwargs.update({'queryset': self.get_form_instance(step)})
+        return self.form_list[step](**kwargs)
+
+    def process_step(self, form):
+        """
+        This method is used to postprocess the form data. By default, it
+        returns the raw `form.data` dictionary.
+        """
+        return self.get_form_step_data(form)
+
+    def process_step_files(self, form):
+        """
+        This method is used to postprocess the form files. By default, it
+        returns the raw `form.files` dictionary.
+        """
+        return self.get_form_step_files(form)
+
+    def render_revalidation_failure(self, step, form, **kwargs):
+        """
+        Gets called when a form doesn't validate when rendering the done
+        view. By default, it changed the current step to failing forms step
+        and renders the form.
+        """
+        self.storage.current_step = step
+        return self.render(form, **kwargs)
+
+    def get_form_step_data(self, form):
+        """
+        Is used to return the raw form data. You may use this method to
+        manipulate the data.
+        """
+        return form.data
+
+    def get_form_step_files(self, form):
+        """
+        Is used to return the raw form files. You may use this method to
+        manipulate the data.
+        """
+        return form.files
+
+    def get_all_cleaned_data(self):
+        """
+        Returns a merged dictionary of all step cleaned_data dictionaries.
+        If a step contains a `FormSet`, the key will be prefixed with formset
+        and contain a list of the formset' cleaned_data dictionaries.
+        """
+        cleaned_data = {}
+        for form_key in self.get_form_list():
+            form_obj = self.get_form(
+                step=form_key,
+                data=self.storage.get_step_data(form_key),
+                files=self.storage.get_step_files(form_key)
+            )
+            if form_obj.is_valid():
+                if isinstance(form_obj.cleaned_data, (tuple, list)):
+                    cleaned_data.update({
+                        'formset-%s' % form_key: form_obj.cleaned_data
+                    })
+                else:
+                    cleaned_data.update(form_obj.cleaned_data)
+        return cleaned_data
+
+    def get_cleaned_data_for_step(self, step):
+        """
+        Returns the cleaned data for a given `step`. Before returning the
+        cleaned data, the stored values are being revalidated through the
+        form. If the data doesn't validate, None will be returned.
+        """
+        if step in self.form_list:
+            form_obj = self.get_form(step=step,
+                data=self.storage.get_step_data(step),
+                files=self.storage.get_step_files(step))
+            if form_obj.is_valid():
+                return form_obj.cleaned_data
+        return None
+
+    def get_next_step(self, step=None):
+        """
+        Returns the next step after the given `step`. If no more steps are
+        available, None will be returned. If the `step` argument is None, the
+        current step will be determined automatically.
+        """
+        if step is None:
+            step = self.steps.current
+        form_list = self.get_form_list()
+        key = form_list.keyOrder.index(step) + 1
+        if len(form_list.keyOrder) > key:
+            return form_list.keyOrder[key]
+        return None
+
+    def get_prev_step(self, step=None):
+        """
+        Returns the previous step before the given `step`. If there are no
+        steps available, None will be returned. If the `step` argument is
+        None, the current step will be determined automatically.
+        """
+        if step is None:
+            step = self.steps.current
+        form_list = self.get_form_list()
+        key = form_list.keyOrder.index(step) - 1
+        if key >= 0:
+            return form_list.keyOrder[key]
+        return None
+
+    def get_step_index(self, step=None):
+        """
+        Returns the index for the given `step` name. If no step is given,
+        the current step will be used to get the index.
+        """
+        if step is None:
+            step = self.steps.current
+        return self.get_form_list().keyOrder.index(step)
+
+    def get_context_data(self, form, *args, **kwargs):
+        """
+        Returns the template context for a step. You can overwrite this method
+        to add more data for all or some steps. This method returns a
+        dictionary containing the rendered form step. Available template
+        context variables are:
+
+         * `extra_data` - current extra data
+         * `form` - form instance of the current step
+         * `wizard` - the wizard instance itself
+
+        Example:
+
+        .. code-block:: python
+
+            class MyWizard(FormWizard):
+                def get_context_data(self, form, **kwargs):
+                    context = super(MyWizard, self).get_context_data(form, **kwargs)
+                    if self.steps.current == 'my_step_name':
+                        context.update({'another_var': True})
+                    return context
+        """
+        context = super(WizardView, self).get_context_data(*args, **kwargs)
+        context.update(self.get_extra_data())
+        context['wizard'] = {
+            'form': form,
+            'steps': self.steps,
+            'managenent_form': ManagementForm(prefix=self.prefix, initial={
+                'current_step': self.steps.current,
+            }),
+        }
+        return context
+
+    def get_extra_data(self):
+        """
+        Returns the extra data currently stored in the storage backend.
+        """
+        return self.storage.extra_data
+
+    def update_extra_data(self, data):
+        """
+        Updates the currently stored extra data. Already stored extra
+        context will be kept!
+        """
+        new_extra_data = copy.copy(self.get_extra_data())
+        new_extra_data.update(data)
+        self.storage.extra_data = new_extra_data
+
+    def render(self, form=None, **kwargs):
+        """
+        Returns a ``HttpResponse`` containing a all needed context data.
+        """
+        form = form or self.get_form()
+        context = self.get_context_data(form, **kwargs)
+        return self.render_to_response(context)
+
+    def done(self, form_list, **kwargs):
+        """
+        This method muss be overrided by a subclass to process to form data
+        after processing all steps.
+        """
+        raise NotImplementedError("Your %s class has not defined a done() "
+            "method, which is required." % self.__class__.__name__)
+
+
+class SessionWizardView(WizardView):
+    """
+    A WizardView with pre-configured SessionStorage backend.
+    """
+    storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage'
+
+
+class CookieWizardView(WizardView):
+    """
+    A WizardView with pre-configured CookieStorage backend.
+    """
+    storage_name = 'django.contrib.formtools.wizard.storage.cookie.CookieStorage'
+
+
+class NamedUrlWizardView(WizardView):
+    """
+    A WizardView with URL named steps support.
+    """
+    url_name = None
+    done_step_name = None
+
+    @classmethod
+    def get_initkwargs(cls, *args, **kwargs):
+        """
+        We require a url_name to reverse URLs later. Additionally users can
+        pass a done_step_name to change the URL name of the "done" view.
+        """
+        extra_kwargs = {
+            'done_step_name': 'done'
+        }
+        assert 'url_name' in kwargs, 'URL name is needed to resolve correct wizard URLs'
+        extra_kwargs['url_name'] = kwargs.pop('url_name')
+
+        if 'done_step_name' in kwargs:
+            extra_kwargs['done_step_name'] = kwargs.pop('done_step_name')
+
+        initkwargs = super(NamedUrlWizardView, cls).get_initkwargs(*args, **kwargs)
+        initkwargs.update(extra_kwargs)
+
+        assert initkwargs['done_step_name'] not in initkwargs['form_list'], \
+            'step name "%s" is reserved for "done" view' % initkwargs['done_step_name']
+
+        return initkwargs
+
+    def get(self, *args, **kwargs):
+        """
+        This renders the form or, if needed, does the http redirects.
+        """
+        self.update_extra_data(kwargs.get('extra_context', {}))
+        step_url = kwargs.get('step', None)
+        if step_url is None:
+            if 'reset' in self.request.GET:
+                self.storage.reset()
+                self.storage.current_step = self.steps.first
+            if self.request.GET:
+                query_string = "?%s" % self.request.GET.urlencode()
+            else:
+                query_string = ""
+            next_step_url = reverse(self.url_name, kwargs={
+                'step': self.steps.current,
+            }) + query_string
+            return redirect(next_step_url)
+
+        # is the current step the "done" name/view?
+        elif step_url == self.done_step_name:
+            last_step = self.steps.last
+            return self.render_done(self.get_form(step=last_step,
+                data=self.storage.get_step_data(last_step),
+                files=self.storage.get_step_files(last_step)
+            ), **kwargs)
+
+        # is the url step name not equal to the step in the storage?
+        # if yes, change the step in the storage (if name exists)
+        elif step_url == self.steps.current:
+            # URL step name and storage step name are equal, render!
+            return self.render(self.get_form(
+                data=self.storage.current_step_data,
+                files=self.storage.current_step_data,
+            ), **kwargs)
+
+        elif step_url in self.get_form_list():
+            self.storage.current_step = step_url
+            return self.render(self.get_form(
+                data=self.storage.current_step_data,
+                files=self.storage.current_step_data,
+            ), **kwargs)
+
+        # invalid step name, reset to first and redirect.
+        else:
+            self.storage.current_step = self.steps.first
+            return redirect(self.url_name, step=self.steps.first)
+
+    def post(self, *args, **kwargs):
+        """
+        Do a redirect if user presses the prev. step button. The rest of this
+        is super'd from FormWizard.
+        """
+        prev_step = self.request.POST.get('wizard_prev_step', None)
+        if prev_step and prev_step in self.get_form_list():
+            self.storage.current_step = prev_step
+            return redirect(self.url_name, step=prev_step)
+        return super(NamedUrlWizardView, self).post(*args, **kwargs)
+
+    def render_next_step(self, form, **kwargs):
+        """
+        When using the NamedUrlFormWizard, we have to redirect to update the
+        browser's URL to match the shown step.
+        """
+        next_step = self.get_next_step()
+        self.storage.current_step = next_step
+        return redirect(self.url_name, step=next_step)
+
+    def render_revalidation_failure(self, failed_step, form, **kwargs):
+        """
+        When a step fails, we have to redirect the user to the first failing
+        step.
+        """
+        self.storage.current_step = failed_step
+        return redirect(self.url_name, step=failed_step)
+
+    def render_done(self, form, **kwargs):
+        """
+        When rendering the done view, we have to redirect first (if the URL
+        name doesn't fit).
+        """
+        if kwargs.get('step', None) != self.done_step_name:
+            return redirect(self.url_name, step=self.done_step_name)
+        return super(NamedUrlWizardView, self).render_done(form, **kwargs)
+
+
+class NamedUrlSessionWizardView(NamedUrlWizardView):
+    """
+    A NamedUrlWizardView with pre-configured SessionStorage backend.
+    """
+    storage_name = 'django.contrib.formtools.wizard.storage.session.SessionStorage'
+
+
+class NamedUrlCookieWizardView(NamedUrlWizardView):
+    """
+    A NamedUrlFormWizard with pre-configured CookieStorageBackend.
+    """
+    storage_name = 'django.contrib.formtools.wizard.storage.cookie.CookieStorage'
+
diff --git a/django/utils/functional.py b/django/utils/functional.py
index 21463bd..7d52794 100644
--- a/django/utils/functional.py
+++ b/django/utils/functional.py
@@ -267,3 +267,24 @@ class SimpleLazyObject(LazyObject):
 
     def _setup(self):
         self._wrapped = self._setupfunc()
+
+
+class lazy_property(property):
+    """
+    A property that works with subclasses by wrapping the decorated
+    functions of the base class.
+    """
+    def __new__(cls, fget=None, fset=None, fdel=None, doc=None):
+        if fget is not None:
+            @wraps(fget)
+            def fget(instance, instance_type=None, name=fget.__name__):
+                return getattr(instance, name)()
+        if fset is not None:
+            @wraps(fset)
+            def fset(instance, value, name=fset.__name__):
+                return getattr(instance, name)(value)
+        if fdel is not None:
+            @wraps(fdel)
+            def fdel(instance, name=fdel.__name__):
+                return getattr(instance, name)()
+        return property(fget, fset, fdel, doc)
diff --git a/docs/ref/contrib/formtools/form-wizard.txt b/docs/ref/contrib/formtools/form-wizard.txt
index cbacd59..8539842 100644
--- a/docs/ref/contrib/formtools/form-wizard.txt
+++ b/docs/ref/contrib/formtools/form-wizard.txt
@@ -2,23 +2,22 @@
 Form wizard
 ===========
 
-.. module:: django.contrib.formtools.wizard
+.. module:: django.contrib.formtools.wizard.views
     :synopsis: Splits forms across multiple Web pages.
 
 Django comes with an optional "form wizard" application that splits
 :doc:`forms </topics/forms/index>` across multiple Web pages. It maintains
-state in hashed HTML :samp:`<input type="hidden">` fields so that the full
-server-side processing can be delayed until the submission of the final form.
+state in one of the backends so that the full server-side processing can be
+delayed until the submission of the final form.
 
 You might want to use this if you have a lengthy form that would be too
 unwieldy for display on a single page. The first page might ask the user for
 core information, the second page might ask for less important information,
 etc.
 
-The term "wizard," in this context, is `explained on Wikipedia`_.
+The term "wizard", in this context, is `explained on Wikipedia`_.
 
 .. _explained on Wikipedia: http://en.wikipedia.org/wiki/Wizard_%28software%29
-.. _forms: ../forms/
 
 How it works
 ============
@@ -28,10 +27,8 @@ Here's the basic workflow for how a user would use a wizard:
     1. The user visits the first page of the wizard, fills in the form and
        submits it.
     2. The server validates the data. If it's invalid, the form is displayed
-       again, with error messages. If it's valid, the server calculates a
-       secure hash of the data and presents the user with the next form,
-       saving the validated data and hash in :samp:`<input type="hidden">`
-       fields.
+       again, with error messages. If it's valid, the server saves the current
+       state of the wizard in the backend and redirects to the next step.
     3. Step 1 and 2 repeat, for every subsequent form in the wizard.
     4. Once the user has submitted all the forms and all the data has been
        validated, the wizard processes the data -- saving it to the database,
@@ -40,30 +37,33 @@ Here's the basic workflow for how a user would use a wizard:
 Usage
 =====
 
-This application handles as much machinery for you as possible. Generally, you
-just have to do these things:
+This application handles as much machinery for you as possible. Generally,
+you just have to do these things:
 
-    1. Define a number of :class:`~django.forms.Form` classes -- one per wizard
-       page.
+    1. Define a number of :class:`~django.forms.Form` classes -- one per
+       wizard page.
 
-    2. Create a :class:`FormWizard` class that specifies what to do once all of
-       your forms have been submitted and validated. This also lets you
-       override some of the wizard's behavior.
+    2. Create a :class:`WizardView` subclass that specifies what to do once
+       all of your forms have been submitted and validated. This also lets
+       you override some of the wizard's behavior.
 
     3. Create some templates that render the forms. You can define a single,
        generic template to handle every one of the forms, or you can define a
        specific template for each form.
 
-    4. Point your URLconf at your :class:`FormWizard` class.
+    4. Add ``django.contrib.formtools.wizard`` to your
+       :setting:`INSTALLED_APPS` list in your settings file.
+
+    5. Point your URLconf at your :class:`WizardView` :meth:`~WizardView.as_view` method.
 
 Defining ``Form`` classes
-=========================
+-------------------------
 
 The first step in creating a form wizard is to create the
 :class:`~django.forms.Form` classes.  These should be standard
 :class:`django.forms.Form` classes, covered in the :doc:`forms documentation
-</topics/forms/index>`.  These classes can live anywhere in your codebase, but
-convention is to put them in a file called :file:`forms.py` in your
+</topics/forms/index>`.  These classes can live anywhere in your codebase,
+but convention is to put them in a file called :file:`forms.py` in your
 application.
 
 For example, let's write a "contact form" wizard, where the first page's form
@@ -79,85 +79,100 @@ the message itself. Here's what the :file:`forms.py` might look like::
     class ContactForm2(forms.Form):
         message = forms.CharField(widget=forms.Textarea)
 
-**Important limitation:** Because the wizard uses HTML hidden fields to store
-data between pages, you may not include a :class:`~django.forms.FileField`
-in any form except the last one.
 
-Creating a ``FormWizard`` class
-===============================
+.. note::
+
+    In order to use :class:`~django.forms.FileField` in any form, see the
+    section :ref:`Handling files <wizard-files>` below to learn more about what
+    to do.
+
+Creating a ``WizardView`` class
+-------------------------------
 
 The next step is to create a
-:class:`django.contrib.formtools.wizard.FormWizard` subclass.  As with your
-:class:`~django.forms.Form` classes, this :class:`FormWizard` class can live
-anywhere in your codebase, but convention is to put it in :file:`forms.py`.
+:class:`django.contrib.formtools.wizard.view.WizardView` subclass. You can
+also use the :class:`SessionWizardView` or :class:`CookieWizardView` class
+which preselects the wizard storage backend (session-based or cookie-based).
+
+We will use the :class:`SessionWizardView` in all examples but is is completly
+fine to use the :class:`CookieWizardView` instead. As with your
+:class:`~django.forms.Form` classes, this :class:`WizardView` class can live
+anywhere in your codebase, but convention is to put it in :file:`views.py`.
 
 The only requirement on this subclass is that it implement a
-:meth:`~FormWizard.done()` method.
+:meth:`~WizardView.done()` method.
 
-.. method:: FormWizard.done
+.. method:: WizardView.done(form_list)
 
     This method specifies what should happen when the data for *every* form is
-    submitted and validated.  This method is passed two arguments:
-
-        * ``request`` -- an :class:`~django.http.HttpRequest` object
-        * ``form_list`` -- a list of :class:`~django.forms.Form` classes
+    submitted and validated. This method is passed a list of validated
+    :class:`~django.forms.Form` instances.
 
-In this simplistic example, rather than perform any database operation, the
-method simply renders a template of the validated data::
+    In this simplistic example, rather than performing any database operation,
+    the method simply renders a template of the validated data::
 
-    from django.shortcuts import render_to_response
-    from django.contrib.formtools.wizard import FormWizard
+        from django.shortcuts import render_to_response
+        from django.contrib.formtools.wizard.views import SessionWizardView
 
-    class ContactWizard(FormWizard):
-        def done(self, request, form_list):
-            return render_to_response('done.html', {
-                'form_data': [form.cleaned_data for form in form_list],
-            })
+        class ContactWizard(SessionWizardView):
+            def done(self, form_list, **kwargs):
+                return render_to_response('done.html', {
+                    'form_data': [form.cleaned_data for form in form_list],
+                })
 
-Note that this method will be called via ``POST``, so it really ought to be a
-good Web citizen and redirect after processing the data. Here's another
-example::
+    Note that this method will be called via ``POST``, so it really ought to be a
+    good Web citizen and redirect after processing the data. Here's another
+    example::
 
-    from django.http import HttpResponseRedirect
-    from django.contrib.formtools.wizard import FormWizard
+        from django.http import HttpResponseRedirect
+        from django.contrib.formtools.wizard.views import SessionWizardView
 
-    class ContactWizard(FormWizard):
-        def done(self, request, form_list):
-            do_something_with_the_form_data(form_list)
-            return HttpResponseRedirect('/page-to-redirect-to-when-done/')
+        class ContactWizard(SessionWizardView):
+            def done(self, form_list, **kwargs):
+                do_something_with_the_form_data(form_list)
+                return HttpResponseRedirect('/page-to-redirect-to-when-done/')
 
-See the section `Advanced FormWizard methods`_ below to learn about more
-:class:`FormWizard` hooks.
+See the section :ref:`Advanced WizardView methods <wizardview-advanced-methods>`
+below to learn about more :class:`WizardView` hooks.
 
 Creating templates for the forms
-================================
+--------------------------------
 
 Next, you'll need to create a template that renders the wizard's forms. By
-default, every form uses a template called :file:`forms/wizard.html`. (You can
-change this template name by overriding :meth:`~FormWizard.get_template()`,
-which is documented below. This hook also allows you to use a different
-template for each form.)
-
-This template expects the following context:
-
-    * ``step_field`` -- The name of the hidden field containing the step.
-    * ``step0`` -- The current step (zero-based).
-    * ``step`` -- The current step (one-based).
-    * ``step_count`` -- The total number of steps.
-    * ``form`` -- The :class:`~django.forms.Form` instance for the current step
-      (either empty or with errors).
-    * ``previous_fields`` -- A string representing every previous data field,
-      plus hashes for completed forms, all in the form of hidden fields. Note
-      that you'll need to run this through the :tfilter:`safe` template filter,
-      to prevent auto-escaping, because it's raw HTML.
+default, every form uses a template called
+:file:`formtools/wizard/wizard_form.html`. You can change this template name
+by overriding either the :attr:`~WizardView.template_name` attribute or the
+:meth:`~WizardView.get_template_names()` method, which is documented below.
+This hook also allows you to use a different template for each form.
+
+This template expects a ``wizard`` object that has various items attached to
+it:
+
+    * ``form`` -- The :class:`~django.forms.Form` instance for the current
+      step (either empty or with errors).
+
+    * ``steps`` -- A helper object to access the various steps related data:
+
+        * ``step0`` -- The current step (zero-based).
+        * ``step1`` -- The current step (one-based).
+        * ``count`` -- The total number of steps.
+        * ``first`` -- The first step.
+        * ``last`` -- The last step.
+        * ``current`` -- The current (or first) step.
+        * ``next`` -- The next step.
+        * ``prev`` -- The previous step.
+        * ``index`` -- The index of the current step.
+        * ``all`` -- A list of all steps of the wizard.
 
 You can supply extra context to this template in two ways:
 
     * Set the :attr:`~FormWizard.extra_context` attribute on your
       :class:`FormWizard` subclass to a dictionary.
 
-    * Pass a dictionary as a parameter named ``extra_context`` to your wizard's
-      URL pattern in your URLconf.  See :ref:`hooking-wizard-into-urlconf`.
+    * Pass a dictionary as a parameter named ``extra_context`` to your
+      wizard's URL pattern in your URLconf.
+
+      See :ref:`Hooking the wizard into a URLconf <wizard-urlconf>`.
 
 Here's a full example template:
 
@@ -166,170 +181,426 @@ Here's a full example template:
     {% extends "base.html" %}
 
     {% block content %}
-    <p>Step {{ step }} of {{ step_count }}</p>
+    <p>Step {{ wizard.steps.current }} of {{ wizard.steps.count }}</p>
     <form action="." method="post">{% csrf_token %}
     <table>
-    {{ form }}
+    {{ wizard.management_form }}
+    {% if wizard.form.forms %}
+        {{ wizard.form.management_form }}
+        {% for form in wizard.form.forms %}
+            {{ form }}
+        {% endfor %}
+    {% else %}
+        {{ wizard.form }}
+    {% endif %}
+    {% if wizard.steps.prev %}
+    <button name="wizard_prev_step" value="{{ wizard.steps.first }}">{% trans "first step" %}</button>
+    <button name="wizard_prev_step" value="{{ wizard.steps.prev }}">{% trans "prev step" %}</button>
+    {% endif %}
     </table>
-    <input type="hidden" name="{{ step_field }}" value="{{ step0 }}" />
-    {{ previous_fields|safe }}
     <input type="submit">
     </form>
     {% endblock %}
 
-Note that ``previous_fields``, ``step_field`` and ``step0`` are all required
-for the wizard to work properly.
+.. note::
+
+    Note that ``management_form`` **must be used** for the wizard to work properly.
 
-.. _hooking-wizard-into-urlconf:
+.. _wizard-urlconf:
 
 Hooking the wizard into a URLconf
-=================================
+---------------------------------
 
 Finally, we need to specify which forms to use in the wizard, and then
-deploy the new :class:`FormWizard` object a URL in ``urls.py``. The
-wizard takes a list of your :class:`~django.forms.Form` objects as
-arguments when you instantiate the Wizard::
+deploy the new :class:`WizardView` object a URL in the ``urls.py``. The
+wizard's :meth:`as_view` method takes a list of your
+:class:`~django.forms.Form` classes as an argument during instantiation::
 
-    from django.conf.urls.defaults import *
-    from testapp.forms import ContactForm1, ContactForm2, ContactWizard
+    from django.conf.urls.defaults import patterns
+
+    from myapp.forms import ContactForm1, ContactForm2
+    from myapp.views import ContactWizard
 
     urlpatterns = patterns('',
-        (r'^contact/$', ContactWizard([ContactForm1, ContactForm2])),
+        (r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
     )
 
-Advanced ``FormWizard`` methods
+.. _wizardview-advanced-methods:
+
+Advanced ``WizardView`` methods
 ===============================
 
-.. class:: FormWizard
+.. class:: WizardView
 
-    Aside from the :meth:`~done()` method, :class:`FormWizard` offers a few
+    Aside from the :meth:`~done()` method, :class:`WizardView` offers a few
     advanced method hooks that let you customize how your wizard works.
 
     Some of these methods take an argument ``step``, which is a zero-based
-    counter representing the current step of the wizard. (E.g., the first form
-    is ``0`` and the second form is ``1``.)
+    counter as string representing the current step of the wizard. (E.g., the
+    first form is ``'0'`` and the second form is ``'1'``)
 
-.. method:: FormWizard.prefix_for_step
+.. method:: WizardView.get_form_prefix(step)
 
-    Given the step, returns a form prefix to use.  By default, this simply uses
+    Given the step, returns a form prefix to use. By default, this simply uses
     the step itself. For more, see the :ref:`form prefix documentation
     <form-prefix>`.
 
+.. method:: WizardView.process_step(form)
+
+    Hook for modifying the wizard's internal state, given a fully validated
+    :class:`~django.forms.Form` object. The Form is guaranteed to have clean,
+    valid data.
+
+    Note that this method is called every time a page is rendered for *all*
+    submitted steps.
+
+    The default implementation::
+
+        def process_step(self, form):
+            return self.get_form_step_data(form)
+
+.. method:: WizardView.get_form_initial(step)
+
+    Returns a dictionary which will be passed to the form for ``step`` as
+    ``initial``. If no initial data was provied while initializing the
+    form wizard, a empty dictionary should be returned.
+
+    The default implementation::
+
+        def get_form_initial(self, step):
+            return self.initial_dict.get(step, {})
+
+.. method:: WizardView.get_form_instance(step)
+
+    Returns a object which will be passed to the form for ``step`` as
+    ``instance``. If no instance object was provied while initializing
+    the form wizard, None be returned.
+
+    The default implementation::
+
+        def get_form_instance(self, step):
+            return self.instance_dict.get(step, None)
+
+.. method:: WizardView.get_context_data(form, **kwargs)
+
+    Returns the template context for a step. You can overwrite this method
+    to add more data for all or some steps. This method returns a dictionary
+    containing the rendered form step.
+
+    The default template context variables are:
+
+    * Any data that :attr:`WizardView.get_extra_data` returns.
+    * ``form`` -- form instance of the current step
+    * ``wizard`` -- the wizard instance itself
+
+    Example to add extra variables for a specific step::
+
+        def get_context_data(self, form, **kwargs):
+            context = super(MyWizard, self).get_context_data(form, **kwargs)
+            if self.steps.current == 'my_step_name':
+                context.update({'another_var': True})
+            return context
+
+.. method:: WizardView.get_wizard_name()
+
+    This method can be used to change the wizard's internal name.
+
     Default implementation::
 
-        def prefix_for_step(self, step):
-            return str(step)
+        def get_wizard_name(self):
+            return normalize_name(self.__class__.__name__)
+
+.. method:: WizardView.get_prefix()
 
-.. method:: FormWizard.render_hash_failure
+    This method returns a prefix for the storage backends. These backends use
+    the prefix to fetch the correct data for the wizard. (Multiple wizards
+    could save their data in one session)
 
-    Renders a template if the hash check fails. It's rare that you'd need to
-    override this.
+    You can change this method to make the wizard data prefix more unique to,
+    e.g. have multiple instances of one wizard in one session.
 
     Default implementation::
 
-        def render_hash_failure(self, request, step):
-            return self.render(self.get_form(step), request, step,
-                context={'wizard_error':
-                             'We apologize, but your form has expired. Please'
-                             ' continue filling out the form from this page.'})
+        def get_prefix(self):
+            return self.wizard_name
 
-.. method:: FormWizard.security_hash
+.. method:: WizardView.get_form(step=None, data=None, files=None)
 
-    Calculates the security hash for the given request object and
-    :class:`~django.forms.Form` instance.
+    This method constructs the form for a given ``step``. If no ``step`` is
+    defined, the current step will be determined automatically.
+    The method gets three arguments:
 
-    By default, this generates a SHA1 HMAC using your form data and your
-    :setting:`SECRET_KEY` setting. It's rare that somebody would need to
-    override this.
+    * ``step`` -- The step for which the form instance should be generated.
+    * ``data`` -- Gets passed to the form's data argument
+    * ``files`` -- Gets passed to the form's files argument
 
-    Example::
+    You can override this method to add extra arguments to the form instance.
 
-        def security_hash(self, request, form):
-            return my_hash_function(request, form)
+    Example code to add a user attribute to the form on step 2::
 
-.. method:: FormWizard.parse_params
+        def get_form(self, step=None, data=None, files=None):
+            form = super(MyWizard, self).get_form(step, data, files)
+            if step == '1':
+                form.user = self.request.user
+            return form
 
-    A hook for saving state from the request object and ``args`` / ``kwargs``
-    that were captured from the URL by your URLconf.
+.. method:: WizardView.process_step(form)
 
-    By default, this does nothing.
+    This method gives you a way to post-process the form data before the data
+    gets stored within the storage backend. By default it just passed the
+    form.data dictionary. You should not manipulate the data here but you can
+    use the data to do some extra work if needed (e.g. set extra_data).
 
-    Example::
+    Default implementation::
 
-        def parse_params(self, request, *args, **kwargs):
-            self.my_state = args[0]
+        def process_step(self, form):
+            return self.get_form_step_data(form)
 
-.. method:: FormWizard.get_template
+.. method:: WizardView.process_step_files(form)
 
-    Returns the name of the template that should be used for the given step.
+    This method gives you a way to post-process the form files before the
+    files gets stored within the storage backend. By default it just passed
+    the ``form.files`` dictionary. You should not manipulate the data here
+    but you can use the data to do some extra work if needed (e.g. set extra
+    data).
 
-    By default, this returns :file:`'forms/wizard.html'`, regardless of step.
+    Default implementation::
 
-    Example::
+        def process_step_files(self, form):
+            return self.get_form_step_files(form)
 
-        def get_template(self, step):
-            return 'myapp/wizard_%s.html' % step
+.. method:: WizardView.render_revalidation_failure(step, form, **kwargs)
 
-    If :meth:`~FormWizard.get_template` returns a list of strings, then the
-    wizard will use the template system's
-    :func:`~django.template.loader.select_template` function.
-    This means the system will use the first template that exists on the
-    filesystem. For example::
+    When the wizard thinks, all steps passed it revalidates all forms with the
+    data from the backend storage.
 
-        def get_template(self, step):
-            return ['myapp/wizard_%s.html' % step, 'myapp/wizard.html']
+    If any of the forms don't validate correctly, this method gets called.
+    This method expects two arguments, ``step`` and ``form``.
 
-.. method:: FormWizard.render_template
+    The default implementation resets the current step to the first failing
+    form and redirects the user to the invalid form.
 
-    Renders the template for the given step, returning an
-    :class:`~django.http.HttpResponse` object.
+    Default implementation::
 
-    Override this method if you want to add a custom context, return a
-    different MIME type, etc. If you only need to override the template name,
-    use :meth:`~FormWizard.get_template` instead.
+        def render_revalidation_failure(self, step, form, **kwargs):
+            self.storage.current_step = step
+            return self.render(form, **kwargs)
 
-    The template will be rendered with the context documented in the
-    "Creating templates for the forms" section above.
+.. method:: WizardView.get_form_step_data(form)
 
-.. method:: FormWizard.process_step
+    This method fetches the form data from and returns the dictionary. You
+    can use this method to manipulate the values before the data gets stored
+    in the storage backend.
 
-    Hook for modifying the wizard's internal state, given a fully validated
-    :class:`~django.forms.Form` object. The Form is guaranteed to have clean,
-    valid data.
+    Default implementation::
 
-    This method should *not* modify any of that data. Rather, it might want to
-    set ``self.extra_context`` or dynamically alter ``self.form_list``, based
-    on previously submitted forms.
+        def get_form_step_data(self, form):
+            return form.data
 
-    Note that this method is called every time a page is rendered for *all*
-    submitted steps.
+.. method:: WizardView.get_form_step_files(form)
+
+    This method returns the form files. You can use this method to manipulate
+    the files before the data gets stored in the storage backend.
+
+    Default implementation::
+
+        def get_form_step_files(self, form):
+            return form.files
+
+.. method:: WizardView.get_extra_data
+
+    This method returns the content of the stored extra data. You can override
+    this method to change the extra data which gets passed to the template.
+    The default implementation passes the extra data dictionary from the
+    storage backend.
+
+    Default implementation::
+
+        def get_extra_data(self):
+            return self.storage.extra_data
+
+.. method:: WizardView.update_extra_data(data)
+
+    This method expects one argument ``data``. The method will fetch the
+    current extra data, update the dictionary with the passed data and store
+    the content back to storage backend.
+
+    You could change this method to protect the extra_data dictionary from
+    external changes by just doing nothing.
 
-    The function signature::
+    Example code::
 
-        def process_step(self, request, form, step):
-            # ...
+        def update_extra_data(self, data):
+            pass
+
+.. method:: WizardView.render(form, **kwargs)
+
+    This method gets called after the get or post request was handled. You can
+    hook in this method to, e.g. change the type of http response.
+
+    Default implementation::
+
+        def render(self, form=None, **kwargs):
+            form = form or self.get_form()
+            context = self.get_context_data(form, **kwargs)
+            return self.render_to_response(context)
 
 Providing initial data for the forms
 ====================================
 
-.. attribute:: FormWizard.initial
+.. attribute:: WizardView.initial_dict
 
     Initial data for a wizard's :class:`~django.forms.Form` objects can be
-    provided using the optional :attr:`~FormWizard.initial` keyword argument.
-    This argument should be a dictionary mapping a step to a dictionary
-    containing the initial data for that step. The dictionary of initial data
+    provided using the optional :attr:`~Wizard.initial_dict` keyword argument.
+    This argument should be a dictionary mapping the steps to dictionaries
+    containing the initial data for each step. The dictionary of initial data
     will be passed along to the constructor of the step's
     :class:`~django.forms.Form`::
 
-        >>> from testapp.forms import ContactForm1, ContactForm2, ContactWizard
+        >>> from myapp.forms import ContactForm1, ContactForm2
+        >>> from myapp.views import ContactWizard
         >>> initial = {
-        ...     0: {'subject': 'Hello', 'sender': 'user@example.com'},
-        ...     1: {'message': 'Hi there!'}
+        ...     '0': {'subject': 'Hello', 'sender': 'user@example.com'},
+        ...     '1': {'message': 'Hi there!'}
         ... }
-        >>> wiz = ContactWizard([ContactForm1, ContactForm2], initial=initial)
-        >>> form1 = wiz.get_form(0)
-        >>> form2 = wiz.get_form(1)
+        >>> wiz = ContactWizard.as_view([ContactForm1, ContactForm2], initial_dict=initial)
+        >>> form1 = wiz.get_form('0')
+        >>> form2 = wiz.get_form('1')
         >>> form1.initial
         {'sender': 'user@example.com', 'subject': 'Hello'}
         >>> form2.initial
         {'message': 'Hi there!'}
+
+    The ``initial_dict`` can also take a list of dictionaries for a specific
+    step if the step is a ``FormSet``.
+
+.. _wizard-files:
+
+Handling files
+==============
+
+To handle :class:`~django.forms.FileField` within any step form of the wizard,
+you have to add a :attr:`file_storage` to your :class:`WizardView` subclass.
+
+This storage will temporarilyy store the uploaded files for the wizard. The
+:attr:`file_storage` attribute should be a
+:class:`~django.core.files.storage.Storage` subclass.
+
+.. warning::
+
+    Please remember to take care of removing old files as the
+    :class:`WizardView` won't remove any files, whether the wizard gets
+    finished corretly or not.
+
+Conditionally view/skip specific steps
+======================================
+
+.. attribute:: WizardView.condition_dict
+
+The :meth:`~WizardView.as_view` accepts a ``condition_dict`` argument. You can pass a
+dictionary of boolean values or callables. The key should match the steps
+name (e.g. '0', '1').
+
+If the value of a specific step is callable it will be called with the
+:class:`WizardView` instance as the only argument. If the return value is true,
+the step's form will be used.
+
+This example provides a contact form including a condition. The condition is
+used to show a message from only if a checkbox in the first step was checked.
+
+The steps are defined in a ``forms.py``::
+
+    from django import forms
+
+    class ContactForm1(forms.Form):
+        subject = forms.CharField(max_length=100)
+        sender = forms.EmailField()
+        leave_message = forms.BooleanField(required=False)
+
+    class ContactForm2(forms.Form):
+        message = forms.CharField(widget=forms.Textarea)
+
+We define our wizard in a ``views.py``::
+
+    from django.shortcuts import render_to_response
+    from django.contrib.formtools.wizard.views import SessionWizardView
+
+    def show_message_form_condition(wizard):
+        # try to get the cleaned data of step 1
+        cleaned_data = wizard.get_cleaned_data_for_step('0') or {}
+        # check if the field ``leave_message`` was checked.
+        return cleaned_data.get('leave_message', True)
+
+    class ContactWizard(SessionWizardView):
+
+        def done(self, form_list, **kwargs):
+            return render_to_response('done.html', {
+                'form_data': [form.cleaned_data for form in form_list],
+            })
+
+We need to add the ``ContactWizard`` to our ``urls.py`` file::
+
+    from django.conf.urls.defaults import pattern
+
+    from myapp.forms import ContactForm1, ContactForm2
+    from myapp.views import ContactWizard, show_message_form_condition
+
+    contact_forms = [ContactForm1, ContactForm2]
+
+    urlpatterns = patterns('',
+        (r'^contact/$', ContactWizard.as_view(contact_forms,
+            condition_dict={'1': show_message_form_condition}
+        )),
+    )
+
+As you can see, we defined a ``show_message_form_condition`` next to our
+:class:`WizardView` subclass and added a ``condition_dict`` argument to the
+:meth:`~WizardView.as_view` method. The key refers to the second wizard step
+(because of the zero based step index).
+
+How to work with ModelForm and ModelFormSet
+===========================================
+
+The WizardView supports :class:`~django.forms.ModelForm` and
+:class:`~django.forms.ModelFormSet`. Additionally to the ``initial_dict``,
+the :meth:`~WizardView.as_view` method takes a ``instance_dict`` argument
+with a list of instances for the ``ModelForm`` and ``ModelFormSet``.
+
+Usage of NamedUrlWizardView
+===========================
+
+.. class:: NamedUrlWizardView
+
+There is a :class:`WizardView` subclass which adds named-urls support to the wizard.
+By doing this, you can have single urls for every step.
+
+To use the named urls, you have to change the ``urls.py``.
+
+Below you will see an example of a contact wizard with two steps, step 1 with
+"contactdata" as its name and step 2 with "leavemessage" as its name.
+
+Additionally you have to pass two more arguments to the
+:meth:`~WizardView.as_view` method:
+
+   * ``url_name`` -- the name of the url (as provided in the urls.py)
+   * ``done_step_name`` -- the name in the url for the done step
+
+Example code for the changed ``urls.py`` file::
+
+    from django.conf.urls.defaults import url, patterns
+
+    from myapp.forms import ContactForm1, ContactForm2
+    from myapp.views import ContactWizard
+
+    named_contact_forms = (
+        ('contactdata', ContactForm1),
+        ('leavemessage', ContactForm2),
+    )
+
+    contact_wizard = ContactWizard.as_view(named_contact_forms,
+        url_name='contact_step', done_step_name='finished')
+
+    urlpatterns = patterns('',
+        url(r'^contact/(?P<step>.+)/$', contact_wizard, name='contact_step'),
+        url(r'^contact/$', contact_wizard, name='contact'),
+    )
diff --git a/tests/regressiontests/formwizard/tests.py b/tests/regressiontests/formwizard/tests.py
index 0c94d2e..944569d 100644
--- a/tests/regressiontests/formwizard/tests.py
+++ b/tests/regressiontests/formwizard/tests.py
@@ -1,7 +1,12 @@
 import re
+import warnings
 from django import forms
 from django.test import TestCase
 
+warnings.filterwarnings('ignore', category=PendingDeprecationWarning,
+                        module='django.contrib.formtools.wizard')
+
+
 class FormWizardWithNullBooleanField(TestCase):
     urls = 'regressiontests.formwizard.urls'
 
diff --git a/tests/regressiontests/utils/functional.py b/tests/regressiontests/utils/functional.py
index 2784ddd..90a6f08 100644
--- a/tests/regressiontests/utils/functional.py
+++ b/tests/regressiontests/utils/functional.py
@@ -1,5 +1,5 @@
 from django.utils import unittest
-from django.utils.functional import lazy
+from django.utils.functional import lazy, lazy_property
 
 
 class FunctionalTestCase(unittest.TestCase):
@@ -20,3 +20,20 @@ class FunctionalTestCase(unittest.TestCase):
 
         t = lazy(lambda: Klazz(), Klazz)()
         self.assertTrue('base_method' in dir(t))
+
+    def test_lazy_property(self):
+
+        class A(object):
+
+            def _get_do(self):
+                raise NotImplementedError
+            def _set_do(self, value):
+                raise NotImplementedError
+            do = lazy_property(_get_do, _set_do)
+
+        class B(A):
+            def _get_do(self):
+                return "DO IT"
+
+        self.assertRaises(NotImplementedError, lambda: A().do)
+        self.assertEqual(B().do, 'DO IT')
