summaryrefslogtreecommitdiff
path: root/docs
diff options
context:
space:
mode:
authorAndrew Gorcester <andrew.gorcester@gmail.com>2013-03-20 23:27:06 -0700
committerCarl Meyer <carl@oddbird.net>2013-03-21 01:27:24 -0700
commitf9ab543720532400e8b0d490cdbe67ea09ae9c17 (patch)
treeb333dd7117ef4369c1f8a397ac155db27dc1c4e1 /docs
parentaaec4f2bd8a63b3dceebad7804c5897e7874833d (diff)
Fixed #20084 -- Provided option to validate formset max_num on server.
This is provided as a new "validate_max" formset_factory option defaulting to False, since the non-validating behavior of max_num is longstanding, and there is certainly code relying on it. (In fact, even the Django admin relies on it for the case where there are more existing inlines than the given max_num). It may be that at some point we want to deprecate validate_max=False and eventually remove the option, but this commit takes no steps in that direction. This also fixes the DoS-prevention absolute_max enforcement so that it causes a form validation error rather than an IndexError, and ensures that absolute_max is always 1000 more than max_num, to prevent surprising changes in behavior with max_num close to absolute_max. Lastly, this commit fixes the previous inconsistency between a regular formset and a model formset in the precedence of max_num and initial data. Previously in a regular formset, if the provided initial data was longer than max_num, it was truncated; in a model formset, all initial forms would be displayed regardless of max_num. Now regular formsets are the same as model formsets; all initial forms are displayed, even if more than max_num. (But if validate_max is True, submitting these forms will result in a "too many forms" validation error!) This combination of behaviors was chosen to keep the max_num validation simple and consistent, and avoid silent data loss due to truncation of initial data. Thanks to Preston for discussion of the design choices.
Diffstat (limited to 'docs')
-rw-r--r--docs/ref/contrib/contenttypes.txt2
-rw-r--r--docs/ref/forms/formsets.txt16
-rw-r--r--docs/ref/forms/index.txt1
-rw-r--r--docs/ref/forms/models.txt15
-rw-r--r--docs/releases/1.6.txt8
-rw-r--r--docs/topics/forms/formsets.txt78
-rw-r--r--docs/topics/forms/modelforms.txt7
7 files changed, 107 insertions, 20 deletions
diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt
index fb85653ce8..388172c43e 100644
--- a/docs/ref/contrib/contenttypes.txt
+++ b/docs/ref/contrib/contenttypes.txt
@@ -492,7 +492,7 @@ information.
Subclasses of :class:`GenericInlineModelAdmin` with stacked and tabular
layouts, respectively.
-.. function:: generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None)
+.. function:: generic_inlineformset_factory(model, form=ModelForm, formset=BaseGenericInlineFormSet, ct_field="content_type", fk_field="object_id", fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False)
Returns a ``GenericInlineFormSet`` using
:func:`~django.forms.models.modelformset_factory`.
diff --git a/docs/ref/forms/formsets.txt b/docs/ref/forms/formsets.txt
new file mode 100644
index 0000000000..0ab2590fce
--- /dev/null
+++ b/docs/ref/forms/formsets.txt
@@ -0,0 +1,16 @@
+====================
+Formset Functions
+====================
+
+.. module:: django.forms.formsets
+ :synopsis: Django's functions for building formsets.
+
+.. function:: formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None, validate_max=False)
+
+ Returns a ``FormSet`` class for the given ``form`` class.
+
+ See :ref:`formsets` for example usage.
+
+ .. versionchanged:: 1.6
+
+ The ``validate_max`` parameter was added.
diff --git a/docs/ref/forms/index.txt b/docs/ref/forms/index.txt
index 446fdb82de..e6edc88ca1 100644
--- a/docs/ref/forms/index.txt
+++ b/docs/ref/forms/index.txt
@@ -10,5 +10,6 @@ Detailed form API reference. For introductory material, see :doc:`/topics/forms/
api
fields
models
+ formsets
widgets
validation
diff --git a/docs/ref/forms/models.txt b/docs/ref/forms/models.txt
index f3382d32c7..dd0a422fd0 100644
--- a/docs/ref/forms/models.txt
+++ b/docs/ref/forms/models.txt
@@ -25,7 +25,7 @@ Model Form Functions
See :ref:`modelforms-factory` for example usage.
-.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None)
+.. function:: modelformset_factory(model, form=ModelForm, formfield_callback=None, formset=BaseModelFormSet, extra=1, can_delete=False, can_order=False, max_num=None, fields=None, exclude=None, widgets=None, validate_max=False)
Returns a ``FormSet`` class for the given ``model`` class.
@@ -33,17 +33,18 @@ Model Form Functions
``formfield_callback`` and ``widgets`` are all passed through to
:func:`~django.forms.models.modelform_factory`.
- Arguments ``formset``, ``extra``, ``max_num``, ``can_order``, and
- ``can_delete`` are passed through to ``formset_factory``. See
- :ref:`formsets` for details.
+ Arguments ``formset``, ``extra``, ``max_num``, ``can_order``,
+ ``can_delete`` and ``validate_max`` are passed through to
+ :func:`~django.forms.formsets.formset_factory`. See :ref:`formsets` for
+ details.
See :ref:`model-formsets` for example usage.
.. versionchanged:: 1.6
- The widgets parameter was added.
+ The ``widgets`` and the ``validate_max`` parameters were added.
-.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None)
+.. function:: inlineformset_factory(parent_model, model, form=ModelForm, formset=BaseInlineFormSet, fk_name=None, fields=None, exclude=None, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, widgets=None, validate_max=False)
Returns an ``InlineFormSet`` using :func:`modelformset_factory` with
defaults of ``formset=BaseInlineFormSet``, ``can_delete=True``, and
@@ -56,4 +57,4 @@ Model Form Functions
.. versionchanged:: 1.6
- The widgets parameter was added.
+ The ``widgets`` and the ``validate_max`` parameters were added.
diff --git a/docs/releases/1.6.txt b/docs/releases/1.6.txt
index 132ce68232..16039851c2 100644
--- a/docs/releases/1.6.txt
+++ b/docs/releases/1.6.txt
@@ -167,6 +167,14 @@ Minor features
field mixins to implement ``__init__()`` methods that will reliably be
called.
+* The ``validate_max`` parameter was added to ``BaseFormSet`` and
+ :func:`~django.forms.formsets.formset_factory`, and ``ModelForm`` and inline
+ versions of the same. The behavior of validation for formsets with
+ ``max_num`` was clarified. The previously undocumented behavior that
+ hardened formsets against memory exhaustion attacks was documented,
+ and the undocumented limit of the higher of 1000 or ``max_num`` forms
+ was changed so it is always 1000 more than ``max_num``.
+
Backwards incompatible changes in 1.6
=====================================
diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt
index 2534947dd3..f0a7668e0d 100644
--- a/docs/topics/forms/formsets.txt
+++ b/docs/topics/forms/formsets.txt
@@ -32,8 +32,8 @@ would with a regular form::
As you can see it only displayed one empty form. The number of empty forms
that is displayed is controlled by the ``extra`` parameter. By default,
-``formset_factory`` defines one extra form; the following example will
-display two blank forms::
+:func:`~django.forms.formsets.formset_factory` defines one extra form; the
+following example will display two blank forms::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2)
@@ -84,8 +84,9 @@ list of dictionaries as the initial data.
Limiting the maximum number of forms
------------------------------------
-The ``max_num`` parameter to ``formset_factory`` gives you the ability to
-limit the maximum number of empty forms the formset will display::
+The ``max_num`` parameter to :func:`~django.forms.formsets.formset_factory`
+gives you the ability to limit the maximum number of empty forms the formset
+will display::
>>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1)
>>> formset = ArticleFormSet()
@@ -101,6 +102,20 @@ so long as the total number of forms does not exceed ``max_num``.
A ``max_num`` value of ``None`` (the default) puts a high limit on the number
of forms displayed (1000). In practice this is equivalent to no limit.
+If the number of forms in the initial data exceeds ``max_num``, all initial
+data forms will be displayed regardless. (No extra forms will be displayed.)
+
+By default, ``max_num`` only affects how many forms are displayed and does not
+affect validation. If ``validate_max=True`` is passed to the
+:func:`~django.forms.formsets.formset_factory`, then ``max_num`` will affect
+validation. See :ref:`validate_max`.
+
+.. versionchanged:: 1.6
+ The ``validate_max`` parameter was added to
+ :func:`~django.forms.formsets.formset_factory`. Also, the behavior of
+ ``FormSet`` was brought in line with that of ``ModelFormSet`` so that it
+ displays initial data regardless of ``max_num``.
+
Formset validation
------------------
@@ -248,14 +263,59 @@ The formset ``clean`` method is called after all the ``Form.clean`` methods
have been called. The errors will be found using the ``non_form_errors()``
method on the formset.
+.. _validate_max:
+
+Validating the number of forms in a formset
+-------------------------------------------
+
+If ``validate_max=True`` is passed to
+:func:`~django.forms.formsets.formset_factory`, validation will also check
+that the number of forms in the data set is less than or equal to ``max_num``.
+
+ >>> ArticleFormSet = formset_factory(ArticleForm, max_num=1, validate_max=True)
+ >>> data = {
+ ... 'form-TOTAL_FORMS': u'2',
+ ... 'form-INITIAL_FORMS': u'0',
+ ... 'form-MAX_NUM_FORMS': u'',
+ ... 'form-0-title': u'Test',
+ ... 'form-0-pub_date': u'1904-06-16',
+ ... 'form-1-title': u'Test 2',
+ ... 'form-1-pub_date': u'1912-06-23',
+ ... }
+ >>> formset = ArticleFormSet(data)
+ >>> formset.is_valid()
+ False
+ >>> formset.errors
+ [{}, {}]
+ >>> formset.non_form_errors()
+ [u'Please submit 1 or fewer forms.']
+
+``validate_max=True`` validates against ``max_num`` strictly even if
+``max_num`` was exceeded because the amount of initial data supplied was
+excessive.
+
+Applications which need more customizable validation of the number of forms
+should use custom formset validation.
+
+.. note::
+
+ Regardless of ``validate_max``, if the number of forms in a data set
+ exceeds ``max_num`` by more than 1000, then the form will fail to validate
+ as if ``validate_max`` were set, and additionally only the first 1000
+ forms above ``max_num`` will be validated. The remainder will be
+ truncated entirely. This is to protect against memory exhaustion attacks
+ using forged POST requests.
+
+.. versionchanged:: 1.6
+ The ``validate_max`` parameter was added to
+ :func:`~django.forms.formsets.formset_factory`.
+
Dealing with ordering and deletion of forms
-------------------------------------------
-Common use cases with a formset is dealing with ordering and deletion of the
-form instances. This has been dealt with for you. The ``formset_factory``
-provides two optional parameters ``can_order`` and ``can_delete`` that will do
-the extra work of adding the extra fields and providing simpler ways of
-getting to that data.
+The :func:`~django.forms.formsets.formset_factory` provides two optional
+parameters ``can_order`` and ``can_delete`` to help with ordering of forms in
+formsets and deletion of forms from a formset.
``can_order``
~~~~~~~~~~~~~
diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt
index 62020e461e..eaf2bbbaf2 100644
--- a/docs/topics/forms/modelforms.txt
+++ b/docs/topics/forms/modelforms.txt
@@ -597,9 +597,10 @@ with the ``Author`` model. It works just like a regular formset::
.. note::
- :func:`~django.forms.models.modelformset_factory` uses ``formset_factory``
- to generate formsets. This means that a model formset is just an extension
- of a basic formset that knows how to interact with a particular model.
+ :func:`~django.forms.models.modelformset_factory` uses
+ :func:`~django.forms.formsets.formset_factory` to generate formsets. This
+ means that a model formset is just an extension of a basic formset that
+ knows how to interact with a particular model.
Changing the queryset
---------------------