[Checkins] SVN: Zope/trunk/ Merge of the traversal refactoring branch.

Lennart Regebro regebro at gmail.com
Fri Apr 28 14:07:59 EDT 2006


Log message for revision 67730:
  Merge of the traversal refactoring branch.
  

Changed:
  U   Zope/trunk/doc/CHANGES.txt
  U   Zope/trunk/lib/python/OFS/Traversable.py
  _U  Zope/trunk/lib/python/Products/
  U   Zope/trunk/lib/python/Products/PageTemplates/Expressions.py
  U   Zope/trunk/lib/python/ZPublisher/BaseRequest.py

-=-
Modified: Zope/trunk/doc/CHANGES.txt
===================================================================
--- Zope/trunk/doc/CHANGES.txt	2006-04-28 18:07:25 UTC (rev 67729)
+++ Zope/trunk/doc/CHANGES.txt	2006-04-28 18:07:58 UTC (rev 67730)
@@ -49,6 +49,17 @@
 
     Features added
 
+      - The traversal has been refactored to take heed of Zope3s 
+        IPublishTraverse adapter interfaces. The ZCML directives 
+        five:traversable and five:defaultViewable are therefore no
+        longer needed, as everything now is five:traversable and
+        five:defaultViewable. 
+
+        There was a bug in earlier versions of Five that allowed you 
+        to do custom publishing traversal with ITraversable adapters.
+        This bug has been corrected. Anybody using ITraversable 
+        adapters need to convert them to IPublishTraversal adapters.
+
       - Testing.makerequest: Added an 'environ' argument so
         clients can use mappings other than os.environ.
 

Modified: Zope/trunk/lib/python/OFS/Traversable.py
===================================================================
--- Zope/trunk/lib/python/OFS/Traversable.py	2006-04-28 18:07:25 UTC (rev 67729)
+++ Zope/trunk/lib/python/OFS/Traversable.py	2006-04-28 18:07:58 UTC (rev 67730)
@@ -25,9 +25,14 @@
 from Acquisition import Acquired, aq_inner, aq_parent, aq_base
 from zExceptions import NotFound
 from ZODB.POSException import ConflictError
-from zope.interface import implements
+from zope.interface import implements, Interface
 
 from interfaces import ITraversable
+from zope.app.traversing.interfaces import ITraversable as IZope3Traversable
+from zope.component import queryMultiAdapter
+from zope.app.traversing.interfaces import TraversalError
+from zope.app.traversing.namespace import nsParse
+from zope.app.traversing.namespace import namespaceLookup
 
 _marker = object()
 
@@ -59,6 +64,7 @@
             return self.virtual_url_path()
 
         spp = self.getPhysicalPath()
+            
         try:
             toUrl = self.REQUEST.physicalPathToURL
         except AttributeError:
