Browse Source

Refs #24121 -- Added __repr__() to BaseFormSet.

Baptiste Mispelon 3 years ago
parent
commit
e95e6425ac
2 changed files with 79 additions and 0 deletions
  1. 16 0
      django/forms/formsets.py
  2. 63 0
      tests/forms_tests/tests/test_formsets.py

+ 16 - 0
django/forms/formsets.py

@@ -103,6 +103,22 @@ class BaseFormSet(RenderableFormMixin):
         """
         return True
 
+    def __repr__(self):
+        if self._errors is None:
+            is_valid = 'Unknown'
+        else:
+            is_valid = (
+                self.is_bound and
+                not self._non_form_errors and
+                not any(form_errors for form_errors in self._errors)
+            )
+        return '<%s: bound=%s valid=%s total_forms=%s>' % (
+            self.__class__.__qualname__,
+            self.is_bound,
+            is_valid,
+            self.total_form_count(),
+        )
+
     @cached_property
     def management_form(self):
         """Return the ManagementForm instance for this FormSet."""

+ 63 - 0
tests/forms_tests/tests/test_formsets.py

@@ -25,6 +25,12 @@ class Choice(Form):
 ChoiceFormSet = formset_factory(Choice)
 
 
+class ChoiceFormsetWithNonFormError(ChoiceFormSet):
+    def clean(self):
+        super().clean()
+        raise ValidationError('non-form error')
+
+
 class FavoriteDrinkForm(Form):
     name = CharField()
 
@@ -1328,6 +1334,63 @@ class FormsFormsetTestCase(SimpleTestCase):
         self.assertEqual(formset.non_form_errors().renderer, renderer)
         self.assertEqual(formset.empty_form.renderer, renderer)
 
+    def test_repr(self):
+        valid_formset = self.make_choiceformset([('test', 1)])
+        valid_formset.full_clean()
+        invalid_formset = self.make_choiceformset([('test', '')])
+        invalid_formset.full_clean()
+        partially_invalid_formset = self.make_choiceformset(
+            [('test', '1'), ('test', '')],
+        )
+        partially_invalid_formset.full_clean()
+        invalid_formset_non_form_errors_only = self.make_choiceformset(
+            [('test', '')],
+            formset_class=ChoiceFormsetWithNonFormError,
+        )
+        invalid_formset_non_form_errors_only.full_clean()
+
+        cases = [
+            (
+                self.make_choiceformset(),
+                '<ChoiceFormSet: bound=False valid=Unknown total_forms=1>',
+            ),
+            (
+                self.make_choiceformset(
+                    formset_class=formset_factory(Choice, extra=10),
+                ),
+                '<ChoiceFormSet: bound=False valid=Unknown total_forms=10>',
+            ),
+            (
+                self.make_choiceformset([]),
+                '<ChoiceFormSet: bound=True valid=Unknown total_forms=0>',
+            ),
+            (
+                self.make_choiceformset([('test', 1)]),
+                '<ChoiceFormSet: bound=True valid=Unknown total_forms=1>',
+            ),
+            (valid_formset, '<ChoiceFormSet: bound=True valid=True total_forms=1>'),
+            (invalid_formset, '<ChoiceFormSet: bound=True valid=False total_forms=1>'),
+            (
+                partially_invalid_formset,
+                '<ChoiceFormSet: bound=True valid=False total_forms=2>',
+            ),
+            (
+                invalid_formset_non_form_errors_only,
+                '<ChoiceFormsetWithNonFormError: bound=True valid=False total_forms=1>',
+            ),
+        ]
+        for formset, expected_repr in cases:
+            with self.subTest(expected_repr=expected_repr):
+                self.assertEqual(repr(formset), expected_repr)
+
+    def test_repr_do_not_trigger_validation(self):
+        formset = self.make_choiceformset([('test', 1)])
+        with mock.patch.object(formset, 'full_clean') as mocked_full_clean:
+            repr(formset)
+            mocked_full_clean.assert_not_called()
+            formset.is_valid()
+            mocked_full_clean.assert_called()
+
 
 @jinja2_tests
 class Jinja2FormsFormsetTestCase(FormsFormsetTestCase):