Fixed #11418 -- formset.cleaned_data no longer raises AttributeError when is_valid is True. Thanks mlavin!
This also introduces a slightly backwards-incompatible change in FormSet's behavior, see the release docs for details. git-svn-id: http://code.djangoproject.com/svn/django/trunk@14667 bcc190cf-cafb-0310-a4f2-bffc1f526a37
This commit is contained in:
parent
752bd8bf75
commit
65b380e74a
|
@ -37,8 +37,8 @@ class BaseFormSet(StrAndUnicode):
|
||||||
self.is_bound = data is not None or files is not None
|
self.is_bound = data is not None or files is not None
|
||||||
self.prefix = prefix or self.get_default_prefix()
|
self.prefix = prefix or self.get_default_prefix()
|
||||||
self.auto_id = auto_id
|
self.auto_id = auto_id
|
||||||
self.data = data
|
self.data = data or {}
|
||||||
self.files = files
|
self.files = files or {}
|
||||||
self.initial = initial
|
self.initial = initial
|
||||||
self.error_class = error_class
|
self.error_class = error_class
|
||||||
self._errors = None
|
self._errors = None
|
||||||
|
@ -51,7 +51,7 @@ class BaseFormSet(StrAndUnicode):
|
||||||
|
|
||||||
def _management_form(self):
|
def _management_form(self):
|
||||||
"""Returns the ManagementForm instance for this FormSet."""
|
"""Returns the ManagementForm instance for this FormSet."""
|
||||||
if self.data or self.files:
|
if self.is_bound:
|
||||||
form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
|
form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
|
||||||
if not form.is_valid():
|
if not form.is_valid():
|
||||||
raise ValidationError('ManagementForm data is missing or has been tampered with')
|
raise ValidationError('ManagementForm data is missing or has been tampered with')
|
||||||
|
@ -66,7 +66,7 @@ class BaseFormSet(StrAndUnicode):
|
||||||
|
|
||||||
def total_form_count(self):
|
def total_form_count(self):
|
||||||
"""Returns the total number of forms in this FormSet."""
|
"""Returns the total number of forms in this FormSet."""
|
||||||
if self.data or self.files:
|
if self.is_bound:
|
||||||
return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
|
return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
|
||||||
else:
|
else:
|
||||||
initial_forms = self.initial_form_count()
|
initial_forms = self.initial_form_count()
|
||||||
|
@ -81,7 +81,7 @@ class BaseFormSet(StrAndUnicode):
|
||||||
|
|
||||||
def initial_form_count(self):
|
def initial_form_count(self):
|
||||||
"""Returns the number of forms that are required in this FormSet."""
|
"""Returns the number of forms that are required in this FormSet."""
|
||||||
if self.data or self.files:
|
if self.is_bound:
|
||||||
return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
|
return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
|
||||||
else:
|
else:
|
||||||
# Use the length of the inital data if it's there, 0 otherwise.
|
# Use the length of the inital data if it's there, 0 otherwise.
|
||||||
|
@ -101,7 +101,7 @@ class BaseFormSet(StrAndUnicode):
|
||||||
Instantiates and returns the i-th form instance in a formset.
|
Instantiates and returns the i-th form instance in a formset.
|
||||||
"""
|
"""
|
||||||
defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
|
defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
|
||||||
if self.data or self.files:
|
if self.is_bound:
|
||||||
defaults['data'] = self.data
|
defaults['data'] = self.data
|
||||||
defaults['files'] = self.files
|
defaults['files'] = self.files
|
||||||
if self.initial:
|
if self.initial:
|
||||||
|
@ -133,7 +133,7 @@ class BaseFormSet(StrAndUnicode):
|
||||||
'prefix': self.add_prefix('__prefix__'),
|
'prefix': self.add_prefix('__prefix__'),
|
||||||
'empty_permitted': True,
|
'empty_permitted': True,
|
||||||
}
|
}
|
||||||
if self.data or self.files:
|
if self.is_bound:
|
||||||
defaults['data'] = self.data
|
defaults['data'] = self.data
|
||||||
defaults['files'] = self.files
|
defaults['files'] = self.files
|
||||||
defaults.update(kwargs)
|
defaults.update(kwargs)
|
||||||
|
|
|
@ -266,6 +266,36 @@ local flavors:
|
||||||
has been removed from the province list in favor of the new
|
has been removed from the province list in favor of the new
|
||||||
official designation "Aceh (ACE)".
|
official designation "Aceh (ACE)".
|
||||||
|
|
||||||
|
FormSet updates
|
||||||
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
In Django 1.3 ``FormSet`` creation behavior is modified slightly. Historically
|
||||||
|
the class didn't make a distinction between not being passed data and being
|
||||||
|
passed empty dictionary. This was inconsistent with behavior in other parts of
|
||||||
|
the framework. Starting with 1.3 if you pass in empty dictionary the
|
||||||
|
``FormSet`` will raise a ``ValidationError``.
|
||||||
|
|
||||||
|
For example with a ``FormSet``::
|
||||||
|
|
||||||
|
>>> class ArticleForm(Form):
|
||||||
|
... title = CharField()
|
||||||
|
... pub_date = DateField()
|
||||||
|
>>> ArticleFormSet = formset_factory(ArticleForm)
|
||||||
|
|
||||||
|
the following code will raise a ``ValidationError``::
|
||||||
|
|
||||||
|
>>> ArticleFormSet({})
|
||||||
|
Traceback (most recent call last):
|
||||||
|
...
|
||||||
|
ValidationError: [u'ManagementForm data is missing or has been tampered with']
|
||||||
|
|
||||||
|
if you need to instantiate an empty ``FormSet``, don't pass in the data or use
|
||||||
|
``None``::
|
||||||
|
|
||||||
|
>>> formset = ArticleFormSet()
|
||||||
|
>>> formset = ArticleFormSet(data=None)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
.. _deprecated-features-1.3:
|
.. _deprecated-features-1.3:
|
||||||
|
|
||||||
|
|
|
@ -100,7 +100,12 @@ an ``is_valid`` method on the formset to provide a convenient way to validate
|
||||||
all forms in the formset::
|
all forms in the formset::
|
||||||
|
|
||||||
>>> ArticleFormSet = formset_factory(ArticleForm)
|
>>> ArticleFormSet = formset_factory(ArticleForm)
|
||||||
>>> formset = ArticleFormSet({})
|
>>> data = {
|
||||||
|
... 'form-TOTAL_FORMS': u'1',
|
||||||
|
... 'form-INITIAL_FORMS': u'0',
|
||||||
|
... 'form-MAX_NUM_FORMS': u'',
|
||||||
|
... }
|
||||||
|
>>> formset = ArticleFormSet(data)
|
||||||
>>> formset.is_valid()
|
>>> formset.is_valid()
|
||||||
True
|
True
|
||||||
|
|
||||||
|
@ -113,7 +118,7 @@ provide an invalid article::
|
||||||
... 'form-INITIAL_FORMS': u'0',
|
... 'form-INITIAL_FORMS': u'0',
|
||||||
... 'form-MAX_NUM_FORMS': u'',
|
... 'form-MAX_NUM_FORMS': u'',
|
||||||
... 'form-0-title': u'Test',
|
... 'form-0-title': u'Test',
|
||||||
... 'form-0-pub_date': u'16 June 1904',
|
... 'form-0-pub_date': u'1904-06-16',
|
||||||
... 'form-1-title': u'Test',
|
... '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
|
||||||
... }
|
... }
|
||||||
|
@ -208,9 +213,9 @@ is where you define your own validation that works at the formset level::
|
||||||
... 'form-INITIAL_FORMS': u'0',
|
... 'form-INITIAL_FORMS': u'0',
|
||||||
... 'form-MAX_NUM_FORMS': u'',
|
... 'form-MAX_NUM_FORMS': u'',
|
||||||
... 'form-0-title': u'Test',
|
... 'form-0-title': u'Test',
|
||||||
... 'form-0-pub_date': u'16 June 1904',
|
... 'form-0-pub_date': u'1904-06-16',
|
||||||
... 'form-1-title': u'Test',
|
... 'form-1-title': u'Test',
|
||||||
... 'form-1-pub_date': u'23 June 1912',
|
... 'form-1-pub_date': u'1912-06-23',
|
||||||
... }
|
... }
|
||||||
>>> formset = ArticleFormSet(data)
|
>>> formset = ArticleFormSet(data)
|
||||||
>>> formset.is_valid()
|
>>> formset.is_valid()
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
# -*- coding: utf-8 -*-
|
# -*- coding: utf-8 -*-
|
||||||
from django.forms import Form, CharField, IntegerField, ValidationError
|
from django.forms import Form, CharField, IntegerField, ValidationError, DateField
|
||||||
from django.forms.formsets import formset_factory, BaseFormSet
|
from django.forms.formsets import formset_factory, BaseFormSet
|
||||||
from django.utils.unittest import TestCase
|
from django.utils.unittest import TestCase
|
||||||
|
|
||||||
|
@ -741,7 +741,12 @@ class FormsFormsetTestCase(TestCase):
|
||||||
formset = FavoriteDrinksFormSet()
|
formset = FavoriteDrinksFormSet()
|
||||||
self.assertEqual(formset.management_form.prefix, 'form')
|
self.assertEqual(formset.management_form.prefix, 'form')
|
||||||
|
|
||||||
formset = FavoriteDrinksFormSet(data={})
|
data = {
|
||||||
|
'form-TOTAL_FORMS': '2',
|
||||||
|
'form-INITIAL_FORMS': '0',
|
||||||
|
'form-MAX_NUM_FORMS': '0',
|
||||||
|
}
|
||||||
|
formset = FavoriteDrinksFormSet(data=data)
|
||||||
self.assertEqual(formset.management_form.prefix, 'form')
|
self.assertEqual(formset.management_form.prefix, 'form')
|
||||||
|
|
||||||
formset = FavoriteDrinksFormSet(initial={})
|
formset = FavoriteDrinksFormSet(initial={})
|
||||||
|
@ -795,3 +800,43 @@ class FormsetAsFooTests(TestCase):
|
||||||
self.assertEqual(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.assertEqual(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" />
|
||||||
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
<li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
|
||||||
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
|
<li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
|
||||||
|
|
||||||
|
|
||||||
|
# Regression test for #11418 #################################################
|
||||||
|
class ArticleForm(Form):
|
||||||
|
title = CharField()
|
||||||
|
pub_date = DateField()
|
||||||
|
|
||||||
|
ArticleFormSet = formset_factory(ArticleForm)
|
||||||
|
|
||||||
|
class TestIsBoundBehavior(TestCase):
|
||||||
|
def test_no_data_raises_validation_error(self):
|
||||||
|
self.assertRaises(ValidationError, ArticleFormSet, {})
|
||||||
|
|
||||||
|
def test_with_management_data_attrs_work_fine(self):
|
||||||
|
data = {
|
||||||
|
'form-TOTAL_FORMS': u'1',
|
||||||
|
'form-INITIAL_FORMS': u'0',
|
||||||
|
}
|
||||||
|
formset = ArticleFormSet(data)
|
||||||
|
self.assertEquals(0, formset.initial_form_count())
|
||||||
|
self.assertEquals(1, formset.total_form_count())
|
||||||
|
self.assertTrue(formset.is_bound)
|
||||||
|
self.assertTrue(formset.forms[0].is_bound)
|
||||||
|
self.assertTrue(formset.is_valid())
|
||||||
|
self.assertTrue(formset.forms[0].is_valid())
|
||||||
|
self.assertEquals([{}], formset.cleaned_data)
|
||||||
|
|
||||||
|
|
||||||
|
def test_form_errors_are_cought_by_formset(self):
|
||||||
|
data = {
|
||||||
|
'form-TOTAL_FORMS': u'2',
|
||||||
|
'form-INITIAL_FORMS': u'0',
|
||||||
|
'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
|
||||||
|
}
|
||||||
|
formset = ArticleFormSet(data)
|
||||||
|
self.assertFalse(formset.is_valid())
|
||||||
|
self.assertEquals([{}, {'pub_date': [u'This field is required.']}], formset.errors)
|
||||||
|
|
Loading…
Reference in New Issue