1
0
mirror of https://github.com/django/django.git synced 2024-12-23 01:25:58 +00:00

WIP: Refs #35281 -- Unified and generalized request error handling.

This commit is contained in:
Claude Paroz 2024-10-26 19:06:15 +02:00
parent 2debd018db
commit 73b904c5e0
16 changed files with 285 additions and 66 deletions

View File

@ -1,9 +1,3 @@
from django.urls import include from django.urls import include
from django.views import defaults
__all__ = ["handler400", "handler403", "handler404", "handler500", "include"] __all__ = ["include"]
handler400 = defaults.bad_request
handler403 = defaults.permission_denied
handler404 = defaults.page_not_found
handler500 = defaults.server_error

View File

@ -131,7 +131,7 @@ def check_custom_error_handlers(app_configs, **kwargs):
errors = [] errors = []
# All handlers take (request, exception) arguments except handler500 # All handlers take (request, exception) arguments except handler500
# which takes (request). # which takes (request).
for status_code, num_parameters in [(400, 2), (403, 2), (404, 2), (500, 1)]: for status_code, num_parameters in [(400, 2), (403, 2), (404, 2), (500, 2)]:
try: try:
handler = resolver.resolve_error_handler(status_code) handler = resolver.resolve_error_handler(status_code)
except (ImportError, ViewDoesNotExist) as e: except (ImportError, ViewDoesNotExist) as e:

View File

@ -211,6 +211,23 @@ class HttpResponseBase:
def get(self, header, alternate=None): def get(self, header, alternate=None):
return self.headers.get(header, alternate) return self.headers.get(header, alternate)
@classmethod
def response_class_by_status_code(cls, status_code):
return {
200: HttpResponse,
301: HttpResponsePermanentRedirect,
302: HttpResponseRedirect,
304: HttpResponseNotModified,
307: HttpResponseRedirect,
308: HttpResponsePermanentRedirect,
400: HttpResponseBadRequest,
403: HttpResponseForbidden,
404: HttpResponseNotFound,
405: HttpResponseNotAllowed,
410: HttpResponseGone,
500: HttpResponseServerError,
}.get(status_code, cls)
def set_cookie( def set_cookie(
self, self,
key, key,

View File

@ -10,6 +10,7 @@ import functools
import inspect import inspect
import re import re
import string import string
import warnings
from importlib import import_module from importlib import import_module
from pickle import PicklingError from pickle import PicklingError
from urllib.parse import quote from urllib.parse import quote
@ -21,6 +22,7 @@ from django.core.checks import Error, Warning
from django.core.checks.urls import check_resolver from django.core.checks.urls import check_resolver
from django.core.exceptions import ImproperlyConfigured from django.core.exceptions import ImproperlyConfigured
from django.utils.datastructures import MultiValueDict from django.utils.datastructures import MultiValueDict
from django.utils.deprecation import RemovedInDjango61Warning
from django.utils.functional import cached_property from django.utils.functional import cached_property
from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes from django.utils.http import RFC3986_SUBDELIMS, escape_leading_slashes
from django.utils.regex_helper import _lazy_re_compile, normalize from django.utils.regex_helper import _lazy_re_compile, normalize
@ -728,15 +730,26 @@ class URLResolver:
raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) from e raise ImproperlyConfigured(msg.format(name=self.urlconf_name)) from e
return patterns return patterns
def resolve_error_handler(self, view_type): def resolve_error_handler(self, status_code):
callback = getattr(self.urlconf_module, "handler%s" % view_type, None) # RemovedInDjango61Warning.
if not callback: callback = getattr(self.urlconf_module, f"handler{status_code}", None)
# No handler specified in file; use lazy import, since if callback:
# django.conf.urls imports this file. warnings.warn(
from django.conf import urls "handler<status> custom error handlers are deprecated, please "
"replace them by a generic `error_handler` view function.",
callback = getattr(urls, "handler%s" % view_type) RemovedInDjango61Warning,
)
return get_callable(callback) return get_callable(callback)
error_view = getattr(self.urlconf_module, "error_handler", None)
if error_view:
error_view.status_code = status_code
else:
# No handler specified in file; use lazy import, since
# django.views.defaults imports this file.
from django.views.defaults import DefaultErrorView
error_view = DefaultErrorView.as_view(status_code=status_code)
return error_view
def reverse(self, lookup_view, *args, **kwargs): def reverse(self, lookup_view, *args, **kwargs):
return self._reverse_with_prefix(lookup_view, "", *args, **kwargs) return self._reverse_with_prefix(lookup_view, "", *args, **kwargs)

