Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Fixed #14655 -- Made formsets iterable. This allows a slightly more n…

…atural 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
  • Loading branch information...
commit 7adffaeaf6dfa22db0b6b2a29632b9150c7ac732 1 parent 059d920
@freakboy3742 freakboy3742 authored
View
1  AUTHORS
@@ -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>
View
17 django/forms/formsets.py
@@ -49,6 +49,17 @@ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
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 @@ def as_table(self):
# 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,
View
28 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>
View
8 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>
View
36 tests/regressiontests/forms/tests/formsets.py
@@ -767,6 +767,38 @@ def test_regression_12878(self):
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 @@ def test_as_ul(self):
<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 @@ def test_form_errors_are_cought_by_formset(self):
'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())
Please sign in to comment.
Something went wrong with that request. Please try again.