Loading django/forms/formsets.py +15 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS' ORDERING_FIELD_NAME = 'ORDER' DELETION_FIELD_NAME = 'DELETE' # default maximum number of forms in a formset, to prevent memory exhaustion DEFAULT_MAX_NUM = 1000 class ManagementForm(Form): """ ``ManagementForm`` is used to keep track of how many form instances Loading Loading @@ -97,7 +100,6 @@ class BaseFormSet(object): total_forms = initial_forms + self.extra # Allow all existing related objects/inlines to be displayed, # but don't allow extra beyond max_num. if self.max_num is not None: if initial_forms > self.max_num >= 0: total_forms = initial_forms elif total_forms > self.max_num >= 0: Loading @@ -111,14 +113,14 @@ class BaseFormSet(object): else: # Use the length of the inital data if it's there, 0 otherwise. initial_forms = self.initial and len(self.initial) or 0 if self.max_num is not None and (initial_forms > self.max_num >= 0): if initial_forms > self.max_num >= 0: initial_forms = self.max_num return initial_forms def _construct_forms(self): # instantiate all the forms and put them in self.forms self.forms = [] for i in xrange(self.total_form_count()): for i in xrange(min(self.total_form_count(), self.absolute_max)): self.forms.append(self._construct_form(i)) def _construct_form(self, i, **kwargs): Loading Loading @@ -368,9 +370,14 @@ class BaseFormSet(object): def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None): """Return a FormSet for the given form class.""" if max_num is None: max_num = DEFAULT_MAX_NUM # hard limit on forms instantiated, to prevent memory-exhaustion attacks # limit defaults to DEFAULT_MAX_NUM, but developer can increase it via max_num absolute_max = max(DEFAULT_MAX_NUM, max_num) attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete, 'max_num': max_num} 'max_num': max_num, 'absolute_max': absolute_max} return type(form.__name__ + str('FormSet'), (formset,), attrs) def all_valid(formsets): Loading docs/topics/forms/formsets.txt +2 −2 Original line number Diff line number Diff line Loading @@ -98,8 +98,8 @@ If the value of ``max_num`` is greater than the number of existing objects, up to ``extra`` additional blank forms will be added to the formset, so long as the total number of forms does not exceed ``max_num``. A ``max_num`` value of ``None`` (the default) puts no limit on the number of forms displayed. 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. Formset validation ------------------ Loading docs/topics/forms/modelforms.txt +2 −2 Original line number Diff line number Diff line Loading @@ -727,8 +727,8 @@ so long as the total number of forms does not exceed ``max_num``:: <tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr> <tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr> A ``max_num`` value of ``None`` (the default) puts no limit on the number of forms displayed. 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. Using a model formset in a view ------------------------------- Loading tests/regressiontests/forms/tests/formsets.py +64 −6 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.forms import (CharField, DateField, FileField, Form, IntegerField, ValidationError) ValidationError, formsets) from django.forms.formsets import BaseFormSet, formset_factory from django.forms.util import ErrorList from django.test import TestCase Loading Loading @@ -51,7 +51,7 @@ class FormsFormsetTestCase(TestCase): # for adding data. By default, it displays 1 blank form. It can display more, # but we'll look at how to do so later. formset = ChoiceFormSet(auto_id=False, prefix='choices') self.assertHTMLEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" /> self.assertHTMLEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="1000" /> <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr> <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""") Loading Loading @@ -654,8 +654,8 @@ class FormsFormsetTestCase(TestCase): # Limiting the maximum number of forms ######################################## # Base case for max_num. # When not passed, max_num will take its default value of None, i.e. unlimited # number of forms, only controlled by the value of the extra parameter. # When not passed, max_num will take a high default value, leaving the # number of forms only controlled by the value of the extra parameter. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3) formset = LimitedFavoriteDrinkFormSet() Loading Loading @@ -702,8 +702,8 @@ class FormsFormsetTestCase(TestCase): def test_max_num_with_initial_data(self): # max_num with initial data # When not passed, max_num will take its default value of None, i.e. unlimited # number of forms, only controlled by the values of the initial and extra # When not passed, max_num will take a high default value, leaving the # number of forms only controlled by the value of the initial and extra # parameters. initial = [ Loading Loading @@ -878,6 +878,64 @@ class FormsFormsetTestCase(TestCase): self.assertTrue(formset.is_valid()) self.assertTrue(all([form.is_valid_called for form in formset.forms])) def test_hard_limit_on_instantiated_forms(self): """A formset has a hard limit on the number of forms instantiated.""" # reduce the default limit of 1000 temporarily for testing _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM try: formsets.DEFAULT_MAX_NUM = 3 ChoiceFormSet = formset_factory(Choice) # someone fiddles with the mgmt form data... formset = ChoiceFormSet( { 'choices-TOTAL_FORMS': '4', 'choices-INITIAL_FORMS': '0', 'choices-MAX_NUM_FORMS': '4', 'choices-0-choice': 'Zero', 'choices-0-votes': '0', 'choices-1-choice': 'One', 'choices-1-votes': '1', 'choices-2-choice': 'Two', 'choices-2-votes': '2', 'choices-3-choice': 'Three', 'choices-3-votes': '3', }, prefix='choices', ) # But we still only instantiate 3 forms self.assertEqual(len(formset.forms), 3) finally: formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM def test_increase_hard_limit(self): """Can increase the built-in forms limit via a higher max_num.""" # reduce the default limit of 1000 temporarily for testing _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM try: formsets.DEFAULT_MAX_NUM = 3 # for this form, we want a limit of 4 ChoiceFormSet = formset_factory(Choice, max_num=4) formset = ChoiceFormSet( { 'choices-TOTAL_FORMS': '4', 'choices-INITIAL_FORMS': '0', 'choices-MAX_NUM_FORMS': '4', 'choices-0-choice': 'Zero', 'choices-0-votes': '0', 'choices-1-choice': 'One', 'choices-1-votes': '1', 'choices-2-choice': 'Two', 'choices-2-votes': '2', 'choices-3-choice': 'Three', 'choices-3-votes': '3', }, prefix='choices', ) # This time four forms are instantiated self.assertEqual(len(formset.forms), 4) finally: formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM data = { 'choices-TOTAL_FORMS': '1', # the number of forms rendered Loading tests/regressiontests/generic_inline_admin/tests.py +2 −1 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ from django.contrib import admin from django.contrib.admin.sites import AdminSite from django.contrib.contenttypes.generic import ( generic_inlineformset_factory, GenericTabularInline) from django.forms.formsets import DEFAULT_MAX_NUM from django.forms.models import ModelForm from django.test import TestCase from django.test.utils import override_settings Loading Loading @@ -244,7 +245,7 @@ class GenericInlineModelAdminTest(TestCase): # Create a formset with default arguments formset = media_inline.get_formset(request) self.assertEqual(formset.max_num, None) self.assertEqual(formset.max_num, DEFAULT_MAX_NUM) self.assertEqual(formset.can_order, False) # Create a formset with custom keyword arguments Loading Loading
django/forms/formsets.py +15 −8 Original line number Diff line number Diff line Loading @@ -21,6 +21,9 @@ MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS' ORDERING_FIELD_NAME = 'ORDER' DELETION_FIELD_NAME = 'DELETE' # default maximum number of forms in a formset, to prevent memory exhaustion DEFAULT_MAX_NUM = 1000 class ManagementForm(Form): """ ``ManagementForm`` is used to keep track of how many form instances Loading Loading @@ -97,7 +100,6 @@ class BaseFormSet(object): total_forms = initial_forms + self.extra # Allow all existing related objects/inlines to be displayed, # but don't allow extra beyond max_num. if self.max_num is not None: if initial_forms > self.max_num >= 0: total_forms = initial_forms elif total_forms > self.max_num >= 0: Loading @@ -111,14 +113,14 @@ class BaseFormSet(object): else: # Use the length of the inital data if it's there, 0 otherwise. initial_forms = self.initial and len(self.initial) or 0 if self.max_num is not None and (initial_forms > self.max_num >= 0): if initial_forms > self.max_num >= 0: initial_forms = self.max_num return initial_forms def _construct_forms(self): # instantiate all the forms and put them in self.forms self.forms = [] for i in xrange(self.total_form_count()): for i in xrange(min(self.total_form_count(), self.absolute_max)): self.forms.append(self._construct_form(i)) def _construct_form(self, i, **kwargs): Loading Loading @@ -368,9 +370,14 @@ class BaseFormSet(object): def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None): """Return a FormSet for the given form class.""" if max_num is None: max_num = DEFAULT_MAX_NUM # hard limit on forms instantiated, to prevent memory-exhaustion attacks # limit defaults to DEFAULT_MAX_NUM, but developer can increase it via max_num absolute_max = max(DEFAULT_MAX_NUM, max_num) attrs = {'form': form, 'extra': extra, 'can_order': can_order, 'can_delete': can_delete, 'max_num': max_num} 'max_num': max_num, 'absolute_max': absolute_max} return type(form.__name__ + str('FormSet'), (formset,), attrs) def all_valid(formsets): Loading
docs/topics/forms/formsets.txt +2 −2 Original line number Diff line number Diff line Loading @@ -98,8 +98,8 @@ If the value of ``max_num`` is greater than the number of existing objects, up to ``extra`` additional blank forms will be added to the formset, so long as the total number of forms does not exceed ``max_num``. A ``max_num`` value of ``None`` (the default) puts no limit on the number of forms displayed. 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. Formset validation ------------------ Loading
docs/topics/forms/modelforms.txt +2 −2 Original line number Diff line number Diff line Loading @@ -727,8 +727,8 @@ so long as the total number of forms does not exceed ``max_num``:: <tr><th><label for="id_form-2-name">Name:</label></th><td><input id="id_form-2-name" type="text" name="form-2-name" value="Walt Whitman" maxlength="100" /><input type="hidden" name="form-2-id" value="2" id="id_form-2-id" /></td></tr> <tr><th><label for="id_form-3-name">Name:</label></th><td><input id="id_form-3-name" type="text" name="form-3-name" maxlength="100" /><input type="hidden" name="form-3-id" id="id_form-3-id" /></td></tr> A ``max_num`` value of ``None`` (the default) puts no limit on the number of forms displayed. 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. Using a model formset in a view ------------------------------- Loading
tests/regressiontests/forms/tests/formsets.py +64 −6 Original line number Diff line number Diff line Loading @@ -2,7 +2,7 @@ from __future__ import unicode_literals from django.forms import (CharField, DateField, FileField, Form, IntegerField, ValidationError) ValidationError, formsets) from django.forms.formsets import BaseFormSet, formset_factory from django.forms.util import ErrorList from django.test import TestCase Loading Loading @@ -51,7 +51,7 @@ class FormsFormsetTestCase(TestCase): # for adding data. By default, it displays 1 blank form. It can display more, # but we'll look at how to do so later. formset = ChoiceFormSet(auto_id=False, prefix='choices') self.assertHTMLEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" /> self.assertHTMLEqual(str(formset), """<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="1000" /> <tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr> <tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""") Loading Loading @@ -654,8 +654,8 @@ class FormsFormsetTestCase(TestCase): # Limiting the maximum number of forms ######################################## # Base case for max_num. # When not passed, max_num will take its default value of None, i.e. unlimited # number of forms, only controlled by the value of the extra parameter. # When not passed, max_num will take a high default value, leaving the # number of forms only controlled by the value of the extra parameter. LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=3) formset = LimitedFavoriteDrinkFormSet() Loading Loading @@ -702,8 +702,8 @@ class FormsFormsetTestCase(TestCase): def test_max_num_with_initial_data(self): # max_num with initial data # When not passed, max_num will take its default value of None, i.e. unlimited # number of forms, only controlled by the values of the initial and extra # When not passed, max_num will take a high default value, leaving the # number of forms only controlled by the value of the initial and extra # parameters. initial = [ Loading Loading @@ -878,6 +878,64 @@ class FormsFormsetTestCase(TestCase): self.assertTrue(formset.is_valid()) self.assertTrue(all([form.is_valid_called for form in formset.forms])) def test_hard_limit_on_instantiated_forms(self): """A formset has a hard limit on the number of forms instantiated.""" # reduce the default limit of 1000 temporarily for testing _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM try: formsets.DEFAULT_MAX_NUM = 3 ChoiceFormSet = formset_factory(Choice) # someone fiddles with the mgmt form data... formset = ChoiceFormSet( { 'choices-TOTAL_FORMS': '4', 'choices-INITIAL_FORMS': '0', 'choices-MAX_NUM_FORMS': '4', 'choices-0-choice': 'Zero', 'choices-0-votes': '0', 'choices-1-choice': 'One', 'choices-1-votes': '1', 'choices-2-choice': 'Two', 'choices-2-votes': '2', 'choices-3-choice': 'Three', 'choices-3-votes': '3', }, prefix='choices', ) # But we still only instantiate 3 forms self.assertEqual(len(formset.forms), 3) finally: formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM def test_increase_hard_limit(self): """Can increase the built-in forms limit via a higher max_num.""" # reduce the default limit of 1000 temporarily for testing _old_DEFAULT_MAX_NUM = formsets.DEFAULT_MAX_NUM try: formsets.DEFAULT_MAX_NUM = 3 # for this form, we want a limit of 4 ChoiceFormSet = formset_factory(Choice, max_num=4) formset = ChoiceFormSet( { 'choices-TOTAL_FORMS': '4', 'choices-INITIAL_FORMS': '0', 'choices-MAX_NUM_FORMS': '4', 'choices-0-choice': 'Zero', 'choices-0-votes': '0', 'choices-1-choice': 'One', 'choices-1-votes': '1', 'choices-2-choice': 'Two', 'choices-2-votes': '2', 'choices-3-choice': 'Three', 'choices-3-votes': '3', }, prefix='choices', ) # This time four forms are instantiated self.assertEqual(len(formset.forms), 4) finally: formsets.DEFAULT_MAX_NUM = _old_DEFAULT_MAX_NUM data = { 'choices-TOTAL_FORMS': '1', # the number of forms rendered Loading
tests/regressiontests/generic_inline_admin/tests.py +2 −1 Original line number Diff line number Diff line Loading @@ -6,6 +6,7 @@ from django.contrib import admin from django.contrib.admin.sites import AdminSite from django.contrib.contenttypes.generic import ( generic_inlineformset_factory, GenericTabularInline) from django.forms.formsets import DEFAULT_MAX_NUM from django.forms.models import ModelForm from django.test import TestCase from django.test.utils import override_settings Loading Loading @@ -244,7 +245,7 @@ class GenericInlineModelAdminTest(TestCase): # Create a formset with default arguments formset = media_inline.get_formset(request) self.assertEqual(formset.max_num, None) self.assertEqual(formset.max_num, DEFAULT_MAX_NUM) self.assertEqual(formset.can_order, False) # Create a formset with custom keyword arguments Loading