Log message for revision 113725: - LP #374810: ``__bobo_traverse__`` implementation can raise ``ZPublisher.interfaces.UseTraversalDefault`` to indicate that there is no special casing for the given name and that standard traversal logic should be applied.
Changed: U Zope/trunk/doc/CHANGES.rst U Zope/trunk/src/OFS/Traversable.py U Zope/trunk/src/OFS/tests/testTraverse.py U Zope/trunk/src/ZPublisher/BaseRequest.py U Zope/trunk/src/ZPublisher/interfaces.py U Zope/trunk/src/ZPublisher/tests/testBaseRequest.py -=- Modified: Zope/trunk/doc/CHANGES.rst =================================================================== --- Zope/trunk/doc/CHANGES.rst 2010-06-21 11:08:59 UTC (rev 113724) +++ Zope/trunk/doc/CHANGES.rst 2010-06-21 14:47:19 UTC (rev 113725) @@ -127,6 +127,11 @@ Features Added ++++++++++++++ +- LP #374810: ``__bobo_traverse__`` implementation can raise + ``ZPublisher.interfaces.UseTraversalDefault`` to indicate that there is no + special casing for the given name and that standard traversal logic should + be applied. + - LP #142226: Added an extra keyword argument to the HTTPResponse setCookie method to suppress enclosing the cookie value field in double quotes. Modified: Zope/trunk/src/OFS/Traversable.py =================================================================== --- Zope/trunk/src/OFS/Traversable.py 2010-06-21 11:08:59 UTC (rev 113724) +++ Zope/trunk/src/OFS/Traversable.py 2010-06-21 14:47:19 UTC (rev 113725) @@ -30,6 +30,7 @@ from Acquisition.interfaces import IAcquirer from OFS.interfaces import ITraversable from zExceptions import NotFound +from ZPublisher.interfaces import UseTraversalDefault from ZODB.POSException import ConflictError from zope.interface import implements @@ -207,60 +208,66 @@ except LocationError: raise AttributeError(name) - elif bobo_traverse is not None: - next = bobo_traverse(REQUEST, name) - if restricted: - if aq_base(next) is not next: - # The object is wrapped, so the acquisition - # context is the container. - container = aq_parent(aq_inner(next)) - elif getattr(next, 'im_self', None) is not None: - # Bound method, the bound instance - # is the container - container = next.im_self - elif getattr(aq_base(obj), name, _marker) is next: - # Unwrapped direct attribute of the object so - # object is the container - container = obj + else: + next = UseTraversalDefault # indicator + try: + if bobo_traverse is not None: + next = bobo_traverse(REQUEST, name) + if restricted: + if aq_base(next) is not next: + # The object is wrapped, so the acquisition + # context is the container. + container = aq_parent(aq_inner(next)) + elif getattr(next, 'im_self', None) is not None: + # Bound method, the bound instance + # is the container + container = next.im_self + elif getattr(aq_base(obj), name, _marker) is next: + # Unwrapped direct attribute of the object so + # object is the container + container = obj + else: + # Can't determine container + container = None + # If next is a simple unwrapped property, its + # parentage is indeterminate, but it may have + # been acquired safely. In this case validate + # will raise an error, and we can explicitly + # check that our value was acquired safely. + try: + ok = validate(obj, container, name, next) + except Unauthorized: + ok = False + if not ok: + if (container is not None or + guarded_getattr(obj, name, _marker) + is not next): + raise Unauthorized(name) + except UseTraversalDefault: + # behave as if there had been no '__bobo_traverse__' + bobo_traverse = None + if next is UseTraversalDefault: + if getattr(aq_base(obj), name, _marker) is not _marker: + if restricted: + next = guarded_getattr(obj, name) + else: + next = getattr(obj, name) else: - # Can't determine container - container = None - # If next is a simple unwrapped property, its - # parentage is indeterminate, but it may have - # been acquired safely. In this case validate - # will raise an error, and we can explicitly - # check that our value was acquired safely. - try: - ok = validate(obj, container, name, next) - except Unauthorized: - ok = False - if not ok: - if (container is not None or - guarded_getattr(obj, name, _marker) - is not next): + try: + next = obj[name] + # The item lookup may return a NullResource, + # if this is the case we save it and return it + # if all other lookups fail. + if isinstance(next, NullResource): + resource = next + raise KeyError(name) + except AttributeError: + # Raise NotFound for easier debugging + # instead of AttributeError: __getitem__ + raise NotFound(name) + if restricted and not validate( + obj, obj, None, next): raise Unauthorized(name) - else: - if getattr(aq_base(obj), name, _marker) is not _marker: - if restricted: - next = guarded_getattr(obj, name) - else: - next = getattr(obj, name) - else: - try: - next = obj[name] - # The item lookup may return a NullResource, - # if this is the case we save it and return it - # if all other lookups fail. - if isinstance(next, NullResource): - resource = next - raise KeyError(name) - except AttributeError: - # Raise NotFound for easier debugging - # instead of AttributeError: __getitem__ - raise NotFound(name) - if restricted and not validate( - obj, obj, None, next): - raise Unauthorized(name) except (AttributeError, NotFound, KeyError), e: # Try to look for a view Modified: Zope/trunk/src/OFS/tests/testTraverse.py =================================================================== --- Zope/trunk/src/OFS/tests/testTraverse.py 2010-06-21 11:08:59 UTC (rev 113724) +++ Zope/trunk/src/OFS/tests/testTraverse.py 2010-06-21 14:47:19 UTC (rev 113725) @@ -354,6 +354,33 @@ self.failUnlessRaises(Unauthorized, self.root.folder1.restrictedTraverse, 'stuff') + def testBoboTraverseTraversalDefault(self): + from OFS.SimpleItem import SimpleItem + from ZPublisher.interfaces import UseTraversalDefault + class BoboTraversableUseTraversalDefault(SimpleItem): + """ + A BoboTraversable class which may use "UseTraversalDefault" + (dependent on "name") to indicate that standard traversal should + be used. + """ + default = 'Default' + + def __bobo_traverse__(self, request, name): + if name == 'normal': return 'Normal' + raise UseTraversalDefault + + + bb = BoboTraversableUseTraversalDefault() + # normal access -- no traversal default used + self.assertEqual(bb.unrestrictedTraverse('normal'), 'Normal') + # use traversal default + self.assertEqual(bb.unrestrictedTraverse('default'), 'Default') + # test traversal default with acqires attribute + si = SimpleItem() + si.default_acquire = 'Default_Acquire' + si.bb = bb + self.assertEqual(si.unrestrictedTraverse('bb/default_acquire'), 'Default_Acquire') + def testAcquiredAttributeDenial(self): # Verify that restrictedTraverse raises the right kind of exception # on denial of access to an acquired attribute. If it raises Modified: Zope/trunk/src/ZPublisher/BaseRequest.py =================================================================== --- Zope/trunk/src/ZPublisher/BaseRequest.py 2010-06-21 11:08:59 UTC (rev 113724) +++ Zope/trunk/src/ZPublisher/BaseRequest.py 2010-06-21 14:47:19 UTC (rev 113725) @@ -20,6 +20,7 @@ from Acquisition import aq_base from Acquisition.interfaces import IAcquirer +from ZPublisher.interfaces import UseTraversalDefault from zExceptions import Forbidden from zExceptions import NotFound from zope.component import queryMultiAdapter @@ -79,31 +80,35 @@ if name[:1]=='_': raise Forbidden("Object name begins with an underscore at: %s" % URL) - if hasattr(object,'__bobo_traverse__'): - try: - subobject=object.__bobo_traverse__(request, name) - if type(subobject) is type(()) and len(subobject) > 1: - # Add additional parents into the path - # XXX There are no tests for this: - request['PARENTS'][-1:] = list(subobject[:-1]) - object, subobject = subobject[-2:] - except (AttributeError, KeyError, NotFound), e: - # Try to find a view - 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. - if IAcquirer.providedBy(subobject): - subobject = subobject.__of__(object) - return subobject - # No view found. Reraise the error raised by __bobo_traverse__ - raise e - else: - # No __bobo_traverse__ + subobject = UseTraversalDefault # indicator + try: + if hasattr(object,'__bobo_traverse__'): + try: + subobject=object.__bobo_traverse__(request, name) + if type(subobject) is type(()) and len(subobject) > 1: + # Add additional parents into the path + # XXX There are no tests for this: + request['PARENTS'][-1:] = list(subobject[:-1]) + object, subobject = subobject[-2:] + except (AttributeError, KeyError, NotFound), e: + # Try to find a view + 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. + if IAcquirer.providedBy(subobject): + subobject = subobject.__of__(object) + return subobject + # No view found. Reraise the error raised by __bobo_traverse__ + raise e + except UseTraversalDefault: + pass + if subobject is UseTraversalDefault: + # No __bobo_traverse__ or default traversal requested # Try with an unacquired attribute: if hasattr(aq_base(object), name): subobject = getattr(object, name) Modified: Zope/trunk/src/ZPublisher/interfaces.py =================================================================== --- Zope/trunk/src/ZPublisher/interfaces.py 2010-06-21 11:08:59 UTC (rev 113724) +++ Zope/trunk/src/ZPublisher/interfaces.py 2010-06-21 14:47:19 UTC (rev 113725) @@ -56,5 +56,17 @@ something calls response.write() for the first time. Note that this is carries a reference to the *response*, not the request. """ - + response = Attribute(u"The current HTTP response") + + +# Exceptions + +class UseTraversalDefault(Exception): + """Indicate default traversal in ``__bobo_traverse__`` + + This exception can be raised by '__bobo_traverse__' implementations to + indicate that it has no special casing for the given name and that standard + traversal logic should be applied. + + """ Modified: Zope/trunk/src/ZPublisher/tests/testBaseRequest.py =================================================================== --- Zope/trunk/src/ZPublisher/tests/testBaseRequest.py 2010-06-21 11:08:59 UTC (rev 113724) +++ Zope/trunk/src/ZPublisher/tests/testBaseRequest.py 2010-06-21 14:47:19 UTC (rev 113725) @@ -124,6 +124,27 @@ return self, self._default_path return DummyObjectWithBD() + def _makeObjectWithBBT(self): + from ZPublisher.interfaces import UseTraversalDefault + + class _DummyResult(object): + ''' ''' + def __init__(self, tag): + self.tag = tag + + class DummyObjectWithBBT(self._makeBasicObjectClass()): + """ Dummy class with __bobo_traverse__ + """ + default = _DummyResult('Default') + + def __bobo_traverse__(self, REQUEST, name): + if name == 'normal': + return _DummyResult('Normal') + elif name == 'default': + raise UseTraversalDefault + raise AttributeError(name) + return DummyObjectWithBBT() + def _makeObjectWithBDBBT(self): class DummyObjectWithBDBBT(self._makeBasicObjectClass()): """Dummy class with __browser_default__.""" @@ -255,6 +276,16 @@ self.failUnlessRaises(NotFound, r.traverse, 'folder/objWithBBT/bbt_foo') + def test_traverse_UseTraversalDefault(self): + root, folder = self._makeRootAndFolder() + folder._setObject('objWithBBT', self._makeObjectWithBBT()) + # test non default usage + r = self._makeOne(root) + self.assertEqual(r.traverse('folder/objWithBBT/normal').tag, 'Normal') + # test default usage + r = self._makeOne(root) + self.assertEqual(r.traverse('folder/objWithBBT/default').tag, 'Default') + def test_traverse_withBDBBT(self): # Test for an object which has a __browser_default__ # and __bobo_traverse__ _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org https://mail.zope.org/mailman/listinfo/zope-checkins