Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Forms in model formsets and inline formsets can now be deleted even i…

…f they don't validate. Related to #9587.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10283 bcc190cf-cafb-0310-a4f2-bffc1f526a37
  • Loading branch information...
commit 15becf23a9e4c9b230745738d2d42f6ab8f0f031 1 parent 98f5f68
Joseph Kocherhans authored March 31, 2009
9  django/forms/forms.py
@@ -205,6 +205,15 @@ def non_field_errors(self):
205 205
         """
206 206
         return self.errors.get(NON_FIELD_ERRORS, self.error_class())
207 207
 
  208
+    def _raw_value(self, fieldname):
  209
+        """
  210
+        Returns the raw_value for a particular field name. This is just a
  211
+        convenient wrapper around widget.value_from_datadict.
  212
+        """
  213
+        field = self.fields[fieldname]
  214
+        prefix = self.add_prefix(fieldname)
  215
+        return field.widget.value_from_datadict(self.data, self.files, prefix)
  216
+
208 217
     def full_clean(self):
209 218
         """
210 219
         Cleans all of self.data and populates self._errors and
5  django/forms/formsets.py
@@ -228,9 +228,8 @@ def is_valid(self):
228 228
                 # more code than we'd like, but the form's cleaned_data will
229 229
                 # not exist if the form is invalid.
230 230
                 field = form.fields[DELETION_FIELD_NAME]
231  
-                prefix = form.add_prefix(DELETION_FIELD_NAME)
232  
-                value = field.widget.value_from_datadict(self.data, self.files, prefix)
233  
-                should_delete = field.clean(value)
  231
+                raw_value = form._raw_value(DELETION_FIELD_NAME)
  232
+                should_delete = field.clean(raw_value)
234 233
                 if should_delete:
235 234
                     # This form is going to be deleted so any of its errors
236 235
                     # should not cause the entire formset to be invalid.
33  django/forms/models.py
@@ -425,16 +425,22 @@ def save_existing_objects(self, commit=True):
425 425
             existing_objects[obj.pk] = obj
426 426
         saved_instances = []
427 427
         for form in self.initial_forms:
428  
-            obj = existing_objects[form.cleaned_data[self._pk_field.name].pk]
429  
-            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
430  
-                self.deleted_objects.append(obj)
431  
-                obj.delete()
432  
-            else:
433  
-                if form.changed_data:
434  
-                    self.changed_objects.append((obj, form.changed_data))
435  
-                    saved_instances.append(self.save_existing(form, obj, commit=commit))
436  
-                    if not commit:
437  
-                        self.saved_forms.append(form)
  428
+            pk_name = self._pk_field.name
  429
+            raw_pk_value = form._raw_value(pk_name)
  430
+            pk_value = form.fields[pk_name].clean(raw_pk_value).pk
  431
+            obj = existing_objects[pk_value]
  432
+            if self.can_delete:
  433
+                raw_delete_value = form._raw_value(DELETION_FIELD_NAME)
  434
+                should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value)
  435
+                if should_delete:
  436
+                    self.deleted_objects.append(obj)
  437
+                    obj.delete()
  438
+                    continue
  439
+            if form.changed_data:
  440
+                self.changed_objects.append((obj, form.changed_data))
  441
+                saved_instances.append(self.save_existing(form, obj, commit=commit))
  442
+                if not commit:
  443
+                    self.saved_forms.append(form)
438 444
         return saved_instances
439 445
 
440 446
     def save_new_objects(self, commit=True):
@@ -444,8 +450,11 @@ def save_new_objects(self, commit=True):
444 450
                 continue
445 451
             # If someone has marked an add form for deletion, don't save the
446 452
             # object.
447  
-            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
448  
-                continue
  453
+            if self.can_delete:
  454
+                raw_delete_value = form._raw_value(DELETION_FIELD_NAME)
  455
+                should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value)
  456
+                if should_delete:
  457
+                    continue
449 458
             self.new_objects.append(self.save_new(form, commit=commit))
450 459
             if not commit:
451 460
                 self.saved_forms.append(form)
