Refs #33348 -- Removed support for passing response object and form/formset name to SimpleTestCase.assertFormError()/assertFormSetError().

Per deprecation timeline.
This commit is contained in:
Mariusz Felisiak 2023-01-13 08:31:30 +01:00
parent d6816bff73
commit 71d1203b07
4 changed files with 6 additions and 389 deletions

View File

@ -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.

View File

@ -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.

View File

@ -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

View File

@ -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")]