diff --git a/AUTHORS b/AUTHORS index 4e4dbc00fd..72a87ce7b7 100644 --- a/AUTHORS +++ b/AUTHORS @@ -906,6 +906,7 @@ answer newbie questions, and generally made Django that much better: Thomas Stromberg Thomas Tanner tibimicu@gmx.net + Ties Jan Hefting Tim Allen Tim Givois Tim Graham diff --git a/django/forms/formsets.py b/django/forms/formsets.py index a89c35599f..b8e0d62fd9 100644 --- a/django/forms/formsets.py +++ b/django/forms/formsets.py @@ -333,7 +333,7 @@ class BaseFormSet: self._non_form_errors. """ self._errors = [] - self._non_form_errors = self.error_class() + self._non_form_errors = self.error_class(error_class='nonform') empty_forms_count = 0 if not self.is_bound: # Stop further processing. @@ -380,7 +380,10 @@ class BaseFormSet: # Give self.clean() a chance to do cross-form validation. self.clean() except ValidationError as e: - self._non_form_errors = self.error_class(e.error_list) + self._non_form_errors = self.error_class( + e.error_list, + error_class='nonform' + ) def clean(self): """ diff --git a/docs/releases/4.0.txt b/docs/releases/4.0.txt index f0742db7bd..ee3922c9b6 100644 --- a/docs/releases/4.0.txt +++ b/docs/releases/4.0.txt @@ -218,6 +218,10 @@ Forms error message. This allows custom error messages to use the ``%(value)s`` placeholder. +* :class:`~django.forms.formsets.BaseFormSet` now renders non-form errors with + an additional class of ``nonform`` to help distinguish them from + form-specific errors. + Generic Views ~~~~~~~~~~~~~ diff --git a/docs/topics/forms/formsets.txt b/docs/topics/forms/formsets.txt index 1f2c7c3fad..0281b6a4d6 100644 --- a/docs/topics/forms/formsets.txt +++ b/docs/topics/forms/formsets.txt @@ -365,6 +365,20 @@ The formset ``clean`` method is called after all the ``Form.clean`` methods have been called. The errors will be found using the ``non_form_errors()`` method on the formset. +Non-form errors will be rendered with an additional class of ``nonform`` to +help distinguish them from form-specific errors. For example, +``{{ formset.non_form_errors }}`` would look like: + +.. code-block:: html+django + +
    +
  • Articles in a set must have distinct titles.
  • +
+ +.. versionchanged:: 4.0 + + The additional ``nonform`` class was added. + Validating the number of forms in a formset =========================================== diff --git a/tests/admin_views/tests.py b/tests/admin_views/tests.py index 880ba0b85f..b277476296 100644 --- a/tests/admin_views/tests.py +++ b/tests/admin_views/tests.py @@ -3348,7 +3348,10 @@ class AdminViewListEditable(TestCase): response = self.client.post(reverse('admin:admin_views_person_changelist'), data) non_form_errors = response.context['cl'].formset.non_form_errors() self.assertIsInstance(non_form_errors, ErrorList) - self.assertEqual(str(non_form_errors), str(ErrorList(["Grace is not a Zombie"]))) + self.assertEqual( + str(non_form_errors), + str(ErrorList(['Grace is not a Zombie'], error_class='nonform')), + ) def test_list_editable_ordering(self): collector = Collector.objects.create(id=1, name="Frederick Clegg") diff --git a/tests/forms_tests/tests/test_formsets.py b/tests/forms_tests/tests/test_formsets.py index 889560aa74..5afb816f7f 100644 --- a/tests/forms_tests/tests/test_formsets.py +++ b/tests/forms_tests/tests/test_formsets.py @@ -337,6 +337,10 @@ class FormsFormsetTestCase(SimpleTestCase): formset = ChoiceFormSet(data, auto_id=False, prefix='choices') self.assertFalse(formset.is_valid()) self.assertEqual(formset.non_form_errors(), ['Please submit at most 1 form.']) + self.assertEqual( + str(formset.non_form_errors()), + '
  • Please submit at most 1 form.
', + ) def test_formset_validate_min_flag(self): """ @@ -359,6 +363,11 @@ class FormsFormsetTestCase(SimpleTestCase): formset = ChoiceFormSet(data, auto_id=False, prefix='choices') self.assertFalse(formset.is_valid()) self.assertEqual(formset.non_form_errors(), ['Please submit at least 3 forms.']) + self.assertEqual( + str(formset.non_form_errors()), + '
  • ' + 'Please submit at least 3 forms.
', + ) def test_formset_validate_min_unchanged_forms(self): """ @@ -983,6 +992,11 @@ class FormsFormsetTestCase(SimpleTestCase): formset = FavoriteDrinksFormSet(data, prefix='drinks') self.assertFalse(formset.is_valid()) self.assertEqual(formset.non_form_errors(), ['You may only specify a drink once.']) + self.assertEqual( + str(formset.non_form_errors()), + '
  • ' + 'You may only specify a drink once.
', + ) def test_formset_iteration(self): """Formset instances are iterable."""