`).
-
-This template expects a ``wizard`` object that has various items attached to
-it:
-
-* ``form`` -- The :class:`~django.forms.Form` or
- :class:`~django.forms.formsets.BaseFormSet` 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 additional context variables by using the
-:meth:`~WizardView.get_context_data` method of your :class:`WizardView`
-subclass.
-
-Here's a full example template:
-
-.. code-block:: html+django
-
- {% extends "base.html" %}
- {% load i18n %}
-
- {% block head %}
- {{ wizard.form.media }}
- {% endblock %}
-
- {% block content %}
- Step {{ wizard.steps.step1 }} of {{ wizard.steps.count }}
-
- {% endblock %}
-
-.. note::
-
- Note that ``{{ wizard.management_form }}`` **must be used** for
- the wizard to work properly.
-
-.. _wizard-urlconf:
-
-Hooking the wizard into a URLconf
----------------------------------
-
-.. method:: WizardView.as_view()
-
-Finally, we need to specify which forms to use in the wizard, and then
-deploy the new :class:`WizardView` object at a URL in the ``urls.py``. The
-wizard's ``as_view()`` method takes a list of your
-:class:`~django.forms.Form` classes as an argument during instantiation::
-
- from django.conf.urls import url
-
- from myapp.forms import ContactForm1, ContactForm2
- from myapp.views import ContactWizard
-
- urlpatterns = [
- url(r'^contact/$', ContactWizard.as_view([ContactForm1, ContactForm2])),
- ]
-
-You can also pass the form list as a class attribute named ``form_list``::
-
- class ContactWizard(WizardView):
- form_list = [ContactForm1, ContactForm2]
-
-.. _wizard-template-for-each-form:
-
-Using a different template for each form
-----------------------------------------
-
-As mentioned above, you may specify a different template for each form.
-Consider an example using a form wizard to implement a multi-step checkout
-process for an online store. In the first step, the user specifies a billing
-and shipping address. In the second step, the user chooses payment type. If
-they chose to pay by credit card, they will enter credit card information in
-the next step. In the final step, they will confirm the purchase.
-
-Here's what the view code might look like::
-
- from django.http import HttpResponseRedirect
- from django.contrib.formtools.wizard.views import SessionWizardView
-
- FORMS = [("address", myapp.forms.AddressForm),
- ("paytype", myapp.forms.PaymentChoiceForm),
- ("cc", myapp.forms.CreditCardForm),
- ("confirmation", myapp.forms.OrderForm)]
-
- TEMPLATES = {"address": "checkout/billingaddress.html",
- "paytype": "checkout/paymentmethod.html",
- "cc": "checkout/creditcard.html",
- "confirmation": "checkout/confirmation.html"}
-
- def pay_by_credit_card(wizard):
- """Return true if user opts to pay by credit card"""
- # Get cleaned data from payment step
- cleaned_data = wizard.get_cleaned_data_for_step('paytype') or {'method': 'none'}
- # Return true if the user selected credit card
- return cleaned_data['method'] == 'cc'
-
-
- class OrderWizard(SessionWizardView):
- def get_template_names(self):
- return [TEMPLATES[self.steps.current]]
-
- def done(self, form_list, **kwargs):
- do_something_with_the_form_data(form_list)
- return HttpResponseRedirect('/page-to-redirect-to-when-done/')
- ...
-
-The ``urls.py`` file would contain something like::
-
- urlpatterns = [
- url(r'^checkout/$', OrderWizard.as_view(FORMS, condition_dict={'cc': pay_by_credit_card})),
- ]
-
-The ``condition_dict`` can be passed as attribute for the ``as_view()`
-method or as a class attribute named ``condition_dict``::
-
- class OrderWizard(WizardView):
- condition_dict = {'cc': pay_by_credit_card}
-
-Note that the ``OrderWizard`` object is initialized with a list of pairs.
-The first element in the pair is a string that corresponds to the name of the
-step and the second is the form class.
-
-In this example, the
-:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()`
-method returns a list containing a single template, which is selected based on
-the name of the current step.
-
-.. _wizardview-advanced-methods:
-
-Advanced ``WizardView`` methods
-===============================
-
-.. class:: WizardView
-
- 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 as string representing the current step of the wizard. (E.g., the
- first form is ``'0'`` and the second form is ``'1'``)
-
-.. method:: WizardView.get_form_prefix(step=None, form=None)
-
- Returns the prefix which will be used when calling the form for the given
- step. ``step`` contains the step name, ``form`` the form class which will
- be called with the returned prefix.
-
- If no ``step`` is given, it will be determined automatically. By default,
- this simply uses the step itself and the ``form`` parameter is not used.
-
- For more, see the :ref:`form prefix documentation `.
-
-.. method:: WizardView.get_form_initial(step)
-
- Returns a dictionary which will be passed as the
- :attr:`~django.forms.Form.initial` argument when instantiating the Form
- instance for step ``step``. If no initial data was provided while
- initializing the form wizard, an empty dictionary should be returned.
-
- The default implementation::
-
- def get_form_initial(self, step):
- return self.initial_dict.get(step, {})
-
-.. method:: WizardView.get_form_kwargs(step)
-
- Returns a dictionary which will be used as the keyword arguments when
- instantiating the form instance on given ``step``.
-
- The default implementation::
-
- def get_form_kwargs(self, step):
- return {}
-
-.. method:: WizardView.get_form_instance(step)
-
- This method will be called only if a :class:`~django.forms.ModelForm` is
- used as the form for step ``step``.
-
- Returns an :class:`~django.db.models.Model` object which will be passed as
- the ``instance`` argument when instantiating the ``ModelForm`` for step
- ``step``. If no instance object was provided while initializing the form
- wizard, ``None`` will 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 extra data the storage backend has stored
- * ``wizard`` -- a dictionary representation of the wizard instance with the
- following key/values:
-
- * ``form`` -- :class:`~django.forms.Form` or
- :class:`~django.forms.formsets.BaseFormSet` instance for the current step
- * ``steps`` -- A helper object to access the various steps related data
- * ``management_form`` -- all the management data for the current step
-
- Example to add extra variables for a specific step::
-
- def get_context_data(self, form, **kwargs):
- context = super(MyWizard, self).get_context_data(form=form, **kwargs)
- if self.steps.current == 'my_step_name':
- context.update({'another_var': True})
- return context
-
-.. method:: WizardView.get_prefix(*args, **kwargs)
-
- This method returns a prefix for use by the storage backends. Backends use
- the prefix as a mechanism to allow data to be stored separately for each
- wizard. This allows wizards to store their data in a single backend
- without overwriting each other.
-
- 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 get_prefix(self, *args, **kwargs):
- # use the lowercase underscore version of the class name
- return normalize_name(self.__class__.__name__)
-
-.. method:: WizardView.get_form(step=None, data=None, files=None)
-
- This method constructs the form for a given ``step``. If no ``step`` is
- defined, the current step will be determined automatically. If you override
- ``get_form``, however, you will need to set ``step`` yourself using
- ``self.steps.current`` as in the example below. The method gets three
- arguments:
-
- * ``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
-
- You can override this method to add extra arguments to the form instance.
-
- Example code to add a user attribute to the form on step 2::
-
- def get_form(self, step=None, data=None, files=None):
- form = super(MyWizard, self).get_form(step, data, files)
-
- # determine the step if not given
- if step is None:
- step = self.steps.current
-
- if step == '1':
- form.user = self.request.user
- return form
-
-.. 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.
-
- 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 returns the
- ``form.data`` dictionary. You should not manipulate the data here but you
- can use it to do some extra work if needed (e.g. set storage extra 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.process_step_files(form)
-
- 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 returns
- the ``form.files`` dictionary. You should not manipulate the data here
- but you can use it to do some extra work if needed (e.g. set storage
- extra data).
-
- Default implementation::
-
- def process_step_files(self, form):
- return self.get_form_step_files(form)
-
-.. method:: WizardView.render_goto_step(step, goto_step, **kwargs)
-
- This method is called when the step should be changed to something else
- than the next step. By default, this method just stores the requested
- step ``goto_step`` in the storage and then renders the new step.
-
- If you want to store the entered data of the current step before rendering
- the next step, you can overwrite this method.
-
-.. method:: WizardView.render_revalidation_failure(step, form, **kwargs)
-
- When the wizard thinks all steps have passed it revalidates all forms with
- the data from the backend storage.
-
- If any of the forms don't validate correctly, this method gets called.
- This method expects two arguments, ``step`` and ``form``.
-
- The default implementation resets the current step to the first failing
- form and redirects the user to the invalid form.
-
- Default implementation::
-
- def render_revalidation_failure(self, step, form, **kwargs):
- self.storage.current_step = step
- return self.render(form, **kwargs)
-
-.. method:: WizardView.get_form_step_data(form)
-
- This method fetches the data from the ``form`` Form instance and returns the
- dictionary. You can use this method to manipulate the values before the data
- gets stored in the storage backend.
-
- Default implementation::
-
- def get_form_step_data(self, form):
- return form.data
-
-.. 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.render(form, **kwargs)
-
- This method gets called after the GET or POST request has been 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=form, **kwargs)
- return self.render_to_response(context)
-
-.. method:: WizardView.get_cleaned_data_for_step(step)
-
- This method returns the cleaned data for a given ``step``. Before returning
- the cleaned data, the stored values are revalidated through the form. If
- the data doesn't validate, ``None`` will be returned.
-
-.. method:: WizardView.get_all_cleaned_data()
-
- This method returns a merged dictionary of all form steps' ``cleaned_data``
- dictionaries. If a step contains a ``FormSet``, the key will be prefixed
- with ``formset-`` and contain a list of the formset's ``cleaned_data``
- dictionaries. Note that if two or more steps have a field with the same
- name, the value for that field from the latest step will overwrite the
- value from any earlier steps.
-
-Providing initial data for the forms
-====================================
-
-.. attribute:: WizardView.initial_dict
-
- Initial data for a wizard's :class:`~django.forms.Form` objects can be
- provided using the optional :attr:`~WizardView.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 myapp.forms import ContactForm1, ContactForm2
- >>> from myapp.views import ContactWizard
- >>> initial = {
- ... '0': {'subject': 'Hello', 'sender': 'user@example.com'},
- ... '1': {'message': 'Hi there!'}
- ... }
- >>> # This example is illustrative only and isn't meant to be run in
- >>> # the shell since it requires an HttpRequest to pass to the view.
- >>> wiz = ContactWizard.as_view([ContactForm1, ContactForm2], initial_dict=initial)(request)
- >>> 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``.
-
- The ``initial_dict`` can also be added as a class attribute named
- ``initial_dict`` to avoid having the initial data in the ``urls.py``.
-
-.. _wizard-files:
-
-Handling files
-==============
-
-.. attribute:: WizardView.file_storage
-
-To handle :class:`~django.forms.FileField` within any step form of the wizard,
-you have to add a ``file_storage`` to your :class:`WizardView` subclass.
-
-This storage will temporarily store the uploaded files for the wizard. The
-``file_storage`` attribute should be a
-:class:`~django.core.files.storage.Storage` subclass.
-
-Django provides a built-in storage class (see :ref:`the built-in filesystem
-storage class `)::
-
- from django.conf import settings
- from django.core.files.storage import FileSystemStorage
-
- class CustomWizardView(WizardView):
- ...
- file_storage = FileSystemStorage(location=os.path.join(settings.MEDIA_ROOT, 'photos'))
-
-.. warning::
-
- Please remember to take care of removing old temporary files, as the
- :class:`WizardView` will only remove these files if the wizard finishes
- correctly.
-
-Conditionally view/skip specific steps
-======================================
-
-.. attribute:: WizardView.condition_dict
-
-The :meth:`~WizardView.as_view` method accepts a ``condition_dict`` argument.
-You can pass a dictionary of boolean values or callables. The key should match
-the steps names (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 form only if a checkbox in the first step was checked.
-
-The steps are defined in a ``forms.py`` file::
-
- 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 import url
-
- from myapp.forms import ContactForm1, ContactForm2
- from myapp.views import ContactWizard, show_message_form_condition
-
- contact_forms = [ContactForm1, ContactForm2]
-
- urlpatterns = [
- url(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
-===========================================
-
-.. attribute:: WizardView.instance_dict
-
-WizardView supports :doc:`ModelForms ` and
-:ref:`ModelFormSets `. Additionally to
-:attr:`~WizardView.initial_dict`, the :meth:`~WizardView.as_view` method takes
-an ``instance_dict`` argument that should contain model instances for steps
-based on ``ModelForm`` and querysets for steps based on ``ModelFormSet``.
-
-Usage of ``NamedUrlWizardView``
-===============================
-
-.. class:: NamedUrlWizardView
-.. class:: NamedUrlSessionWizardView
-.. class:: NamedUrlCookieWizardView
-
-``NamedUrlWizardView`` is a :class:`WizardView` subclass which adds named-urls
-support to the wizard. This allows you to have separate URLs for every step.
-You can also use the :class:`NamedUrlSessionWizardView` or :class:`NamedUrlCookieWizardView`
-classes which preselect the backend used for storing information (Django sessions and
-browser cookies respectively).
-
-To use the named URLs, you should not only use the :class:`NamedUrlWizardView` instead of
-:class:`WizardView`, but you will also have to change your ``urls.py``.
-
-The :meth:`~WizardView.as_view` method takes two additional arguments:
-
-* a required ``url_name`` -- the name of the url (as provided in the ``urls.py``)
-* an optional ``done_step_name`` -- the name of the done step, to be used in the URL
-
-This is an example of a ``urls.py`` for a contact wizard with two steps, step 1 named
-``contactdata`` and step 2 named ``leavemessage``::
-
- from django.conf.urls import url
-
- 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 = [
- url(r'^contact/(?P.+)/$', contact_wizard, name='contact_step'),
- url(r'^contact/$', contact_wizard, name='contact'),
- ]
-
-Advanced ``NamedUrlWizardView`` methods
-=======================================
-
-.. method:: NamedUrlWizardView.get_step_url(step)
-
-This method returns the URL for a specific step.
-
-Default implementation::
-
- def get_step_url(self, step):
- return reverse(self.url_name, kwargs={'step': step})
diff --git a/docs/ref/contrib/formtools/index.txt b/docs/ref/contrib/formtools/index.txt
index e768c0e655..da5e02d4a3 100644
--- a/docs/ref/contrib/formtools/index.txt
+++ b/docs/ref/contrib/formtools/index.txt
@@ -1,12 +1,43 @@
django.contrib.formtools
========================
-.. module:: django.contrib.formtools
-
A set of high-level abstractions for Django forms (:mod:`django.forms`).
-.. toctree::
- :maxdepth: 1
+Historically, Django shipped with ``django.contrib.formtools`` -- a collection
+of assorted utilities that are useful for specific form use cases. This code is
+now distributed separately from Django, for easier maintenance and to trim the
+size of Django's codebase. In Django 1.8, importing from
+``django.contrib.formtools`` will no longer work.
- form-preview
- form-wizard
+The new formtools package is named ``django-formtools``, with a main module
+called ``formtools``. Version 1.0 includes the same two primary features that
+the code included when it shipped with Django: a helper for form previews and a
+form wizard view.
+
+See the `official documentation`_ for more information.
+
+.. _official documentation: http://django-formtools.readthedocs.org/
+
+.. _formtools-how-to-migrate:
+
+How to migrate
+--------------
+
+If you've used the old ``django.contrib.formtools`` package follow these
+two easy steps to update your code:
+
+1. Install version 1.0 of the third-party ``django-formtools`` package.
+
+2. Change your app's import statements to reference the new packages.
+
+ For example, change::
+
+ from django.contrib.formtools.wizard.views import WizardView
+
+ to::
+
+ from formtools.wizard.views import WizardView
+
+The code in version 1.0 of the new package is the same (it was copied directly
+from Django), so you don't have to worry about backwards compatibility in terms
+of functionality. Only the imports have changed.
diff --git a/docs/ref/contrib/index.txt b/docs/ref/contrib/index.txt
index a4787f8643..2cbc691c5b 100644
--- a/docs/ref/contrib/index.txt
+++ b/docs/ref/contrib/index.txt
@@ -71,27 +71,6 @@ See the :doc:`flatpages documentation `.
Requires the sites_ contrib package to be installed as well.
-formtools
-=========
-
-A set of high-level abstractions for Django forms (django.forms).
-
-django.contrib.formtools.preview
---------------------------------
-
-An abstraction of the following workflow:
-
-"Display an HTML form, force a preview, then do something with the submission."
-
-See the :doc:`form preview documentation `.
-
-django.contrib.formtools.wizard
--------------------------------
-
-Splits forms across multiple Web pages.
-
-See the :doc:`form wizard documentation `.
-
gis
====
diff --git a/docs/ref/settings.txt b/docs/ref/settings.txt
index 58fc22eaa1..fc00ad2d57 100644
--- a/docs/ref/settings.txt
+++ b/docs/ref/settings.txt
@@ -2018,11 +2018,11 @@ The secret key is used for:
* All :doc:`messages ` if you are using
:class:`~django.contrib.messages.storage.cookie.CookieStorage` or
:class:`~django.contrib.messages.storage.fallback.FallbackStorage`.
-* :doc:`Form wizard ` progress when using
+* :mod:`Form wizard ` progress when using
cookie storage with
- :class:`django.contrib.formtools.wizard.views.CookieWizardView`.
+ :class:`formtools.wizard.views.CookieWizardView`.
* All :func:`~django.contrib.auth.views.password_reset` tokens.
-* All in progress :doc:`form previews `.
+* All in progress :mod:`form previews `.
* Any usage of :doc:`cryptographic signing `, unless a
different key is provided.
diff --git a/docs/releases/1.4.txt b/docs/releases/1.4.txt
index 5b10f4c428..965a20a057 100644
--- a/docs/releases/1.4.txt
+++ b/docs/releases/1.4.txt
@@ -358,7 +358,7 @@ more information.
New form wizard
~~~~~~~~~~~~~~~
-The previous ``FormWizard`` from :mod:`django.contrib.formtools` has been
+The previous ``FormWizard`` from ``django.contrib.formtools`` has been
replaced with a new implementation based on the class-based views
introduced in Django 1.3. It features a pluggable storage API and doesn't
require the wizard to pass around hidden fields for every previous step.
@@ -368,9 +368,6 @@ storage backend. The latter uses the tools for
:doc:`cryptographic signing ` also introduced in
Django 1.4 to store the wizard's state in the user's cookies.
-See the :doc:`form wizard ` docs for
-more information.
-
``reverse_lazy``
~~~~~~~~~~~~~~~~
diff --git a/docs/releases/1.7.txt b/docs/releases/1.7.txt
index 3be203d229..e14fd8bc48 100644
--- a/docs/releases/1.7.txt
+++ b/docs/releases/1.7.txt
@@ -440,11 +440,11 @@ Minor features
enabled. See :ref:`session-invalidation-on-password-change` for more details
including upgrade considerations when enabling this new middleware.
-:mod:`django.contrib.formtools`
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+``django.contrib.formtools``
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^
* Calls to :meth:`WizardView.done()
- ` now include a
+ ` now include a
``form_dict`` to allow easier access to forms by their step name.
:mod:`django.contrib.gis`
diff --git a/docs/releases/1.8.txt b/docs/releases/1.8.txt
index b342234ef9..1593f26b79 100644
--- a/docs/releases/1.8.txt
+++ b/docs/releases/1.8.txt
@@ -122,17 +122,6 @@ Minor features
:attr:`~django.contrib.auth.models.CustomUser.REQUIRED_FIELDS` now supports
:class:`~django.db.models.ForeignKey`\s.
-:mod:`django.contrib.formtools`
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
-
-* A :doc:`form wizard ` using the
- :class:`~django.contrib.formtools.wizard.views.CookieWizardView` will now ignore
- an invalid cookie, and the wizard will restart from the first step. An invalid
- cookie can occur in cases of intentional manipulation, but also after a secret
- key change. Previously, this would raise ``WizardViewCookieModified``, a
- ``SuspiciousOperation``, causing an exception for any user with an invalid cookie
- upon every request to the wizard, until the cookie is removed.
-
:mod:`django.contrib.gis`
^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -710,6 +699,17 @@ The decorators :func:`~django.test.override_settings` and
class decorators. As a consequence, when overriding ``setUpClass()`` or
``tearDownClass()``, the ``super`` implementation should always be called.
+Removal of ``django.contrib.formtools``
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The formtools contrib app has been moved into a separate package.
+``django.contrib.formtools`` itself has been removed. The docs provide
+:ref:`migration instructions `.
+
+The new package is available `on Github`_ and on PyPI.
+
+.. _on GitHub: https://github.com/django/django-formtools/
+
Miscellaneous
~~~~~~~~~~~~~