【问题标题】:How to test 500.html error page in django development env?如何在 django 开发环境中测试 500.html 错误页面?
【发布时间】:2011-02-13 23:07:03
【问题描述】:

我正在为一个项目使用 Django,并且已经投入生产。

在生产环境中,只要发生服务器错误,就会渲染 500.html。

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

背景:我包含一些基于页面的页面元素,并且在调用 500.html 并希望在开发环境中对其进行调试时缺少一些元素。

【问题讨论】:

    标签: django development-environment


    【解决方案1】:

    两个调试设置都是假的吗?

    settings.DEBUG = False
    settings.TEMPLATE_DEBUG = False
    

    【讨论】:

    • 您确定没有多个可能会相互覆盖的设置文件吗?
    • 我错了多个设置,我仍然收到模板调试消息。
    【解决方案2】:

    我不想关闭 DEBUG。相反,我将以下 sn-p 放在 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'}),
        )
    

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

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

    【讨论】:

    • 如果您只想查看它们的外观,这很有用,但请不要误以为这不是“测试”您应用中的 http 错误代码。
    • 从 v1.5 开始,direct_to_template 消失了。使用例如from django.views.generic.base import TemplateView 然后url(r'^tests/404/$', TemplateView.as_view(template_name='404.html'), name='test404'),
    • 从 Django v3.0 开始,使用如下内容:path("404.html", TemplateView.as_view(template_name="404.html"), name="404-test")
    【解决方案3】:

    如果你想使用默认的 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'}),
        )
    

    【讨论】:

      【解决方案4】:

      继续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'),
          )
      

      【讨论】:

      • 在 django 1.4 中工作。 (使用 debug=True)
      【解决方案5】:

      在 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),
      ]
      

      【讨论】:

      • 这很好,但需要一个点django.views.defaults
      • 嗯,我得到 page_not_found() 至少需要 2 个参数(1 个给定)
      • 在更高版本的 Django 中(不确定何时更改),使用url(r"^404/$", page_not_found, {'exception': Http404()})
      • ... 你可能需要from django.http import Http404
      【解决方案6】:

      您可以简单地为您的主 views.py 文件中的错误定义 handler404handler500,如本答案所述:

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

      当 Django 路由到该处理程序时,这将返回您想要的错误。无需自定义 URL 配置即可路由到不同的 URL 名称。

      【讨论】:

        【解决方案7】:

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

        【讨论】:

          【解决方案8】:

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

          基于TemplateView定义自定义View

          # 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'))
          

          【讨论】:

            【解决方案9】:

            在 Django 版本

            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 的代码。

            【讨论】:

              【解决方案10】:

              更新 Django > 1.6 并且没有得到 ​​p>

              page_not_found() missing 1 required positional argument: 'exception'
              

              灵感来自this answer

              # urls.py
              from django.views.defaults import page_not_found, server_error, permission_denied, bad_request
              [...]
              if settings.DEBUG:
                  # This allows the error pages to be debugged during development, just visit
                  # these url in browser to see how these error pages look like.
                  urlpatterns += [
                  path('400/', bad_request, kwargs={'exception': Exception('Bad Request!')}),
                  path('403/', permission_denied, kwargs={'exception': Exception('Permission Denied')}),
                  path('404/', page_not_found, kwargs={'exception': Exception('Page not Found')}),
                  path('500/', server_error),
              

              【讨论】:

                【解决方案11】:

                对于 Django > 3.0,只需将 raise_request_exception 值设置为 False

                from django.test import TestCase
                
                
                class ViewTestClass(TestCase):
                    def test_error_page(self):
                        self.client.raise_request_exception = False
                        response = self.client.get(reverse('error-page'))
                        self.assertEqual(response.status_code, 500)
                        self.assertTrue(
                            'some text from the custom 500 page'
                            in response.content.decode('utf8'))
                
                

                文档:https://docs.djangoproject.com/en/3.2/topics/testing/tools/

                注意:如果错误页面引发异常,则会在测试日志中显示为错误。您可以默认将测试日志设置为 CRITICAL 以抑制该错误。

                【讨论】:

                • 这是唯一有帮助的答案:让您的代码引发 500 错误,并确保响应为 500。(此外,我检查了 response.content 是否有我的自定义模板内容。)跨度>
                猜你喜欢
                • 1970-01-01
                • 2011-11-27
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-06-02
                • 2011-08-09
                • 1970-01-01
                相关资源
                最近更新 更多