diff --git a/django/contrib/contenttypes/forms.py b/django/contrib/contenttypes/forms.py index de1fcaf4bb..ce1c361b6c 100644 --- a/django/contrib/contenttypes/forms.py +++ b/django/contrib/contenttypes/forms.py @@ -56,7 +56,8 @@ def generic_inlineformset_factory(model, form=ModelForm, extra=3, can_order=False, can_delete=True, max_num=None, formfield_callback=None, validate_max=False, for_concrete_model=True, - min_num=None, validate_min=False): + min_num=None, validate_min=False, + absolute_max=None): """ Return a ``GenericInlineFormSet`` for the given kwargs. @@ -75,6 +76,7 @@ def generic_inlineformset_factory(model, form=ModelForm, formset=formset, extra=extra, can_delete=can_delete, can_order=can_order, fields=fields, exclude=exclude, max_num=max_num, validate_max=validate_max, min_num=min_num, validate_min=validate_min, + absolute_max=absolute_max, ) FormSet.ct_field = ct_field FormSet.ct_fk_field = fk_field diff --git a/docs/ref/contrib/contenttypes.txt b/docs/ref/contrib/contenttypes.txt index 66dbfe45ef..6cc0033132 100644 --- a/docs/ref/contrib/contenttypes.txt +++ b/docs/ref/contrib/contenttypes.txt @@ -466,7 +466,7 @@ The :mod:`django.contrib.contenttypes.forms` module provides: .. class:: BaseGenericInlineFormSet -.. 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, for_concrete_model=True, min_num=None, validate_min=False) +.. 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, for_concrete_model=True, min_num=None, validate_min=False, absolute_max=None) Returns a ``GenericInlineFormSet`` using :func:`~django.forms.models.modelformset_factory`. @@ -481,6 +481,10 @@ The :mod:`django.contrib.contenttypes.forms` module provides: :class:`~django.contrib.contenttypes.fields.GenericForeignKey.for_concrete_model` argument on ``GenericForeignKey``. + .. versionchanged:: 3.2 + + The ``absolute_max`` argument was added. + .. module:: django.contrib.contenttypes.admin Generic relations in admin diff --git a/docs/releases/3.2.txt b/docs/releases/3.2.txt index 65a2e6dc99..1acea66cff 100644 --- a/docs/releases/3.2.txt +++ b/docs/releases/3.2.txt @@ -85,7 +85,10 @@ Minor features :mod:`django.contrib.contenttypes` ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -* ... +* The new ``absolute_max`` argument for + :func:`~django.contrib.contenttypes.forms.generic_inlineformset_factory` + allows customizing the maximum number of forms that can be instantiated when + supplying ``POST`` data. See :ref:`formsets-absolute-max` for more details. :mod:`django.contrib.gis` ~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/tests/generic_relations/test_forms.py b/tests/generic_relations/test_forms.py index 4b94f24531..1a05681711 100644 --- a/tests/generic_relations/test_forms.py +++ b/tests/generic_relations/test_forms.py @@ -237,3 +237,37 @@ id="id_generic_relations-taggeditem-content_type-object_id-1-id">

""" % tagge self.assertEqual([tag.tag for tag in tags], ['hunts', 'roars']) hunts, roars = tags self.assertSequenceEqual(lion.tags.order_by('tag'), [hairy, hunts, roars, yellow]) + + def test_absolute_max(self): + GenericFormSet = generic_inlineformset_factory(TaggedItem, absolute_max=1500) + data = { + 'form-TOTAL_FORMS': '1501', + 'form-INITIAL_FORMS': '0', + 'form-MAX_NUM_FORMS': '0', + } + formset = GenericFormSet(data=data, prefix='form') + self.assertIs(formset.is_valid(), False) + self.assertEqual(len(formset.forms), 1500) + self.assertEqual( + formset.non_form_errors(), + ['Please submit 1000 or fewer forms.'], + ) + + def test_absolute_max_with_max_num(self): + GenericFormSet = generic_inlineformset_factory( + TaggedItem, + max_num=20, + absolute_max=100, + ) + data = { + 'form-TOTAL_FORMS': '101', + 'form-INITIAL_FORMS': '0', + 'form-MAX_NUM_FORMS': '0', + } + formset = GenericFormSet(data=data, prefix='form') + self.assertIs(formset.is_valid(), False) + self.assertEqual(len(formset.forms), 100) + self.assertEqual( + formset.non_form_errors(), + ['Please submit 20 or fewer forms.'], + )