70  tests/modeltests/model_formsets/tests.py
... ...
@@ -0,0 +1,70 @@
  1
+from django.test import TestCase
  2
+from django.forms.models import modelformset_factory
  3
+from modeltests.model_formsets.models import Poet, Poem
  4
+
  5
+class DeletionTests(TestCase):
  6
+    def test_deletion(self):
  7
+        PoetFormSet = modelformset_factory(Poet, can_delete=True)
  8
+        poet = Poet.objects.create(name='test')
  9
+        data = {
  10
+            'form-TOTAL_FORMS': u'1',
  11
+            'form-INITIAL_FORMS': u'1',
  12
+            'form-0-id': u'1',
  13
+            'form-0-name': u'test',
  14
+            'form-0-DELETE': u'on',
  15
+        }
  16
+        formset = PoetFormSet(data, queryset=Poet.objects.all())
  17
+        formset.save()
  18
+        self.assertTrue(formset.is_valid())
  19
+        self.assertEqual(Poet.objects.count(), 0)
  20
+
  21
+    def test_add_form_deletion_when_invalid(self):
  22
+        """
  23
+        Make sure that an add form that is filled out, but marked for deletion
  24
+        doesn't cause validation errors.
  25
+        """
  26
+        PoetFormSet = modelformset_factory(Poet, can_delete=True)
  27
+        data = {
  28
+            'form-TOTAL_FORMS': u'1',
  29
+            'form-INITIAL_FORMS': u'0',
  30
+            'form-0-id': u'',
  31
+            'form-0-name': u'x' * 1000,
  32
+        }
  33
+        formset = PoetFormSet(data, queryset=Poet.objects.all())
  34
+        # Make sure this form doesn't pass validation.
  35
+        self.assertEqual(formset.is_valid(), False)
  36
+        self.assertEqual(Poet.objects.count(), 0)
  37
+
  38
+        # Then make sure that it *does* pass validation and delete the object,
  39
+        # even though the data isn't actually valid.
  40
+        data['form-0-DELETE'] = 'on'
  41
+        formset = PoetFormSet(data, queryset=Poet.objects.all())
  42
+        self.assertEqual(formset.is_valid(), True)
  43
+        formset.save()
  44
+        self.assertEqual(Poet.objects.count(), 0)
  45
+
  46
+    def test_change_form_deletion_when_invalid(self):
  47
+        """
  48
+        Make sure that an add form that is filled out, but marked for deletion
  49
+        doesn't cause validation errors.
  50
+        """
  51
+        PoetFormSet = modelformset_factory(Poet, can_delete=True)
  52
+        poet = Poet.objects.create(name='test')
  53
+        data = {
  54
+            'form-TOTAL_FORMS': u'1',
  55
+            'form-INITIAL_FORMS': u'1',
  56
+            'form-0-id': u'1',
  57
+            'form-0-name': u'x' * 1000,
  58
+        }
  59
+        formset = PoetFormSet(data, queryset=Poet.objects.all())
  60
+        # Make sure this form doesn't pass validation.
  61
+        self.assertEqual(formset.is_valid(), False)
  62
+        self.assertEqual(Poet.objects.count(), 1)
  63
+
  64
+        # Then make sure that it *does* pass validation and delete the object,
  65
+        # even though the data isn't actually valid.
  66
+        data['form-0-DELETE'] = 'on'
  67
+        formset = PoetFormSet(data, queryset=Poet.objects.all())
  68
+        self.assertEqual(formset.is_valid(), True)
  69
+        formset.save()
  70
+        self.assertEqual(Poet.objects.count(), 0)
13  tests/regressiontests/inline_formsets/models.py
@@ -13,6 +13,19 @@ class Child(models.Model):
13 13
     school = models.ForeignKey(School)
14 14
     name = models.CharField(max_length=100)
15 15
 
  16
+class Poet(models.Model):
  17
+    name = models.CharField(max_length=100)
  18
+
  19
+    def __unicode__(self):
  20
+        return self.name
  21
+
  22
+class Poem(models.Model):
  23
+    poet = models.ForeignKey(Poet)
  24
+    name = models.CharField(max_length=100)
  25
