Commit 2c737818 authored by Christian Zagrodnick's avatar Christian Zagrodnick

- 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.
parent 56178d52
...@@ -127,6 +127,11 @@ Restructuring ...@@ -127,6 +127,11 @@ Restructuring
Features Added 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 - LP #142226: Added an extra keyword argument to the HTTPResponse
setCookie method to suppress enclosing the cookie value field setCookie method to suppress enclosing the cookie value field
in double quotes. in double quotes.
......
...@@ -30,6 +30,7 @@ from Acquisition import aq_parent ...@@ -30,6 +30,7 @@ from Acquisition import aq_parent
from Acquisition.interfaces import IAcquirer from Acquisition.interfaces import IAcquirer
from OFS.interfaces import ITraversable from OFS.interfaces import ITraversable
from zExceptions import NotFound from zExceptions import NotFound
from ZPublisher.interfaces import UseTraversalDefault
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from zope.interface import implements from zope.interface import implements
...@@ -207,60 +208,66 @@ class Traversable: ...@@ -207,60 +208,66 @@ class Traversable:
except LocationError: except LocationError:
raise AttributeError(name) 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:
# 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)
else: else:
if getattr(aq_base(obj), name, _marker) is not _marker: next = UseTraversalDefault # indicator
if restricted: try:
next = guarded_getattr(obj, name) 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: else:
next = getattr(obj, name) try:
else: next = obj[name]
try: # The item lookup may return a NullResource,
next = obj[name] # if this is the case we save it and return it
# The item lookup may return a NullResource, # if all other lookups fail.
# if this is the case we save it and return it if isinstance(next, NullResource):
# if all other lookups fail. resource = next
if isinstance(next, NullResource): raise KeyError(name)
resource = next except AttributeError:
raise KeyError(name) # Raise NotFound for easier debugging
except AttributeError: # instead of AttributeError: __getitem__
# Raise NotFound for easier debugging raise NotFound(name)
# instead of AttributeError: __getitem__ if restricted and not validate(
raise NotFound(name) obj, obj, None, next):
if restricted and not validate( raise Unauthorized(name)
obj, obj, None, next):
raise Unauthorized(name)
except (AttributeError, NotFound, KeyError), e: except (AttributeError, NotFound, KeyError), e:
# Try to look for a view # Try to look for a view
......
...@@ -354,6 +354,33 @@ class TestTraverse( unittest.TestCase ): ...@@ -354,6 +354,33 @@ class TestTraverse( unittest.TestCase ):
self.failUnlessRaises(Unauthorized, self.failUnlessRaises(Unauthorized,
self.root.folder1.restrictedTraverse, 'stuff') 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): def testAcquiredAttributeDenial(self):
# Verify that restrictedTraverse raises the right kind of exception # Verify that restrictedTraverse raises the right kind of exception
# on denial of access to an acquired attribute. If it raises # on denial of access to an acquired attribute. If it raises
......
...@@ -20,6 +20,7 @@ import xmlrpc ...@@ -20,6 +20,7 @@ import xmlrpc
from Acquisition import aq_base from Acquisition import aq_base
from Acquisition.interfaces import IAcquirer from Acquisition.interfaces import IAcquirer
from ZPublisher.interfaces import UseTraversalDefault
from zExceptions import Forbidden from zExceptions import Forbidden
from zExceptions import NotFound from zExceptions import NotFound
from zope.component import queryMultiAdapter from zope.component import queryMultiAdapter
...@@ -79,31 +80,35 @@ class DefaultPublishTraverse(object): ...@@ -79,31 +80,35 @@ class DefaultPublishTraverse(object):
if name[:1]=='_': if name[:1]=='_':
raise Forbidden("Object name begins with an underscore at: %s" % URL) raise Forbidden("Object name begins with an underscore at: %s" % URL)
if hasattr(object,'__bobo_traverse__'): subobject = UseTraversalDefault # indicator
try: try:
subobject=object.__bobo_traverse__(request, name) if hasattr(object,'__bobo_traverse__'):
if type(subobject) is type(()) and len(subobject) > 1: try:
# Add additional parents into the path subobject=object.__bobo_traverse__(request, name)
# XXX There are no tests for this: if type(subobject) is type(()) and len(subobject) > 1:
request['PARENTS'][-1:] = list(subobject[:-1]) # Add additional parents into the path
object, subobject = subobject[-2:] # XXX There are no tests for this:
except (AttributeError, KeyError, NotFound), e: request['PARENTS'][-1:] = list(subobject[:-1])
# Try to find a view object, subobject = subobject[-2:]
subobject = queryMultiAdapter((object, request), Interface, name) except (AttributeError, KeyError, NotFound), e:
if subobject is not None: # Try to find a view
# OFS.Application.__bobo_traverse__ calls subobject = queryMultiAdapter((object, request), Interface, name)
# REQUEST.RESPONSE.notFoundError which sets the HTTP if subobject is not None:
# status code to 404 # OFS.Application.__bobo_traverse__ calls
request.response.setStatus(200) # REQUEST.RESPONSE.notFoundError which sets the HTTP
# We don't need to do the docstring security check # status code to 404
# for views, so lets skip it and return the object here. request.response.setStatus(200)
if IAcquirer.providedBy(subobject): # We don't need to do the docstring security check
subobject = subobject.__of__(object) # for views, so lets skip it and return the object here.
return subobject if IAcquirer.providedBy(subobject):
# No view found. Reraise the error raised by __bobo_traverse__ subobject = subobject.__of__(object)
raise e return subobject
else: # No view found. Reraise the error raised by __bobo_traverse__
# No __bobo_traverse__ raise e
except UseTraversalDefault:
pass
if subobject is UseTraversalDefault:
# No __bobo_traverse__ or default traversal requested
# Try with an unacquired attribute: # Try with an unacquired attribute:
if hasattr(aq_base(object), name): if hasattr(aq_base(object), name):
subobject = getattr(object, name) subobject = getattr(object, name)
......
...@@ -56,5 +56,17 @@ class IPubBeforeStreaming(Interface): ...@@ -56,5 +56,17 @@ class IPubBeforeStreaming(Interface):
something calls response.write() for the first time. Note that this is something calls response.write() for the first time. Note that this is
carries a reference to the *response*, not the request. carries a reference to the *response*, not the request.
""" """
response = Attribute(u"The current HTTP response") 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.
"""
...@@ -124,6 +124,27 @@ class BaseRequest_factory: ...@@ -124,6 +124,27 @@ class BaseRequest_factory:
return self, self._default_path return self, self._default_path
return DummyObjectWithBD() 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): def _makeObjectWithBDBBT(self):
class DummyObjectWithBDBBT(self._makeBasicObjectClass()): class DummyObjectWithBDBBT(self._makeBasicObjectClass()):
"""Dummy class with __browser_default__.""" """Dummy class with __browser_default__."""
...@@ -255,6 +276,16 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory): ...@@ -255,6 +276,16 @@ class TestBaseRequest(unittest.TestCase, BaseRequest_factory):
self.failUnlessRaises(NotFound, r.traverse, self.failUnlessRaises(NotFound, r.traverse,
'folder/objWithBBT/bbt_foo') '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): def test_traverse_withBDBBT(self):
# Test for an object which has a __browser_default__ # Test for an object which has a __browser_default__
# and __bobo_traverse__ # and __bobo_traverse__
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment