[Zope-Checkins] SVN: Products.Five/branches/1.2/ Merge
philikon-fix-lookup-priorities branch.
Philipp von Weitershausen
philikon at philikon.de
Sun Mar 26 17:19:15 EST 2006
Log message for revision 66209:
Merge philikon-fix-lookup-priorities branch.
Changed:
U Products.Five/branches/1.2/CHANGES.txt
U Products.Five/branches/1.2/browser/tests/test_traversable.py
U Products.Five/branches/1.2/fiveconfigure.py
U Products.Five/branches/1.2/tests/testing/fancycontent.py
U Products.Five/branches/1.2/traversable.py
-=-
Modified: Products.Five/branches/1.2/CHANGES.txt
===================================================================
--- Products.Five/branches/1.2/CHANGES.txt 2006-03-26 22:18:34 UTC (rev 66208)
+++ Products.Five/branches/1.2/CHANGES.txt 2006-03-26 22:19:14 UTC (rev 66209)
@@ -5,9 +5,24 @@
Five 1.2.1 (2006-03-26)
=======================
+Bugfixes
+--------
+
* A local utility registered with an derived interface will now be available
by the inherited interface as well, in the same way as Zope3.
+* Fixed look-up order during Five traversal. It is now as follows:
+
+ 1. If an object has __bobo_traverse__, use it.
+
+ 2. Otherwise do attribute look-up or, if that doesn't work, key item
+ lookup.
+
+ 3. If neither __bobo_traverse__ nor attribute/key look-up work, it
+ tries to find a Zope 3-style view.
+
+ This change requires Zope 2.9.2 or higher.
+
Five 1.2.1 (2006-02-25)
=======================
Modified: Products.Five/branches/1.2/browser/tests/test_traversable.py
===================================================================
--- Products.Five/branches/1.2/browser/tests/test_traversable.py 2006-03-26 22:18:34 UTC (rev 66208)
+++ Products.Five/branches/1.2/browser/tests/test_traversable.py 2006-03-26 22:19:14 UTC (rev 66209)
@@ -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="fancy"
... 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,49 @@
...
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/fancy HTTP/1.1
+ ... 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
+ ... ''', handle_errors=False)
+ Traceback (most recent call last):
+ ...
+ 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 +159,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 +195,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.tests.placelesssetup import tearDown
+ >>> tearDown()
+ """
+
+
def test_suite():
from Testing.ZopeTestCase import FunctionalDocTestSuite
return FunctionalDocTestSuite()
Modified: Products.Five/branches/1.2/fiveconfigure.py
===================================================================
--- Products.Five/branches/1.2/fiveconfigure.py 2006-03-26 22:18:34 UTC (rev 66208)
+++ Products.Five/branches/1.2/fiveconfigure.py 2006-03-26 22:19:14 UTC (rev 66209)
@@ -35,10 +35,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
@@ -126,14 +126,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/1.2/tests/testing/fancycontent.py
===================================================================
--- Products.Five/branches/1.2/tests/testing/fancycontent.py 2006-03-26 22:18:34 UTC (rev 66208)
+++ Products.Five/branches/1.2/tests/testing/fancycontent.py 2006-03-26 22:19:14 UTC (rev 66209)
@@ -52,11 +52,19 @@
security = ClassSecurityInfo()
def __bobo_traverse__(self, REQUEST, name):
+ if name == 'raise-attributeerror':
+ raise AttributeError(name)
+ elif name == 'raise-keyerror':
+ raise KeyError(name)
+ elif name == 'raise-valueerror':
+ raise ValueError(name)
return FancyAttribute(name).__of__(self)
def get_size(self):
return 43
+InitializeClass(FancyContent)
+
# A copy of the above class used to demonstrate some baseline behavior
class NonTraversableFancyContent(SimpleItem):
"""A class that already comes with its own __bobo_traverse__ handler.
@@ -70,12 +78,18 @@
security = ClassSecurityInfo()
def __bobo_traverse__(self, REQUEST, name):
+ if name == 'raise-attributeerror':
+ raise AttributeError(name)
+ elif name == 'raise-keyerror':
+ raise KeyError(name)
+ elif name == 'raise-valueerror':
+ raise ValueError(name)
return FancyAttribute(name).__of__(self)
def get_size(self):
return 43
-InitializeClass(FancyContent)
+InitializeClass(NonTraversableFancyContent)
def manage_addFancyContent(self, id, REQUEST=None):
"""Add the fancy fancy content."""
Modified: Products.Five/branches/1.2/traversable.py
===================================================================
--- Products.Five/branches/1.2/traversable.py 2006-03-26 22:18:34 UTC (rev 66208)
+++ Products.Five/branches/1.2/traversable.py 2006-03-26 22:19:14 UTC (rev 66209)
@@ -15,7 +15,6 @@
$Id$
"""
-from zExceptions import NotFound
from zope.exceptions import NotFoundError
from zope.component import getView, ComponentLookupError
from zope.interface import implements
@@ -24,11 +23,10 @@
from zope.app.traversing.adapters import DefaultTraversable
from zope.app.traversing.adapters import traversePathElement
-from AccessControl import getSecurityManager
-from Products.Five.security import newInteraction
+import Products.Five.security
+from zExceptions import NotFound
+from ZPublisher import xmlrpc
-_marker = object
-
class FakeRequest(dict):
implements(IBrowserRequest)
@@ -46,18 +44,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
@@ -65,35 +51,58 @@
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 or, if that doesn't work,
+ # key item lookup.
+
+ if hasattr(self, '__fallback_traverse__'):
+ try:
+ return self.__fallback_traverse__(REQUEST, name)
+ except (AttributeError, KeyError):
+ pass
+ else:
+ try:
+ return getattr(self, name)
+ except AttributeError:
+ pass
+
+ try:
+ return self[name]
+ except (KeyError, IndexError, TypeError, AttributeError):
+ pass
+
+ # This is the part Five adds:
+ # 3. 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()
- # con Zope 3 into using Zope 2's checkPermission
- newInteraction()
+
+ # Con Zope 3 into using Zope 2's checkPermission
+ Products.Five.security.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, NotFoundError,
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
__bobo_traverse__.__five_method__ = True
More information about the Zope-Checkins
mailing list