Ticket #9532: 9532.patch

File 9532.patch, 11.7 KB (added by michal@…, 13 years ago)

Added min_num argument to formset_factory, updated docs

  • django/forms/formsets.py

    diff --git a/django/forms/formsets.py b/django/forms/formsets.py
    index dcd2f01..0c53006 100644
    a b __all__ = ('BaseFormSet', 'all_valid')  
    1616TOTAL_FORM_COUNT = 'TOTAL_FORMS'
    1717INITIAL_FORM_COUNT = 'INITIAL_FORMS'
    1818MAX_NUM_FORM_COUNT = 'MAX_NUM_FORMS'
     19MIN_NUM_FORM_COUNT = 'MIN_NUM_FORMS'
    1920ORDERING_FIELD_NAME = 'ORDER'
    2021DELETION_FIELD_NAME = 'DELETE'
    2122
    class ManagementForm(Form):  
    2930        self.base_fields[TOTAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
    3031        self.base_fields[INITIAL_FORM_COUNT] = IntegerField(widget=HiddenInput)
    3132        self.base_fields[MAX_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
     33        self.base_fields[MIN_NUM_FORM_COUNT] = IntegerField(required=False, widget=HiddenInput)
    3234        super(ManagementForm, self).__init__(*args, **kwargs)
    3335
    3436class BaseFormSet(StrAndUnicode):
    class BaseFormSet(StrAndUnicode):  
    7779            form = ManagementForm(auto_id=self.auto_id, prefix=self.prefix, initial={
    7880                TOTAL_FORM_COUNT: self.total_form_count(),
    7981                INITIAL_FORM_COUNT: self.initial_form_count(),
    80                 MAX_NUM_FORM_COUNT: self.max_num
     82                MAX_NUM_FORM_COUNT: self.max_num,
     83                MIN_NUM_FORM_COUNT: self.min_num
    8184            })
    8285        return form
    8386    management_form = property(_management_form)
    class BaseFormSet(StrAndUnicode):  
    9598                total_forms = initial_forms
    9699            elif total_forms > self.max_num >= 0:
    97100                total_forms = self.max_num
     101
     102            total_forms = max(total_forms, self.min_num)
     103
    98104        return total_forms
    99105
    100106    def initial_form_count(self):
    class BaseFormSet(StrAndUnicode):  
    358364        return mark_safe(u'\n'.join([unicode(self.management_form), forms]))
    359365
    360366def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False,
    361                     can_delete=False, max_num=None):
     367                    can_delete=False, max_num=None, min_num=None):
    362368    """Return a FormSet for the given form class."""
     369    if not None in (max_num, min_num) and max_num < min_num:
     370        raise ValueError('max_num cannot be smaller than min_num')
     371
    363372    attrs = {'form': form, 'extra': extra,
    364373             'can_order': can_order, 'can_delete': can_delete,
    365              'max_num': max_num}
     374             'max_num': max_num, 'min_num': min_num }
    366375    return type(form.__name__ + 'FormSet', (formset,), attrs)
    367376
     377
    368378def all_valid(formsets):
    369379    """Returns true if every formset in formsets is valid."""
    370380    valid = True
  • docs/topics/forms/formsets.txt

    diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt
    index b524c24..9a91182 100644
    a b A ``max_num`` value of ``None`` (the default) puts no limit on the number of  
    112112forms displayed. Please note that the default value of ``max_num`` was changed
    113113from ``0`` to ``None`` in version 1.2 to allow ``0`` as a valid value.
    114114
     115.. _formsets-min-num:
     116
     117Ensuring the minimum number of forms
     118------------------------------------
     119
     120The ``min_num`` parameter to ``formset_factory`` gives you the ability to make
     121sure that at least a certain number of forms will be displayed::
     122
     123    >>> ArticleFormSet = formset_factory(ArticleForm, min_num=2)
     124    >>> formset = ArticleFormset(initial=[
     125    ...     {'title': u'Django is now open source',
     126    ...      'pub_date': datetime.date.today(),}
     127    ... ])
     128
     129    >>> for form in formset:
     130    ...     print form.as_table()
     131    <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Django is now open source" id="id_form-0-title" /></td></tr>
     132    <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2012-04-02" id="id_form-0-pub_date" /></td></tr>
     133    <tr><th><label for="id_form-1-title">Title:</label></th><td><input type="text" name="form-1-title" id="id_form-1-title" /></td></tr>
     134    <tr><th><label for="id_form-1-pub_date">Pub date:</label></th><td><input type="text" name="form-1-pub_date" id="id_form-1-pub_date" /></td></tr>
     135
     136.. versionchanged:: 1.4
     137
     138If the value of ``min_num`` is greater than the number of elements that would
     139be displayed normally (including extra forms), additional blank forms will be
     140added to the formset.
     141
     142If ``min_num`` is greater than ``max_num``, ``ValueError`` will be raised by
     143``formset_factory`` function.
     144
    115145Formset validation
    116146------------------
    117147
  • tests/regressiontests/forms/tests/formsets.py

    diff --git a/tests/regressiontests/forms/tests/formsets.py b/tests/regressiontests/forms/tests/formsets.py
    index 05ef978..957f04b 100644
    a b class FormsFormsetTestCase(TestCase):  
    4747        # for adding data. By default, it displays 1 blank form. It can display more,
    4848        # but we'll look at how to do so later.
    4949        formset = ChoiceFormSet(auto_id=False, prefix='choices')
    50         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" />
     50        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" /><input type="hidden" name="choices-MIN_NUM_FORMS" />
    5151<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" /></td></tr>
    5252<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" /></td></tr>""")
    5353
    class FormsFormsetTestCase(TestCase):  
    646646        self.assertTrue(formset.is_valid())
    647647        self.assertEqual(formset.non_form_errors(), [])
    648648
     649    def test_appending_min_forms(self):
     650        # Ensuring the minimum number of forms ########################################
     651        # Base case for min_num.
     652
     653        # When not passed, min_num will take its default value of None, i.e. no required
     654        # forms, only controller by the value of the extra parameter.
     655
     656        initial = [ dict(name='Pony') ]
     657        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=0, min_num=1)
     658        formset = LimitedFavoriteDrinkFormSet()
     659
     660        self.assertHTMLEqual('\n'.join(str(form) for form in formset.forms), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>""")
     661
     662        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=0, min_num=2)
     663        formset = LimitedFavoriteDrinkFormSet(initial=initial)
     664
     665        self.assertHTMLEqual('\n'.join(str(form) for form in formset.forms), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" value="Pony" /></td></tr>
     666<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
     667
     668        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=0, min_num=1)
     669        formset = LimitedFavoriteDrinkFormSet(initial=initial)
     670
     671        self.assertHTMLEqual('\n'.join(str(form) for form in formset.forms), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" value="Pony" /></td></tr>""")
     672
     673        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, min_num=1)
     674        formset = LimitedFavoriteDrinkFormSet(initial=initial)
     675
     676        self.assertHTMLEqual('\n'.join(str(form) for form in formset.forms), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" value="Pony" /></td></tr>
     677<tr><th><label for="id_form-1-name">Name:</label></th><td><input type="text" name="form-1-name" id="id_form-1-name" /></td></tr>""")
     678
     679        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=1, min_num=1, max_num=1)
     680        formset = LimitedFavoriteDrinkFormSet(initial=initial)
     681
     682        self.assertHTMLEqual('\n'.join(str(form) for form in formset.forms), """<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" value="Pony" /></td></tr>""")
     683
     684    def test_formset_management_form_contains_min_num(self):
     685        self.maxDiff = None
     686        LimitedFavoriteDrinkFormSet = formset_factory(FavoriteDrinkForm, extra=0, min_num=1)
     687        formset = LimitedFavoriteDrinkFormSet()
     688
     689        self.assertHTMLEqual(str(formset), """<input type="hidden" id="id_form-TOTAL_FORMS" name="form-TOTAL_FORMS" value="1" /><input type="hidden" id="id_form-INITIAL_FORMS" name="form-INITIAL_FORMS" value="0" /><input type="hidden" id="id_form-MAX_NUM_FORMS" name="form-MAX_NUM_FORMS" /><input type="hidden" id="id_form-MIN_NUM_FORMS" name="form-MIN_NUM_FORMS" value="1" />
     690<tr><th><label for="id_form-0-name">Name:</label></th><td><input type="text" name="form-0-name" id="id_form-0-name" /></td></tr>""")
     691
     692    def test_wrong_min_forms(self):
     693        self.assertRaises(ValueError, formset_factory, FavoriteDrinkForm, min_num=2, max_num=1)
     694
    649695    def test_limiting_max_forms(self):
    650696        # Limiting the maximum number of forms ########################################
    651697        # Base case for max_num.
    ChoiceFormSet = formset_factory(Choice)  
    862908class FormsetAsFooTests(TestCase):
    863909    def test_as_table(self):
    864910        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    865         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" />
     911        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" /><input type="hidden" name="choices-MIN_NUM_FORMS" />
    866912<tr><th>Choice:</th><td><input type="text" name="choices-0-choice" value="Calexico" /></td></tr>
    867913<tr><th>Votes:</th><td><input type="text" name="choices-0-votes" value="100" /></td></tr>""")
    868914
    869915    def test_as_p(self):
    870916        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    871         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" />
     917        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" /><input type="hidden" name="choices-MIN_NUM_FORMS" />
    872918<p>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></p>
    873919<p>Votes: <input type="text" name="choices-0-votes" value="100" /></p>""")
    874920
    875921    def test_as_ul(self):
    876922        formset = ChoiceFormSet(data, auto_id=False, prefix='choices')
    877         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" />
     923        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" /><input type="hidden" name="choices-MIN_NUM_FORMS" />
    878924<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
    879925<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
    880926
Back to Top