[Zope-Checkins] SVN: Products.Five/branches/philikon-fix-lookup-priorities/ Fix http://codespeak.net/pipermail/z3-five/2006q1/001186.html:

Philipp von Weitershausen philikon at philikon.de
Sun Mar 12 13:27:37 EST 2006


Log message for revision 65923:
  Fix http://codespeak.net/pipermail/z3-five/2006q1/001186.html:
  Modeled Five's __bobo_traverse__ very much like BaseRequest.traverse:
  1. Look for an existing __bobo_traverse__ (in Five's case, it's called
     __fallback_traverse__) and call it if it exists
  2. If not, try attribute lookup (possibly without acquisition wrappers
     for WebDAV non-POST/non-GET requests) or key item lookup.
  3. If all that doesn't work, we try view lookup.  This used to be #1
     and would therefore shadow existing attributes.
  

Changed:
  U   Products.Five/branches/philikon-fix-lookup-priorities/browser/tests/test_traversable.py
  U   Products.Five/branches/philikon-fix-lookup-priorities/fiveconfigure.py
  U   Products.Five/branches/philikon-fix-lookup-priorities/traversable.py

-=-
Modified: Products.Five/branches/philikon-fix-lookup-priorities/browser/tests/test_traversable.py
===================================================================
--- Products.Five/branches/philikon-fix-lookup-priorities/browser/tests/test_traversable.py	2006-03-12 18:22:05 UTC (rev 65922)
+++ Products.Five/branches/philikon-fix-lookup-priorities/browser/tests/test_traversable.py	2006-03-12 18:27:36 UTC (rev 65923)
@@ -67,7 +67,8 @@
       ... <five:traversable
       ...     class="Products.Five.tests.testing.FiveTraversableFolder"
       ...     />
-      ... 
+      ...
+      ... <!-- this view will never be found -->
       ... <browser:page
       ...     for="Products.Five.tests.testing.fancycontent.IFancyContent"
       ...     class="Products.Five.browser.tests.pages.FancyView"
@@ -75,7 +76,21 @@
       ...     name="fancyview"
       ...     permission="zope2.Public"
       ...     />
