Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

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
  • Loading branch information...
commit 65b380e74ab79ee011f7556e342dd4d656b018a5 1 parent 752bd8b
Honza Král authored November 21, 2010
14  django/forms/formsets.py
@@ -37,8 +37,8 @@ def __init__(self, data=None, files=None, auto_id='id_%s', prefix=None,
37 37
         self.is_bound = data is not None or files is not None
38 38
         self.prefix = prefix or self.get_default_prefix()
39 39
         self.auto_id = auto_id
40  
-        self.data = data
41  
-        self.files = files
  40
+        self.data = data or {}
  41
+        self.files = files or {}
42 42
         self.initial = initial
43 43
         self.error_class = error_class
44 44
         self._errors = None
@@ -51,7 +51,7 @@ def __unicode__(self):
51 51
 
52 52
     def _management_form(self):
53 53
         """Returns the ManagementForm instance for this FormSet."""
54  
-        if self.data or self.files:
  54
+        if self.is_bound:
55 55
             form = ManagementForm(self.data, auto_id=self.auto_id, prefix=self.prefix)
56 56
             if not form.is_valid():
57 57
                 raise ValidationError('ManagementForm data is missing or has been tampered with')
@@ -66,7 +66,7 @@ def _management_form(self):
66 66
 
67 67
     def total_form_count(self):
68 68
         """Returns the total number of forms in this FormSet."""
69  
-        if self.data or self.files:
  69
+        if self.is_bound:
70 70
             return self.management_form.cleaned_data[TOTAL_FORM_COUNT]
71 71
         else:
72 72
             initial_forms = self.initial_form_count()
@@ -81,7 +81,7 @@ def total_form_count(self):
81 81
 
82 82
     def initial_form_count(self):
83 83
         """Returns the number of forms that are required in this FormSet."""
84  
-        if self.data or self.files:
  84
+        if self.is_bound:
85 85
             return self.management_form.cleaned_data[INITIAL_FORM_COUNT]
86 86
         else:
87 87
             # Use the length of the inital data if it's there, 0 otherwise.
@@ -101,7 +101,7 @@ def _construct_form(self, i, **kwargs):
101 101
         Instantiates and returns the i-th form instance in a formset.
102 102
         """
103 103
         defaults = {'auto_id': self.auto_id, 'prefix': self.add_prefix(i)}
104  
-        if self.data or self.files:
  104
+        if self.is_bound:
105 105
             defaults['data'] = self.data
106 106
             defaults['files'] = self.files
107 107
         if self.initial:
@@ -133,7 +133,7 @@ def _get_empty_form(self, **kwargs):
133 133
             'prefix': self.add_prefix('__prefix__'),
134 134
             'empty_permitted': True,
135 135
         }
136  
-        if self.data or self.files:
  136
+        if self.is_bound:
137 137
             defaults['data'] = self.data
138 138
             defaults['files'] = self.files
139 139
         defaults.update(kwargs)
30  docs/releases/1.3.txt
@@ -266,6 +266,36 @@ local flavors:
266 266
       has been removed from the province list in favor of the new
267 267
       official designation "Aceh (ACE)".
268 268
 
  269
+FormSet updates
  270
+~~~~~~~~~~~~~~~
  271
+
  272
+In Django 1.3 ``FormSet`` creation behavior is modified slightly. Historically
  273
+the class didn't make a distinction between not being passed data and being
  274
+passed empty dictionary. This was inconsistent with behavior in other parts of
  275
+the framework. Starting with 1.3 if you pass in empty dictionary the
  276
+``FormSet`` will raise a ``ValidationError``.
  277
+
  278
+For example with a ``FormSet``::
  279
+
  280
+    >>> class ArticleForm(Form):
  281
+    ...     title = CharField()
  282
+    ...     pub_date = DateField()
  283
+    >>> ArticleFormSet = formset_factory(ArticleForm)
  284
+
  285
+the following code will raise a ``ValidationError``::
  286
+
  287
+    >>> ArticleFormSet({})
  288
+    Traceback (most recent call last):
  289
+    ...
  290
+    ValidationError: [u'ManagementForm data is missing or has been tampered with']
  291
+
  292
+if you need to instantiate an empty ``FormSet``, don't pass in the data or use
  293
+``None``::
  294
+
  295
+    >>> formset = ArticleFormSet()
  296
+    >>> formset = ArticleFormSet(data=None)
  297
+
  298
+
269 299
 
270 300
 .. _deprecated-features-1.3:
271 301
 
13  docs/topics/forms/formsets.txt
@@ -100,7 +100,12 @@ an ``is_valid`` method on the formset to provide a convenient way to validate
100 100
 all forms in the formset::
101 101
 
102 102
     >>> ArticleFormSet = formset_factory(ArticleForm)
103  
-    >>> formset = ArticleFormSet({})
  103
+    >>> data = {
  104
+    ...     'form-TOTAL_FORMS': u'1',
  105
+    ...     'form-INITIAL_FORMS': u'0',
  106
+    ...     'form-MAX_NUM_FORMS': u'',
  107
+    ... }
  108
+    >>> formset = ArticleFormSet(data)
104 109
     >>> formset.is_valid()
105 110
     True
106 111
 
@@ -113,7 +118,7 @@ provide an invalid article::
113 118
     ...     'form-INITIAL_FORMS': u'0',
114 119
     ...     'form-MAX_NUM_FORMS': u'',
115 120
     ...     'form-0-title': u'Test',
116  
-    ...     'form-0-pub_date': u'16 June 1904',
  121
+    ...     'form-0-pub_date': u'1904-06-16',
117 122
     ...     'form-1-title': u'Test',
118 123
     ...     'form-1-pub_date': u'', # <-- this date is missing but required
119 124
     ... }
@@ -208,9 +213,9 @@ is where you define your own validation that works at the formset level::
208 213
     ...     'form-INITIAL_FORMS': u'0',
209 214
     ...     'form-MAX_NUM_FORMS': u'',
210 215
     ...     'form-0-title': u'Test',
211  
-    ...     'form-0-pub_date': u'16 June 1904',
  216
+    ...     'form-0-pub_date': u'1904-06-16',
212 217
     ...     'form-1-title': u'Test',
213  
-    ...     'form-1-pub_date': u'23 June 1912',
  218
+    ...     'form-1-pub_date': u'1912-06-23',
214 219
     ... }
215 220
     >>> formset = ArticleFormSet(data)
216 221
     >>> formset.is_valid()
49  tests/regressiontests/forms/tests/formsets.py
... ...
@@ -1,5 +1,5 @@
1 1
 # -*- coding: utf-8 -*-
2  
-from django.forms import Form, CharField, IntegerField, ValidationError
  2
+from django.forms import Form, CharField, IntegerField, ValidationError, DateField
3 3
 from django.forms.formsets import formset_factory, BaseFormSet
4 4
 from django.utils.unittest import TestCase
5 5
 
@@ -741,7 +741,12 @@ def test_regression_6926(self):
741 741
         formset = FavoriteDrinksFormSet()
742 742
         self.assertEqual(formset.management_form.prefix, 'form')
743 743
 
744  
-        formset = FavoriteDrinksFormSet(data={})
  744
+        data = {
  745
+            'form-TOTAL_FORMS': '2',
  746
+            'form-INITIAL_FORMS': '0',
  747
+            'form-MAX_NUM_FORMS': '0',
  748
+        }
  749
+        formset = FavoriteDrinksFormSet(data=data)
745 750
         self.assertEqual(formset.management_form.prefix, 'form')
746 751
 
747 752
         formset = FavoriteDrinksFormSet(initial={})
@@ -795,3 +800,43 @@ def test_as_ul(self):
795 800
         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" />
796 801
 <li>Choice: <input type="text" name="choices-0-choice" value="Calexico" /></li>
797 802
 <li>Votes: <input type="text" name="choices-0-votes" value="100" /></li>""")
  803