@@ -133,7 +139,6 @@
         If true, then all of the objects along the path are validated with
         the security machinery. Usually invoked using restrictedTraverse().
         """
-
         if not path:
             return self
 
@@ -188,7 +193,19 @@
                         continue
 
                 bobo_traverse = _getattr(obj, '__bobo_traverse__', _none)
-                if bobo_traverse is not _none:
+                if name and name[:1] in '@+':
+                    # Process URI segment parameters.
+                    ns, nm = nsParse(name)
+                    if ns:
+                        try:
+                            next = namespaceLookup(ns, nm, obj, 
+                                                   self.REQUEST).__of__(obj)
+                            if restricted and not securityManager.validate(
+                                obj, obj, name, next):
+                                raise Unauthorized, name
+                        except TraversalError:
+                            raise AttributeError(name)
+                elif bobo_traverse is not _none:
                     next = bobo_traverse(REQUEST, name)
                     if restricted:
                         if aq_base(next) is not next:
@@ -228,11 +245,20 @@
                         next = _getattr(obj, name, marker)
                     if next is marker:
                         try:
-                            next=obj[name]
-                        except AttributeError:
-                            # Raise NotFound for easier debugging
-                            # instead of AttributeError: __getitem__
-                            raise NotFound, name
+                            try:
+                                next=obj[name]
+                            except AttributeError:
+                                # Raise NotFound for easier debugging
+                                # instead of AttributeError: __getitem__
+                                raise NotFound, name
+                        except (NotFound, KeyError): 
+                            # Try to look for a view
+                            next = queryMultiAdapter((obj, self.REQUEST), 
+                                                     Interface, name)
+                            if next is None:
+                                # Didn't find one, reraise the error:
+                                raise
+                            next = next.__of__(obj)
                         if restricted and not securityManager.validate(
                             obj, obj, _none, next):
                             raise Unauthorized, name


Property changes on: Zope/trunk/lib/python/Products
___________________________________________________________________
Name: svn:externals
   - # updated to get a more recent working Five snapshot (what will be Five 1.5
# and actually included with Zope 2.10

Five -r67270 svn://svn.zope.org/repos/main/Products.Five/trunk

   + # updated to get a more recent working Five snapshot (what will be Five 1.5
# and actually included with Zope 2.10

Five -r67728 svn://svn.zope.org/repos/main/Products.Five/trunk


Modified: Zope/trunk/lib/python/Products/PageTemplates/Expressions.py
===================================================================
--- Zope/trunk/lib/python/Products/PageTemplates/Expressions.py	2006-04-28 18:07:25 UTC (rev 67729)
+++ Zope/trunk/lib/python/Products/PageTemplates/Expressions.py	2006-04-28 18:07:58 UTC (rev 67730)
@@ -248,12 +248,28 @@
     def __repr__(self):
         return 'not:%s' % `self._s`
 
+from zope.interface import Interface, implements
+from zope.component import queryMultiAdapter
+from zope.app.traversing.namespace import nsParse
+from zope.app.traversing.namespace import namespaceLookup
+from zope.app.traversing.interfaces import TraversalError
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.app.publication.browser import setDefaultSkin
+
+class FakeRequest(dict):
+    implements(IBrowserRequest)
+
+    def getURL(self):
+        return "http://codespeak.net/z3/five"
+
 def restrictedTraverse(object, path, securityManager,
                        get=getattr, has=hasattr, N=None, M=[],
                        TupleType=type(()) ):
 
-    REQUEST = {'path': path}
+    REQUEST = FakeRequest()
+    REQUEST['path'] = path
     REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
+    setDefaultSkin(REQUEST)
     path.reverse()
     validate = securityManager.validate
     __traceback_info__ = REQUEST
@@ -282,7 +298,20 @@
                 continue
 
         t=get(object, '__bobo_traverse__', N)
-        if t is not N:
+        if name and name[:1] in '@+':
+            import pdb
+            pdb.set_trace()
+            # Process URI segment parameters.
+            ns, nm = nsParse(name)
+            if ns:
+                try:
+                    o = namespaceLookup(ns, nm, object, 
+                                           REQUEST).__of__(object)
+                    if not validate(object, object, name, o):
+                        raise Unauthorized, name
+                except TraversalError:
+                    raise AttributeError(name)
+        elif t is not N:
             o=t(REQUEST, name)
 
             container = None
@@ -305,7 +334,16 @@
                     # XXX maybe in Python 2.2 we can just check whether
                     # the object has the attribute "__getitem__"
                     # instead of blindly catching exceptions.
-                    o = object[name]
+                    try:
+                        o = object[name]
+                    except (AttributeError, KeyError):
+                        # Try to look for a view
+                        o = queryMultiAdapter((object, REQUEST), 
+                                                 Interface, name)
+                        if o is None:
+                            # Didn't find one, reraise the error:
+                            raise
+                        o = o.__of__(object)
                 except AttributeError, exc:
                     if str(exc).find('__getitem__') >= 0:
                         # The object does not support the item interface.

Modified: Zope/trunk/lib/python/ZPublisher/BaseRequest.py
===================================================================
--- Zope/trunk/lib/python/ZPublisher/BaseRequest.py	2006-04-28 18:07:25 UTC (rev 67729)
+++ Zope/trunk/lib/python/ZPublisher/BaseRequest.py	2006-04-28 18:07:58 UTC (rev 67730)
@@ -16,10 +16,22 @@
 """
 from urllib import quote
 import xmlrpc
-from zExceptions import Forbidden
+from zExceptions import Forbidden, Unauthorized, NotFound
 
+from zope.interface import implements, providedBy, Interface
+from zope.component import queryMultiAdapter
+from zope.component import getSiteManager
+from zope.component.interfaces import ComponentLookupError
 from zope.event import notify
 from zope.app.publication.interfaces import EndRequestEvent
+from zope.app.publisher.browser import queryDefaultViewName
+from zope.publisher.interfaces import IPublishTraverse
+from zope.component.interfaces import IDefaultViewName
+from zope.publisher.interfaces.browser import IBrowserPublisher
+from zope.publisher.interfaces.browser import IBrowserRequest
+from zope.app.traversing.interfaces import TraversalError
+from zope.app.traversing.namespace import nsParse
+from zope.app.traversing.namespace import namespaceLookup
 
 UNSPECIFIED_ROLES=''
 
