mirror of
https://github.com/django/django.git
synced 2024-12-22 17:16:24 +00:00
WIP: Refs #35281 -- Unified and generalized request error handling.
This commit is contained in:
parent
2debd018db
commit
73b904c5e0
@ -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
|
|
||||||
|
@ -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:
|
||||||
|
@ -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,
|
||||||
|
@ -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)
|
||||||
|
if callback:
|
||||||
|
warnings.warn(
|
||||||
|
"handler<status> custom error handlers are deprecated, please "
|
||||||
|
"replace them by a generic `error_handler` view function.",
|
||||||
|
RemovedInDjango61Warning,
|
||||||
|
)
|
||||||
|
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
|
# No handler specified in file; use lazy import, since
|
||||||
# django.conf.urls imports this file.
|
# django.views.defaults imports this file.
|
||||||
from django.conf import urls
|
from django.views.defaults import DefaultErrorView
|
||||||
|
|
||||||
callback = getattr(urls, "handler%s" % view_type)
|
error_view = DefaultErrorView.as_view(status_code=status_code)
|
||||||
return get_callable(callback)
|
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)
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
||||||
|
@ -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
|
||||||
|
@ -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:
|
||||||
|
|
||||||
|
@ -138,33 +138,46 @@ 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)::
|
||||||
|
|
||||||
handler404 = "mysite.views.my_custom_page_not_found_view"
|
error_handler = views.my_custom_error_view
|
||||||
|
|
||||||
The :func:`~django.views.defaults.server_error` view is overridden by
|
or if you have a class-based-view::
|
||||||
:data:`~django.conf.urls.handler500`::
|
|
||||||
|
|
||||||
handler500 = "mysite.views.my_custom_error_view"
|
error_handler = views.MyCustomErrorView.as_view()
|
||||||
|
|
||||||
The :func:`~django.views.defaults.permission_denied` view is overridden by
|
.. versionchanged:: 5.2
|
||||||
:data:`~django.conf.urls.handler403`::
|
|
||||||
|
|
||||||
handler403 = "mysite.views.my_custom_permission_denied_view"
|
On previous versions, there are multiple variables to define to override
|
||||||
|
particular views.
|
||||||
|
|
||||||
The :func:`~django.views.defaults.bad_request` view is overridden by
|
The :func:`~django.views.defaults.page_not_found` view is overridden by
|
||||||
:data:`~django.conf.urls.handler400`::
|
:data:`~django.conf.urls.handler404`::
|
||||||
|
|
||||||
handler400 = "mysite.views.my_custom_bad_request_view"
|
handler404 = "mysite.views.my_custom_page_not_found_view"
|
||||||
|
|
||||||
|
The :func:`~django.views.defaults.server_error` view is overridden by
|
||||||
|
:data:`~django.conf.urls.handler500`::
|
||||||
|
|
||||||
|
handler500 = "mysite.views.my_custom_error_view"
|
||||||
|
|
||||||
|
The :func:`~django.views.defaults.permission_denied` view is overridden by
|
||||||
|
:data:`~django.conf.urls.handler403`::
|
||||||
|
|
||||||
|
handler403 = "mysite.views.my_custom_permission_denied_view"
|
||||||
|
|
||||||
|
The :func:`~django.views.defaults.bad_request` view is overridden by
|
||||||
|
:data:`~django.conf.urls.handler400`::
|
||||||
|
|
||||||
|
handler400 = "mysite.views.my_custom_bad_request_view"
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
|
@ -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, [])
|
||||||
|
@ -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.
|
||||||
|
@ -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.
|
||||||
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
@ -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
|
||||||
|
Loading…
Reference in New Issue
Block a user