[Zope-Checkins] SVN: Zope/trunk/ Integrate Zope 3-based exception views. Patch by Sidnei, integration

Martijn Faassen faassen at infrae.com
Thu Jul 5 10:35:50 EDT 2007


Log message for revision 77459:
  Integrate Zope 3-based exception views. Patch by Sidnei, integration
  work done for Infrae.
  

Changed:
  U   Zope/trunk/doc/CHANGES.txt
  U   Zope/trunk/lib/python/Zope2/App/startup.py
  A   Zope/trunk/lib/python/Zope2/App/tests/
  A   Zope/trunk/lib/python/Zope2/App/tests/__init__.py
  A   Zope/trunk/lib/python/Zope2/App/tests/testExceptionHook.py
  U   Zope/trunk/lib/python/zExceptions/__init__.py
  U   Zope/trunk/lib/python/zExceptions/unauthorized.py

-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt	2007-07-05 13:34:33 UTC (rev 77458)
+++ Zope/trunk/doc/CHANGES.txt	2007-07-05 14:35:49 UTC (rev 77459)
@@ -100,6 +100,27 @@
       - AccessControl: the form behind the "Security" tab has a new form
         for user-related reporting of permissions and roles
 
+      - Zope 3-based exception views can now be registered in ZCML for
+        various exceptions that can be raised by Zope. Registering an
+        exception view can be done like this:
+
+          <browser:page
+            for="zope.publisher.interfaces.INotFound"
+            class=".view.SomeView"
+            name="index.html"
+            permission="zope.Public" />
+
+        Relevant exceptions that can have views are:
+
+        zope.interface.common.interfaces.IException
+        zope.publisher.interfaces.INotFound
+        zope.security.interfaces.IForbidden
+        zope.security.interfaces.IUnauthorized
+
+        Note that the name has to be 'index.html' for the exception
+        view to work. (patch by Sidnei da Silva from Enfold,
+        integration by Martijn Faassen (Startifact) for Infrae)
+
     Bugs Fixed
 
       - Collector #1306: Missing acquisition context on local roles screen.

Modified: Zope/trunk/lib/python/Zope2/App/startup.py
===================================================================
--- Zope/trunk/lib/python/Zope2/App/startup.py	2007-07-05 13:34:33 UTC (rev 77458)
+++ Zope/trunk/lib/python/Zope2/App/startup.py	2007-07-05 14:35:49 UTC (rev 77459)
@@ -13,6 +13,7 @@
 """Initialize the Zope2 Package and provide a published module
 """
 
+from zope.component import queryMultiAdapter
 from AccessControl.SecurityManagement import getSecurityManager
 from AccessControl.SecurityManagement import newSecurityManager
 from AccessControl.SecurityManagement import noSecurityManager
@@ -20,7 +21,7 @@
 from App.config import getConfiguration
 from time import asctime
 from types import StringType, ListType
-from zExceptions import Unauthorized
+from zExceptions import Unauthorized, Redirect
 from ZODB.POSException import ConflictError
 import transaction
 import AccessControl.User
@@ -37,6 +38,8 @@
 import Zope2
 import ZPublisher
 
+app = None
+startup_time = asctime()
 
 def startup():
     global app
@@ -132,100 +135,124 @@
                 )
             Zope2.DB.removeVersionPool(version)
             raise Unauthorized, "You don't have permission to enter versions."
-    
 
 
+
 class RequestContainer(ExtensionClass.Base):
     def __init__(self,r): self.REQUEST=r
 
-conflict_errors = 0
-unresolved_conflict_errors = 0
+class ZPublisherExceptionHook:
 
-conflict_logger = logging.getLogger('ZPublisher.Conflict')
+    def __init__(self):
+        self.conflict_errors = 0
+        self.unresolved_conflict_errors = 0
+        self.conflict_logger = logging.getLogger('ZPublisher.Conflict')
+        self.error_message = 'standard_error_message'
+        self.raise_error_message = 'raise_standardErrorMessage'
 