@@ -45,7 +57,87 @@
     def getRoles(container, name, value, default):
         return getattr(value, '__roles__', default)
 
+class DefaultPublishTraverse(object):
 
+    implements(IBrowserPublisher)
+    
+    def __init__(self, context, request):
+        self.context = context
+        self.request = request
+        
+    def publishTraverse(self, request, name):
+        object = self.context
+        URL=request['URL']
+
+        if name[:1]=='_':
+            raise Forbidden("Object name begins with an underscore at: %s" % URL)
+
+        try:
+            if hasattr(object,'__bobo_traverse__'):
+                subobject=object.__bobo_traverse__(request, name)
+                if type(subobject) is type(()) and len(subobject) > 1:
+                    # Add additional parents into the path
+                    # XXX This needs handling. Check the publish refactor branch...
+                    parents[-1:] = list(subobject[:-1])
+                    object, subobject = subobject[-2:]
+            else:
+                try:
+                    subobject=getattr(object, name)
+                except AttributeError:
+                    subobject=object[name]
+             
+        except (AttributeError, KeyError, NotFound):
+            # Find a view even if it doesn't start with @@, but only
+            # If nothing else could be found
+            subobject = queryMultiAdapter((object, request), Interface, name)
+            if subobject is not None:
+                # OFS.Application.__bobo_traverse__ calls
+                # REQUEST.RESPONSE.notFoundError which sets the HTTP
+                # status code to 404
+                request.RESPONSE.setStatus(200)
+                # We don't need to do the docstring security check
+                # for views, so lets skip it and return the object here.
+                return subobject.__of__(object) 
+            raise
+
+        # Ensure that the object has a docstring, or that the parent
+        # object has a pseudo-docstring for the object. Objects that
+        # have an empty or missing docstring are not published.
+        doc = getattr(subobject, '__doc__', None)
+        if doc is None:
+            doc = getattr(object, '%s__doc__' % name, None)
+        if not doc:
+            raise Forbidden(
+                "The object at %s has an empty or missing " \
+                "docstring. Objects must have a docstring to be " \
+                "published." % URL
+                )
+
+        # Hack for security: in Python 2.2.2, most built-in types
+        # gained docstrings that they didn't have before. That caused
+        # certain mutable types (dicts, lists) to become publishable
+        # when they shouldn't be. The following check makes sure that
+        # the right thing happens in both 2.2.2+ and earlier versions.
+
+        if not typeCheck(subobject):
+            raise Forbidden(
+                "The object at %s is not publishable." % URL
+                )
+
+        return subobject
+    
+    def browserDefault(self, request):
+        if hasattr(self.context, '__browser_default__'):
+            return self.context.__browser_default__(request)
+        # Zope 3.2 still uses IDefaultView name when it
+        # registeres default views, even though it's
+        # deprecated. So we handle that here:
+        default_name = queryDefaultViewName(self.context, request)
+        if default_name is not None:
+            return self.context, (default_name,)
+        return self.context, ()
+        
+
 _marker=[]
 class BaseRequest:
     """Provide basic ZPublisher request management
@@ -184,6 +276,35 @@
     __repr__=__str__
 
 
+    def traverseName(self, ob, name):
+        if name and name[:1] in '@+':
+            # Process URI segment parameters.
+            ns, nm = nsParse(name)
+            if ns:
+                try:
+                    ob2 = namespaceLookup(ns, nm, ob, self)
+                except TraversalError:
+                    raise KeyError(ob, name)
+
+                return ob2.__of__(ob)
+
+        if name == '.':
+            return ob
+
+        if IPublishTraverse.providedBy(ob):
+            ob2 = ob.publishTraverse(self, name)
+        else:
+            adapter = queryMultiAdapter((ob, self), IPublishTraverse)
+            if adapter is None:
+                ## Zope2 doesn't set up its own adapters in a lot of cases
+                ## so we will just use a default adapter.
+                adapter = DefaultPublishTraverse(ob, self)
+
+            ob2 = adapter.publishTraverse(self, name)
+
+        return ob2
+
+
     def traverse(self, path, response=None, validated_hook=None):
         """Traverse the object space
 
@@ -193,7 +314,6 @@
         request=self
         request_get=request.get
         if response is None: response=self.response
-        debug_mode=response.debug_mode
 
         # remember path for later use
         browser_path = path
@@ -235,14 +355,14 @@
         object=parents[-1]
         del parents[:]
 
-        roles = getRoles(None, None, object, UNSPECIFIED_ROLES)
+        self.roles = getRoles(None, None, object, UNSPECIFIED_ROLES)
 
         # if the top object has a __bobo_traverse__ method, then use it
         # to possibly traverse to an alternate top-level object.
         if hasattr(object,'__bobo_traverse__'):
             try:
                 object=object.__bobo_traverse__(request)
