diff --git a/django/contrib/admin/helpers.py b/django/contrib/admin/helpers.py index 9c78e758f0d..cc5317199d8 100644 --- a/django/contrib/admin/helpers.py +++ b/django/contrib/admin/helpers.py @@ -83,6 +83,10 @@ class AdminForm: def non_field_errors(self): return self.form.non_field_errors + @property + def fields(self): + return self.form.fields + @property def is_bound(self): return self.form.is_bound diff --git a/django/test/testcases.py b/django/test/testcases.py index a29b77c2773..1acc39fd761 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -563,6 +563,28 @@ class SimpleTestCase(unittest.TestCase): "the Django test Client." ) + def _assert_form_error(self, form, field, errors, msg_prefix, form_repr): + if not form.is_bound: + self.fail( + f"{msg_prefix} The {form_repr} is not bound, it will never have any " + f"errors." + ) + + if field is not None and field not in form.fields: + self.fail( + f"{msg_prefix}The {form_repr} does not contain the field {field!r}." + ) + if field is None: + field_errors = form.non_field_errors() + failure_message = f"The non-field errors of {form_repr} don't match." + else: + field_errors = form.errors.get(field, []) + failure_message = ( + f"The errors of field {field!r} on {form_repr} don't match." + ) + + self.assertEqual(field_errors, errors, msg_prefix + failure_message) + def assertFormError(self, response, form, field, errors, msg_prefix=""): """ Assert that a form used to render the response has a specific field @@ -593,53 +615,11 @@ class SimpleTestCase(unittest.TestCase): # Search all contexts for the error. found_form = False for i, context in enumerate(contexts): - if form not in context: - continue - if not context[form].is_bound: - form_repr = repr(context[form]) - self.fail( - f"{msg_prefix}The form {form_repr} is not bound, it will never " - f"have any errors." + if form in context: + found_form = True + self._assert_form_error( + context[form], field, errors, msg_prefix, "form %r" % context[form] ) - found_form = True - for err in errors: - if field: - if field in context[form].errors: - field_errors = context[form].errors[field] - self.assertTrue( - err in field_errors, - msg_prefix + "The field '%s' on form '%s' in" - " context %d does not contain the error '%s'" - " (actual errors: %s)" - % (field, form, i, err, repr(field_errors)), - ) - elif field in context[form].fields: - self.fail( - msg_prefix - + ( - "The field '%s' on form '%s' in context %d contains no " - "errors" - ) - % (field, form, i) - ) - else: - self.fail( - msg_prefix - + ( - "The form '%s' in context %d does not contain the " - "field '%s'" - ) - % (form, i, field) - ) - else: - non_field_errors = context[form].non_field_errors() - self.assertTrue( - err in non_field_errors, - msg_prefix + "The form '%s' in context %d does not" - " contain the non-field error '%s'" - " (actual errors: %s)" - % (form, i, err, non_field_errors or "none"), - ) if not found_form: self.fail( msg_prefix + "The form '%s' was not used to render the response" % form @@ -701,64 +681,23 @@ class SimpleTestCase(unittest.TestCase): f"{msg_prefix}The formset {formset_repr} only has " f"{form_count} {form_or_forms}." ) - for err in errors: - if field is not None: - if field in context[formset].forms[form_index].errors: - field_errors = context[formset].forms[form_index].errors[field] - self.assertTrue( - err in field_errors, - msg_prefix + "The field '%s' on formset '%s', " - "form %d in context %d does not contain the " - "error '%s' (actual errors: %s)" - % (field, formset, form_index, i, err, repr(field_errors)), - ) - elif field in context[formset].forms[form_index].fields: - self.fail( - msg_prefix - + ( - "The field '%s' on formset '%s', form %d in context " - "%d contains no errors" - ) - % (field, formset, form_index, i) - ) - else: - self.fail( - msg_prefix - + ( - "The formset '%s', form %d in context %d does not " - "contain the field '%s'" - ) - % (formset, form_index, i, field) - ) - elif form_index is not None: - non_field_errors = ( - context[formset].forms[form_index].non_field_errors() - ) - self.assertFalse( - not non_field_errors, - msg_prefix + "The formset '%s', form %d in context %d " - "does not contain any non-field errors." - % (formset, form_index, i), - ) - self.assertTrue( - err in non_field_errors, - msg_prefix + "The formset '%s', form %d in context %d " - "does not contain the non-field error '%s' (actual errors: %s)" - % (formset, form_index, i, err, repr(non_field_errors)), - ) - else: - non_form_errors = context[formset].non_form_errors() - self.assertFalse( - not non_form_errors, - msg_prefix + "The formset '%s' in context %d does not " - "contain any non-form errors." % (formset, i), - ) - self.assertTrue( - err in non_form_errors, - msg_prefix + "The formset '%s' in context %d does not " - "contain the non-form error '%s' (actual errors: %s)" - % (formset, i, err, repr(non_form_errors)), - ) + if form_index is not None: + form_repr = f"form {form_index} of formset {formset_repr}" + self._assert_form_error( + context[formset].forms[form_index], + field, + errors, + msg_prefix, + form_repr, + ) + else: + failure_message = ( + f"{msg_prefix}The non-form errors of formset {formset_repr} don't " + f"match." + ) + self.assertEqual( + context[formset].non_form_errors(), errors, failure_message + ) if not found_formset: self.fail( msg_prefix diff --git a/tests/test_client_regress/tests.py b/tests/test_client_regress/tests.py index 11f63f87318..43cc9580cc2 100644 --- a/tests/test_client_regress/tests.py +++ b/tests/test_client_regress/tests.py @@ -651,7 +651,11 @@ class AssertFormErrorTests(SimpleTestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "Invalid POST Template") - msg = "The form 'form' in context 0 does not contain the field 'some_field'" + msg = ( + "The form does not contain the field " + "'some_field'." + ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormError(response, "form", "some_field", "Some error.") with self.assertRaisesMessage(AssertionError, "abc: " + msg): @@ -672,7 +676,10 @@ class AssertFormErrorTests(SimpleTestCase): self.assertEqual(response.status_code, 200) self.assertTemplateUsed(response, "Invalid POST Template") - msg = "The field 'value' on form 'form' in context 0 contains no errors" + msg = ( + "The errors of field 'value' on form don't match." + ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormError(response, "form", "value", "Some error.") with self.assertRaisesMessage(AssertionError, "abc: " + msg): @@ -694,9 +701,8 @@ class AssertFormErrorTests(SimpleTestCase): self.assertTemplateUsed(response, "Invalid POST Template") msg = ( - "The field 'email' on form 'form' in context 0 does not contain " - "the error 'Some error.' (actual errors: ['Enter a valid email " - "address.'])" + "The errors of field 'email' on form don't match." ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormError(response, "form", "email", "Some error.") @@ -722,8 +728,8 @@ class AssertFormErrorTests(SimpleTestCase): self.assertTemplateUsed(response, "Invalid POST Template") msg = ( - "The form 'form' in context 0 does not contain the non-field " - "error 'Some error.' (actual errors: none)" + "The non-field errors of form don't match." ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormError(response, "form", None, "Some error.") @@ -801,9 +807,8 @@ class AssertFormsetErrorTests(SimpleTestCase): "An assertion is raised if the field name is unknown" for prefix, kwargs in self.msg_prefixes: msg = ( - prefix - + "The formset 'my_formset', form 0 in context 0 does not contain the " - "field 'Some_field'" + f"{prefix}The form 0 of formset does not contain the field 'Some_field'." ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( @@ -819,9 +824,8 @@ class AssertFormsetErrorTests(SimpleTestCase): "An assertion is raised if the field doesn't have any errors" for prefix, kwargs in self.msg_prefixes: msg = ( - prefix - + "The field 'value' on formset 'my_formset', form 1 in context 0 " - "contains no errors" + f"{prefix}The errors of field 'value' on form 1 of formset " + f" don't match." ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( @@ -836,10 +840,9 @@ class AssertFormsetErrorTests(SimpleTestCase): def test_unknown_error(self): "An assertion is raised if the field doesn't contain the specified error" for prefix, kwargs in self.msg_prefixes: - msg = prefix + ( - "The field 'email' on formset 'my_formset', form 0 " - "in context 0 does not contain the error 'Some error.' " - "(actual errors: ['Enter a valid email address.'])" + msg = ( + f"{prefix}The errors of field 'email' on form 0 of formset " + f" don't match." ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( @@ -866,9 +869,8 @@ class AssertFormsetErrorTests(SimpleTestCase): """ for prefix, kwargs in self.msg_prefixes: msg = ( - prefix - + "The formset 'my_formset', form 1 in context 0 does not contain any " - "non-field errors." + f"{prefix}The non-field errors of form 1 of formset don't match." ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( @@ -886,10 +888,9 @@ class AssertFormsetErrorTests(SimpleTestCase): the provided error. """ for prefix, kwargs in self.msg_prefixes: - msg = prefix + ( - "The formset 'my_formset', form 0 in context 0 does not " - "contain the non-field error 'Some error.' (actual errors: " - "['Non-field error.'])" + msg = ( + f"{prefix}The non-field errors of form 0 of formset don't match." ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( @@ -923,9 +924,8 @@ class AssertFormsetErrorTests(SimpleTestCase): """ for prefix, kwargs in self.msg_prefixes: msg = ( - prefix - + "The formset 'my_formset' in context 0 does not contain any non-form " - "errors." + f"{prefix}The non-form errors of formset don't match" ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( @@ -943,10 +943,9 @@ class AssertFormsetErrorTests(SimpleTestCase): the provided error. """ for prefix, kwargs in self.msg_prefixes: - msg = prefix + ( - "The formset 'my_formset' in context 0 does not contain the " - "non-form error 'Some error.' (actual errors: ['Forms in a set " - "must have distinct email addresses.'])" + msg = ( + f"{prefix}The non-form errors of formset don't match" ) with self.assertRaisesMessage(AssertionError, msg): self.assertFormsetError( diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index 038e79732b6..506773b7128 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -1404,46 +1404,33 @@ class AssertFormErrorTests(SimpleTestCase): self.assertFormError(response, "form", "field", "invalid value") def test_field_not_in_form(self): - msg = "The form 'form' in context 0 does not contain the field 'other_field'" + msg = ( + "The form does not " + "contain the field 'other_field'." + ) response = mock.Mock(context=[{"form": TestForm.invalid()}]) with self.assertRaisesMessage(AssertionError, msg): self.assertFormError(response, "form", "other_field", "invalid value") - def test_field_not_in_form_multicontext(self): - msg = "The form 'form' in context 1 does not contain the field 'other_field'" - response = mock.Mock(context=[{}, {"form": TestForm.invalid()}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormError(response, "form", "other_field", "invalid value") - def test_field_with_no_errors(self): - msg = "The field 'field' on form 'form' in context 0 contains no errors" + msg = ( + "The errors of field 'field' on form don't match." + ) response = mock.Mock(context=[{"form": TestForm.valid()}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormError(response, "form", "field", "invalid value") - - def test_field_with_no_errors_multicontext(self): - msg = "The field 'field' on form 'form' in context 1 contains no errors" - response = mock.Mock(context=[{}, {"form": TestForm.valid()}]) - with self.assertRaisesMessage(AssertionError, msg): + with self.assertRaisesMessage(AssertionError, msg) as ctx: self.assertFormError(response, "form", "field", "invalid value") + self.assertIn("[] != ['invalid value']", str(ctx.exception)) def test_field_with_different_error(self): msg = ( - "The field 'field' on form 'form' in context 0 does not contain " - "the error 'other error' (actual errors: ['invalid value'])" + "The errors of field 'field' on form don't match." ) response = mock.Mock(context=[{"form": TestForm.invalid()}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormError(response, "form", "field", "other error") - - def test_field_with_different_error_multicontext(self): - msg = ( - "The field 'field' on form 'form' in context 1 does not contain " - "the error 'other error' (actual errors: ['invalid value'])" - ) - response = mock.Mock(context=[{}, {"form": TestForm.invalid()}]) - with self.assertRaisesMessage(AssertionError, msg): + with self.assertRaisesMessage(AssertionError, msg) as ctx: self.assertFormError(response, "form", "field", "other error") + self.assertIn("['invalid value'] != ['other error']", str(ctx.exception)) def test_basic_positive_assertion(self): response = mock.Mock(context=[{"form": TestForm.invalid()}]) @@ -1467,8 +1454,14 @@ class AssertFormErrorTests(SimpleTestCase): self.assertFormError(response, "form", "field", []) def test_empty_errors_invalid_form(self): + msg = ( + "The errors of field 'field' on form don't match." + ) response = mock.Mock(context=[{"form": TestForm.invalid()}]) - self.assertFormError(response, "form", "field", []) + with self.assertRaisesMessage(AssertionError, msg) as ctx: + self.assertFormError(response, "form", "field", []) + self.assertIn("['invalid value'] != []", str(ctx.exception)) def test_non_field_errors(self): response = mock.Mock(context=[{"form": TestForm.invalid(nonfield=True)}]) @@ -1476,17 +1469,22 @@ class AssertFormErrorTests(SimpleTestCase): @ignore_warnings(category=RemovedInDjango50Warning) def test_errors_none(self): + msg = ( + "The errors of field 'field' on form don't match." + ) response = mock.Mock(context=[{"form": TestForm.invalid()}]) - self.assertFormError(response, "form", "field", None) + with self.assertRaisesMessage(AssertionError, msg): + self.assertFormError(response, "form", "field", None) def test_errors_none_warning(self): - response = mock.Mock(context=[{"form": TestForm.invalid()}]) + response = mock.Mock(context=[{"form": TestForm.valid()}]) msg = ( "Passing errors=None to assertFormError() is deprecated, use " "errors=[] instead." ) with self.assertWarnsMessage(RemovedInDjango50Warning, msg): - self.assertFormError(response, "form", "value", None) + self.assertFormError(response, "form", "field", None) class AssertFormsetErrorTests(SimpleTestCase): @@ -1520,8 +1518,8 @@ class AssertFormsetErrorTests(SimpleTestCase): def test_field_not_in_form(self): msg = ( - "The formset 'formset', form 0 in context 0 does not contain the " - "field 'other_field'" + "The form 0 of formset " + "does not contain the field 'other_field'." ) response = mock.Mock(context=[{"formset": TestFormset.invalid()}]) with self.assertRaisesMessage(AssertionError, msg): @@ -1533,58 +1531,25 @@ class AssertFormsetErrorTests(SimpleTestCase): "invalid value", ) - def test_field_not_in_form_multicontext(self): - msg = ( - "The formset 'formset', form 0 in context 1 does not contain the " - "field 'other_field'" - ) - response = mock.Mock(context=[{}, {"formset": TestFormset.invalid()}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormsetError( - response, - "formset", - 0, - "other_field", - "invalid value", - ) - def test_field_with_no_errors(self): msg = ( - "The field 'field' on formset 'formset', form 0 in context 0 " - "contains no errors" + "The errors of field 'field' on form 0 of formset don't match." ) response = mock.Mock(context=[{"formset": TestFormset.valid()}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormsetError(response, "formset", 0, "field", "invalid value") - - def test_field_with_no_errors_multicontext(self): - msg = ( - "The field 'field' on formset 'formset', form 0 in context 1 " - "contains no errors" - ) - response = mock.Mock(context=[{}, {"formset": TestFormset.valid()}]) - with self.assertRaisesMessage(AssertionError, msg): + with self.assertRaisesMessage(AssertionError, msg) as ctx: self.assertFormsetError(response, "formset", 0, "field", "invalid value") + self.assertIn("[] != ['invalid value']", str(ctx.exception)) def test_field_with_different_error(self): msg = ( - "The field 'field' on formset 'formset', form 0 in context 0 does" - " not contain the error 'other error' (actual errors: ['invalid " - "value'])" + "The errors of field 'field' on form 0 of formset don't match." ) response = mock.Mock(context=[{"formset": TestFormset.invalid()}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormsetError(response, "formset", 0, "field", "other error") - - def test_field_with_different_error_multicontext(self): - msg = ( - "The field 'field' on formset 'formset', form 0 in context 1 does" - " not contain the error 'other error' (actual errors: ['invalid " - "value'])" - ) - response = mock.Mock(context=[{}, {"formset": TestFormset.invalid()}]) - with self.assertRaisesMessage(AssertionError, msg): + with self.assertRaisesMessage(AssertionError, msg) as ctx: self.assertFormsetError(response, "formset", 0, "field", "other error") + self.assertIn("['invalid value'] != ['other error']", str(ctx.exception)) def test_basic_positive_assertion(self): response = mock.Mock(context=[{"formset": TestFormset.invalid()}]) @@ -1608,8 +1573,14 @@ class AssertFormsetErrorTests(SimpleTestCase): self.assertFormsetError(response, "formset", 0, "field", []) def test_empty_errors_invalid_formset(self): + msg = ( + "The errors of field 'field' on form 0 of formset don't match." + ) response = mock.Mock(context=[{}, {"formset": TestFormset.invalid()}]) - self.assertFormsetError(response, "formset", 0, "field", []) + with self.assertRaisesMessage(AssertionError, msg) as ctx: + self.assertFormsetError(response, "formset", 0, "field", []) + self.assertIn("['invalid value'] != []", str(ctx.exception)) def test_non_field_errors(self): response = mock.Mock( @@ -1670,11 +1641,16 @@ class AssertFormsetErrorTests(SimpleTestCase): @ignore_warnings(category=RemovedInDjango50Warning) def test_errors_none(self): + msg = ( + "The errors of field 'field' on form 0 of formset don't match." + ) response = mock.Mock(context=[{"formset": TestFormset.invalid()}]) - self.assertFormsetError(response, "formset", 0, "field", None) + with self.assertRaisesMessage(AssertionError, msg): + self.assertFormsetError(response, "formset", 0, "field", None) def test_errors_none_warning(self): - response = mock.Mock(context=[{"formset": TestFormset.invalid()}]) + response = mock.Mock(context=[{"formset": TestFormset.valid()}]) msg = ( "Passing errors=None to assertFormsetError() is deprecated, use " "errors=[] instead."