如何在Django开发环境中测试500.html错误页面?

问题描述 投票:47回答:9

我正在将Django用于项目,并且已经在生产中。

在生产环境中,每当发生服务器错误时都会呈现500.html。

如何在开发环境中测试500.html的呈现?或如何在开发中渲染500.html,如果我关闭调试,仍然会收到错误,而不是500.html

背景:我包含一些基于页面的页面元素,当调用500.html并想在开发环境中对其进行调试时,某些元素将丢失。

django development-environment
9个回答
46
投票

我不想关闭调试。相反,我将以下代码段放在urls.py中:

if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^500/$', 'your_custom_view_if_you_wrote_one'),
        (r'^404/$', 'django.views.generic.simple.direct_to_template', {'template': '404.html'}),
    )

在上面的代码段中,错误页面使用了自定义视图,不过您可以轻松地将其替换为Django的direct_to_template视图。

现在您可以通过调用它们的网址来测试500和404页面:http://example.com/500http://example.com/404


28
投票

在Django 1.6中django.views.generic.simple.direct_to_template不再存在,这些是我对特殊视图的设置:

# urls.py

from django.views.generic import TemplateView
from django.views.defaults import page_not_found, server_error

urlpatterns += [
    url(r'^400/$', TemplateView.as_view(template_name='400.html')),
    url(r'^403/$', TemplateView.as_view(template_name='403.html')),
    url(r'^404/$', page_not_found),
    url(r'^500/$', server_error),
]

17
投票

并且如果您想使用默认的Django 500视图而不是自定义视图:

if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^500/$', 'django.views.defaults.server_error'),
        (r'^404/$', 'django.views.generic.simple.direct_to_template', {'template': '404.html'}),
    )

7
投票

继续shanyu's answer,在Django 1.3+中使用:

if settings.DEBUG:
    urlpatterns += patterns('',
        (r'^500/$', 'django.views.defaults.server_error'),
        (r'^404/$', 'django.views.defaults.page_not_found'),
    )

5
投票

两个调试设置都为假吗?

settings.DEBUG = False
settings.TEMPLATE_DEBUG = False

1
投票

urls.py

handler500 = 'project.apps.core.views.handler500'
handler404 = 'project.apps.core.views.handler404'

views.py

from django.template.loader import get_template
from django.template import Context
from django.http import HttpResponseServerError, HttpResponseNotFound


def handler500(request, template_name='500.html'):
    t = get_template(template_name)
    ctx = Context({})
    return HttpResponseServerError(t.render(ctx))


def handler404(request, template_name='404.html'):
    t = get_template(template_name)
    ctx = Context({})
    return HttpResponseNotFound(t.render(ctx))

tests.py

from django.test import TestCase
from django.test.client import RequestFactory

from project import urls

from ..views import handler404, handler500


class TestErrorPages(TestCase):

    def test_error_handlers(self):
        self.assertTrue(urls.handler404.endswith('.handler404'))
        self.assertTrue(urls.handler500.endswith('.handler500'))
        factory = RequestFactory()
        request = factory.get('/')
        response = handler404(request)
        self.assertEqual(response.status_code, 404)
        self.assertIn('404 Not Found!!', unicode(response))
        response = handler500(request)
        self.assertEqual(response.status_code, 500)
        self.assertIn('500 Internal Server Error', unicode(response))

1
投票

我如何做和测试自定义错误处理程序

基于View定义自定义TemplateView

# views.py
from django.views.generic import TemplateView

class ErrorHandler(TemplateView):

    """ Render error template """

    error_code = 404
    template_name = 'index/error.html'

    def dispatch(self, request, *args, **kwargs):
        """ For error on any methods return just GET """
        return self.get(request, *args, **kwargs)

    def get_context_data(self, **kwargs):
        context = super().get_context_data(**kwargs)
        context['error_code'] = self.error_code
        return context

    def render_to_response(self, context, **response_kwargs):
        """ Return correct status code """
        response_kwargs = response_kwargs or {}
        response_kwargs.update(status=self.error_code)
        return super().render_to_response(context, **response_kwargs)

告诉Django使用自定义错误处理程序

# urls.py

from index.views import ErrorHandler

# error handing handlers - fly binding
for code in (400, 403, 404, 500):
    vars()['handler{}'.format(code)] = ErrorHandler.as_view(error_code=code)

自定义错误处理程序的测试用例

# tests.py

from unittest import mock

from django.test import TestCase
from django.core.exceptions import SuspiciousOperation, PermissionDenied
from django.http import Http404
from index import views

class ErrorHandlersTestCase(TestCase):

    """ Check is correct error handlers work """

    def raise_(exception):
        def wrapped(*args, **kwargs):
            raise exception('Test exception')
        return wrapped

    def test_index_page(self):
        """ Should check is 200 on index page """
        response = self.client.get('/')
        self.assertEqual(response.status_code, 200)
        self.assertTemplateUsed(response, 'index/index.html')

    @mock.patch('index.views.IndexView.get', raise_(Http404))
    def test_404_page(self):
        """ Should check is 404 page correct """
        response = self.client.get('/')
        self.assertEqual(response.status_code, 404)
        self.assertTemplateUsed(response, 'index/error.html')
        self.assertIn('404 Page not found', response.content.decode('utf-8'))

    @mock.patch('index.views.IndexView.get', views.ErrorHandler.as_view(error_code=500))
    def test_500_page(self):
        """ Should check is 500 page correct """
        response = self.client.get('/')
        self.assertEqual(response.status_code, 500)
        self.assertTemplateUsed(response, 'index/error.html')
        self.assertIn('500 Server Error', response.content.decode('utf-8'))

    @mock.patch('index.views.IndexView.get', raise_(SuspiciousOperation))
    def test_400_page(self):
        """ Should check is 400 page correct """
        response = self.client.get('/')
        self.assertEqual(response.status_code, 400)
        self.assertTemplateUsed(response, 'index/error.html')
        self.assertIn('400 Bad request', response.content.decode('utf-8'))

    @mock.patch('index.views.IndexView.get', raise_(PermissionDenied))
    def test_403_page(self):
        """ Should check is 403 page correct """
        response = self.client.get('/')
        self.assertEqual(response.status_code, 403)
        self.assertTemplateUsed(response, 'index/error.html')
        self.assertIn('403 Permission Denied', response.content.decode('utf-8'))