-                roles = getRoles(None, None, object, UNSPECIFIED_ROLES)
+                self.roles = getRoles(None, None, object, UNSPECIFIED_ROLES)
             except: pass
 
         if not path and not method:
@@ -277,125 +397,101 @@
                 path = request.path = request['TraversalRequestNameStack']
                 # Check for method:
                 if path:
-                    entry_name = path.pop()
-                elif hasattr(object, '__browser_default__'):
-                    # If we have reached the end of the path. We look to see
-                    # if the object implements __browser_default__. If so, we
-                    # call it to let the object tell us how to publish it
-                    # __browser_default__ returns the object to be published
+                    entry_name = path.pop() 
+                else:
+                    # If we have reached the end of the path, we look to see
+                    # if we can find IBrowserPublisher.browserDefault. If so,
+                    # we call it to let the object tell us how to publish it
+                    # BrowserDefault returns the object to be published
                     # (usually self) and a sequence of names to traverse to
-                    # find the method to be published. (Casey)
-                    request._hacked_path=1
-                    object, default_path = object.__browser_default__(request)
-                    if len(default_path) > 1:
-                        path = list(default_path)
-                        method = path.pop()
-                        request['TraversalRequestNameStack'] = path
-                        continue
+                    # find the method to be published.
+                    if (IBrowserPublisher.providedBy(object) or 
+                        IDefaultViewName.providedBy(object)):
+                        adapter = object
                     else:
-                        entry_name = default_path[0]
-                elif (method and hasattr(object,method)
-                      and entry_name != method
-                      and getattr(object, method) is not None):
-                    request._hacked_path=1
-                    entry_name = method
-                    method = 'index_html'
-                else:
-                    if (hasattr(object, '__call__')):
-                        roles = getRoles(object, '__call__', object.__call__,
-                                         roles)
-                    if request._hacked_path:
-                        i=URL.rfind('/')
-                        if i > 0: response.setBase(URL[:i])
-                    break
+                        adapter = queryMultiAdapter((object, self), 
+                                                    IBrowserPublisher)
+                        if adapter is None:
+                            # Zope2 doesn't set up its own adapters in a lot
+                            # of cases so we will just use a default adapter.
+                            adapter = DefaultPublishTraverse(object, self)
+                    
+                    newobject, default_path = adapter.browserDefault(self)
+                    if default_path or newobject is not object:
+                        object = newobject
+                        request._hacked_path=1
+                        if len(default_path) > 1:
+                            path = list(default_path)
+                            method = path.pop()
+                            request['TraversalRequestNameStack'] = path
+                            continue
+                        else:
+                            entry_name = default_path[0]
+                    elif (method and hasattr(object,method)
+                          and entry_name != method
+                          and getattr(object, method) is not None):
+                        request._hacked_path=1
+                        entry_name = method
+                        method = 'index_html'
+                    else:
+                        if hasattr(object, '__call__'):
+                            self.roles = getRoles(object, '__call__', object.__call__,
+                                                  self.roles)
+                        if request._hacked_path:
+                            i=URL.rfind('/')
+                            if i > 0: response.setBase(URL[:i])
+                        break
                 step = quote(entry_name)
                 _steps.append(step)
                 request['URL'] = URL = '%s/%s' % (request['URL'], step)
-                got = 0
-                if entry_name[:1]=='_':
-                    if debug_mode:
+                
+                try:
+                    subobject = self.traverseName(object, entry_name)
+                    if (hasattr(object,'__bobo_traverse__') or 
+                        hasattr(object, entry_name)):
+                        check_name = entry_name
+                    else:
+                        check_name = None
+                    
+                    self.roles = getRoles(
+                        object, check_name, subobject,
+                        self.roles)
+                    object = subobject
+                except (KeyError, AttributeError):
+                    if response.debug_mode:
                         return response.debugError(
-                          "Object name begins with an underscore at: %s" % URL)
-                    else: return response.forbiddenError(entry_name)
+                            "Cannot locate object at: %s" % URL)
+                    else:
+                        return response.notFoundError(URL)
+                except Forbidden, e:
+                    if self.response.debug_mode:
+                        return response.debugError(e.args)
+                    else: 
+                        return response.forbiddenError(entry_name)
+                    
 