+
  804
+
  805
+# Regression test for #11418 ################################################# 
  806
+class ArticleForm(Form):
  807
+    title = CharField()
  808
+    pub_date = DateField()
  809
+
  810
+ArticleFormSet = formset_factory(ArticleForm)
  811
+
  812
+class TestIsBoundBehavior(TestCase):
  813
+    def test_no_data_raises_validation_error(self):
  814
+        self.assertRaises(ValidationError, ArticleFormSet, {})
  815
+
  816
+    def test_with_management_data_attrs_work_fine(self):
  817
+        data = {
  818
+            'form-TOTAL_FORMS': u'1',
  819
+            'form-INITIAL_FORMS': u'0',
  820
+        }
  821
+        formset = ArticleFormSet(data)
  822
+        self.assertEquals(0, formset.initial_form_count())
  823
+        self.assertEquals(1, formset.total_form_count())
  824
+        self.assertTrue(formset.is_bound)
  825
+        self.assertTrue(formset.forms[0].is_bound)
  826
+        self.assertTrue(formset.is_valid())
  827
+        self.assertTrue(formset.forms[0].is_valid())
  828
+        self.assertEquals([{}], formset.cleaned_data)
  829
+
  830
+
  831
+    def test_form_errors_are_cought_by_formset(self):
  832
+        data = {
  833
+            'form-TOTAL_FORMS': u'2',
  834
+            'form-INITIAL_FORMS': u'0',
  835
+            'form-0-title': u'Test',
  836
+            'form-0-pub_date': u'1904-06-16',
  837
+            'form-1-title': u'Test',
  838
+            'form-1-pub_date': u'', # <-- this date is missing but required 
  839
+        }
  840
+        formset = ArticleFormSet(data)
  841
+        self.assertFalse(formset.is_valid())
  842
+        self.assertEquals([{}, {'pub_date': [u'This field is required.']}], formset.errors)

0 notes on commit 65b380e

Please sign in to comment.
Something went wrong with that request. Please try again.