Log message for revision 66191: Merge philikon-fix-lookup-priorities and fix up doc files for upcoming release.
Changed: U Products.Five/branches/1.3/CHANGES.txt U Products.Five/branches/1.3/INSTALL.txt U Products.Five/branches/1.3/browser/tests/test_traversable.py U Products.Five/branches/1.3/fiveconfigure.py U Products.Five/branches/1.3/tests/testing/fancycontent.py U Products.Five/branches/1.3/traversable.py U Products.Five/branches/1.3/version.txt -=- Modified: Products.Five/branches/1.3/CHANGES.txt =================================================================== --- Products.Five/branches/1.3/CHANGES.txt 2006-03-26 20:40:18 UTC (rev 66190) +++ Products.Five/branches/1.3/CHANGES.txt 2006-03-26 20:42:53 UTC (rev 66191) @@ -2,9 +2,24 @@ Five Changes ============ -Five 1.3.3 (unreleased) +Five 1.3.3 (2006-03-26) ======================= - + +Bugfixes +-------- + +* 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. + * A local utility registered with an derived interface will now be available by the inherited interface as well, in the same way as Zope3. Modified: Products.Five/branches/1.3/INSTALL.txt =================================================================== --- Products.Five/branches/1.3/INSTALL.txt 2006-03-26 20:40:18 UTC (rev 66190) +++ Products.Five/branches/1.3/INSTALL.txt 2006-03-26 20:42:53 UTC (rev 66191) @@ -4,7 +4,7 @@ Requirements for Five 1.3 ------------------------- -* Zope 2.9+ with Python 2.4.1+ +* Zope 2.9.2+ with Python 2.4.1+ Note that Five 1.3 is already part of Zope 2.9. You can still install a newer Five version in your instance, if you like. It will override Modified: Products.Five/branches/1.3/browser/tests/test_traversable.py =================================================================== --- Products.Five/branches/1.3/browser/tests/test_traversable.py 2006-03-26 20:40:18 UTC (rev 66190) +++ Products.Five/branches/1.3/browser/tests/test_traversable.py 2006-03-26 20:42:53 UTC (rev 66191) @@ -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/1.3/fiveconfigure.py =================================================================== --- Products.Five/branches/1.3/fiveconfigure.py 2006-03-26 20:40:18 UTC (rev 66190) +++ Products.Five/branches/1.3/fiveconfigure.py 2006-03-26 20:42:53 UTC (rev 66191) @@ -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/1.3/tests/testing/fancycontent.py =================================================================== --- Products.Five/branches/1.3/tests/testing/fancycontent.py 2006-03-26 20:40:18 UTC (rev 66190) +++ Products.Five/branches/1.3/tests/testing/fancycontent.py 2006-03-26 20:42:53 UTC (rev 66191) @@ -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.3/traversable.py =================================================================== --- Products.Five/branches/1.3/traversable.py 2006-03-26 20:40:18 UTC (rev 66190) +++ Products.Five/branches/1.3/traversable.py 2006-03-26 20:42:53 UTC (rev 66191) @@ -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,11 +26,10 @@ 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 - class FakeRequest(dict): implements(IBrowserRequest) @@ -47,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 @@ -66,43 +51,61 @@ 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() + setDefaultSkin(REQUEST) - # 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 + Products.Five.security.newInteraction() - # 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 Modified: Products.Five/branches/1.3/version.txt =================================================================== --- Products.Five/branches/1.3/version.txt 2006-03-26 20:40:18 UTC (rev 66190) +++ Products.Five/branches/1.3/version.txt 2006-03-26 20:42:53 UTC (rev 66191) @@ -1 +1 @@ -Five 1.3.2 +Five 1.3.3 _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org http://mail.zope.org/mailman/listinfo/zope-checkins