-                if hasattr(object,'__bobo_traverse__'):
-                    try:
-                        subobject=object.__bobo_traverse__(request,entry_name)
-                        if type(subobject) is type(()) and len(subobject) > 1:
-                            # Add additional parents into the path
-                            parents[-1:] = list(subobject[:-1])
-                            object, subobject = subobject[-2:]
-                    except (AttributeError, KeyError):
-                        if debug_mode:
-                            return response.debugError(
-                                "Cannot locate object at: %s" % URL)
-                        else:
-                            return response.notFoundError(URL)
-                else:
-                    try:
-                        # 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 :(
-
-                        if (no_acquire_flag and len(path) == 0 and
-                            hasattr(object, 'aq_base')):
-                            if hasattr(object.aq_base, entry_name):
-                                subobject=getattr(object, entry_name)
-                            else: raise AttributeError, entry_name
-                        else: subobject=getattr(object, entry_name)
-                    except AttributeError:
-                        got=1
-                        try: subobject=object[entry_name]
-                        except (KeyError, IndexError,
-                                TypeError, AttributeError):
-                            if debug_mode:
-                                return response.debugError(
-                                    "Cannot locate object at: %s" % URL)
-                            else:
-                                return response.notFoundError(URL)
-
-                # Ensure that the object has a docstring, or that the parent
-                # object has a pseudo-docstring for the object. Objects that
-                # have an empty or missing docstring are not published.
-                doc = getattr(subobject, '__doc__', None)
-                if doc is None:
-                    doc = getattr(object, '%s__doc__' % entry_name, None)
-                if not doc:
-                    return response.debugError(
-                        "The object at %s has an empty or missing " \
-                        "docstring. Objects must have a docstring to be " \
-                        "published." % URL
-                        )
-
-                # Hack for security: in Python 2.2.2, most built-in types
-                # gained docstrings that they didn't have before. That caused
-                # certain mutable types (dicts, lists) to become publishable
-                # when they shouldn't be. The following check makes sure that
-                # the right thing happens in both 2.2.2+ and earlier versions.
-
-                if not typeCheck(subobject):
-                    return response.debugError(
-                        "The object at %s is not publishable." % URL
-                        )
-
-                roles = getRoles(
-                    object, (not got) and entry_name or None, subobject,
-                    roles)
-
-                # Promote subobject to object
-                object=subobject
                 parents.append(object)
 
                 steps.append(entry_name)
         finally:
             parents.reverse()
- 
+        
+        # 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 :(
+        if (no_acquire_flag and
+            hasattr(parents[1], 'aq_base') and 
+            not hasattr(parents[1],'__bobo_traverse__')):
+            if not (hasattr(parents[1].aq_base, entry_name) or
+                    parents[1].aq_base.has_key(entry_name)):
+                raise AttributeError, entry_name
+            
         # After traversal post traversal hooks aren't available anymore
         del self._post_traverse
 
@@ -427,25 +523,25 @@
 
                 auth=request._auth
 
-                if v is old_validation and roles is UNSPECIFIED_ROLES:
+                if v is old_validation and self.roles is UNSPECIFIED_ROLES:
                     # No roles, so if we have a named group, get roles from
                     # group keys
-                    if hasattr(groups,'keys'): roles=groups.keys()
+                    if hasattr(groups,'keys'): self.roles=groups.keys()
                     else:
                         try: groups=groups()
                         except: pass
-                        try: roles=groups.keys()
+                        try: self.roles=groups.keys()
                         except: pass
 
                     if groups is None:
                         # Public group, hack structures to get it to validate
-                        roles=None
+                        self.roles=None
                         auth=''
 
                 if v is old_validation:
-                    user=old_validation(groups, request, auth, roles)
-                elif roles is UNSPECIFIED_ROLES: user=v(request, auth)
-                else: user=v(request, auth, roles)
+                    user=old_validation(groups, request, auth, self.roles)
+                elif self.roles is UNSPECIFIED_ROLES: user=v(request, auth)
+                else: user=v(request, auth, self.roles)
 
                 while user is None and i < last_parent_index:
                     parent=parents[i]
@@ -456,11 +552,11 @@
                     if hasattr(groups,'validate'): v=groups.validate
                     else: v=old_validation
                     if v is old_validation:
-                        user=old_validation(groups, request, auth, roles)
-                    elif roles is UNSPECIFIED_ROLES: user=v(request, auth)
-                    else: user=v(request, auth, roles)
+                        user=old_validation(groups, request, auth, self.roles)
+                    elif self.roles is UNSPECIFIED_ROLES: user=v(request, auth)
+                    else: user=v(request, auth, self.roles)
 
-            if user is None and roles != UNSPECIFIED_ROLES:
+            if user is None and self.roles != UNSPECIFIED_ROLES:
                 response.unauthorized()
 
         if user is not None:



More information about the Checkins mailing list