0
投票

您可以简单地在主handler404文件中为错误定义handler500views.py,如此答案中所述:

https://stackoverflow.com/a/18009660/1913888

这将返回Django路由到该处理程序时所需的错误。无需自定义URL配置即可路由到其他URL名称。


0
投票

在Django版本<3.0中,您应该执行以下操作:

client.py

from django.core.signals import got_request_exception
from django.template import TemplateDoesNotExist
from django.test import signals
from django.test.client import Client as DjangoClient, store_rendered_templates
from django.urls import resolve
from django.utils import six
from django.utils.functional import SimpleLazyObject, curry


class Client(DjangoClient):
    """Test client that does not raise Exceptions if requested."""

    def __init__(self, 
                 enforce_csrf_checks=False, 
                 raise_request_exception=True, **defaults):
        super(Client, self).__init__(enforce_csrf_checks=enforce_csrf_checks, 
                                     **defaults)
        self.raise_request_exception = raise_request_exception

    def request(self, **request):
        """
        The master request method. Composes the environment dictionary
        and passes to the handler, returning the result of the handler.
        Assumes defaults for the query environment, which can be overridden
        using the arguments to the request.
        """
        environ = self._base_environ(**request)

        # Curry a data dictionary into an instance of the template renderer
        # callback function.
        data = {}
        on_template_render = curry(store_rendered_templates, data)
        signal_uid = "template-render-%s" % id(request)
        signals.template_rendered.connect(on_template_render, 
                                          dispatch_uid=signal_uid)
        # Capture exceptions created by the handler.
        exception_uid = "request-exception-%s" % id(request)
        got_request_exception.connect(self.store_exc_info, 
                                      dispatch_uid=exception_uid)
        try:
            try:
                response = self.handler(environ)
            except TemplateDoesNotExist as e:
                # If the view raises an exception, Django will attempt to show
                # the 500.html template. If that template is not available,
                # we should ignore the error in favor of re-raising the
                # underlying exception that caused the 500 error. Any other
                # template found to be missing during view error handling
                # should be reported as-is.
                if e.args != ('500.html',):
                    raise

            # Look for a signalled exception, clear the current context
            # exception data, then re-raise the signalled exception.
            # Also make sure that the signalled exception is cleared from
            # the local cache!
            response.exc_info = self.exc_info  # Patch exception handling
            if self.exc_info:
                exc_info = self.exc_info
                self.exc_info = None
                if self.raise_request_exception:  # Patch exception handling
                    six.reraise(*exc_info)

            # Save the client and request that stimulated the response.
            response.client = self
            response.request = request

            # Add any rendered template detail to the response.
            response.templates = data.get("templates", [])
            response.context = data.get("context")

            response.json = curry(self._parse_json, response)

            # Attach the ResolverMatch instance to the response
            response.resolver_match = SimpleLazyObject(
                lambda: resolve(request['PATH_INFO'])
            )

            # Flatten a single context. Not really necessary anymore thanks to
            # the __getattr__ flattening in ContextList, but has some edge-case
            # backwards-compatibility implications.
            if response.context and len(response.context) == 1:
                response.context = response.context[0]

            # Update persistent cookie data.
            if response.cookies:
                self.cookies.update(response.cookies)

            return response
        finally:
            signals.template_rendered.disconnect(dispatch_uid=signal_uid)
            got_request_exception.disconnect(dispatch_uid=exception_uid)

tests.py

from unittest import mock

from django.contrib.auth import get_user_model
from django.core.urlresolvers import reverse
from django.test import TestCase, override_settings

from .client import Client  # Important, we use our own Client here!


class TestErrors(TestCase):
    """Test errors."""

    @classmethod
    def setUpClass(cls):
        super(TestErrors, cls).setUpClass()
        cls.username = 'admin'
        cls.email = 'admin@localhost'
        cls.password = 'test1234test1234'
        cls.not_found_url = '/i-do-not-exist/'
        cls.internal_server_error_url = reverse('password_reset')

    def setUp(self):
        super(TestErrors, self).setUp()
        User = get_user_model()

        User.objects.create_user(
            self.username,
            self.email,
            self.password,
            is_staff=True,
            is_active=True
        )
        self.client = Client(raise_request_exception=False)

    # Mock in order to trigger Exception and resulting Internal server error
    @mock.patch('django.contrib.auth.views.PasswordResetView.form_class', None)
    @override_settings(DEBUG=False)
    def test_errors(self):
        self.client.login(username=self.username, password=self.password)

        with self.subTest("Not found (404)"):
            response = self.client.get(self.not_found_url, follow=True)
            self.assertNotIn('^admin/', str(response.content))

        with self.subTest("Internal server error (500)"):
            response = self.client.get(self.internal_server_error_url, 
                                       follow=True)
            self.assertNotIn('TypeError', str(response.content))

[从Django 3.0开始,您可以跳过自定义Client定义,而仅使用tests.py中的代码。

© www.soinside.com 2019 - 2024. All rights reserved.