-def zpublisher_exception_hook(published, REQUEST, t, v, traceback):
-    global unresolved_conflict_errors
-    global conflict_errors
-    try:
-        if isinstance(t, StringType):
-            if t.lower() in ('unauthorized', 'redirect'):
-                raise
-        else:
-            if t is SystemExit:
-                raise
-            if issubclass(t, ConflictError):
-                conflict_errors += 1
-                level = getConfiguration().conflict_error_log_level
-                if level:
-                    conflict_logger.log(level,
-                        "%s at %s: %s (%d conflicts (%d unresolved) "
-                        "since startup at %s)",
-                        v.__class__.__name__,
-                        REQUEST.get('PATH_INFO', '<unknown>'),
-                        v,
-                        conflict_errors,
-                        unresolved_conflict_errors,
-                        startup_time)
-                raise ZPublisher.Retry(t, v, traceback)
-            if t is ZPublisher.Retry:
-                try:
-                    v.reraise()
-                except:
-                    # we catch the re-raised exception so that it gets
-                    # stored in the error log and gets rendered with
-                    # standard_error_message
-                    t, v, traceback = sys.exc_info()
-                if issubclass(t, ConflictError):
-                    # ouch, a user saw this conflict error :-(
-                    unresolved_conflict_errors += 1
+    def logConflicts(self, v, REQUEST):
+        self.conflict_errors += 1
+        level = getattr(getConfiguration(), 'conflict_error_log_level', 0)
+        if not self.conflict_logger.isEnabledFor(level):
+            return False
+        self.conflict_logger.log(
+            level,
+            "%s at %s: %s (%d conflicts (%d unresolved) "
+            "since startup at %s)",
+            v.__class__.__name__,
+            REQUEST.get('PATH_INFO', '<unknown>'),
+            v,
+            self.conflict_errors,
+            self.unresolved_conflict_errors,
+            startup_time)
+        return True
 
+    def __call__(self, published, REQUEST, t, v, traceback):
         try:
-            log = aq_acquire(published, '__error_log__', containment=1)
-        except AttributeError:
-            error_log_url = ''
-        else:
-            error_log_url = log.raising((t, v, traceback))
+            if isinstance(t, StringType):
+                if t.lower() in ('unauthorized', 'redirect'):
+                    raise
+            else:
+                if t is SystemExit or t is Redirect:
+                    raise
 
-        if (getattr(REQUEST.get('RESPONSE', None), '_error_format', '')
-            !='text/html'):
-            raise t, v, traceback
+                if issubclass(t, ConflictError):
+                    self.logConflicts(v, REQUEST)
+                    raise ZPublisher.Retry(t, v, traceback)
 
-        if (published is None or published is app or
-            type(published) is ListType):
-            # At least get the top-level object
-            published=app.__bobo_traverse__(REQUEST).__of__(
-                RequestContainer(REQUEST))
+                if t is ZPublisher.Retry:
+                    try:
+                        v.reraise()
+                    except:
+                        # we catch the re-raised exception so that it gets
+                        # stored in the error log and gets rendered with
+                        # standard_error_message
+                        t, v, traceback = sys.exc_info()
+                    if issubclass(t, ConflictError):
+                        # ouch, a user saw this conflict error :-(
+                        self.unresolved_conflict_errors += 1
 
-        published=getattr(published, 'im_self', published)
-        while 1:
-            f=getattr(published, 'raise_standardErrorMessage', None)
-            if f is None:
-                published=getattr(published, 'aq_parent', None)
-                if published is None:
-                    raise t, v, traceback
+            try:
+                log = aq_acquire(published, '__error_log__', containment=1)
+            except AttributeError:
+                error_log_url = ''
             else:
-                break
+                error_log_url = log.raising((t, v, traceback))
 
