From e93135b067c4f2bfe423e605bdfe0786eaecaef2 Mon Sep 17 00:00:00 2001 From: Tim Graham Date: Fri, 28 Apr 2017 09:32:40 -0400 Subject: [PATCH] [1.11.x] Fixed #28130 -- Fixed formset min_num validation with initial, unchanged forms. Regression in f5c6295797b8332134fd89e0209a18a1d1d45e0c. Backport of f04a40491764bdc9a2ebbfc03fa7be424fb3ce63 from master --- django/forms/formsets.py | 3 ++- docs/releases/1.11.1.txt | 3 +++ tests/forms_tests/tests/test_formsets.py | 25 ++++++++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/django/forms/formsets.py b/django/forms/formsets.py index 7d9e84113f..3b72697c24 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -342,7 +342,8 @@ class BaseFormSet(object): return for i in range(0, self.total_form_count()): form = self.forms[i] - if not form.has_changed(): + # Empty forms are unchanged forms beyond those with initial data. + if not form.has_changed() and i >= self.initial_form_count(): empty_forms_count += 1 self._errors.append(form.errors) diff --git a/docs/releases/1.11.1.txt b/docs/releases/1.11.1.txt index a76806c2fb..2cf7e26dfd 100644 --- a/docs/releases/1.11.1.txt +++ b/docs/releases/1.11.1.txt @@ -58,3 +58,6 @@ Bugfixes * Fixed crash when overriding the template of ``django.views.static.directory_index()`` (:ticket:`28122`). + +* Fixed a regression in formset ``min_num`` validation with unchanged forms + that have initial data (:ticket:`28130`). diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index 29f0befc18..1c431e5e22 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -405,6 +405,31 @@ class FormsFormsetTestCase(SimpleTestCase): self.assertFalse(formset.is_valid()) self.assertEqual(formset.non_form_errors(), ['Please submit 3 or more forms.']) + def test_formset_validate_min_unchanged_forms(self): + """ + min_num validation doesn't consider unchanged forms with initial data + as "empty". + """ + initial = [ + {'choice': 'Zero', 'votes': 0}, + {'choice': 'One', 'votes': 0}, + ] + data = { + 'choices-TOTAL_FORMS': '2', + 'choices-INITIAL_FORMS': '2', + 'choices-MIN_NUM_FORMS': '0', + 'choices-MAX_NUM_FORMS': '2', + 'choices-0-choice': 'Zero', + 'choices-0-votes': '0', + 'choices-1-choice': 'One', + 'choices-1-votes': '1', # changed from initial + } + ChoiceFormSet = formset_factory(Choice, min_num=2, validate_min=True) + formset = ChoiceFormSet(data, auto_id=False, prefix='choices', initial=initial) + self.assertFalse(formset.forms[0].has_changed()) + self.assertTrue(formset.forms[1].has_changed()) + self.assertTrue(formset.is_valid()) + def test_formset_validate_min_excludes_empty_forms(self): data = { 'choices-TOTAL_FORMS': '2',