Fixed #9532 -- Added min_num and validate_min on formsets.
Thanks gsf for the suggestion.
This commit is contained in:
parent
59a34c43a8
commit
df27803a55
|
@ -18,10 +18,14 @@ __all__ = ('BaseFormSet', 'all_valid')
|
|||
# special field names
|
||||
TOTAL_FORM_COUNT = 'TOTAL_FORMS'
|
||||
INITIAL_FORM_COUNT = 'INITIAL_FORMS'
|
||||
MIN_NUM_FORM_COUNT = 'MIN_NUM_FORMS'
|
||||
MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
|
||||
ORDERING_FIELD_NAME = 'ORDER'
|
||||
DELETION_FIELD_NAME = 'DELETE'
|
||||
|
||||
# default minimum number of forms in a formset
|
||||
DEFAULT_MIN_NUM = 0
|
||||
|
||||
# default maximum number of forms in a formset, to prevent memory exhaustion
|
||||
DEFAULT_MAX_NUM = 1000
|
||||
|
||||
|
@ -34,9 +38,10 @@ class ManagementForm(Form):
|
|||
def __init__(self, *args, **kwargs):
|
||||
self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
|
||||
self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
|
||||
# MAX_NUM_FORM_COUNT is output with the rest of the management form,
|
||||
# but only for the convenience of client-side code. The POST
|
||||
# value of MAX_NUM_FORM_COUNT returned from the client is not checked.
|
||||
# MIN_NUM_FORM_COUNT and MAX_NUM_FORM_COUNT are output with the rest of
|
||||
# the management form, but only for the convenience of client-side
|
||||
# code. The POST value of them returned from the client is not checked.
|
||||
self.base_fields[MIN_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
|
||||
self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
|
||||
super(ManagementForm, self).__init__(*args, **kwargs)
|
||||
|
||||
|
@ -92,6 +97,7 @@ class BaseFormSet(object):
|
|||
form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
|
||||
TOTAL_FORM_COUNT: self.total_form_count(),
|
||||
INITIAL_FORM_COUNT: self.initial_form_count(),
|
||||
MIN_NUM_FORM_COUNT: self.min_num,
|
||||
MAX_NUM_FORM_COUNT: self.max_num
|
||||
})
|
||||
return form
|
||||
|
@ -323,6 +329,12 @@ class BaseFormSet(object):
|
|||
"Please submit %d or fewer forms.", self.max_num) % self.max_num,
|
||||
code='too_many_forms',
|
||||
)
|
||||
if (self.validate_min and
|
||||
self.total_form_count() - len(self.deleted_forms) < self.min_num):
|
||||
raise ValidationError(ungettext(
|
||||
"Please submit %d or more forms.",
|
||||
"Please submit %d or more forms.", self.min_num) % self.min_num,
|
||||
code='too_few_forms')
|
||||
# Give self.clean() a chance to do cross-form validation.
|
||||
self.clean()
|
||||
except ValidationError as e:
|
||||
|
@ -395,17 +407,22 @@ class BaseFormSet(object):
|
|||
return mark_safe('\n'.join([six.text_type(self.management_form), forms]))
|
||||
|
||||
def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
|
||||
can_delete=False, max_num=None, validate_max=False):
|
||||
can_delete=False, max_num=None, validate_max=False,
|
||||
min_num=None, validate_min=False):
|
||||
"""Return a FormSet for the given form class."""
|
||||
if min_num is None:
|
||||
min_num = DEFAULT_MIN_NUM
|
||||
if max_num is None:
|
||||
max_num = DEFAULT_MAX_NUM
|
||||
# hard limit on forms instantiated, to prevent memory-exhaustion attacks
|
||||
# limit is simply max_num + DEFAULT_MAX_NUM (which is 2*DEFAULT_MAX_NUM
|
||||
# if max_num is None in the first place)
|
||||
absolute_max = max_num + DEFAULT_MAX_NUM
|
||||
extra += min_num
|
||||
attrs = {'form': form, 'extra': extra,
|
||||
'can_order': can_order, 'can_delete': can_delete,
|
||||
'max_num': max_num, 'absolute_max': absolute_max,
|
||||
'min_num': min_num, 'max_num': max_num,
|
||||
'absolute_max': absolute_max, 'validate_min' : validate_min,
|
||||
'validate_max' : validate_max}
|
||||
return type(form.__name__ + str('FormSet'), (formset,), attrs)
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@ 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)
|
||||
.. function:: formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, can_delete=False, max_num=None, validate_max=False, min_num=None, validate_min=False)
|
||||
|
||||
Returns a ``FormSet`` class for the given ``form`` class.
|
||||
|
||||
|
@ -14,3 +14,7 @@ Formset Functions
|
|||
.. versionchanged:: 1.6
|
||||
|
||||
The ``validate_max`` parameter was added.
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
|
||||
The ``min_num`` and ``validate_min`` parameters were added.
|
||||
|
|
|
@ -234,6 +234,10 @@ Forms
|
|||
<django.forms.extras.widgets.SelectDateWidget.months>` can be used to
|
||||
customize the wording of the months displayed in the select widget.
|
||||
|
||||
* The ``min_num`` and ``validate_min`` parameters were added to
|
||||
:func:`~django.forms.formsets.formset_factory` to allow validating
|
||||
a minimum number of submitted forms.
|
||||
|
||||
Management Commands
|
||||
^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
|
|
@ -298,6 +298,13 @@ method on the formset.
|
|||
Validating the number of forms in a formset
|
||||
-------------------------------------------
|
||||
|
||||
Django provides a couple ways to validate the minimum or maximum number of
|
||||
submitted forms. Applications which need more customizable validation of the
|
||||
number of forms should use custom formset validation.
|
||||
|
||||
``validate_max``
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
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, minus those marked for
|
||||
|
@ -309,6 +316,7 @@ deletion, is less than or equal to ``max_num``.
|
|||
>>> data = {
|
||||
... 'form-TOTAL_FORMS': u'2',
|
||||
... 'form-INITIAL_FORMS': u'0',
|
||||
... 'form-MIN_NUM_FORMS': u'',
|
||||
... 'form-MAX_NUM_FORMS': u'',
|
||||
... 'form-0-title': u'Test',
|
||||
... 'form-0-pub_date': u'1904-06-16',
|
||||
|
@ -327,9 +335,6 @@ deletion, is less than or equal to ``max_num``.
|
|||
``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
|
||||
|
@ -344,6 +349,42 @@ should use custom formset validation.
|
|||
The ``validate_max`` parameter was added to
|
||||
:func:`~django.forms.formsets.formset_factory`.
|
||||
|
||||
``validate_min``
|
||||
~~~~~~~~~~~~~~~~
|
||||
|
||||
.. versionadded:: 1.7
|
||||
|
||||
If ``validate_min=True`` is passed to
|
||||
:func:`~django.forms.formsets.formset_factory`, validation will also check
|
||||
that the number of forms in the data set, minus those marked for
|
||||
deletion, is greater than or equal to ``min_num``.
|
||||
|
||||
>>> from django.forms.formsets import formset_factory
|
||||
>>> from myapp.forms import ArticleForm
|
||||
>>> ArticleFormSet = formset_factory(ArticleForm, min_num=3, validate_min=True)
|
||||
>>> data = {
|
||||
... 'form-TOTAL_FORMS': u'2',
|
||||
... 'form-INITIAL_FORMS': u'0',
|
||||
... 'form-MIN_NUM_FORMS': u'',
|
||||
... '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 3 or more forms.']
|
||||
|
||||
.. versionchanged:: 1.7
|
||||
|
||||
The ``min_num`` and ``validate_min`` parameters were added to
|
||||
:func:`~django.forms.formsets.formset_factory`.
|
||||
|
||||
Dealing with ordering and deletion of forms
|
||||
-------------------------------------------
|
||||
|
||||
|
|
|
@ -1874,14 +1874,14 @@ class AdminViewListEditable(TestCase):
|
|||
def test_changelist_input_html(self):
|
||||
response = self.client.get('/test_admin/admin/admin_views/person/')
|
||||
# 2 inputs per object(the field and the hidden id field) = 6
|
||||
# 3 management hidden fields = 3
|
||||
# 4 management hidden fields = 4
|
||||
# 4 action inputs (3 regular checkboxes, 1 checkbox to select all)
|
||||
# main form submit button = 1
|
||||
# search field and search submit button = 2
|
||||
# CSRF field = 1
|
||||
# field to track 'select all' across paginated views = 1
|
||||
# 6 + 3 + 4 + 1 + 2 + 1 + 1 = 18 inputs
|
||||
self.assertContains(response, "<input", count=18)
|
||||
# 6 + 4 + 4 + 1 + 2 + 1 + 1 = 19 inputs
|
||||
self.assertContains(response, "<input", count=19)
|
||||
# 1 select per object = 3 selects
|
||||
self.assertContains(response, "<select", count=4)
|
||||
|
||||
|
@ -3629,9 +3629,9 @@ class ReadonlyTest(TestCase):
|
|||
response = self.client.get('/test_admin/admin/admin_views/post/add/')
|
||||
self.assertEqual(response.status_code, 200)
|
||||
self.assertNotContains(response, 'name="posted"')
|
||||
# 3 fields + 2 submit buttons + 4 inline management form fields, + 2
|
||||
# 3 fields + 2 submit buttons + 5 inline management form fields, + 2
|
||||
# hidden fields for inlines + 1 field for the inline + 2 empty form
|
||||
self.assertContains(response, "<input", count=14)
|
||||
self.assertContains(response, "<input", count=15)
|
||||
self.assertContains(response, formats.localize(datetime.date.today()))
|
||||
self.assertContains(response,
|
||||
"<label>Awesomeness level:</label>")
|
||||
|
|
|
@ -57,7 +57,7 @@ SplitDateTimeFormSet = formset_factory(SplitDateTimeForm)
|
|||
class FormsFormsetTestCase(TestCase):
|
||||
|
||||
def make_choiceformset(self, formset_data=None, formset_class=ChoiceFormSet,
|
||||
total_forms=None, initial_forms=0, max_num_forms=0, **kwargs):
|
||||
total_forms=None, initial_forms=0, max_num_forms=0, min_num_forms=0, **kwargs):
|
||||
"""
|
||||
Make a ChoiceFormset from the given formset_data.
|
||||
The data should be given as a list of (choice, votes) tuples.
|
||||
|
@ -79,6 +79,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
prefixed('TOTAL_FORMS'): str(total_forms),
|
||||
prefixed('INITIAL_FORMS'): str(initial_forms),
|
||||
prefixed('MAX_NUM_FORMS'): str(max_num_forms),
|
||||
prefixed('MIN_NUM_FORMS'): str(min_num_forms),
|
||||
}
|
||||
for i, (choice, votes) in enumerate(formset_data):
|
||||
data[prefixed(str(i), 'choice')] = choice
|
||||
|
@ -91,8 +92,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 = self.make_choiceformset()
|
||||
|
||||
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" />
|
||||
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-MIN_NUM_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="number" name="choices-0-votes" /></td></tr>""")
|
||||
|
||||
|
@ -200,6 +200,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': '',
|
||||
'choices-0-votes': '',
|
||||
|
@ -213,12 +214,46 @@ class FormsFormsetTestCase(TestCase):
|
|||
self.assertTrue(formset.is_valid())
|
||||
self.assertEqual([form.cleaned_data for form in formset.forms], [{}, {}, {}])
|
||||
|
||||
def test_min_num_displaying_more_than_one_blank_form(self):
|
||||
# We can also display more than 1 empty form passing min_num argument
|
||||
# to formset_factory. It will increment the extra argument
|
||||
ChoiceFormSet = formset_factory(Choice, extra=1, min_num=1)
|
||||
|
||||
formset = ChoiceFormSet(auto_id=False, prefix='choices')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" /></li>
|
||||
<li>Votes: <input type="number" name="choices-1-votes" /></li>""")
|
||||
|
||||
def test_min_num_displaying_more_than_one_blank_form_with_zero_extra(self):
|
||||
# We can also display more than 1 empty form passing min_num argument
|
||||
ChoiceFormSet = formset_factory(Choice, extra=0, min_num=3)
|
||||
|
||||
formset = ChoiceFormSet(auto_id=False, prefix='choices')
|
||||
form_output = []
|
||||
|
||||
for form in formset.forms:
|
||||
form_output.append(form.as_ul())
|
||||
|
||||
self.assertHTMLEqual('\n'.join(form_output), """<li>Choice: <input type="text" name="choices-0-choice" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" /></li>
|
||||
<li>Choice: <input type="text" name="choices-1-choice" /></li>
|
||||
<li>Votes: <input type="number" name="choices-1-votes" /></li>
|
||||
<li>Choice: <input type="text" name="choices-2-choice" /></li>
|
||||
<li>Votes: <input type="number" name="choices-2-votes" /></li>""")
|
||||
|
||||
def test_single_form_completed(self):
|
||||
# We can just fill out one of the forms.
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -242,6 +277,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '2', # max number of forms - should be ignored
|
||||
'choices-0-choice': 'Zero',
|
||||
'choices-0-votes': '0',
|
||||
|
@ -254,12 +290,35 @@ class FormsFormsetTestCase(TestCase):
|
|||
self.assertFalse(formset.is_valid())
|
||||
self.assertEqual(formset.non_form_errors(), ['Please submit 1 or fewer forms.'])
|
||||
|
||||
def test_formset_validate_min_flag(self):
|
||||
# If validate_min is set and min_num is more than TOTAL_FORMS in the
|
||||
# data, then throw an exception. MIN_NUM_FORMS in the data is
|
||||
# irrelevant here (it's output as a hint for the client but its
|
||||
# value in the returned data is not checked)
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms - should be ignored
|
||||
'choices-0-choice': 'Zero',
|
||||
'choices-0-votes': '0',
|
||||
'choices-1-choice': 'One',
|
||||
'choices-1-votes': '1',
|
||||
}
|
||||
|
||||
ChoiceFormSet = formset_factory(Choice, extra=1, min_num=3, validate_min=True)
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertFalse(formset.is_valid())
|
||||
self.assertEqual(formset.non_form_errors(), ['Please submit 3 or more forms.'])
|
||||
|
||||
def test_second_form_partially_filled_2(self):
|
||||
# And once again, if we try to partially complete a form, validation will fail.
|
||||
|
||||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -281,6 +340,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -344,6 +404,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '2', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -371,6 +432,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'check-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'check-INITIAL_FORMS': '2', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'check-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'check-0-field': '200',
|
||||
'check-0-DELETE': '',
|
||||
|
@ -401,7 +463,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
p = PeopleForm(
|
||||
{'form-0-name': '', 'form-0-DELETE': 'on', # no name!
|
||||
'form-TOTAL_FORMS': 1, 'form-INITIAL_FORMS': 1,
|
||||
'form-MAX_NUM_FORMS': 1})
|
||||
'form-MIN_NUM_FORMS': 0, 'form-MAX_NUM_FORMS': 1})
|
||||
|
||||
self.assertTrue(p.is_valid())
|
||||
self.assertEqual(len(p.deleted_forms), 1)
|
||||
|
@ -438,6 +500,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '2', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -470,6 +533,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '4', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '3', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -506,6 +570,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '3', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
}
|
||||
|
||||
|
@ -558,6 +623,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '4', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '3', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -604,6 +670,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
'form-0-DELETE': 'on', # no name!
|
||||
'form-TOTAL_FORMS': 1,
|
||||
'form-INITIAL_FORMS': 1,
|
||||
'form-MIN_NUM_FORMS': 0,
|
||||
'form-MAX_NUM_FORMS': 1
|
||||
})
|
||||
|
||||
|
@ -620,6 +687,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'drinks-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'drinks-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'drinks-0-name': 'Gin and Tonic',
|
||||
'drinks-1-name': 'Gin and Tonic',
|
||||
|
@ -639,6 +707,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'drinks-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'drinks-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'drinks-0-name': 'Gin and Tonic',
|
||||
'drinks-1-name': 'Bloody Mary',
|
||||
|
@ -791,6 +860,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'form-TOTAL_FORMS': '2',
|
||||
'form-INITIAL_FORMS': '0',
|
||||
'form-MIN_NUM_FORMS': '0',
|
||||
'form-MAX_NUM_FORMS': '0',
|
||||
}
|
||||
formset = FavoriteDrinksFormSet(data=data)
|
||||
|
@ -805,6 +875,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'drinks-TOTAL_FORMS': '2', # the number of forms rendered
|
||||
'drinks-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'drinks-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'drinks-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'drinks-0-name': 'Gin and Tonic',
|
||||
'drinks-1-name': 'Gin and Tonic',
|
||||
|
@ -894,6 +965,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '1', # number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -914,6 +986,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
{
|
||||
'choices-TOTAL_FORMS': '4',
|
||||
'choices-INITIAL_FORMS': '0',
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '4',
|
||||
'choices-0-choice': 'Zero',
|
||||
'choices-0-votes': '0',
|
||||
|
@ -945,6 +1018,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
{
|
||||
'choices-TOTAL_FORMS': '4',
|
||||
'choices-INITIAL_FORMS': '0',
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '4',
|
||||
'choices-0-choice': 'Zero',
|
||||
'choices-0-votes': '0',
|
||||
|
@ -1032,6 +1106,7 @@ class FormsFormsetTestCase(TestCase):
|
|||
data = {
|
||||
'choices-TOTAL_FORMS': '1', # the number of forms rendered
|
||||
'choices-INITIAL_FORMS': '0', # the number of forms with initial data
|
||||
'choices-MIN_NUM_FORMS': '0', # min number of forms
|
||||
'choices-MAX_NUM_FORMS': '0', # max number of forms
|
||||
'choices-0-choice': 'Calexico',
|
||||
'choices-0-votes': '100',
|
||||
|
@ -1046,19 +1121,19 @@ ChoiceFormSet = formset_factory(Choice)
|
|||
class FormsetAsFooTests(TestCase):
|
||||
def test_as_table(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertHTMLEqual(formset.as_table(),"""<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="0" />
|
||||
self.assertHTMLEqual(formset.as_table(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MIN_NUM_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
|
||||
<tr><th>Votes:</th><td><input type="number" name="choices-0-votes" value="100" /></td></tr>""")
|
||||
|
||||
def test_as_p(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertHTMLEqual(formset.as_p(),"""<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="0" />
|
||||
self.assertHTMLEqual(formset.as_p(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MIN_NUM_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
|
||||
<p>Votes: <input type="number" name="choices-0-votes" value="100" /></p>""")
|
||||
|
||||
def test_as_ul(self):
|
||||
formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
|
||||
self.assertHTMLEqual(formset.as_ul(),"""<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="0" />
|
||||
self.assertHTMLEqual(formset.as_ul(),"""<input type="hidden" name="choices-TOTAL_FORMS" value="1" /><input type="hidden" name="choices-INITIAL_FORMS" value="0" /><input type="hidden" name="choices-MIN_NUM_FORMS" value="0" /><input type="hidden" name="choices-MAX_NUM_FORMS" value="0" />
|
||||
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||
<li>Votes: <input type="number" name="choices-0-votes" value="100" /></li>""")
|
||||
|
||||
|
|
Loading…
Reference in New Issue