-        client=published
-        while 1:
-            if getattr(client, 'standard_error_message', None) is not None:
-                break
-            client=getattr(client, 'aq_parent', None)
-            if client is None:
+            if (getattr(REQUEST.get('RESPONSE', None), '_error_format', '')
+                !='text/html'):
                 raise t, v, traceback
 
-        if REQUEST.get('AUTHENTICATED_USER', None) is None:
-            REQUEST['AUTHENTICATED_USER']=AccessControl.User.nobody
+            # Lookup a view for the exception and render it, then
+            # raise the rendered value as the exception value
+            # (basically the same that 'raise_standardErrorMessage'
+            # does. The view is named 'index.html' because that's what
+            # Zope 3 uses as well.
+            view = queryMultiAdapter((v, REQUEST), name=u'index.html')
+            if view is not None:
+                v = view()
+                response = REQUEST.RESPONSE
+                response.setStatus(t)
+                response.setBody(v)
+                return response
 
-        try:
-            f(client, REQUEST, t, v, traceback, error_log_url=error_log_url)
-        except TypeError:
-            # Pre 2.6 call signature
-            f(client, REQUEST, t, v, traceback)
+            if (published is None or published is app or
+                type(published) is ListType):
+                # At least get the top-level object
+                published=app.__bobo_traverse__(REQUEST).__of__(
+                    RequestContainer(REQUEST))
 
-    finally:
-        traceback=None
+            published = getattr(published, 'im_self', published)
+            while 1:
+                f = getattr(published, self.raise_error_message, None)
+                if f is None:
+                    published = getattr(published, 'aq_parent', None)
+                    if published is None:
+                        raise t, v, traceback
+                else:
+                    break
 
+            client = published
+            while 1:
+                if getattr(client, self.error_message, None) is not None:
+                    break
+                client = getattr(client, 'aq_parent', None)
+                if client is None:
+                    raise t, v, traceback
+
+            if REQUEST.get('AUTHENTICATED_USER', None) is None:
+                REQUEST['AUTHENTICATED_USER'] = AccessControl.User.nobody
+
+            try:
+                f(client, REQUEST, t, v, traceback, error_log_url=error_log_url)
+            except TypeError:
+                # Pre 2.6 call signature
+                f(client, REQUEST, t, v, traceback)
+
+        finally:
+            traceback = None
+
+zpublisher_exception_hook = ZPublisherExceptionHook()
 ac_logger = logging.getLogger('event.AccessControl')
 
 class TransactionsManager:

Added: Zope/trunk/lib/python/Zope2/App/tests/__init__.py
===================================================================
--- Zope/trunk/lib/python/Zope2/App/tests/__init__.py	                        (rev 0)
+++ Zope/trunk/lib/python/Zope2/App/tests/__init__.py	2007-07-05 14:35:49 UTC (rev 77459)
@@ -0,0 +1,15 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# 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.
+#
+##############################################################################
+
+"""Tests of the Zope2.App package."""

