diff --git a/CHANGES.rst b/CHANGES.rst index f89df0faaf..9fc3f5fb02 100644 --- a/CHANGES.rst +++ b/CHANGES.rst @@ -59,6 +59,9 @@ Other changes - Provide additional links on PyPI with ``project_urls`` in ``setup.py`` (`#434 `_) +- Resurrect automatic support for ``standard_error_message`` DTML Method + (`#238 `_) + 4.0b9 (2019-02-09) ------------------ diff --git a/src/OFS/browser/__init__.py b/src/OFS/browser/__init__.py new file mode 100644 index 0000000000..6f0e3e9021 --- /dev/null +++ b/src/OFS/browser/__init__.py @@ -0,0 +1,27 @@ +############################################################################## +# +# Copyright (c) 2002 Zope Foundation and Contributors. +# +# This software is subject to the provisions of the Zope Public License, +# Version 2.1 (ZPL). A copy of the ZPL should accompany this distribution. +# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED +# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS +# FOR A PARTICULAR PURPOSE +# +############################################################################## + +from Products.Five import BrowserView + + +class StandardErrorMessageView(BrowserView): + """ View rendered on SiteError. + + Requires a DTML Method named ``standard_error_message`` + """ + + def __call__(self): + root = self.request['PARENTS'][-1] + return root.standard_error_message( + error_type=self.context.__class__.__name__, + error_value=str(self.context)) diff --git a/src/OFS/browser/configure.zcml b/src/OFS/browser/configure.zcml new file mode 100644 index 0000000000..a577c0daf6 --- /dev/null +++ b/src/OFS/browser/configure.zcml @@ -0,0 +1,12 @@ + + + + + diff --git a/src/OFS/configure.zcml b/src/OFS/configure.zcml index aa8ce7d6fa..9cde96af53 100644 --- a/src/OFS/configure.zcml +++ b/src/OFS/configure.zcml @@ -4,4 +4,6 @@ + + diff --git a/src/ZPublisher/WSGIPublisher.py b/src/ZPublisher/WSGIPublisher.py index 4cb3041acb..93d4587577 100644 --- a/src/ZPublisher/WSGIPublisher.py +++ b/src/ZPublisher/WSGIPublisher.py @@ -19,6 +19,7 @@ from AccessControl.SecurityManagement import newSecurityManager from AccessControl.SecurityManagement import noSecurityManager +from Acquisition import aq_acquire from six import PY3 from six import reraise from six.moves._thread import allocate_lock @@ -96,9 +97,22 @@ def get_module_info(module_name='Zope2'): def _exc_view_created_response(exc, request, response): view = queryMultiAdapter((exc, request), name=u'index.html') + parents = request.get('PARENTS') + + if view is None and parents: + # Try a fallback based on the old standard_error_message + # DTML Method in the ZODB + view = queryMultiAdapter((exc, request), + name=u'standard_error_message') + root_parent = parents[0] + try: + standard_error_message = aq_acquire(root_parent, + 'standard_error_message') + except (AttributeError, KeyError): + view = None + if view is not None: # Wrap the view in the context in which the exception happened. - parents = request.get('PARENTS') if parents: view.__parent__ = parents[0] @@ -113,6 +127,7 @@ def _exc_view_created_response(exc, request, response): # Set the response body to the result of calling the view. response.setBody(view()) return True + return False diff --git a/src/ZPublisher/tests/test_WSGIPublisher.py b/src/ZPublisher/tests/test_WSGIPublisher.py index 02326903d4..70c1ffcd77 100644 --- a/src/ZPublisher/tests/test_WSGIPublisher.py +++ b/src/ZPublisher/tests/test_WSGIPublisher.py @@ -491,7 +491,7 @@ def testCustomExceptionViewConflictErrorHandling(self): directlyProvides(_request, IDefaultBrowserLayer) _request.response = DummyResponse() _request.retry_count = 0 - _request.retry_max_count = 3 + _request.retry_max_count = 2 _request.environ = {} def _close(): @@ -519,6 +519,7 @@ def _request_factory(stdin, environ, response): # have been retried up to retry_max_count times self.assertTrue(app_iter[1].startswith('Exception View: ConflictError')) self.assertEqual(_request.retry_count, _request.retry_max_count) + unregisterExceptionView(Exception) def testCustomExceptionViewUnauthorized(self): from AccessControl import Unauthorized @@ -531,6 +532,7 @@ def testCustomExceptionViewUnauthorized(self): body = b''.join(app_iter) self.assertEqual(start_response._called_with[0][0], '401 Unauthorized') self.assertTrue(b'Exception View: Unauthorized' in body) + unregisterExceptionView(IUnauthorized) def testCustomExceptionViewForbidden(self): from zExceptions import Forbidden @@ -543,6 +545,7 @@ def testCustomExceptionViewForbidden(self): body = b''.join(app_iter) self.assertEqual(start_response._called_with[0][0], '403 Forbidden') self.assertTrue(b'Exception View: Forbidden' in body) + unregisterExceptionView(IForbidden) def testCustomExceptionViewNotFound(self): from zExceptions import NotFound @@ -555,6 +558,7 @@ def testCustomExceptionViewNotFound(self): body = b''.join(app_iter) self.assertEqual(start_response._called_with[0][0], '404 Not Found') self.assertTrue(b'Exception View: NotFound' in body) + unregisterExceptionView(INotFound) def testCustomExceptionViewZTKNotFound(self): from zope.publisher.interfaces import NotFound as ZTK_NotFound @@ -567,6 +571,7 @@ def testCustomExceptionViewZTKNotFound(self): body = b''.join(app_iter) self.assertEqual(start_response._called_with[0][0], '404 Not Found') self.assertTrue(b'Exception View: NotFound' in body) + unregisterExceptionView(INotFound) def testCustomExceptionViewBadRequest(self): from zExceptions import BadRequest @@ -623,6 +628,51 @@ def testHandleErrorsFalseBypassesExceptionResponse(self): self._callFUT(environ, start_response, _publish) +class ExcViewCreatedTests(ZopeTestCase): + + def _callFUT(self, exc): + from zope.interface import directlyProvides + from zope.publisher.browser import IDefaultBrowserLayer + from ZPublisher.WSGIPublisher import _exc_view_created_response + req = DummyRequest() + req['PARENTS'] = [self.app] + directlyProvides(req, IDefaultBrowserLayer) + return _exc_view_created_response(exc, req, DummyResponse()) + + def _registerStandardErrorView(self): + from OFS.browser import StandardErrorMessageView + from zope.interface import Interface + registerExceptionView(Interface, factory=StandardErrorMessageView, + name=u'standard_error_message') + + def _unregisterStandardErrorView(self): + from OFS.browser import StandardErrorMessageView + from zope.interface import Interface + unregisterExceptionView(Interface, factory=StandardErrorMessageView, + name=u'standard_error_message') + + def testNoStandardErrorMessage(self): + from zExceptions import NotFound + self._registerStandardErrorView() + + try: + self.assertFalse(self._callFUT(NotFound)) + finally: + self._unregisterStandardErrorView() + + def testWithStandardErrorMessage(self): + from OFS.DTMLMethod import addDTMLMethod + from zExceptions import NotFound + self._registerStandardErrorView() + + addDTMLMethod(self.app, 'standard_error_message', file='OOPS') + + try: + self.assertTrue(self._callFUT(NotFound)) + finally: + self._unregisterStandardErrorView() + + class WSGIPublisherTests(FunctionalTestCase): def test_can_handle_non_ascii_URLs(self): @@ -708,28 +758,29 @@ def __call__(self): self.__parent__.__class__.__name__)) -def registerExceptionView(for_): +def registerExceptionView(for_, factory=CustomExceptionView, name=u'index.html'): from zope.interface import Interface from zope.component import getGlobalSiteManager from zope.publisher.interfaces.browser import IDefaultBrowserLayer gsm = getGlobalSiteManager() gsm.registerAdapter( - CustomExceptionView, + factory, required=(for_, IDefaultBrowserLayer), provided=Interface, - name=u'index.html', + name=name, ) -def unregisterExceptionView(for_): +def unregisterExceptionView(for_, factory=CustomExceptionView, + name=u'index.html'): from zope.interface import Interface from zope.component import getGlobalSiteManager from zope.publisher.interfaces.browser import IDefaultBrowserLayer gsm = getGlobalSiteManager() gsm.unregisterAdapter( - CustomExceptionView, + factory, required=(for_, IDefaultBrowserLayer), provided=Interface, - name=u'index.html', + name=name, ) diff --git a/src/Zope2/App/meta.zcml b/src/Zope2/App/meta.zcml index 2df64798e8..f0e11bd8ca 100644 --- a/src/Zope2/App/meta.zcml +++ b/src/Zope2/App/meta.zcml @@ -4,6 +4,7 @@ +