diff options
| author | Russell Keith-Magee <russell@keith-magee.com> | 2010-12-19 13:41:43 +0000 |
|---|---|---|
| committer | Russell Keith-Magee <russell@keith-magee.com> | 2010-12-19 13:41:43 +0000 |
| commit | 7adffaeaf6dfa22db0b6b2a29632b9150c7ac732 (patch) | |
| tree | 760220d871d4246985cdc71ed2879ac82333c4f4 | |
| parent | 059d9205d40243117730aba4baea1373834e57d2 (diff) | |
Fixed #14655 -- Made formsets iterable. This allows a slightly more natural iteration API (`for form in formsets`), and allows you to easily override the form rendering order. Thanks to Kent Hauser for the suggestion and patch.
git-svn-id: http://code.djangoproject.com/svn/django/trunk@14986 bcc190cf-cafb-0310-a4f2-bffc1f526a37
| -rw-r--r-- | AUTHORS | 1 | ||||
| -rw-r--r-- | django/forms/formsets.py | 17 | ||||
| -rw-r--r-- | docs/topics/forms/formsets.txt | 28 | ||||
| -rw-r--r-- | docs/topics/forms/modelforms.txt | 8 | ||||
| -rw-r--r-- | tests/regressiontests/forms/tests/formsets.py | 36 |
5 files changed, 74 insertions, 16 deletions
@@ -216,6 +216,7 @@ answer newbie questions, and generally made Django that much better: Brant Harris Ronny Haryanto <http://ronny.haryan.to/> Hawkeye + Kent Hauser <kent@khauser.net> Joe Heck <http://www.rhonabwy.com/wp/> Joel Heenan <joelh-django@planetjoel.com> Mikko Hellsing <mikko@sorl.net> diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 6d92236686..9a1b5a8cac 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -49,6 +49,17 @@ class BaseFormSet(StrAndUnicode): def __unicode__(self): return self.as_table() + def __iter__(self): + """Yields the forms in the order they should be rendered""" + return iter(self.forms) + + def __getitem__(self, index): + """Returns the form at the given index, based on the rendering order""" + return list(self)[index] + + def __len__(self): + return len(self.forms) + def _management_form(self): """Returns the ManagementForm instance for this FormSet.""" if self.is_bound: @@ -323,17 +334,17 @@ class BaseFormSet(StrAndUnicode): # XXX: there is no semantic division between forms here, there # probably should be. It might make sense to render each form as a # table row with each field as a td. - forms = u' '.join([form.as_table() for form in self.forms]) + forms = u' '.join([form.as_table() for form in self]) return mark_safe(u'\n'.join([unicode(self.management_form), forms])) def as_p(self): "Returns this formset rendered as HTML <p>s." - forms = u' '.join([form.as_p() for form in self.forms]) + forms = u' '.join([form.as_p() for form in self]) return mark_safe(u'\n'.join([unicode(self.management_form), forms])) def as_ul(self): "Returns this formset rendered as HTML <li>s." - forms = u' '.join([form.as_ul() for form in self.forms]) + forms = u' '.join([form.as_ul() for form in self]) return mark_safe(u'\n'.join([unicode(self.management_form), forms])) def formset_factory(form, formset=BaseFormSet, extra=1, can_order=False, diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index db53fc296a..8280c7736f 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -23,7 +23,7 @@ the ability to iterate over the forms in the formset and display them as you would with a regular form:: >>> formset = ArticleFormSet() - >>> for form in formset.forms: + >>> for form in formset: ... print form.as_table() <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> @@ -35,6 +35,20 @@ display two blank forms:: >>> ArticleFormSet = formset_factory(ArticleForm, extra=2) +.. versionchanged:: 1.3 + +Prior to Django 1.3, formset instances were not iterable. To render +the formset you iterated over the ``forms`` attribute:: + + >>> formset = ArticleFormSet() + >>> for form in formset.forms: + ... print form.as_table() + +Iterating over ``formset.forms`` will render the forms in the order +they were created. The default formset iterator also renders the forms +in this order, but you can change this order by providing an alternate +implementation for the :method:`__iter__()` method. + Using initial data with a formset --------------------------------- @@ -50,7 +64,7 @@ example:: ... 'pub_date': datetime.date.today()}, ... ]) - >>> for form in formset.forms: + >>> for form in formset: ... print form.as_table() <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> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-12" id="id_form-0-pub_date" /></td></tr> @@ -77,7 +91,7 @@ limit the maximum number of empty forms the formset will display:: >>> ArticleFormSet = formset_factory(ArticleForm, extra=2, max_num=1) >>> formset = ArticleFormset() - >>> for form in formset.forms: + >>> for form in formset: ... print form.as_table() <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> @@ -250,7 +264,7 @@ Lets create a formset with the ability to order:: ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)}, ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)}, ... ]) - >>> for form in formset.forms: + >>> for form in formset: ... print form.as_table() <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" value="2008-05-10" id="id_form-0-pub_date" /></td></tr> @@ -306,7 +320,7 @@ Lets create a formset with the ability to delete:: ... {'title': u'Article #1', 'pub_date': datetime.date(2008, 5, 10)}, ... {'title': u'Article #2', 'pub_date': datetime.date(2008, 5, 11)}, ... ]) - >>> for form in formset.forms: + >>> for form in formset: .... print form.as_table() <input type="hidden" name="form-TOTAL_FORMS" value="3" id="id_form-TOTAL_FORMS" /><input type="hidden" name="form-INITIAL_FORMS" value="2" id="id_form-INITIAL_FORMS" /><input type="hidden" name="form-MAX_NUM_FORMS" id="id_form-MAX_NUM_FORMS" /> <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" value="Article #1" id="id_form-0-title" /></td></tr> @@ -360,7 +374,7 @@ default fields/attributes of the order and deletion fields:: >>> ArticleFormSet = formset_factory(ArticleForm, formset=BaseArticleFormSet) >>> formset = ArticleFormSet() - >>> for form in formset.forms: + >>> for form in formset: ... print form.as_table() <tr><th><label for="id_form-0-title">Title:</label></th><td><input type="text" name="form-0-title" id="id_form-0-title" /></td></tr> <tr><th><label for="id_form-0-pub_date">Pub date:</label></th><td><input type="text" name="form-0-pub_date" id="id_form-0-pub_date" /></td></tr> @@ -393,7 +407,7 @@ The ``manage_articles.html`` template might look like this: <form method="post" action=""> {{ formset.management_form }} <table> - {% for form in formset.forms %} + {% for form in formset %} {{ form }} {% endfor %} </table> diff --git a/docs/topics/forms/modelforms.txt b/docs/topics/forms/modelforms.txt index 23ed9a79ae..eb55aaf397 100644 --- a/docs/topics/forms/modelforms.txt +++ b/docs/topics/forms/modelforms.txt @@ -684,7 +684,7 @@ so long as the total number of forms does not exceed ``max_num``:: >>> AuthorFormSet = modelformset_factory(Author, max_num=4, extra=2) >>> formset = AuthorFormSet(queryset=Author.objects.order_by('name')) - >>> for form in formset.forms: + >>> for form in formset: ... print form.as_table() <tr><th><label for="id_form-0-name">Name:</label></th><td><input id="id_form-0-name" type="text" name="form-0-name" value="Charles Baudelaire" maxlength="100" /><input type="hidden" name="form-0-id" value="1" id="id_form-0-id" /></td></tr> <tr><th><label for="id_form-1-name">Name:</label></th><td><input id="id_form-1-name" type="text" name="form-1-name" value="Paul Verlaine" maxlength="100" /><input type="hidden" name="form-1-id" value="3" id="id_form-1-id" /></td></tr> @@ -778,7 +778,7 @@ itself:: <form method="post" action=""> {{ formset.management_form }} - {% for form in formset.forms %} + {% for form in formset %} {{ form }} {% endfor %} </form> @@ -791,7 +791,7 @@ Third, you can manually render each field:: <form method="post" action=""> {{ formset.management_form }} - {% for form in formset.forms %} + {% for form in formset %} {% for field in form %} {{ field.label_tag }}: {{ field }} {% endfor %} @@ -804,7 +804,7 @@ if you were rendering the ``name`` and ``age`` fields of a model:: <form method="post" action=""> {{ formset.management_form }} - {% for form in formset.forms %} + {% for form in formset %} {{ form.id }} <ul> <li>{{ form.name }}</li> diff --git a/tests/regressiontests/forms/tests/formsets.py b/tests/regressiontests/forms/tests/formsets.py index ed2581f6ac..4050231844 100644 --- a/tests/regressiontests/forms/tests/formsets.py +++ b/tests/regressiontests/forms/tests/formsets.py @@ -767,6 +767,38 @@ class FormsFormsetTestCase(TestCase): self.assertFalse(formset.is_valid()) self.assertEqual(formset.non_form_errors(), [u'You may only specify a drink once.']) + def test_formset_iteration(self): + # Regression tests for #16455 -- formset instances are iterable + ChoiceFormset = formset_factory(Choice, extra=3) + formset = ChoiceFormset() + + # confirm iterated formset yields formset.forms + forms = list(formset) + self.assertEqual(forms, formset.forms) + self.assertEqual(len(formset), len(forms)) + + # confirm indexing of formset + self.assertEqual(formset[0], forms[0]) + try: + formset[3] + self.fail('Requesting an invalid formset index should raise an exception') + except IndexError: + pass + + # Formets can override the default iteration order + class BaseReverseFormSet(BaseFormSet): + def __iter__(self): + for form in reversed(self.forms): + yield form + + ReverseChoiceFormset = formset_factory(Choice, BaseReverseFormSet, extra=3) + reverse_formset = ReverseChoiceFormset() + + # confirm that __iter__ modifies rendering order + # compare forms from "reverse" formset with forms from original formset + self.assertEqual(str(reverse_formset[0]), str(forms[-1])) + self.assertEqual(str(reverse_formset[1]), str(forms[-2])) + self.assertEqual(len(reverse_formset), len(forms)) data = { 'choices-TOTAL_FORMS': '1', # the number of forms rendered @@ -802,7 +834,7 @@ class FormsetAsFooTests(TestCase): <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""") -# Regression test for #11418 ################################################# +# Regression test for #11418 ################################################# class ArticleForm(Form): title = CharField() pub_date = DateField() @@ -835,7 +867,7 @@ class TestIsBoundBehavior(TestCase): 'form-0-title': u'Test', 'form-0-pub_date': u'1904-06-16', 'form-1-title': u'Test', - 'form-1-pub_date': u'', # <-- this date is missing but required + 'form-1-pub_date': u'', # <-- this date is missing but required } formset = ArticleFormSet(data) self.assertFalse(formset.is_valid()) |