View File

@ -1,13 +1,17 @@
from urllib.parse import quote from urllib.parse import quote
from django.http import ( from django.http import (
HttpResponse,
HttpResponseBadRequest, HttpResponseBadRequest,
HttpResponseForbidden, HttpResponseForbidden,
HttpResponseNotFound, HttpResponseNotFound,
HttpResponseServerError, HttpResponseServerError,
) )
from django.template import Context, Engine, TemplateDoesNotExist, loader from django.template import Context, Engine, TemplateDoesNotExist, loader
from django.utils.decorators import method_decorator
from django.views.debug import DEBUG_ENGINE
from django.views.decorators.csrf import requires_csrf_token from django.views.decorators.csrf import requires_csrf_token
from django.views.generic.base import ContextMixin, View
ERROR_404_TEMPLATE_NAME = "404.html" ERROR_404_TEMPLATE_NAME = "404.html"
ERROR_403_TEMPLATE_NAME = "403.html" ERROR_403_TEMPLATE_NAME = "403.html"
@ -148,3 +152,63 @@ def permission_denied(request, exception, template_name=ERROR_403_TEMPLATE_NAME)
return HttpResponseForbidden( return HttpResponseForbidden(
template.render(request=request, context={"exception": str(exception)}) template.render(request=request, context={"exception": str(exception)})
) )
@method_decorator(requires_csrf_token, name="dispatch")
class DefaultErrorView(ContextMixin, View):
status_code = None
context_by_status = {
400: {"title": "Bad Request (400)", "details": ""},
403: {"title": "403 Forbidden", "details": ""},
404: {
"title": "Not Found",
"details": "The requested resource was not found on this server.",
},
500: {"title": "Server Error (500)", "details": ""},
}
def setup(self, request, exception=None, **kwargs):
self.exception = exception
return super().setup(request, **kwargs)
def get(self, request, *args, **kwargs):
response_class = HttpResponse.response_class_by_status_code(self.status_code)
context = self.get_context_data(**kwargs)
try:
template = loader.get_template(self.get_template_name())
content = template.render(context, request)
except TemplateDoesNotExist:
template = DEBUG_ENGINE.from_string(ERROR_PAGE_TEMPLATE % context)
content = template.render(context=Context(context))
return response_class(content, status=self.status_code)
def post(self, *args, **kwargs):
return self.get(*args, **kwargs)
def get_template_name(self):
return f"{self.status_code}.html"
def get_context_data(self, **kwargs):
context = super().get_context_data(**kwargs)
context |= self.context_by_status.get(
self.status_code, {"title": f"Error ({self.status_code})", "details": ""}
)
context |= {
"request_path": quote(self.request.path),
"exception": self.exception_as_string(),
}
return context
def exception_as_string(self):
if self.status_code == 404:
# Try to get an "interesting" exception message, if any (and not the
# ugly Resolver404 dictionary)
try:
message = self.exception.args[0]
except (AttributeError, IndexError):
pass
else:
if isinstance(message, str):
return message
return self.exception.__class__.__name__
return str(self.exception)

View File

