From dcdd4aad0a1add66c59e2b411912fbccecd74719 Mon Sep 17 00:00:00 2001 From: Jannis Leidel Date: Mon, 6 Oct 2014 22:17:43 +0200 Subject: [PATCH] Added docs. Fix bug #3. --- docs/Makefile | 177 +++++++++ docs/conf.py | 325 +++++++++++++++ docs/extensions/__init__.py | 0 docs/extensions/settings.py | 6 + docs/index.rst | 112 ++++++ docs/make.bat | 242 ++++++++++++ docs/preview.rst | 120 ++++++ docs/requirements.txt | 1 + docs/wizard.rst | 761 ++++++++++++++++++++++++++++++++++++ 9 files changed, 1744 insertions(+) create mode 100644 docs/Makefile create mode 100644 docs/conf.py create mode 100644 docs/extensions/__init__.py create mode 100644 docs/extensions/settings.py create mode 100644 docs/index.rst create mode 100644 docs/make.bat create mode 100644 docs/preview.rst create mode 100644 docs/requirements.txt create mode 100644 docs/wizard.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 0000000..2518991 --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,177 @@ +# Makefile for Sphinx documentation +# + +# You can set these variables from the command line. +SPHINXOPTS = +SPHINXBUILD = sphinx-build +PAPER = +BUILDDIR = _build + +# User-friendly check for sphinx-build +ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) +$(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) +endif + +# Internal variables. +PAPEROPT_a4 = -D latex_paper_size=a4 +PAPEROPT_letter = -D latex_paper_size=letter +ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . +# the i18n builder cannot share the environment and doctrees with the others +I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . + +.PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext + +help: + @echo "Please use \`make ' where is one of" + @echo " html to make standalone HTML files" + @echo " dirhtml to make HTML files named index.html in directories" + @echo " singlehtml to make a single large HTML file" + @echo " pickle to make pickle files" + @echo " json to make JSON files" + @echo " htmlhelp to make HTML files and a HTML help project" + @echo " qthelp to make HTML files and a qthelp project" + @echo " devhelp to make HTML files and a Devhelp project" + @echo " epub to make an epub" + @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" + @echo " latexpdf to make LaTeX files and run them through pdflatex" + @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" + @echo " text to make text files" + @echo " man to make manual pages" + @echo " texinfo to make Texinfo files" + @echo " info to make Texinfo files and run them through makeinfo" + @echo " gettext to make PO message catalogs" + @echo " changes to make an overview of all changed/added/deprecated items" + @echo " xml to make Docutils-native XML files" + @echo " pseudoxml to make pseudoxml-XML files for display purposes" + @echo " linkcheck to check all external links for integrity" + @echo " doctest to run all doctests embedded in the documentation (if enabled)" + +clean: + rm -rf $(BUILDDIR)/* + +html: + $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." + +dirhtml: + $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml + @echo + @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." + +singlehtml: + $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml + @echo + @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." + +pickle: + $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle + @echo + @echo "Build finished; now you can process the pickle files." + +json: + $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json + @echo + @echo "Build finished; now you can process the JSON files." + +htmlhelp: + $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp + @echo + @echo "Build finished; now you can run HTML Help Workshop with the" \ + ".hhp project file in $(BUILDDIR)/htmlhelp." + +qthelp: + $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp + @echo + @echo "Build finished; now you can run "qcollectiongenerator" with the" \ + ".qhcp project file in $(BUILDDIR)/qthelp, like this:" + @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/django-formtools.qhcp" + @echo "To view the help file:" + @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/django-formtools.qhc" + +devhelp: + $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp + @echo + @echo "Build finished." + @echo "To view the help file:" + @echo "# mkdir -p $$HOME/.local/share/devhelp/django-formtools" + @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/django-formtools" + @echo "# devhelp" + +epub: + $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub + @echo + @echo "Build finished. The epub file is in $(BUILDDIR)/epub." + +latex: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo + @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." + @echo "Run \`make' in that directory to run these through (pdf)latex" \ + "(use \`make latexpdf' here to do that automatically)." + +latexpdf: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through pdflatex..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +latexpdfja: + $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex + @echo "Running LaTeX files through platex and dvipdfmx..." + $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja + @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." + +text: + $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text + @echo + @echo "Build finished. The text files are in $(BUILDDIR)/text." + +man: + $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man + @echo + @echo "Build finished. The manual pages are in $(BUILDDIR)/man." + +texinfo: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo + @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." + @echo "Run \`make' in that directory to run these through makeinfo" \ + "(use \`make info' here to do that automatically)." + +info: + $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo + @echo "Running Texinfo files through makeinfo..." + make -C $(BUILDDIR)/texinfo info + @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." + +gettext: + $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale + @echo + @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." + +changes: + $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes + @echo + @echo "The overview file is in $(BUILDDIR)/changes." + +linkcheck: + $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck + @echo + @echo "Link check complete; look for any errors in the above output " \ + "or in $(BUILDDIR)/linkcheck/output.txt." + +doctest: + $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest + @echo "Testing of doctests in the sources finished, look at the " \ + "results in $(BUILDDIR)/doctest/output.txt." + +xml: + $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml + @echo + @echo "Build finished. The XML files are in $(BUILDDIR)/xml." + +pseudoxml: + $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml + @echo + @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 0000000..5d8cb76 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,325 @@ +# -*- coding: utf-8 -*- +# +# django-formtools documentation build configuration file, created by +# sphinx-quickstart on Mon Oct 6 21:51:30 2014. +# +# This file is execfile()d with the current directory set to its containing dir. +# +# Note that not all possible configuration values are present in this +# autogenerated file. +# +# All configuration values have a default; values that are commented out +# serve to show the default. + +import sys +import os + +os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'tests.settings') + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +sys.path.insert(0, os.path.abspath('extensions')) +sys.path.insert(0, os.path.abspath('..')) + +# -- General configuration ----------------------------------------------------- + +# If your documentation needs a minimal Sphinx version, state it here. +#needs_sphinx = '1.0' + +# Add any Sphinx extension module names here, as strings. They can be extensions +# coming with Sphinx (named 'sphinx.ext.*') or your custom ones. +extensions = ['sphinx.ext.autodoc', 'sphinx.ext.intersphinx', + 'sphinx.ext.todo', 'sphinx.ext.coverage', 'sphinx.ext.ifconfig', + 'sphinx.ext.viewcode', 'settings'] + +# Add any paths that contain templates here, relative to this directory. +# templates_path = ['_templates'] + +# The suffix of source filenames. +source_suffix = '.rst' + +# The encoding of source files. +#source_encoding = 'utf-8-sig' + +# The master toctree document. +master_doc = 'index' + +# General information about the project. +project = u'django-formtools' +copyright = u'2014, Django Software Foundation and individual contributors' + +# The version info for the project you're documenting, acts as replacement for +# |version| and |release|, also used in various other places throughout the +# built documents. +# +# The short X.Y version. +try: + from formtools import __version__ + # The short X.Y version. + version = '.'.join(__version__.split('.')[:2]) + # The full version, including alpha/beta/rc tags. + release = __version__ +except ImportError: + version = release = 'dev' + +# The language for content autogenerated by Sphinx. Refer to documentation +# for a list of supported languages. +#language = None + +# There are two options for replacing |today|: either, you set today to some +# non-false value, then it is used: +#today = '' +# Else, today_fmt is used as the format for a strftime call. +#today_fmt = '%B %d, %Y' + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +exclude_patterns = ['_build'] + +# The reST default role (used for this markup: `text`) to use for all documents. +#default_role = None + +# If true, '()' will be appended to :func: etc. cross-reference text. +#add_function_parentheses = True + +# If true, the current module name will be prepended to all description +# unit titles (such as .. function::). +#add_module_names = True + +# If true, sectionauthor and moduleauthor directives will be shown in the +# output. They are ignored by default. +#show_authors = False + +# The name of the Pygments (syntax highlighting) style to use. +pygments_style = 'sphinx' + +# A list of ignored prefixes for module index sorting. +#modindex_common_prefix = [] + +# If true, keep warnings as "system message" paragraphs in the built documents. +#keep_warnings = False + + +# -- Options for HTML output --------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +html_theme = 'default' + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +#html_theme_options = {} + +# Add any paths that contain custom themes here, relative to this directory. +#html_theme_path = ['_theme'] + +# The name for this set of Sphinx documents. If None, it defaults to +# " v documentation". +#html_title = None + +# A shorter title for the navigation bar. Default is the same as html_title. +#html_short_title = None + +# The name of an image file (relative to this directory) to place at the top +# of the sidebar. +#html_logo = None + +# The name of an image file (within the static path) to use as favicon of the +# docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 +# pixels large. +#html_favicon = None + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +# html_static_path = ['_static'] + +# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, +# using the given strftime format. +#html_last_updated_fmt = '%b %d, %Y' + +# If true, SmartyPants will be used to convert quotes and dashes to +# typographically correct entities. +#html_use_smartypants = True + +# Custom sidebar templates, maps document names to template names. +#html_sidebars = {} + +# Additional templates that should be rendered to pages, maps page names to +# template names. +#html_additional_pages = {} + +# If false, no module index is generated. +#html_domain_indices = True + +# If false, no index is generated. +#html_use_index = True + +# If true, the index is split into individual pages for each letter. +#html_split_index = False + +# If true, links to the reST sources are added to the pages. +#html_show_sourcelink = True + +# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. +#html_show_sphinx = True + +# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. +#html_show_copyright = True + +# If true, an OpenSearch description file will be output, and all pages will +# contain a tag referring to it. The value of this option must be the +# base URL from which the finished HTML is served. +#html_use_opensearch = '' + +# This is the file name suffix for HTML files (e.g. ".xhtml"). +#html_file_suffix = None + +# Output file base name for HTML help builder. +htmlhelp_basename = 'django-formtoolsdoc' + + +# -- Options for LaTeX output -------------------------------------------------- + +latex_elements = { +# The paper size ('letterpaper' or 'a4paper'). +#'papersize': 'letterpaper', + +# The font size ('10pt', '11pt' or '12pt'). +#'pointsize': '10pt', + +# Additional stuff for the LaTeX preamble. +#'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, author, documentclass [howto/manual]). +latex_documents = [ + ('index', 'django-formtools.tex', u'django-formtools Documentation', + u'Django Software Foundation and individual contributors', 'manual'), +] + +# The name of an image file (relative to this directory) to place at the top of +# the title page. +#latex_logo = None + +# For "manual" documents, if this is true, then toplevel headings are parts, +# not chapters. +#latex_use_parts = False + +# If true, show page references after internal links. +#latex_show_pagerefs = False + +# If true, show URL addresses after external links. +#latex_show_urls = False + +# Documents to append as an appendix to all manuals. +#latex_appendices = [] + +# If false, no module index is generated. +#latex_domain_indices = True + + +# -- Options for manual page output -------------------------------------------- + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [ + ('index', 'django-formtools', u'django-formtools Documentation', + [u'Django Software Foundation and individual contributors'], 1) +] + +# If true, show URL addresses after external links. +#man_show_urls = False + + +# -- Options for Texinfo output ------------------------------------------------ + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + ('index', 'django-formtools', u'django-formtools Documentation', + u'Django Software Foundation and individual contributors', 'django-formtools', 'One line description of project.', + 'Miscellaneous'), +] + +# Documents to append as an appendix to all manuals. +#texinfo_appendices = [] + +# If false, no module index is generated. +#texinfo_domain_indices = True + +# How to display URL addresses: 'footnote', 'no', or 'inline'. +#texinfo_show_urls = 'footnote' + +# If true, do not generate a @detailmenu in the "Top" node's menu. +#texinfo_no_detailmenu = False + + +# -- Options for Epub output --------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = u'django-formtools' +epub_author = u'Django Software Foundation and individual contributors' +epub_publisher = u'Django Software Foundation and individual contributors' +epub_copyright = u'2014, Django Software Foundation and individual contributors' + +# The language of the text. It defaults to the language option +# or en if the language is not set. +#epub_language = '' + +# The scheme of the identifier. Typical schemes are ISBN or URL. +#epub_scheme = '' + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +#epub_identifier = '' + +# A unique identification for the text. +#epub_uid = '' + +# A tuple containing the cover image and cover page html template filenames. +#epub_cover = () + +# A sequence of (type, uri, title) tuples for the guide element of content.opf. +#epub_guide = () + +# HTML files that should be inserted before the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_pre_files = [] + +# HTML files shat should be inserted after the pages created by sphinx. +# The format is a list of tuples containing the path and title. +#epub_post_files = [] + +# A list of files that should not be packed into the epub file. +#epub_exclude_files = [] + +# The depth of the table of contents in toc.ncx. +#epub_tocdepth = 3 + +# Allow duplicate toc entries. +#epub_tocdup = True + +# Fix unsupported image types using the PIL. +#epub_fix_images = False + +# Scale large images. +#epub_max_image_width = 0 + +# If 'no', URL addresses will not be shown. +#epub_show_urls = 'inline' + +# If false, no index is generated. +#epub_use_index = True + + +# Example configuration for intersphinx: refer to the Python standard library. +intersphinx_mapping = { + 'http://docs.python.org/': None, + 'django': ('http://docs.djangoproject.com/en/dev/', + 'http://docs.djangoproject.com/en/dev/_objects/'), +} diff --git a/docs/extensions/__init__.py b/docs/extensions/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/docs/extensions/settings.py b/docs/extensions/settings.py new file mode 100644 index 0000000..2551233 --- /dev/null +++ b/docs/extensions/settings.py @@ -0,0 +1,6 @@ +def setup(app): + app.add_crossref_type( + directivename="setting", + rolename="setting", + indextemplate="pair: %s; setting", + ) diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 0000000..db0b878 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,112 @@ +==================== +The "form tools" app +==================== + +.. module:: formtools + :synopsis: A set of high-level abstractions for Django forms + (:mod:`django.forms`).. + +django-formtools is a collection of assorted utilities that are useful for +specific form use cases. + +Currently there are two tools: a helper for form previews and a form wizard +view. + +.. toctree:: + + preview + wizard + +Installation +============ + +To install django-formtools use your favorite packaging tool, e.g.pip:: + + pip install django-formtools + +Or download the source distribution from PyPI_ at +https://pypi.python.org/pypi/django-formtools, decompress the file and +run ``python setup.py install`` in the unpacked directory. + +Then add ``'formtools'`` to your :setting:`INSTALLED_APPS` setting:: + + INSTALLED_APPS = ( + # ... + 'formtools', + ) + +.. note:: + + Adding ``'formtools'`` to your ``INSTALLED_APPS`` setting is required + for translations and templates to work. Using django-formtools without + adding it to your ``INSTALLED_APPS`` setting is not recommended. + +.. _PyPI: https://pypi.python.org/ + +Internationalization +==================== + +Formtools has its own catalog of translations, in the directory +``formtools/locale``, and it's not loaded automatically like Django's +general catalog in ``django/conf/locale``. If you want formtools's +texts to be translated, like the templates, you must include +:mod:`formtools` in the :setting:`INSTALLED_APPS` setting, so +the internationalization system can find the catalog, as explained in +:ref:`django:how-django-discovers-translations`. + +Contributing tools +================== + +We'd love to add more of these, so please `create a ticket`_ with +any code you'd like to contribute. One thing we ask is that you please use +Unicode objects (``u'mystring'``) for strings, rather than setting the encoding +in the file. See any of the existing flavors for examples. + +See the `contributing documentation`_ for how to run the tests while working on a +local flavor. + +.. _create a ticket: https://github.com/django/django-formtools/issues +.. _contributing documentation: https://github.com/django/django-formtools/blob/master/CONTRIBUTING.rst + +Releases +======== + +Due to django-formtools' history as a former contrib app, the app is +required to be working with the actively maintained Django versions. See +the documenation about `Django's release process`_ for more information. + +django-formtools releases are not tied to the release cycle of Django. +Version numbers follow the appropriate Python standards, e.g. PEPs 386_ and 440_. + +.. _386: http://www.python.org/dev/peps/pep-0386/ +.. _440: http://www.python.org/dev/peps/pep-0440/ +.. _`Django's release process`: https://docs.djangoproject.com/en/dev/internals/release-process/ + +How to migrate +============== + +If you've used the old ``django.contrib.formtools`` package follow these +two easy steps to update your code: + +1. Install the third-party ``django-formtools`` package. + +2. Change your app's import statements to reference the new packages. + + For example, change this:: + + from django.contrib.formtools.wizard.views import WizardView + + ...to this:: + + from formtools.wizard.views import WizardView + +The code in 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. + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/docs/make.bat b/docs/make.bat new file mode 100644 index 0000000..7c9ae02 --- /dev/null +++ b/docs/make.bat @@ -0,0 +1,242 @@ +@ECHO OFF + +REM Command file for Sphinx documentation + +if "%SPHINXBUILD%" == "" ( + set SPHINXBUILD=sphinx-build +) +set BUILDDIR=_build +set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . +set I18NSPHINXOPTS=%SPHINXOPTS% . +if NOT "%PAPER%" == "" ( + set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% + set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% +) + +if "%1" == "" goto help + +if "%1" == "help" ( + :help + echo.Please use `make ^` where ^ is one of + echo. html to make standalone HTML files + echo. dirhtml to make HTML files named index.html in directories + echo. singlehtml to make a single large HTML file + echo. pickle to make pickle files + echo. json to make JSON files + echo. htmlhelp to make HTML files and a HTML help project + echo. qthelp to make HTML files and a qthelp project + echo. devhelp to make HTML files and a Devhelp project + echo. epub to make an epub + echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter + echo. text to make text files + echo. man to make manual pages + echo. texinfo to make Texinfo files + echo. gettext to make PO message catalogs + echo. changes to make an overview over all changed/added/deprecated items + echo. xml to make Docutils-native XML files + echo. pseudoxml to make pseudoxml-XML files for display purposes + echo. linkcheck to check all external links for integrity + echo. doctest to run all doctests embedded in the documentation if enabled + goto end +) + +if "%1" == "clean" ( + for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i + del /q /s %BUILDDIR%\* + goto end +) + + +%SPHINXBUILD% 2> nul +if errorlevel 9009 ( + echo. + echo.The 'sphinx-build' command was not found. Make sure you have Sphinx + echo.installed, then set the SPHINXBUILD environment variable to point + echo.to the full path of the 'sphinx-build' executable. Alternatively you + echo.may add the Sphinx directory to PATH. + echo. + echo.If you don't have Sphinx installed, grab it from + echo.http://sphinx-doc.org/ + exit /b 1 +) + +if "%1" == "html" ( + %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/html. + goto end +) + +if "%1" == "dirhtml" ( + %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. + goto end +) + +if "%1" == "singlehtml" ( + %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. + goto end +) + +if "%1" == "pickle" ( + %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the pickle files. + goto end +) + +if "%1" == "json" ( + %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can process the JSON files. + goto end +) + +if "%1" == "htmlhelp" ( + %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run HTML Help Workshop with the ^ +.hhp project file in %BUILDDIR%/htmlhelp. + goto end +) + +if "%1" == "qthelp" ( + %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; now you can run "qcollectiongenerator" with the ^ +.qhcp project file in %BUILDDIR%/qthelp, like this: + echo.^> qcollectiongenerator %BUILDDIR%\qthelp\django-formtools.qhcp + echo.To view the help file: + echo.^> assistant -collectionFile %BUILDDIR%\qthelp\django-formtools.ghc + goto end +) + +if "%1" == "devhelp" ( + %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. + goto end +) + +if "%1" == "epub" ( + %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The epub file is in %BUILDDIR%/epub. + goto end +) + +if "%1" == "latex" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + if errorlevel 1 exit /b 1 + echo. + echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdf" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "latexpdfja" ( + %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex + cd %BUILDDIR%/latex + make all-pdf-ja + cd %BUILDDIR%/.. + echo. + echo.Build finished; the PDF files are in %BUILDDIR%/latex. + goto end +) + +if "%1" == "text" ( + %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The text files are in %BUILDDIR%/text. + goto end +) + +if "%1" == "man" ( + %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The manual pages are in %BUILDDIR%/man. + goto end +) + +if "%1" == "texinfo" ( + %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. + goto end +) + +if "%1" == "gettext" ( + %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The message catalogs are in %BUILDDIR%/locale. + goto end +) + +if "%1" == "changes" ( + %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes + if errorlevel 1 exit /b 1 + echo. + echo.The overview file is in %BUILDDIR%/changes. + goto end +) + +if "%1" == "linkcheck" ( + %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck + if errorlevel 1 exit /b 1 + echo. + echo.Link check complete; look for any errors in the above output ^ +or in %BUILDDIR%/linkcheck/output.txt. + goto end +) + +if "%1" == "doctest" ( + %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest + if errorlevel 1 exit /b 1 + echo. + echo.Testing of doctests in the sources finished, look at the ^ +results in %BUILDDIR%/doctest/output.txt. + goto end +) + +if "%1" == "xml" ( + %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The XML files are in %BUILDDIR%/xml. + goto end +) + +if "%1" == "pseudoxml" ( + %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml + if errorlevel 1 exit /b 1 + echo. + echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. + goto end +) + +:end diff --git a/docs/preview.rst b/docs/preview.rst new file mode 100644 index 0000000..64752c0 --- /dev/null +++ b/docs/preview.rst @@ -0,0 +1,120 @@ +============ +Form preview +============ + +.. module:: formtools.preview + :synopsis: Displays an HTML form, forces a preview, then does something + with the submission. + +Django comes with an optional "form preview" application that helps automate +the following workflow: + +"Display an HTML form, force a preview, then do something with the submission." + +To force a preview of a form submission, all you have to do is write a short +Python class. + +Overview +========= + +Given a :class:`django.forms.Form` subclass that you define, this +application takes care of the following workflow: + +1. Displays the form as HTML on a Web page. +2. Validates the form data when it's submitted via POST. + a. If it's valid, displays a preview page. + b. If it's not valid, redisplays the form with error messages. +3. When the "confirmation" form is submitted from the preview page, calls + a hook that you define -- a ``done()`` method that gets passed the valid + data. + +The framework enforces the required preview by passing a shared-secret hash to +the preview page via hidden form fields. If somebody tweaks the form parameters +on the preview page, the form submission will fail the hash-comparison test. + +How to use ``FormPreview`` +========================== + +1. Point Django at the default FormPreview templates. There are two ways to + do this: + + * Add ``'formtools'`` to your + :setting:`INSTALLED_APPS` setting. This will work if your + :setting:`TEMPLATE_LOADERS` setting includes the + ``app_directories`` template loader (which is the case by + default). See the :ref:`template loader docs ` + for more. + + * Otherwise, determine the full filesystem path to the + :file:`django/contrib/formtools/templates` directory, and add that + directory to your :setting:`TEMPLATE_DIRS` setting. + +2. Create a :class:`~formtools.preview.FormPreview` subclass that + overrides the ``done()`` method:: + + from django.http import HttpResponseRedirect + from formtools.preview import FormPreview + from myapp.models import SomeModel + + class SomeModelFormPreview(FormPreview): + + def done(self, request, cleaned_data): + # Do something with the cleaned_data, then redirect + # to a "success" page. + return HttpResponseRedirect('/form/success') + + This method takes an :class:`~django.http.HttpRequest` object and a + dictionary of the form data after it has been validated and cleaned. + It should return an :class:`~django.http.HttpResponseRedirect` that + is the end result of the form being submitted. + +3. Change your URLconf to point to an instance of your + :class:`~formtools.preview.FormPreview` subclass:: + + from myapp.preview import SomeModelFormPreview + from myapp.forms import SomeModelForm + from django import forms + + ...and add the following line to the appropriate model in your URLconf:: + + url(r'^post/$', SomeModelFormPreview(SomeModelForm)), + + where ``SomeModelForm`` is a Form or ModelForm class for the model. + +4. Run the Django server and visit :file:`/post/` in your browser. + +``FormPreview`` classes +======================= + +.. class:: FormPreview + +A :class:`~formtools.preview.FormPreview` class is a simple Python class +that represents the preview workflow. +:class:`~formtools.preview.FormPreview` classes must subclass +``formtools.preview.FormPreview`` and override the ``done()`` +method. They can live anywhere in your codebase. + +``FormPreview`` templates +========================= + +.. attribute:: FormPreview.form_template +.. attribute:: FormPreview.preview_template + +By default, the form is rendered via the template :file:`formtools/form.html`, +and the preview page is rendered via the template :file:`formtools/preview.html`. +These values can be overridden for a particular form preview by setting +:attr:`~formtools.preview.FormPreview.preview_template` and +:attr:`~formtools.preview.FormPreview.form_template` attributes on the +FormPreview subclass. See :file:`django/contrib/formtools/templates` for the +default templates. + +Advanced ``FormPreview`` methods +================================ + +.. method:: FormPreview.process_preview() + + Given a validated form, performs any extra processing before displaying the + preview page, and saves any extra data in context. + + By default, this method is empty. It is called after the form is validated, + but before the context is modified with hash information and rendered. diff --git a/docs/requirements.txt b/docs/requirements.txt new file mode 100644 index 0000000..94a0e83 --- /dev/null +++ b/docs/requirements.txt @@ -0,0 +1 @@ +Django diff --git a/docs/wizard.rst b/docs/wizard.rst new file mode 100644 index 0000000..03cd6ed --- /dev/null +++ b/docs/wizard.rst @@ -0,0 +1,761 @@ +=========== +Form wizard +=========== + +.. module:: formtools.wizard.views + :synopsis: Splits forms across multiple Web pages. + +Django comes with an optional "form wizard" application that splits +:mod:`forms ` across multiple Web pages. It maintains +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`_. + +.. _explained on Wikipedia: http://en.wikipedia.org/wiki/Wizard_%28software%29 + +How it works +============ + +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 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, + sending an email, or whatever the application needs to do. + +Usage +===== + +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. + +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. Add ``formtools`` 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 :mod:`forms documentation +`. 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 +collects the sender's email address and subject, and the second page collects +the message itself. Here's what the :file:`forms.py` might look like:: + + from django import forms + + class ContactForm1(forms.Form): + subject = forms.CharField(max_length=100) + sender = forms.EmailField() + + class ContactForm2(forms.Form): + message = forms.CharField(widget=forms.Textarea) + + +.. note:: + + In order to use :class:`~django.forms.FileField` in any form, see the + section :ref:`Handling files ` below to learn more about + what to do. + +Creating a ``WizardView`` subclass +---------------------------------- + +.. class:: SessionWizardView +.. class:: CookieWizardView + +The next step is to create a :class:`formtools.wizard.views.WizardView` +subclass. You can also use the :class:`SessionWizardView` or +:class:`CookieWizardView` classes which preselect the backend used for +storing information during execution of the wizard (as their names indicate, +server-side sessions and browser cookies respectively). + +.. note:: + + To use the :class:`SessionWizardView` follow the instructions + in the :mod:`sessions documentation ` on + how to enable sessions. + +We will use the :class:`SessionWizardView` in all examples but is completely +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:`~WizardView.done()` method. + +.. method:: WizardView.done(form_list, form_dict, **kwargs) + + This method specifies what should happen when the data for *every* form is + submitted and validated. This method is passed a list and dictionary of + validated :class:`~django.forms.Form` instances. + + 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 formtools.wizard.views import SessionWizardView + + 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:: + + from django.http import HttpResponseRedirect + from formtools.wizard.views import SessionWizardView + + 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/') + + In addition to ``form_list``, the :meth:`~WizardView.done` method + is passed a ``form_dict``, which allows you to access the wizard's + forms based on their step names. This is especially useful when using + :class:`NamedUrlWizardView`, for example:: + + def done(self, form_list, form_dict, **kwargs): + user = form_dict['user'].save() + credit_card = form_dict['credit_card'].save() + # ... + + .. versionchanged:: 1.7 + + Previously, the ``form_dict`` argument wasn't passed to the + ``done`` method. + +See the section :ref:`Advanced WizardView 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:`formtools/wizard/wizard_form.html`. You can change this template name +by overriding either the +:attr:`~django.views.generic.base.TemplateResponseMixin.template_name` attribute +or the +:meth:`~django.views.generic.base.TemplateResponseMixin.get_template_names()` +method, which are documented in the +:class:`~django.views.generic.base.TemplateResponseMixin` documentation. The +latter one allows you to use a different template for each form (:ref:`see the +example below `). + +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 }}

+
{% csrf_token %} + + {{ 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 %} + + + {% endif %} + +
+ {% 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 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 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 :mod:`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})