-      ... 
+      ... <!-- these two will -->
+      ... <browser:page
+      ...     for="Products.Five.tests.testing.fancycontent.IFancyContent"
+      ...     class="Products.Five.browser.tests.pages.FancyView"
+      ...     attribute="view"
+      ...     name="raise-attributeerror"
+      ...     permission="zope2.Public"
+      ...     />
+      ... <browser:page
+      ...     for="Products.Five.tests.testing.fancycontent.IFancyContent"
+      ...     class="Products.Five.browser.tests.pages.FancyView"
+      ...     attribute="view"
+      ...     name="raise-keyerror"
+      ...     permission="zope2.Public"
+      ...     />
       ... </configure>'''
       >>> zcml.load_string(configure_zcml)
 
@@ -92,23 +107,50 @@
       ...
       something-else
 
-    Of course we also need to make sure that Zope 3 style view lookup
-    actually works:
+    Once we have a custom __bobo_traverse__ method, though, it always
+    takes over.  Therefore, unless it raises AttributeError or
+    KeyError, it will be the only way traversal is done.
 
       >>> print http(r'''
       ... GET /test_folder_1_/fancy/fancyview HTTP/1.1
       ... ''')
       HTTP/1.1 200 OK
       ...
+      fancyview
+
+    As said, if the original __bobo_traverse__ method *does* raise
+    AttributeError or KeyError, we can get normal view look-up.  Other
+    exceptions are passed through just fine:
+
+      >>> print http(r'''
+      ... GET /test_folder_1_/fancy/raise-attributeerror HTTP/1.1
+      ... ''')
+      HTTP/1.1 200 OK
+      ...
       Fancy, fancy
-      
+
+      >>> print http(r'''
+      ... GET /test_folder_1_/fancy/raise-keyerror HTTP/1.1
+      ... ''')
+      HTTP/1.1 200 OK
+      ...
+      Fancy, fancy
+
+      >>> print http(r'''
+      ... GET /test_folder_1_/fancy/raise-valueerror HTTP/1.1
+      ... ''')
+      HTTP/1.1 500 Internal Server Error
+      ...
+      ...ValueError: raise-valueerror
+      ...
+
     Five's traversable monkeypatches the __bobo_traverse__ method to do view
     lookup and then delegates back to the original __bobo_traverse__ or direct
     attribute/item lookup to do normal lookup.  In the Zope 2 ZPublisher, an 
     object with a __bobo_traverse__ will not do attribute lookup unless the
     __bobo_traverse__ method itself does it (i.e. the __bobo_traverse__ is the
     only element used for traversal lookup).  Let's demonstrate:
-        
+
       >>> from Products.Five.tests.testing.fancycontent import manage_addNonTraversableFancyContent
       >>> info = manage_addNonTraversableFancyContent(self.folder, 'fancy_zope2', '')
       >>> self.folder.fancy_zope2.an_attribute = 'This is an attribute'
@@ -118,7 +160,7 @@
       HTTP/1.1 200 OK
       ...
       an_attribute
-      
+
     Without a __bobo_traverse__ method this would have returned the attribute
     value 'This is an attribute'.  Let's make sure the same thing happens for
     an object that has been marked traversable by Five:
@@ -154,6 +196,70 @@
       False
     """
 
+def test_view_doesnt_shadow_attribute():
+    """
+    Test that views don't shadow attributes, e.g. items in a folder.
+
+    Let's first define a browser page for object managers called
+    ``eagle``:
+
+      >>> configure_zcml = '''
+      ... <configure xmlns="http://namespaces.zope.org/zope"
+      ...            xmlns:meta="http://namespaces.zope.org/meta"
+      ...            xmlns:browser="http://namespaces.zope.org/browser"
+      ...            xmlns:five="http://namespaces.zope.org/five">
+      ... <!-- make the zope2.Public permission work -->
+      ... <meta:redefinePermission from="zope2.Public" to="zope.Public" />
+      ...   <browser:page
+      ...       name="eagle"
+      ...       for="OFS.interfaces.IObjectManager"
+      ...       class="Products.Five.browser.tests.pages.SimpleView"
+      ...       attribute="eagle"
+      ...       permission="zope2.Public"
+      ...       />
+      ... </configure>'''
+      >>> import Products.Five
+      >>> from Products.Five import zcml
+      >>> zcml.load_config("configure.zcml", Products.Five)
+      >>> zcml.load_string(configure_zcml)
+
+    Then we create a traversable folder...
+
+      >>> from Products.Five.tests.testing.folder import manage_addFiveTraversableFolder
+      >>> manage_addFiveTraversableFolder(self.folder, 'ftf')
+
+    and add an object called ``eagle`` to it:
+
+      >>> from Products.Five.tests.testing.simplecontent import manage_addIndexSimpleContent
+      >>> manage_addIndexSimpleContent(self.folder.ftf, 'eagle', 'Eagle')
+
+    When we publish the ``ftf/eagle`` now, we expect the attribute to
+    take precedence over the view during traversal:
+
+      >>> print http(r'''
+      ... GET /test_folder_1_/ftf/eagle HTTP/1.1
+      ... ''')
+      HTTP/1.1 200 OK
+      ...
+      Default index_html called
+
+    Of course, unless we explicitly want to lookup the view using @@:
+
+      >>> print http(r'''
+      ... GET /test_folder_1_/ftf/@@eagle HTTP/1.1
+      ... ''')
+      HTTP/1.1 200 OK
+      ...
+      The eagle has landed
+
+
+    Clean up:
+
+      >>> from zope.app.testing.placelesssetup import tearDown
+      >>> tearDown()
+    """
+
+
 def test_suite():
     from Testing.ZopeTestCase import FunctionalDocTestSuite
     return FunctionalDocTestSuite()

Modified: Products.Five/branches/philikon-fix-lookup-priorities/fiveconfigure.py
===================================================================
--- Products.Five/branches/philikon-fix-lookup-priorities/fiveconfigure.py	2006-03-12 18:22:05 UTC (rev 65922)
+++ Products.Five/branches/philikon-fix-lookup-priorities/fiveconfigure.py	2006-03-12 18:27:36 UTC (rev 65923)
@@ -37,10 +37,10 @@
 from zope.app.component.metaconfigure import adapter
 from zope.app.security.interfaces import IPermission
 
-from viewable import Viewable
-from traversable import Traversable
-from bridge import fromZ2Interface
-from browser.metaconfigure import page
+from Products.Five.viewable import Viewable
+from Products.Five.traversable import Traversable
+from Products.Five.bridge import fromZ2Interface
+from Products.Five.browser.metaconfigure import page
 
 debug_mode = App.config.getConfiguration().debug_mode
 
@@ -128,14 +128,11 @@
             isFiveMethod(class_.__bobo_traverse__)):
             return
 
-    if hasattr(class_, '__bobo_traverse__'):
-        if not isFiveMethod(class_.__bobo_traverse__):
-            # if there's an existing bobo_traverse hook already, use that
-            # as the traversal fallback method
-            setattr(class_, '__fallback_traverse__', class_.__bobo_traverse__)
-    if not hasattr(class_, '__fallback_traverse__'):
-        setattr(class_, '__fallback_traverse__',
-                Traversable.__fallback_traverse__.im_func)
+    if (hasattr(class_, '__bobo_traverse__') and
+        not isFiveMethod(class_.__bobo_traverse__)):
+        # if there's an existing bobo_traverse hook already, use that
+        # as the traversal fallback method
+        setattr(class_, '__fallback_traverse__', class_.__bobo_traverse__)
 
     setattr(class_, '__bobo_traverse__',
             Traversable.__bobo_traverse__.im_func)

Modified: Products.Five/branches/philikon-fix-lookup-priorities/traversable.py
===================================================================
--- Products.Five/branches/philikon-fix-lookup-priorities/traversable.py	2006-03-12 18:22:05 UTC (rev 65922)
+++ Products.Five/branches/philikon-fix-lookup-priorities/traversable.py	2006-03-12 18:27:36 UTC (rev 65923)
@@ -15,8 +15,6 @@
 
 $Id$
 """
-from zExceptions import NotFound
-
 from zope.component import getMultiAdapter, ComponentLookupError
 from zope.interface import implements, Interface
 from zope.publisher.interfaces import ILayer
@@ -28,8 +26,9 @@
 from zope.app.publication.browser import setDefaultSkin
 from zope.app.interface import queryType
 
-from AccessControl import getSecurityManager
-from Products.Five.security import newInteraction
+import Products.Five.security
+from zExceptions import NotFound
+from ZPublisher import xmlrpc
 
 _marker = object
 
@@ -47,18 +46,6 @@
     """
     __five_traversable__ = True
 
-    def __fallback_traverse__(self, REQUEST, name):
-        """Method hook for fallback traversal
-
-        This method is called by __bobo_traverse___ when Zope3-style
-        ITraverser traversal fails.
-
-        Just raise a AttributeError to indicate traversal has failed
-        and let Zope do it's job.
-        """
-        raise NotImplementedError
-    __fallback_traverse__.__five_method__ = True
-
     def __bobo_traverse__(self, REQUEST, name):
         """Hook for Zope 2 traversal
 
@@ -66,43 +53,86 @@
         It allows us to trick it into faking the Zope 3 traversal system
         by using an ITraverser adapter.
         """
+        # We are trying to be compatible with Zope 2 and 3 traversal
+        # behaviour as much as possible.  Therefore the first part of
+        # this method is based on BaseRequest.traverse's behaviour:
+        # 1. If an object has __bobo_traverse__, use it
+        # 2. Otherwise do attribute look-up (w/o acquisition if necessary)
+        # 3. If that doesn't work, try key item lookup.
+
+        if hasattr(self, '__fallback_traverse__'):
+            try:
+                return self.__fallback_traverse__(REQUEST, name)
+            except (AttributeError, KeyError):
+                pass
+        else:
+            # Note - no_acquire_flag is necessary to support things
+            # like DAV.  We have to make sure that the target object
+            # is not acquired if the request_method is other than GET
+            # or POST. Otherwise, you could never use PUT to add a new
+            # object named 'test' if an object 'test' existed above it
+            # in the heirarchy -- you'd always get the existing object
+            # :(
+            no_acquire_flag = False
+            method = REQUEST.get('REQUEST_METHOD', 'GET').upper()
+            if ((method not in ('GET', 'POST') or
+                 isinstance(getattr(REQUEST, 'response', {}), xmlrpc.Response))
+                and getattr(REQUEST, 'maybe_webdav_client', False)):
+                # Probably a WebDAV client.
+                no_acquire_flag = True
+
+            try:
+                if (no_acquire_flag and
+                    len(REQUEST['TraversalRequestNameStack']) == 0 and
+                    hasattr(self, 'aq_base')):
+                    if hasattr(self.aq_base, name):
+                        return getattr(self, name)
+                    else:
+                        pass # attribute not found
+                else:
+                    return getattr(self, name)
+            except AttributeError:
+                try:
+                    return self[name]
+                except (KeyError, IndexError, TypeError, AttributeError):
+                    pass # key not found
+
+        # This is the part Five adds:
+        # 4. If neither __bobo_traverse__ nor attribute/key look-up
+        # work, we try to find a Zope 3-style view
+
+        # For that we need to make sure we have a good request
+        # (sometimes __bobo_traverse__ gets a stub request)
         if not IBrowserRequest.providedBy(REQUEST):
             # Try to get the REQUEST by acquisition
             REQUEST = getattr(self, 'REQUEST', None)
             if not IBrowserRequest.providedBy(REQUEST):
                 REQUEST = FakeRequest()
 
-        # set the default skin on the request if it doesn't have any
+        # Con Zope 3 into using Zope 2's checkPermission
+        Products.Five.security.newInteraction()
+
+        # Set the default skin on the request if it doesn't have any
         # layers set on it yet
         if queryType(REQUEST, ILayer) is None:
             setDefaultSkin(REQUEST)
 
-        # con Zope 3 into using Zope 2's checkPermission
-        newInteraction()
+        # Use the ITraverser adapter (which in turn uses ITraversable
+        # adapters) to traverse to a view.  Note that we're mixing
+        # object-graph and object-publishing traversal here, but Zope
+        # 2 has no way to tell us when to use which...
+        # TODO Perhaps we can decide on object-graph vs.
+        # object-publishing traversal depending on whether REQUEST is
+        # a stub or not?
         try:
             return ITraverser(self).traverse(
                 path=[name], request=REQUEST).__of__(self)
         except (ComponentLookupError, LookupError,
                 AttributeError, KeyError, NotFound):
             pass
-        try:
-            return self.__fallback_traverse__(REQUEST, name)
-        except NotImplementedError:
-            pass
-        # TODO: This should at least make an attempt to deal with
-        # potential WebDAV issues, in particular we should not perform
-        # acquisition for webdav requests. See BaseRequest.traverse for 
-        # details.
-        try:
-            return getattr(self, name)
-        except AttributeError:
-            pass
-        try:
-            return self[name]
-        except (AttributeError, KeyError):
-            pass
-        raise AttributeError, name
 
+        raise AttributeError(name)
+
     __bobo_traverse__.__five_method__ = True
 
 



More information about the Zope-Checkins mailing list