@ -1179,9 +1179,8 @@ details on these changes.
``django.contrib.gis.utils`` will be removed. ``django.contrib.gis.utils`` will be removed.
* ``django.conf.urls.defaults`` will be removed. The functions * ``django.conf.urls.defaults`` will be removed. The functions
``include()``, ``patterns()``, and ``url()``, plus ``include()``, ``patterns()``, and ``url()``, plus ``handler404` and
:data:`~django.conf.urls.handler404` and :data:`~django.conf.urls.handler500` ``handler500`` are now available through ``django.conf.urls``.
are now available through ``django.conf.urls``.
* The functions ``setup_environ()`` and ``execute_manager()`` will be removed * The functions ``setup_environ()`` and ``execute_manager()`` will be removed
from :mod:`django.core.management`. This also means that the old (pre-1.4) from :mod:`django.core.management`. This also means that the old (pre-1.4)

View File

@ -187,6 +187,11 @@ By default, this is :func:`django.views.defaults.bad_request`. If you
implement a custom view, be sure it accepts ``request`` and ``exception`` implement a custom view, be sure it accepts ``request`` and ``exception``
arguments and returns an :class:`~django.http.HttpResponseBadRequest`. arguments and returns an :class:`~django.http.HttpResponseBadRequest`.
.. deprecated:: 5.2
The ``handler<code>`` handlers are deprecated and should be replaced by a
generic ``error_handler`` view function.
``handler403`` ``handler403``
============== ==============
@ -200,6 +205,12 @@ By default, this is :func:`django.views.defaults.permission_denied`. If you
implement a custom view, be sure it accepts ``request`` and ``exception`` implement a custom view, be sure it accepts ``request`` and ``exception``
arguments and returns an :class:`~django.http.HttpResponseForbidden`. arguments and returns an :class:`~django.http.HttpResponseForbidden`.
.. deprecated:: 5.2
The ``handler<code>`` handlers are deprecated and should be replaced by a
generic ``error_handler`` view function.
``handler404`` ``handler404``
============== ==============
@ -212,6 +223,12 @@ By default, this is :func:`django.views.defaults.page_not_found`. If you
implement a custom view, be sure it accepts ``request`` and ``exception`` implement a custom view, be sure it accepts ``request`` and ``exception``
arguments and returns an :class:`~django.http.HttpResponseNotFound`. arguments and returns an :class:`~django.http.HttpResponseNotFound`.
.. deprecated:: 5.2
The ``handler<code>`` handlers are deprecated and should be replaced by a
generic ``error_handler`` view function.
``handler500`` ``handler500``
============== ==============
@ -224,3 +241,8 @@ have runtime errors in view code.
By default, this is :func:`django.views.defaults.server_error`. If you By default, this is :func:`django.views.defaults.server_error`. If you
implement a custom view, be sure it accepts a ``request`` argument and returns implement a custom view, be sure it accepts a ``request`` argument and returns
an :class:`~django.http.HttpResponseServerError`. an :class:`~django.http.HttpResponseServerError`.
.. deprecated:: 5.2
The ``handler<code>`` handlers are deprecated and should be replaced by a
generic ``error_handler`` view function.

View File

@ -58,8 +58,61 @@ parameter will be transparently passed to the view.
Error views Error views
=========== ===========
Django comes with a few views by default for handling HTTP errors. To override When any uncaught exception is produced by a view of your site, Django is
these with your own custom views, see :ref:`customizing-error-views`. calling an error handling view, which is by default the following view.
.. _default_error_view:
The default error view
----------------------
.. class:: defaults.DefaultErrorView
.. versionadded:: 5.2
The view is a class-based-view inheriting from
:class:`~django.views.generic.base.ContextMixin`
and class:`~django.views.generic.base.View`, so refer to theses classes for
common attributes and methods.
.. attribute:: status_code
The status code of the HTTP response to return.
.. attribute:: exception
The exception that produced the error leading to the call of this error
view.
.. method:: get_template_name()
Return by default a name on the pattern ``<status_code>.html``.
.. method:: exception_as_string()
Produce a string from the exception having triggered the error view.
If you provide any template in your root template directory named after an error
response code (``404.html``, ``500.html``, etc.), that template will be used to
produce an error path. Otherwise, Django will produce a very simple page
containing an error title and a standard message.
The context used to render the error templates contains the following keys:
* ``title``: A short title for the error (like ``Not Found`` for a 404
error).
* ``details``: A short message explaining the error, possibley empty.
* ``request_path``: The original request path of the view producing the
error, quoted to prevent a content injection attack.
* ``exception``: The uncaught exception that triggered the error view, as a
string.
which either produces a "Not
Found" message or loads and renders the template ``404.html`` if you created it
in your root template directory.
To override this default view with your custom view, see
:ref:`customizing-error-views`.
.. _http_not_found_view: .. _http_not_found_view:
@ -68,6 +121,11 @@ The 404 (page not found) view
.. function:: defaults.page_not_found(request, exception, template_name='404.html') .. function:: defaults.page_not_found(request, exception, template_name='404.html')
.. deprecated:: 5.2
This view is deprecated as it has been replaced by the :ref:`default error
view <_default_error_view>`.
When you raise :exc:`~django.http.Http404` from within a view, Django loads a When you raise :exc:`~django.http.Http404` from within a view, Django loads a
special view devoted to handling 404 errors. By default, it's the view special view devoted to handling 404 errors. By default, it's the view
:func:`django.views.defaults.page_not_found`, which either produces a "Not :func:`django.views.defaults.page_not_found`, which either produces a "Not
@ -99,6 +157,11 @@ The 500 (server error) view
.. function:: defaults.server_error(request, template_name='500.html') .. function:: defaults.server_error(request, template_name='500.html')
.. deprecated:: 5.2
This view is deprecated as it has been replaced by the :ref:`default error
view <_default_error_view>`.
Similarly, Django executes special-case behavior in the case of runtime errors Similarly, Django executes special-case behavior in the case of runtime errors
in view code. If a view results in an exception, Django will, by default, call in view code. If a view results in an exception, Django will, by default, call
the view ``django.views.defaults.server_error``, which either produces a the view ``django.views.defaults.server_error``, which either produces a
@ -119,6 +182,11 @@ The 403 (HTTP Forbidden) view
.. function:: defaults.permission_denied(request, exception, template_name='403.html') .. function:: defaults.permission_denied(request, exception, template_name='403.html')
.. deprecated:: 5.2
This view is deprecated as it has been replaced by the :ref:`default error
view <_default_error_view>`.
In the same vein as the 404 and 500 views, Django has a view to handle 403 In the same vein as the 404 and 500 views, Django has a view to handle 403
Forbidden errors. If a view results in a 403 exception then Django will, by Forbidden errors. If a view results in a 403 exception then Django will, by
default, call the view ``django.views.defaults.permission_denied``. default, call the view ``django.views.defaults.permission_denied``.
@ -148,6 +216,11 @@ The 400 (bad request) view
.. function:: defaults.bad_request(request, exception, template_name='400.html') .. function:: defaults.bad_request(request, exception, template_name='400.html')
.. deprecated:: 5.2
This view is deprecated as it has been replaced by the :ref:`default error
view <_default_error_view>`.
When a :exc:`~django.core.exceptions.SuspiciousOperation` is raised in Django, When a :exc:`~django.core.exceptions.SuspiciousOperation` is raised in Django,
it may be handled by a component of Django (for example resetting the session it may be handled by a component of Django (for example resetting the session
data). If not specifically handled, Django will consider the current request a data). If not specifically handled, Django will consider the current request a

View File

@ -342,25 +342,28 @@ Error handling
When Django can't find a match for the requested URL, or when an exception is When Django can't find a match for the requested URL, or when an exception is
raised, Django invokes an error-handling view. raised, Django invokes an error-handling view.
The views to use for these cases are specified by four variables. Their The default view Django uses for these cases is
default values should suffice for most projects, but further customization is :class:`django.views.defaults.DefaultErrorView`. This view should suffice for
possible by overriding their default values. most projects but further customization is possible by defining a custom view
to handle errors, either by subclassing this view or by creating your own.
See the documentation on :ref:`customizing error views See the documentation on :ref:`customizing error view
<customizing-error-views>` for the full details. <customizing-error-views>` for the full details.
Such values can be set in your root URLconf. Setting these variables in any .. versionchanged:: 5.2
other URLconf will have no effect.
Values must be callables, or strings representing the full Python import path In previous versions, the views to use for these cases are specified by four
to the view that should be called to handle the error condition at hand. variables.
The variables are: Values must be callables, or strings representing the full Python import path
to the view that should be called to handle the error condition at hand.
* ``handler400`` -- See :data:`django.conf.urls.handler400`. The variables are:
* ``handler403`` -- See :data:`django.conf.urls.handler403`.
* ``handler404`` -- See :data:`django.conf.urls.handler404`. * ``handler400``
* ``handler500`` -- See :data:`django.conf.urls.handler500`. * ``handler403``
* ``handler404``
* ``handler500``
.. _including-other-urlconfs: .. _including-other-urlconfs:

View File

@ -138,31 +138,44 @@ template.
.. _customizing-error-views: .. _customizing-error-views:
Customizing error views Customizing error view
======================= ======================
The default error views in Django should suffice for most web applications, The default error view in Django should suffice for most web applications, but
but can easily be overridden if you need any custom behavior. Specify the but can easily be overridden if you need any custom behavior.
handlers as seen below in your URLconf (setting them anywhere else will have no
effect).
The :func:`~django.views.defaults.page_not_found` view is overridden by The default error view can be overridden by setting a view in the
:data:`~django.conf.urls.handler404`:: ``error_handler`` variable in your URLconf (setting it anywhere else will have
no effect)::
error_handler = views.my_custom_error_view
or if you have a class-based-view::
error_handler = views.MyCustomErrorView.as_view()
.. versionchanged:: 5.2
On previous versions, there are multiple variables to define to override
particular views.
The :func:`~django.views.defaults.page_not_found` view is overridden by
:data:`~django.conf.urls.handler404`::
handler404 = "mysite.views.my_custom_page_not_found_view" handler404 = "mysite.views.my_custom_page_not_found_view"
The :func:`~django.views.defaults.server_error` view is overridden by The :func:`~django.views.defaults.server_error` view is overridden by
:data:`~django.conf.urls.handler500`:: :data:`~django.conf.urls.handler500`::
handler500 = "mysite.views.my_custom_error_view" handler500 = "mysite.views.my_custom_error_view"
The :func:`~django.views.defaults.permission_denied` view is overridden by The :func:`~django.views.defaults.permission_denied` view is overridden by
:data:`~django.conf.urls.handler403`:: :data:`~django.conf.urls.handler403`::
handler403 = "mysite.views.my_custom_permission_denied_view" handler403 = "mysite.views.my_custom_permission_denied_view"
The :func:`~django.views.defaults.bad_request` view is overridden by The :func:`~django.views.defaults.bad_request` view is overridden by
:data:`~django.conf.urls.handler400`:: :data:`~django.conf.urls.handler400`::
handler400 = "mysite.views.my_custom_bad_request_view" handler400 = "mysite.views.my_custom_bad_request_view"

View File

@ -8,8 +8,9 @@ from django.core.checks.urls import (
check_url_settings, check_url_settings,
get_warning_for_invalid_pattern, get_warning_for_invalid_pattern,
) )
from django.test import SimpleTestCase from django.test import SimpleTestCase, ignore_warnings
from django.test.utils import override_settings from django.test.utils import override_settings
from django.utils.deprecation import RemovedInDjango61Warning
class CheckUrlConfigTests(SimpleTestCase): class CheckUrlConfigTests(SimpleTestCase):
@ -243,10 +244,11 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
@override_settings( @override_settings(
ROOT_URLCONF="check_framework.urls.bad_function_based_error_handlers", ROOT_URLCONF="check_framework.urls.bad_function_based_error_handlers",
) )
@ignore_warnings(category=RemovedInDjango61Warning)
def test_bad_function_based_handlers(self): def test_bad_function_based_handlers(self):
result = check_custom_error_handlers(None) result = check_custom_error_handlers(None)
self.assertEqual(len(result), 4) self.assertEqual(len(result), 4)
for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result): for code, error in zip([400, 403, 404, 500], result):
with self.subTest("handler{}".format(code)): with self.subTest("handler{}".format(code)):
self.assertEqual( self.assertEqual(
error, error,
@ -254,9 +256,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
"The custom handler{} view 'check_framework.urls." "The custom handler{} view 'check_framework.urls."
"bad_function_based_error_handlers.bad_handler' " "bad_function_based_error_handlers.bad_handler' "
"does not take the correct number of arguments " "does not take the correct number of arguments "
"(request{}).".format( "(request, exception).".format(code),
code, ", exception" if num_params == 2 else ""
),
id="urls.E007", id="urls.E007",
), ),
) )
@ -264,10 +264,11 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
@override_settings( @override_settings(
ROOT_URLCONF="check_framework.urls.bad_class_based_error_handlers", ROOT_URLCONF="check_framework.urls.bad_class_based_error_handlers",
) )
@ignore_warnings(category=RemovedInDjango61Warning)
def test_bad_class_based_handlers(self): def test_bad_class_based_handlers(self):
result = check_custom_error_handlers(None) result = check_custom_error_handlers(None)
self.assertEqual(len(result), 4) self.assertEqual(len(result), 4)
for code, num_params, error in zip([400, 403, 404, 500], [2, 2, 2, 1], result): for code, error in zip([400, 403, 404, 500], result):
with self.subTest("handler%s" % code): with self.subTest("handler%s" % code):
self.assertEqual( self.assertEqual(
error, error,
@ -275,11 +276,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
"The custom handler%s view 'check_framework.urls." "The custom handler%s view 'check_framework.urls."
"bad_class_based_error_handlers.HandlerView.as_view." "bad_class_based_error_handlers.HandlerView.as_view."
"<locals>.view' does not take the correct number of " "<locals>.view' does not take the correct number of "
"arguments (request%s)." "arguments (request, exception)." % code,
% (
code,
", exception" if num_params == 2 else "",
),
id="urls.E007", id="urls.E007",
), ),
) )
@ -287,6 +284,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
@override_settings( @override_settings(
ROOT_URLCONF="check_framework.urls.bad_error_handlers_invalid_path" ROOT_URLCONF="check_framework.urls.bad_error_handlers_invalid_path"
) )
@ignore_warnings(category=RemovedInDjango61Warning)
def test_bad_handlers_invalid_path(self): def test_bad_handlers_invalid_path(self):
result = check_custom_error_handlers(None) result = check_custom_error_handlers(None)
paths = [ paths = [
@ -318,6 +316,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
@override_settings( @override_settings(
ROOT_URLCONF="check_framework.urls.good_function_based_error_handlers", ROOT_URLCONF="check_framework.urls.good_function_based_error_handlers",
) )
@ignore_warnings(category=RemovedInDjango61Warning)
def test_good_function_based_handlers(self): def test_good_function_based_handlers(self):
result = check_custom_error_handlers(None) result = check_custom_error_handlers(None)
self.assertEqual(result, []) self.assertEqual(result, [])
@ -325,6 +324,7 @@ class CheckCustomErrorHandlersTests(SimpleTestCase):
@override_settings( @override_settings(
ROOT_URLCONF="check_framework.urls.good_class_based_error_handlers", ROOT_URLCONF="check_framework.urls.good_class_based_error_handlers",
) )
@ignore_warnings(category=RemovedInDjango61Warning)
def test_good_class_based_handlers(self): def test_good_class_based_handlers(self):
result = check_custom_error_handlers(None) result = check_custom_error_handlers(None)
self.assertEqual(result, []) self.assertEqual(result, [])

View File

@ -22,7 +22,8 @@ from django.middleware.csrf import (
get_token, get_token,
rotate_token, rotate_token,
) )
from django.test import SimpleTestCase, override_settings from django.test import SimpleTestCase, ignore_warnings, override_settings
from django.utils.deprecation import RemovedInDjango61Warning
from django.views.decorators.csrf import csrf_exempt, requires_csrf_token from django.views.decorators.csrf import csrf_exempt, requires_csrf_token
from .views import ( from .views import (
@ -1477,6 +1478,7 @@ class CsrfViewMiddlewareUseSessionsTests(CsrfViewMiddlewareTestMixin, SimpleTest
@override_settings(ROOT_URLCONF="csrf_tests.csrf_token_error_handler_urls", DEBUG=False) @override_settings(ROOT_URLCONF="csrf_tests.csrf_token_error_handler_urls", DEBUG=False)
class CsrfInErrorHandlingViewsTests(CsrfFunctionTestMixin, SimpleTestCase): class CsrfInErrorHandlingViewsTests(CsrfFunctionTestMixin, SimpleTestCase):
@ignore_warnings(category=RemovedInDjango61Warning)
def test_csrf_token_on_404_stays_constant(self): def test_csrf_token_on_404_stays_constant(self):
response = self.client.get("/does not exist/") response = self.client.get("/does not exist/")
# The error handler returns status code 599. # The error handler returns status code 599.

View File

@ -1,7 +1,13 @@
from django.core.exceptions import PermissionDenied from django.core.exceptions import PermissionDenied
from django.template.response import TemplateResponse from django.template.response import TemplateResponse
from django.test import SimpleTestCase, modify_settings, override_settings from django.test import (
SimpleTestCase,
ignore_warnings,
modify_settings,
override_settings,
)
from django.urls import path from django.urls import path
from django.utils.deprecation import RemovedInDjango61Warning
class MiddlewareAccessingContent: class MiddlewareAccessingContent:
@ -38,6 +44,7 @@ handler403 = template_response_error_handler
} }
) )
class CustomErrorHandlerTests(SimpleTestCase): class CustomErrorHandlerTests(SimpleTestCase):
@ignore_warnings(category=RemovedInDjango61Warning)
def test_handler_renders_template_response(self): def test_handler_renders_template_response(self):
""" """
BaseHandler should render TemplateResponse if necessary. BaseHandler should render TemplateResponse if necessary.

View File

@ -13,7 +13,13 @@ from django.contrib.auth.models import User
from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist from django.core.exceptions import ImproperlyConfigured, ViewDoesNotExist
from django.http import HttpRequest, HttpResponsePermanentRedirect, HttpResponseRedirect from django.http import HttpRequest, HttpResponsePermanentRedirect, HttpResponseRedirect
from django.shortcuts import redirect from django.shortcuts import redirect
from django.test import RequestFactory, SimpleTestCase, TestCase, override_settings from django.test import (
RequestFactory,
SimpleTestCase,
TestCase,
ignore_warnings,
override_settings,
)
from django.test.utils import override_script_prefix from django.test.utils import override_script_prefix
from django.urls import ( from django.urls import (
NoReverseMatch, NoReverseMatch,
@ -32,6 +38,7 @@ from django.urls import (
reverse_lazy, reverse_lazy,
) )
from django.urls.resolvers import RegexPattern from django.urls.resolvers import RegexPattern
from django.utils.deprecation import RemovedInDjango61Warning
from . import middleware, urlconf_outer, views from . import middleware, urlconf_outer, views
from .utils import URLObject from .utils import URLObject
@ -1473,12 +1480,15 @@ class ErrorHandlerResolutionTests(SimpleTestCase):
self.resolver = URLResolver(RegexPattern(r"^$"), urlconf) self.resolver = URLResolver(RegexPattern(r"^$"), urlconf)
self.callable_resolver = URLResolver(RegexPattern(r"^$"), urlconf_callables) self.callable_resolver = URLResolver(RegexPattern(r"^$"), urlconf_callables)
@ignore_warnings(category=RemovedInDjango61Warning)
def test_named_handlers(self): def test_named_handlers(self):
for code in [400, 403, 404, 500]: for code in [400, 403, 404, 500]:
with self.subTest(code=code): with self.subTest(code=code):
self.assertEqual(self.resolver.resolve_error_handler(code), empty_view) self.assertEqual(self.resolver.resolve_error_handler(code), empty_view)
@ignore_warnings(category=RemovedInDjango61Warning)
def test_callable_handlers(self): def test_callable_handlers(self):
# After Django 6.1 removal, only test with 'error_handler' and one code.
for code in [400, 403, 404, 500]: for code in [400, 403, 404, 500]:
with self.subTest(code=code): with self.subTest(code=code):
self.assertEqual( self.assertEqual(

View File

@ -4,7 +4,9 @@ from .views import empty_view
urlpatterns = [] urlpatterns = []
# RemovedInDjango61Warning: the handler<...> variables can be removed
handler400 = empty_view handler400 = empty_view
handler403 = empty_view handler403 = empty_view
handler404 = empty_view handler404 = empty_view
handler500 = empty_view handler500 = empty_view
error_handler = empty_view

View File

@ -1,4 +1,4 @@
# A URLconf that doesn't define any handlerXXX. # A URLconf that doesn't define any error handler view.
from django.urls import path from django.urls import path
from .views import bad_view, empty_view from .views import bad_view, empty_view