Code

Ticket #18906: 18906-1.diff

File 18906-1.diff, 5.4 KB (added by claudep, 23 months ago)

Do not validate deleted forms

Line 
1diff --git a/django/forms/formsets.py b/django/forms/formsets.py
2index 42d25fa..0574daa 100644
3--- a/django/forms/formsets.py
4+++ b/django/forms/formsets.py
5@@ -175,11 +175,10 @@ class BaseFormSet(object):
6     @property
7     def deleted_forms(self):
8         """
9-        Returns a list of forms that have been marked for deletion. Raises an
10-        AttributeError if deletion is not allowed.
11+        Returns a list of forms that have been marked for deletion.
12         """
13         if not self.is_valid() or not self.can_delete:
14-            raise AttributeError("'%s' object has no attribute 'deleted_forms'" % self.__class__.__name__)
15+            return []
16         # construct _deleted_form_indexes which is just a list of form indexes
17         # that have had their deletion widget set to True
18         if not hasattr(self, '_deleted_form_indexes'):
19diff --git a/django/forms/models.py b/django/forms/models.py
20index 1aa49ea..e0822ed 100644
21--- a/django/forms/models.py
22+++ b/django/forms/models.py
23@@ -500,9 +500,9 @@ class BaseModelFormSet(BaseFormSet):
24         # Collect unique_checks and date_checks to run from all the forms.
25         all_unique_checks = set()
26         all_date_checks = set()
27-        for form in self.forms:
28-            if not form.is_valid():
29-                continue
30+        forms_to_delete = self.deleted_forms
31+        valid_forms = [form for form in self.forms if form.is_valid() and form not in forms_to_delete]
32+        for form in valid_forms:
33             exclude = form._get_validation_exclusions()
34             unique_checks, date_checks = form.instance._get_unique_checks(exclude=exclude)
35             all_unique_checks = all_unique_checks.union(set(unique_checks))
36@@ -512,9 +512,7 @@ class BaseModelFormSet(BaseFormSet):
37         # Do each of the unique checks (unique and unique_together)
38         for uclass, unique_check in all_unique_checks:
39             seen_data = set()
40-            for form in self.forms:
41-                if not form.is_valid():
42-                    continue
43+            for form in valid_forms:
44                 # get data for each field of each of unique_check
45                 row_data = tuple([form.cleaned_data[field] for field in unique_check if field in form.cleaned_data])
46                 if row_data and not None in row_data:
47@@ -534,9 +532,7 @@ class BaseModelFormSet(BaseFormSet):
48         for date_check in all_date_checks:
49             seen_data = set()
50             uclass, lookup, field, unique_for = date_check
51-            for form in self.forms:
52-                if not form.is_valid():
53-                    continue
54+            for form in valid_forms:
55                 # see if we have data for both fields
56                 if (form.cleaned_data and form.cleaned_data[field] is not None
57                     and form.cleaned_data[unique_for] is not None):
58@@ -591,10 +587,7 @@ class BaseModelFormSet(BaseFormSet):
59             return []
60 
61         saved_instances = []
62-        try:
63-            forms_to_delete = self.deleted_forms
64-        except AttributeError:
65-            forms_to_delete = []
66+        forms_to_delete = self.deleted_forms
67         for form in self.initial_forms:
68             pk_name = self._pk_field.name
69             raw_pk_value = form._raw_value(pk_name)
70diff --git a/tests/modeltests/model_formsets/tests.py b/tests/modeltests/model_formsets/tests.py
71index e28560b..f3a8618 100644
72--- a/tests/modeltests/model_formsets/tests.py
73+++ b/tests/modeltests/model_formsets/tests.py
74@@ -42,21 +42,29 @@ class DeletionTests(TestCase):
75         doesn't cause validation errors.
76         """
77         PoetFormSet = modelformset_factory(Poet, can_delete=True)
78+        poet = Poet.objects.create(name='test')
79+        # One existing untouched and two new unvalid forms
80         data = {
81-            'form-TOTAL_FORMS': '1',
82-            'form-INITIAL_FORMS': '0',
83+            'form-TOTAL_FORMS': '3',
84+            'form-INITIAL_FORMS': '1',
85             'form-MAX_NUM_FORMS': '0',
86-            'form-0-id': '',
87-            'form-0-name': 'x' * 1000,
88+            'form-0-id': six.text_type(poet.id),
89+            'form-0-name': 'test',
90+            'form-1-id': '',
91+            'form-1-name': 'x' * 1000, # Too long
92+            'form-1-id': six.text_type(poet.id), # Violate unique constraint
93+            'form-1-name': 'test2',
94         }
95         formset = PoetFormSet(data, queryset=Poet.objects.all())
96         # Make sure this form doesn't pass validation.
97         self.assertEqual(formset.is_valid(), False)
98-        self.assertEqual(Poet.objects.count(), 0)
99+        self.assertEqual(Poet.objects.count(), 1)
100 
101         # Then make sure that it *does* pass validation and delete the object,
102-        # even though the data isn't actually valid.
103+        # even though the data in new forms aren't actually valid.
104         data['form-0-DELETE'] = 'on'
105+        data['form-1-DELETE'] = 'on'
106+        data['form-2-DELETE'] = 'on'
107         formset = PoetFormSet(data, queryset=Poet.objects.all())
108         self.assertEqual(formset.is_valid(), True)
109         formset.save()
110@@ -64,7 +72,7 @@ class DeletionTests(TestCase):
111 
112     def test_change_form_deletion_when_invalid(self):
113         """
114-        Make sure that an add form that is filled out, but marked for deletion
115+        Make sure that a change form that is filled out, but marked for deletion
116         doesn't cause validation errors.
117         """
118         PoetFormSet = modelformset_factory(Poet, can_delete=True)