Commit 15becf23 authored by Joseph Kocherhans's avatar Joseph Kocherhans
Browse files

Forms in model formsets and inline formsets can now be deleted even if they...

Forms in model formsets and inline formsets can now be deleted even if they don't validate. Related to #9587.

git-svn-id: http://code.djangoproject.com/svn/django/trunk@10283 bcc190cf-cafb-0310-a4f2-bffc1f526a37
parent 98f5f684
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -205,6 +205,15 @@ class BaseForm(StrAndUnicode):
        """
        return self.errors.get(NON_FIELD_ERRORS, self.error_class())

    def _raw_value(self, fieldname):
        """
        Returns the raw_value for a particular field name. This is just a
        convenient wrapper around widget.value_from_datadict.
        """
        field = self.fields[fieldname]
        prefix = self.add_prefix(fieldname)
        return field.widget.value_from_datadict(self.data, self.files, prefix)

    def full_clean(self):
        """
        Cleans all of self.data and populates self._errors and
+2 −3
Original line number Diff line number Diff line
@@ -228,9 +228,8 @@ class BaseFormSet(StrAndUnicode):
                # more code than we'd like, but the form's cleaned_data will
                # not exist if the form is invalid.
                field = form.fields[DELETION_FIELD_NAME]
                prefix = form.add_prefix(DELETION_FIELD_NAME)
                value = field.widget.value_from_datadict(self.data, self.files, prefix)
                should_delete = field.clean(value)
                raw_value = form._raw_value(DELETION_FIELD_NAME)
                should_delete = field.clean(raw_value)
                if should_delete:
                    # This form is going to be deleted so any of its errors
                    # should not cause the entire formset to be invalid.
+21 −12
Original line number Diff line number Diff line
@@ -425,11 +425,17 @@ class BaseModelFormSet(BaseFormSet):
            existing_objects[obj.pk] = obj
        saved_instances = []
        for form in self.initial_forms:
            obj = existing_objects[form.cleaned_data[self._pk_field.name].pk]
            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
            pk_name = self._pk_field.name
            raw_pk_value = form._raw_value(pk_name)
            pk_value = form.fields[pk_name].clean(raw_pk_value).pk
            obj = existing_objects[pk_value]
            if self.can_delete:
                raw_delete_value = form._raw_value(DELETION_FIELD_NAME)
                should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value)
                if should_delete:
                    self.deleted_objects.append(obj)
                    obj.delete()
            else:
                    continue
            if form.changed_data:
                self.changed_objects.append((obj, form.changed_data))
                saved_instances.append(self.save_existing(form, obj, commit=commit))
@@ -444,7 +450,10 @@ class BaseModelFormSet(BaseFormSet):
                continue
            # If someone has marked an add form for deletion, don't save the
            # object.
            if self.can_delete and form.cleaned_data[DELETION_FIELD_NAME]:
            if self.can_delete:
                raw_delete_value = form._raw_value(DELETION_FIELD_NAME)
                should_delete = form.fields[DELETION_FIELD_NAME].clean(raw_delete_value)
                if should_delete:
                    continue
            self.new_objects.append(self.save_new(form, commit=commit))
            if not commit:
+70 −0
Original line number Diff line number Diff line
from django.test import TestCase
from django.forms.models import modelformset_factory
from modeltests.model_formsets.models import Poet, Poem

class DeletionTests(TestCase):
    def test_deletion(self):
        PoetFormSet = modelformset_factory(Poet, can_delete=True)
        poet = Poet.objects.create(name='test')
        data = {
            'form-TOTAL_FORMS': u'1',
            'form-INITIAL_FORMS': u'1',
            'form-0-id': u'1',
            'form-0-name': u'test',
            'form-0-DELETE': u'on',
        }
        formset = PoetFormSet(data, queryset=Poet.objects.all())
        formset.save()
        self.assertTrue(formset.is_valid())
        self.assertEqual(Poet.objects.count(), 0)

    def test_add_form_deletion_when_invalid(self):
        """
        Make sure that an add form that is filled out, but marked for deletion
        doesn't cause validation errors.
        """
        PoetFormSet = modelformset_factory(Poet, can_delete=True)
        data = {
            'form-TOTAL_FORMS': u'1',
            'form-INITIAL_FORMS': u'0',
            'form-0-id': u'',
            'form-0-name': u'x' * 1000,
        }
        formset = PoetFormSet(data, queryset=Poet.objects.all())
        # Make sure this form doesn't pass validation.
        self.assertEqual(formset.is_valid(), False)
        self.assertEqual(Poet.objects.count(), 0)

        # Then make sure that it *does* pass validation and delete the object,
        # even though the data isn't actually valid.
        data['form-0-DELETE'] = 'on'
        formset = PoetFormSet(data, queryset=Poet.objects.all())
        self.assertEqual(formset.is_valid(), True)
        formset.save()
        self.assertEqual(Poet.objects.count(), 0)

    def test_change_form_deletion_when_invalid(self):
        """
        Make sure that an add form that is filled out, but marked for deletion
        doesn't cause validation errors.
        """
        PoetFormSet = modelformset_factory(Poet, can_delete=True)
        poet = Poet.objects.create(name='test')
        data = {
            'form-TOTAL_FORMS': u'1',
            'form-INITIAL_FORMS': u'1',
            'form-0-id': u'1',
            'form-0-name': u'x' * 1000,
        }
        formset = PoetFormSet(data, queryset=Poet.objects.all())
        # Make sure this form doesn't pass validation.
        self.assertEqual(formset.is_valid(), False)
        self.assertEqual(Poet.objects.count(), 1)

        # Then make sure that it *does* pass validation and delete the object,
        # even though the data isn't actually valid.
        data['form-0-DELETE'] = 'on'
        formset = PoetFormSet(data, queryset=Poet.objects.all())
        self.assertEqual(formset.is_valid(), True)
        formset.save()
        self.assertEqual(Poet.objects.count(), 0)
+13 −0
Original line number Diff line number Diff line
@@ -13,6 +13,19 @@ class Child(models.Model):
    school = models.ForeignKey(School)
    name = models.CharField(max_length=100)

class Poet(models.Model):
    name = models.CharField(max_length=100)

    def __unicode__(self):
        return self.name

class Poem(models.Model):
    poet = models.ForeignKey(Poet)
    name = models.CharField(max_length=100)

    def __unicode__(self):
        return self.name

__test__ = {'API_TESTS': """

>>> from django.forms.models import inlineformset_factory
Loading