Added: Zope/trunk/lib/python/Zope2/App/tests/testExceptionHook.py
===================================================================
--- Zope/trunk/lib/python/Zope2/App/tests/testExceptionHook.py	                        (rev 0)
+++ Zope/trunk/lib/python/Zope2/App/tests/testExceptionHook.py	2007-07-05 14:35:49 UTC (rev 77459)
@@ -0,0 +1,412 @@
+##############################################################################
+#
+# Copyright (c) 2007 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# 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.
+#
+##############################################################################
+
+import sys
+import unittest
+import logging
+
+from zope.publisher.interfaces import INotFound
+from zope.security.interfaces import IUnauthorized
+from zope.security.interfaces import IForbidden
+from zope.interface.common.interfaces import IException
+
+from zope.app.testing import ztapi
+from zope.app.testing.placelesssetup import PlacelessSetup
+from zope.publisher.browser import setDefaultSkin
+
+class ExceptionHookTestCase(unittest.TestCase):
+
+    def _makeOne(self):
+        from Zope2.App.startup import ZPublisherExceptionHook
+        return ZPublisherExceptionHook()
+
+    def _makeRequest(self, stdin=None, environ=None,
+                     response=None, clean=1, stdout=None):
+        from ZPublisher.HTTPRequest import HTTPRequest
+        from ZPublisher.HTTPResponse import HTTPResponse
+
+        if stdin is None:
+            from StringIO import StringIO
+            stdin = StringIO()
+
+        if stdout is None:
+            from StringIO import StringIO
+            stdout = StringIO()
+
+        if environ is None:
+            environ = {}
+
+        if 'SERVER_NAME' not in environ:
+            environ['SERVER_NAME'] = 'http://localhost'
+
+        if 'SERVER_PORT' not in environ:
+            environ['SERVER_PORT'] = '8080'
+
+        if response is None:
+            response = HTTPResponse(stdout=stdout)
+
+        req = HTTPRequest(stdin, environ, response, clean)
+        setDefaultSkin(req)
+        return req
+
+    def call(self, published, request, f, args=None, kw=None):
+        hook = self._makeOne()
+        try:
+            if args is None:
+                args = ()
+            if kw is None:
+                kw = {}
+            f(*args, **kw)
+        except:
+            return hook(published, request,
+                        sys.exc_info()[0],
+                        sys.exc_info()[1],
+                        sys.exc_info()[2],
+                        )
+
+    def call_no_exc(self, hook, published, request, f, args=None, kw=None):
+        if hook is None:
+            hook = self._makeOne()
+        try:
+            if args is None:
+                args = ()
+            if kw is None:
+                kw = {}
+            f(*args, **kw)
+        except:
+            try:
+                hook(published, request,
+                     sys.exc_info()[0],
+                     sys.exc_info()[1],
+                     sys.exc_info()[2],
+                     )
+            except:
+                pass
+            return hook
+
+    def call_exc_value(self, published, request, f, args=None, kw=None):
+        hook = self._makeOne()
+        try:
+            if args is None:
+                args = ()
+            if kw is None:
+                kw = {}
+            f(*args, **kw)
+        except:
+            try:
+                return hook(published, request,
+                            sys.exc_info()[0],
+                            sys.exc_info()[1],
+                            sys.exc_info()[2],
+                            )
+            except Exception, e:
+                return e
+
+class ExceptionHookTest(ExceptionHookTestCase):
+
+    def testStringException1(self):
+        def f():
+            raise 'unauthorized', 'x'
+        self.assertRaises('unauthorized', self.call, None, None, f)
+
+    def testStringException2(self):
+        def f():
+            raise 'redirect', 'x'
+        self.assertRaises('redirect', self.call, None, None, f)
+
+    def testSystemExit(self):
+        def f():
+            raise SystemExit, 1
+        self.assertRaises(SystemExit, self.call, None, None, f)
+
+    def testUnauthorized(self):
+        from AccessControl import Unauthorized
+        def f():
+            raise Unauthorized, 1
+        self.assertRaises(Unauthorized, self.call, None, {}, f)
+
+    def testConflictErrorRaisesRetry(self):
+        from ZPublisher import Retry
+        from ZODB.POSException import ConflictError
+        from App.config import getConfiguration
+        def f():
+            raise ConflictError
+        request = self._makeRequest()
+        old_value = getattr(getConfiguration(), 'conflict_error_log_level', 0)
+        self.assertEquals(old_value, 0) # default value
+        try:
+            getConfiguration().conflict_error_log_level = logging.CRITICAL
+            level = getattr(getConfiguration(), 'conflict_error_log_level', 0)
+            self.assertEquals(level, logging.CRITICAL)
+            self.assertRaises(Retry, self.call, None, request, f)
+        finally:
+            getConfiguration().conflict_error_log_level = old_value
+
+    def testConflictErrorCount(self):
+        from ZPublisher import Retry
+        from ZODB.POSException import ConflictError
+        def f():
+            raise ConflictError
+        hook = self._makeOne()
+        self.assertEquals(hook.conflict_errors, 0)
+        self.call_no_exc(hook, None, None, f)
+        self.assertEquals(hook.conflict_errors, 1)
+        self.call_no_exc(hook, None, None, f)
+        self.assertEquals(hook.conflict_errors, 2)
+
+    def testRetryRaisesOriginalException(self):
+        from ZPublisher import Retry
+        class CustomException(Exception):
+            pass
+        def f():
+            try:
+                raise CustomException, 'Zope'
+            except:
+                raise Retry(sys.exc_info()[0],
+                            sys.exc_info()[1],
+                            sys.exc_info()[2])
+        self.assertRaises(CustomException, self.call, None, {}, f)
+
+    def testRetryRaisesConflictError(self):
+        from ZPublisher import Retry
+        from ZODB.POSException import ConflictError
+        def f():
+            try:
+                raise ConflictError
+            except:
+                raise Retry(sys.exc_info()[0],
+                            sys.exc_info()[1],
+                            sys.exc_info()[2])
+        self.assertRaises(ConflictError, self.call, None, {}, f)
+
+    def testRetryUnresolvedConflictErrorCount(self):
+        from ZPublisher import Retry
+        from ZODB.POSException import ConflictError
+        def f():
+            try:
+                raise ConflictError
+            except:
+                raise Retry(sys.exc_info()[0],
+                            sys.exc_info()[1],
+                            sys.exc_info()[2])
+        hook = self._makeOne()
+        self.assertEquals(hook.unresolved_conflict_errors, 0)
+        self.call_no_exc(hook, None, None, f)
+        self.assertEquals(hook.unresolved_conflict_errors, 1)
+        self.call_no_exc(hook, None, None, f)
+        self.assertEquals(hook.unresolved_conflict_errors, 2)
+
+class Client:
+
+    def __init__(self):
+        self.standard_error_message = True
+        self.messages = []
+
+    def dummyMethod(self):
+        return 'Aye'
+
+class OldClient(Client):
+
+    def raise_standardErrorMessage(self, c, r, t, v, tb):
+        from zExceptions.ExceptionFormatter import format_exception
+        self.messages.append(''.join(format_exception(t, v, tb, as_html=0)))
+
+class StandardClient(Client):
+
+    def raise_standardErrorMessage(self, c, r, t, v, tb, error_log_url):
+        from zExceptions.ExceptionFormatter import format_exception
+        fmt = format_exception(t, v, tb, as_html=0)
+        self.messages.append(''.join([error_log_url] + fmt))
+
+class BrokenClient(Client):
+
+    def raise_standardErrorMessage(self, c, r, t, v, tb, error_log_url):
+        raise AttributeError, 'ouch'
+
+class ExceptionMessageRenderTest(ExceptionHookTestCase):
+
+    def testRenderUnauthorizedOldClient(self):
+        from AccessControl import Unauthorized
+        def f():
+            raise Unauthorized, 1
+        request = self._makeRequest()
+        client = OldClient()
+        self.call(client, request, f)
+        self.failUnless(client.messages, client.messages)
+        tb = client.messages[0]
+        self.failUnless("Unauthorized: You are not allowed" in tb, tb)
+
+    def testRenderUnauthorizedStandardClient(self):
+        from AccessControl import Unauthorized
+        def f():
+            raise Unauthorized, 1
+        request = self._makeRequest()
+        client = StandardClient()
+        self.call(client, request, f)
+        self.failUnless(client.messages, client.messages)
+        tb = client.messages[0]
+        self.failUnless("Unauthorized: You are not allowed" in tb, tb)
+
+    def testRenderUnauthorizedStandardClientMethod(self):
+        from AccessControl import Unauthorized
+        def f():
+            raise Unauthorized, 1
+        request = self._makeRequest()
+        client = StandardClient()
+        self.call(client.dummyMethod, request, f)
+        self.failUnless(client.messages, client.messages)
+        tb = client.messages[0]
+        self.failUnless("Unauthorized: You are not allowed" in tb, tb)
+
+    def testRenderUnauthorizedBrokenClient(self):
+        from AccessControl import Unauthorized
+        def f():
+            raise Unauthorized, 1
+        request = self._makeRequest()
+        client = BrokenClient()
+        self.assertRaises(AttributeError, self.call, client, request, f)
+
+    def testRenderRetryRaisesOriginalException(self):
+        from ZPublisher import Retry
+        class CustomException(Exception):
+            pass
+        def f():
+            try:
+                raise CustomException, 'Zope'
+            except:
+                raise Retry(sys.exc_info()[0],
+                            sys.exc_info()[1],
+                            sys.exc_info()[2])
+        request = self._makeRequest()
+        client = StandardClient()
+        self.call(client, request, f)
+        self.failUnless(client.messages, client.messages)
+        tb = client.messages[0]
+        self.failUnless("CustomException: Zope" in tb, tb)
+
+    def testRenderRetryRaisesConflictError(self):
+        from ZPublisher import Retry
+        from ZODB.POSException import ConflictError
+        def f():
+            try:
+                raise ConflictError
+            except:
+                raise Retry(sys.exc_info()[0],
+                            sys.exc_info()[1],
+                            sys.exc_info()[2])
+        request = self._makeRequest()
+        client = StandardClient()
+        self.call(client, request, f)
+        self.failUnless(client.messages, client.messages)
+        tb = client.messages[0]
+        self.failUnless("ConflictError: database conflict error" in tb, tb)
+
+class CustomExceptionView:
+
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+
+    def __call__(self):
+        return "Exception View: %s" % self.context.__class__.__name__
+
+class ExceptionViewsTest(PlacelessSetup, ExceptionHookTestCase):
+
+    def testCustomExceptionViewUnauthorized(self):
+        from ZPublisher.HTTPResponse import HTTPResponse
+        from AccessControl import Unauthorized
+        ztapi.browserView(IUnauthorized, u'index.html', CustomExceptionView)
+        def f():
+            raise Unauthorized, 1
+        request = self._makeRequest()
+        client = StandardClient()
+        v = self.call_exc_value(client, request, f)
+        self.failUnless(isinstance(v, HTTPResponse), v)
+        self.failUnless(v.status == 401, (v.status, 401))
+        self.failUnless("Exception View: Unauthorized" in str(v))
+
+    def testCustomExceptionViewForbidden(self):
+        from ZPublisher.HTTPResponse import HTTPResponse
+        from zExceptions import Forbidden
+        ztapi.browserView(IForbidden, u'index.html', CustomExceptionView)
+        def f():
+            raise Forbidden, "argh"
+        request = self._makeRequest()
+        client = StandardClient()
+        v = self.call_exc_value(client, request, f)
+        self.failUnless(isinstance(v, HTTPResponse), v)
+        self.failUnless(v.status == 403, (v.status, 403))
+        self.failUnless("Exception View: Forbidden" in str(v))
+
+    def testCustomExceptionViewNotFound(self):
+        from ZPublisher.HTTPResponse import HTTPResponse
+        from zExceptions import NotFound
+        ztapi.browserView(INotFound, u'index.html', CustomExceptionView)
+        def f():
+            raise NotFound, "argh"
+        request = self._makeRequest()
+        client = StandardClient()
+        v = self.call_exc_value(client, request, f)
+        self.failUnless(isinstance(v, HTTPResponse), v)
+        self.failUnless(v.status == 404, (v.status, 404))
+        self.failUnless("Exception View: NotFound" in str(v), v)
+
+    def testCustomExceptionViewBadRequest(self):
+        from ZPublisher.HTTPResponse import HTTPResponse
+        from zExceptions import BadRequest
+        ztapi.browserView(IException, u'index.html', CustomExceptionView)
+        def f():
+            raise BadRequest, "argh"
+        request = self._makeRequest()
+        client = StandardClient()
+        v = self.call_exc_value(client, request, f)
+        self.failUnless(isinstance(v, HTTPResponse), v)
+        self.failUnless(v.status == 400, (v.status, 400))
+        self.failUnless("Exception View: BadRequest" in str(v), v)
+
+    def testCustomExceptionViewInternalError(self):
+        from ZPublisher.HTTPResponse import HTTPResponse
+        from zExceptions import InternalError
+        ztapi.browserView(IException, u'index.html', CustomExceptionView)
+        def f():
+            raise InternalError, "argh"
+        request = self._makeRequest()
+        client = StandardClient()
+        v = self.call_exc_value(client, request, f)
+        self.failUnless(isinstance(v, HTTPResponse), v)
+        self.failUnless(v.status == 500, (v.status, 500))
+        self.failUnless("Exception View: InternalError" in str(v), v)
+
+    def testRedirectNoExceptionView(self):
+        from ZPublisher.HTTPResponse import HTTPResponse
+        from zExceptions import Redirect
+        ztapi.browserView(IException, u'index.html', CustomExceptionView)
+        def f():
+            raise Redirect, "http://zope.org/"
+        request = self._makeRequest()
+        client = StandardClient()
+        v = self.call_exc_value(client, request, f)
+        self.failUnless(isinstance(v, Redirect), v)
+        self.assertEquals(v.args[0], "http://zope.org/")
+
+
+def test_suite():
+    suite = unittest.TestSuite()
+    suite.addTest(unittest.makeSuite(ExceptionHookTest))
+    suite.addTest(unittest.makeSuite(ExceptionMessageRenderTest))
+    suite.addTest(unittest.makeSuite(ExceptionViewsTest))
+    return suite
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')