+
  26
+    def __unicode__(self):
  27
+        return self.name
  28
+
16 29
 __test__ = {'API_TESTS': """
17 30
 
18 31
 >>> from django.forms.models import inlineformset_factory
76  tests/regressiontests/inline_formsets/tests.py
... ...
@@ -0,0 +1,76 @@
  1
+from django.test import TestCase
  2
+from django.forms.models import inlineformset_factory
  3
+from regressiontests.inline_formsets.models import Poet, Poem
  4
+
  5
+class DeletionTests(TestCase):
  6
+    def test_deletion(self):
  7
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
  8
+        poet = Poet.objects.create(name='test')
  9
+        poet.poem_set.create(name='test poem')
  10
+        data = {
  11
+            'poem_set-TOTAL_FORMS': u'1',
  12
+            'poem_set-INITIAL_FORMS': u'1',
  13
+            'poem_set-0-id': u'1',
  14
+            'poem_set-0-poem': u'1',
  15
+            'poem_set-0-name': u'test',
  16
+            'poem_set-0-DELETE': u'on',
  17
+        }
  18
+        formset = PoemFormSet(data, instance=poet)
  19
+        formset.save()
  20
+        self.assertTrue(formset.is_valid())
  21
+        self.assertEqual(Poem.objects.count(), 0)
  22
+
  23
+    def test_add_form_deletion_when_invalid(self):
  24
+        """
  25
+        Make sure that an add form that is filled out, but marked for deletion
  26
+        doesn't cause validation errors.
  27
+        """
  28
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
  29
+        poet = Poet.objects.create(name='test')
  30
+        data = {
  31
+            'poem_set-TOTAL_FORMS': u'1',
  32
+            'poem_set-INITIAL_FORMS': u'0',
  33
+            'poem_set-0-id': u'',
  34
+            'poem_set-0-poem': u'1',
  35
+            'poem_set-0-name': u'x' * 1000,
  36
+        }
  37
+        formset = PoemFormSet(data, instance=poet)
  38
+        # Make sure this form doesn't pass validation.
  39
+        self.assertEqual(formset.is_valid(), False)
  40
+        self.assertEqual(Poem.objects.count(), 0)
  41
+
  42
+        # Then make sure that it *does* pass validation and delete the object,
  43
+        # even though the data isn't actually valid.
  44
+        data['poem_set-0-DELETE'] = 'on'
  45
+        formset = PoemFormSet(data, instance=poet)
  46
+        self.assertEqual(formset.is_valid(), True)
  47
+        formset.save()
  48
+        self.assertEqual(Poem.objects.count(), 0)
  49
+
  50
+    def test_change_form_deletion_when_invalid(self):
  51
+        """
  52
+        Make sure that a change form that is filled out, but marked for deletion
  53
+        doesn't cause validation errors.
  54
+        """
  55
+        PoemFormSet = inlineformset_factory(Poet, Poem, can_delete=True)
  56
+        poet = Poet.objects.create(name='test')
  57
+        poet.poem_set.create(name='test poem')
  58
+        data = {
  59
+            'poem_set-TOTAL_FORMS': u'1',
  60
+            'poem_set-INITIAL_FORMS': u'1',
  61
+            'poem_set-0-id': u'1',
  62
+            'poem_set-0-poem': u'1',
  63
+            'poem_set-0-name': u'x' * 1000,
  64
+        }
  65
+        formset = PoemFormSet(data, instance=poet)
  66
+        # Make sure this form doesn't pass validation.
  67
+        self.assertEqual(formset.is_valid(), False)
  68
+        self.assertEqual(Poem.objects.count(), 1)
  69
+
  70
+        # Then make sure that it *does* pass validation and delete the object,
  71
+        # even though the data isn't actually valid.
  72
+        data['poem_set-0-DELETE'] = 'on'
  73
+        formset = PoemFormSet(data, instance=poet)
  74
+        self.assertEqual(formset.is_valid(), True)
  75
+        formset.save()
  76
+        self.assertEqual(Poem.objects.count(), 0)

0 notes on commit 15becf2

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