From 71d1203b07d3092062aa9ebc0ab198abfc144d54 Mon Sep 17 00:00:00 2001 From: Mariusz Felisiak Date: Fri, 13 Jan 2023 08:31:30 +0100 Subject: [PATCH] Refs #33348 -- Removed support for passing response object and form/formset name to SimpleTestCase.assertFormError()/assertFormSetError(). Per deprecation timeline. --- django/test/testcases.py | 131 +----------------- docs/releases/5.0.txt | 4 + docs/topics/testing/tools.txt | 12 -- tests/test_utils/tests.py | 248 +--------------------------------- 4 files changed, 6 insertions(+), 389 deletions(-) diff --git a/django/test/testcases.py b/django/test/testcases.py index c37e99cdedb..23148537f51 100644 --- a/django/test/testcases.py +++ b/django/test/testcases.py @@ -1,5 +1,4 @@ import difflib -import inspect import json import logging import posixpath @@ -42,7 +41,6 @@ from django.db import DEFAULT_DB_ALIAS, connection, connections, transaction from django.forms.fields import CharField from django.http import QueryDict from django.http.request import split_domain_port, validate_host -from django.http.response import HttpResponseBase from django.test.client import AsyncClient, Client from django.test.html import HTMLParseError, parse_html from django.test.signals import template_rendered @@ -53,7 +51,7 @@ from django.test.utils import ( modify_settings, override_settings, ) -from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning +from django.utils.deprecation import RemovedInDjango51Warning from django.utils.functional import classproperty from django.utils.version import PY310 from django.views.static import serve @@ -168,127 +166,6 @@ class _DatabaseFailure: raise DatabaseOperationForbidden(self.message) -# RemovedInDjango50Warning -class _AssertFormErrorDeprecationHelper: - @staticmethod - def assertFormError(self, response, form, field, errors, msg_prefix=""): - """ - Search through all the rendered contexts of the `response` for a form named - `form` then dispatch to the new assertFormError() using that instance. - If multiple contexts contain the form, they're all checked in order and any - failure will abort (this matches the old behavior). - """ - warning_msg = ( - f"Passing response to assertFormError() is deprecated. Use the form object " - f"directly: assertFormError(response.context[{form!r}], {field!r}, ...)" - ) - warnings.warn(warning_msg, RemovedInDjango50Warning, stacklevel=2) - - full_msg_prefix = f"{msg_prefix}: " if msg_prefix else "" - contexts = to_list(response.context) if response.context is not None else [] - if not contexts: - self.fail( - f"{full_msg_prefix}Response did not use any contexts to render the " - f"response" - ) - # Search all contexts for the error. - found_form = False - for i, context in enumerate(contexts): - if form not in context: - continue - found_form = True - self.assertFormError(context[form], field, errors, msg_prefix=msg_prefix) - if not found_form: - self.fail( - f"{full_msg_prefix}The form '{form}' was not used to render the " - f"response" - ) - - @staticmethod - def assertFormSetError( - self, response, formset, form_index, field, errors, msg_prefix="" - ): - """ - Search for a formset named "formset" in the "response" and dispatch to - the new assertFormSetError() using that instance. If the name is found - in multiple contexts they're all checked in order and any failure will - abort the test. - """ - warning_msg = ( - f"Passing response to assertFormSetError() is deprecated. Use the formset " - f"object directly: assertFormSetError(response.context[{formset!r}], " - f"{form_index!r}, ...)" - ) - warnings.warn(warning_msg, RemovedInDjango50Warning, stacklevel=2) - - full_msg_prefix = f"{msg_prefix}: " if msg_prefix else "" - contexts = to_list(response.context) if response.context is not None else [] - if not contexts: - self.fail( - f"{full_msg_prefix}Response did not use any contexts to render the " - f"response" - ) - found_formset = False - for i, context in enumerate(contexts): - if formset not in context or not hasattr(context[formset], "forms"): - continue - found_formset = True - self.assertFormSetError( - context[formset], form_index, field, errors, msg_prefix - ) - if not found_formset: - self.fail( - f"{full_msg_prefix}The formset '{formset}' was not used to render the " - f"response" - ) - - @classmethod - def patch_signature(cls, new_method): - """ - Replace the decorated method with a new one that inspects the passed - args/kwargs and dispatch to the old implementation (with deprecation - warning) when it detects the old signature. - """ - - @wraps(new_method) - def patched_method(self, *args, **kwargs): - old_method = getattr(cls, new_method.__name__) - old_signature = inspect.signature(old_method) - try: - old_bound_args = old_signature.bind(self, *args, **kwargs) - except TypeError: - # If old signature doesn't match then either: - # 1) new signature will match - # 2) or a TypeError will be raised showing the user information - # about the new signature. - return new_method(self, *args, **kwargs) - - new_signature = inspect.signature(new_method) - try: - new_bound_args = new_signature.bind(self, *args, **kwargs) - except TypeError: - # Old signature matches but not the new one (because of - # previous try/except). - return old_method(self, *args, **kwargs) - - # If both signatures match, decide on which method to call by - # inspecting the first arg (arg[0] = self). - assert old_bound_args.args[1] == new_bound_args.args[1] - if hasattr( - old_bound_args.args[1], "context" - ): # Looks like a response object => old method. - return old_method(self, *args, **kwargs) - elif isinstance(old_bound_args.args[1], HttpResponseBase): - raise ValueError( - f"{old_method.__name__}() is only usable on responses fetched " - f"using the Django test Client." - ) - else: - return new_method(self, *args, **kwargs) - - return patched_method - - class SimpleTestCase(unittest.TestCase): # The class we'll use for the test client self.client. @@ -711,9 +588,6 @@ class SimpleTestCase(unittest.TestCase): self.assertEqual(field_errors, errors, msg_prefix + failure_message) - # RemovedInDjango50Warning: When the deprecation ends, remove the - # decorator. - @_AssertFormErrorDeprecationHelper.patch_signature def assertFormError(self, form, field, errors, msg_prefix=""): """ Assert that a field named "field" on the given form object has specific @@ -738,9 +612,6 @@ class SimpleTestCase(unittest.TestCase): ) return self.assertFormSetError(*args, **kw) - # RemovedInDjango50Warning: When the deprecation ends, remove the - # decorator. - @_AssertFormErrorDeprecationHelper.patch_signature def assertFormSetError(self, formset, form_index, field, errors, msg_prefix=""): """ Similar to assertFormError() but for formsets. diff --git a/docs/releases/5.0.txt b/docs/releases/5.0.txt index 9ae70cbf751..87755fdfc1b 100644 --- a/docs/releases/5.0.txt +++ b/docs/releases/5.0.txt @@ -334,3 +334,7 @@ to remove usage of these features. * The ``django.utils.timezone.utc`` alias to ``datetime.timezone.utc`` is removed. + +* Passing a response object and a form/formset name to + ``SimpleTestCase.assertFormError()`` and ``assertFormSetError()`` is no + longer allowed. diff --git a/docs/topics/testing/tools.txt b/docs/topics/testing/tools.txt index 7c8dd39cfbe..f3e5ec982f7 100644 --- a/docs/topics/testing/tools.txt +++ b/docs/topics/testing/tools.txt @@ -1601,12 +1601,6 @@ your test suite. which means that ``errors='error message'`` is the same as ``errors=['error message']``. - .. deprecated:: 4.1 - - Support for passing a response object and a form name to - ``assertFormError()`` is deprecated and will be removed in Django 5.0. - Use the form instance directly instead. - .. method:: SimpleTestCase.assertFormSetError(formset, form_index, field, errors, msg_prefix='') Asserts that the ``formset`` raises the provided list of errors when @@ -1624,12 +1618,6 @@ your test suite. ``field`` and ``errors`` have the same meaning as the parameters to ``assertFormError()``. - .. deprecated:: 4.1 - - Support for passing a response object and a formset name to - ``assertFormSetError()`` is deprecated and will be removed in Django - 5.0. Use the formset instance directly instead. - .. deprecated:: 4.2 The ``assertFormsetError()`` assertion method is deprecated. Use diff --git a/tests/test_utils/tests.py b/tests/test_utils/tests.py index f0c8735087c..aa58b47a94e 100644 --- a/tests/test_utils/tests.py +++ b/tests/test_utils/tests.py @@ -46,7 +46,7 @@ from django.test.utils import ( setup_test_environment, ) from django.urls import NoReverseMatch, path, reverse, reverse_lazy -from django.utils.deprecation import RemovedInDjango50Warning, RemovedInDjango51Warning +from django.utils.deprecation import RemovedInDjango51Warning from django.utils.log import DEFAULT_LOGGING from django.utils.version import PY311 @@ -1383,44 +1383,6 @@ class TestFormset(formset_factory(TestForm)): class AssertFormErrorTests(SimpleTestCase): - @ignore_warnings(category=RemovedInDjango50Warning) - def test_non_client_response(self): - msg = ( - "assertFormError() is only usable on responses fetched using the " - "Django test Client." - ) - response = HttpResponse() - with self.assertRaisesMessage(ValueError, msg): - self.assertFormError(response, "form", "field", "invalid value") - - @ignore_warnings(category=RemovedInDjango50Warning) - def test_response_with_no_context(self): - msg = "Response did not use any contexts to render the response" - response = mock.Mock(context=[]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormError(response, "form", "field", "invalid value") - msg_prefix = "Custom prefix" - with self.assertRaisesMessage(AssertionError, f"{msg_prefix}: {msg}"): - self.assertFormError( - response, - "form", - "field", - "invalid value", - msg_prefix=msg_prefix, - ) - - @ignore_warnings(category=RemovedInDjango50Warning) - def test_form_not_in_context(self): - msg = "The form 'form' was not used to render the response" - response = mock.Mock(context=[{}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormError(response, "form", "field", "invalid value") - msg_prefix = "Custom prefix" - with self.assertRaisesMessage(AssertionError, f"{msg_prefix}: {msg}"): - self.assertFormError( - response, "form", "field", "invalid value", msg_prefix=msg_prefix - ) - def test_single_error(self): self.assertFormError(TestForm.invalid(), "field", "invalid value") @@ -1523,35 +1485,6 @@ class AssertFormErrorTests(SimpleTestCase): class AssertFormSetErrorTests(SimpleTestCase): - @ignore_warnings(category=RemovedInDjango50Warning) - def test_non_client_response(self): - msg = ( - "assertFormSetError() is only usable on responses fetched using " - "the Django test Client." - ) - response = HttpResponse() - with self.assertRaisesMessage(ValueError, msg): - self.assertFormSetError(response, "formset", 0, "field", "invalid value") - - @ignore_warnings(category=RemovedInDjango50Warning) - def test_response_with_no_context(self): - msg = "Response did not use any contexts to render the response" - response = mock.Mock(context=[]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormSetError(response, "formset", 0, "field", "invalid value") - - @ignore_warnings(category=RemovedInDjango50Warning) - def test_formset_not_in_context(self): - msg = "The formset 'formset' was not used to render the response" - response = mock.Mock(context=[{}]) - with self.assertRaisesMessage(AssertionError, msg): - self.assertFormSetError(response, "formset", 0, "field", "invalid value") - msg_prefix = "Custom prefix" - with self.assertRaisesMessage(AssertionError, f"{msg_prefix}: {msg}"): - self.assertFormSetError( - response, "formset", 0, "field", "invalid value", msg_prefix=msg_prefix - ) - def test_rename_assertformseterror_deprecation_warning(self): msg = "assertFormsetError() is deprecated in favor of assertFormSetError()." with self.assertRaisesMessage(RemovedInDjango51Warning, msg): @@ -1762,185 +1695,6 @@ class AssertFormSetErrorTests(SimpleTestCase): self.assertFormSetError(formset, 2, "field", "error") -# RemovedInDjango50Warning -class AssertFormErrorDeprecationTests(SimpleTestCase): - """ - Exhaustively test all possible combinations of args/kwargs for the old - signature. - """ - - def _assert_form_error_old_api_cases(self, form, field, errors, msg_prefix): - response = mock.Mock(context=[{"form": TestForm.invalid()}]) - return ( - ((response, form, field, errors), {}), - ((response, form, field, errors, msg_prefix), {}), - ((response, form, field, errors), {"msg_prefix": msg_prefix}), - ((response, form, field), {"errors": errors}), - ((response, form, field), {"errors": errors, "msg_prefix": msg_prefix}), - ((response, form), {"field": field, "errors": errors}), - ( - (response, form), - {"field": field, "errors": errors, "msg_prefix": msg_prefix}, - ), - ((response,), {"form": form, "field": field, "errors": errors}), - ( - (response,), - { - "form": form, - "field": field, - "errors": errors, - "msg_prefix": msg_prefix, - }, - ), - ( - (), - {"response": response, "form": form, "field": field, "errors": errors}, - ), - ( - (), - { - "response": response, - "form": form, - "field": field, - "errors": errors, - "msg_prefix": msg_prefix, - }, - ), - ) - - def test_assert_form_error_old_api(self): - deprecation_msg = ( - "Passing response to assertFormError() is deprecated. Use the form object " - "directly: assertFormError(response.context['form'], 'field', ...)" - ) - for args, kwargs in self._assert_form_error_old_api_cases( - form="form", - field="field", - errors=["invalid value"], - msg_prefix="Custom prefix", - ): - with self.subTest(args=args, kwargs=kwargs): - with self.assertWarnsMessage(RemovedInDjango50Warning, deprecation_msg): - self.assertFormError(*args, **kwargs) - - @ignore_warnings(category=RemovedInDjango50Warning) - def test_assert_form_error_old_api_assertion_error(self): - for args, kwargs in self._assert_form_error_old_api_cases( - form="form", - field="field", - errors=["other error"], - msg_prefix="Custom prefix", - ): - with self.subTest(args=args, kwargs=kwargs): - with self.assertRaises(AssertionError): - self.assertFormError(*args, **kwargs) - - def _assert_formset_error_old_api_cases( - self, formset, form_index, field, errors, msg_prefix - ): - response = mock.Mock(context=[{"formset": TestFormset.invalid()}]) - return ( - ((response, formset, form_index, field, errors), {}), - ((response, formset, form_index, field, errors, msg_prefix), {}), - ( - (response, formset, form_index, field, errors), - {"msg_prefix": msg_prefix}, - ), - ((response, formset, form_index, field), {"errors": errors}), - ( - (response, formset, form_index, field), - {"errors": errors, "msg_prefix": msg_prefix}, - ), - ((response, formset, form_index), {"field": field, "errors": errors}), - ( - (response, formset, form_index), - {"field": field, "errors": errors, "msg_prefix": msg_prefix}, - ), - ( - (response, formset), - {"form_index": form_index, "field": field, "errors": errors}, - ), - ( - (response, formset), - { - "form_index": form_index, - "field": field, - "errors": errors, - "msg_prefix": msg_prefix, - }, - ), - ( - (response,), - { - "formset": formset, - "form_index": form_index, - "field": field, - "errors": errors, - }, - ), - ( - (response,), - { - "formset": formset, - "form_index": form_index, - "field": field, - "errors": errors, - "msg_prefix": msg_prefix, - }, - ), - ( - (), - { - "response": response, - "formset": formset, - "form_index": form_index, - "field": field, - "errors": errors, - }, - ), - ( - (), - { - "response": response, - "formset": formset, - "form_index": form_index, - "field": field, - "errors": errors, - "msg_prefix": msg_prefix, - }, - ), - ) - - def test_assert_formset_error_old_api(self): - deprecation_msg = ( - "Passing response to assertFormSetError() is deprecated. Use the formset " - "object directly: assertFormSetError(response.context['formset'], 0, ...)" - ) - for args, kwargs in self._assert_formset_error_old_api_cases( - formset="formset", - form_index=0, - field="field", - errors=["invalid value"], - msg_prefix="Custom prefix", - ): - with self.subTest(args=args, kwargs=kwargs): - with self.assertWarnsMessage(RemovedInDjango50Warning, deprecation_msg): - self.assertFormSetError(*args, **kwargs) - - @ignore_warnings(category=RemovedInDjango50Warning) - def test_assert_formset_error_old_api_assertion_error(self): - for args, kwargs in self._assert_formset_error_old_api_cases( - formset="formset", - form_index=0, - field="field", - errors=["other error"], - msg_prefix="Custom prefix", - ): - with self.subTest(args=args, kwargs=kwargs): - with self.assertRaises(AssertionError): - self.assertFormSetError(*args, **kwargs) - - class FirstUrls: urlpatterns = [path("first/", empty_response, name="first")]