Modified: Zope/trunk/lib/python/zExceptions/__init__.py
===================================================================
--- Zope/trunk/lib/python/zExceptions/__init__.py	2007-07-05 13:34:33 UTC (rev 77458)
+++ Zope/trunk/lib/python/zExceptions/__init__.py	2007-07-05 14:35:49 UTC (rev 77459)
@@ -20,17 +20,22 @@
 
 from unauthorized import Unauthorized
 
+from zope.interface import implements
+from zope.interface.common.interfaces import IException
+from zope.publisher.interfaces import INotFound
+from zope.security.interfaces import IForbidden
+
 class BadRequest(Exception):
-    pass
+    implements(IException)
 
 class InternalError(Exception):
-    pass
+    implements(IException)
 
 class NotFound(Exception):
-    pass
+    implements(INotFound)
 
 class Forbidden(Exception):
-    pass
+    implements(IForbidden)
 
 class MethodNotAllowed(Exception):
     pass

Modified: Zope/trunk/lib/python/zExceptions/unauthorized.py
===================================================================
--- Zope/trunk/lib/python/zExceptions/unauthorized.py	2007-07-05 13:34:33 UTC (rev 77458)
+++ Zope/trunk/lib/python/zExceptions/unauthorized.py	2007-07-05 14:35:49 UTC (rev 77459)
@@ -15,9 +15,13 @@
 """
 
 from types import StringType
+from zope.interface import implements
+from zope.security.interfaces import IUnauthorized
 
 class Unauthorized(Exception):
-    """Some user wasn't allowed to access a resource"""
+    """Some user wasn't allowed to access a resource
+    """
+    implements(IUnauthorized)
 
     def __init__(self, message=None, value=None, needed=None, name=None, **kw):
         """Possible signatures:



More information about the Zope-Checkins mailing list