diff --git a/news/105.bugfix b/news/105.bugfix new file mode 100644 index 0000000..7447cd0 --- /dev/null +++ b/news/105.bugfix @@ -0,0 +1 @@ +Simplify traversal, fixing e.g. plone.app.theming #165 [petri, petschki] diff --git a/plone-5.2.x.cfg b/plone-5.2.x.cfg index b810a87..f4d814f 100644 --- a/plone-5.2.x.cfg +++ b/plone-5.2.x.cfg @@ -14,8 +14,6 @@ virtualenv = 20.0.35 # Error: The requirement ('pep517>=0.9') is not allowed by your [versions] constraint (0.8.2) pep517 = 0.9.1 -# Error: The requirement ('importlib-metadata>=1') is not allowed by your [versions] constraint (0.23) -importlib-metadata = 2.0.0 # cryptography 3.4 requires a rust compiler installed on the system: # https://github.com/pyca/cryptography/blob/master/CHANGELOG.rst#34---2021-02-07 diff --git a/src/plone/rest/tests/test_traversal.py b/src/plone/rest/tests/test_traversal.py index dc67591..8701c2c 100644 --- a/src/plone/rest/tests/test_traversal.py +++ b/src/plone/rest/tests/test_traversal.py @@ -106,17 +106,6 @@ def test_html_request_on_existing_view_returns_view(self): obj = self.traverse(path="/plone/folder1/search", accept="text/html") self.assertFalse(isinstance(obj, Service), "Got a service") - def test_virtual_hosting(self): - app = self.layer["app"] - vhm = VirtualHostMonster() - vhm.id = "virtual_hosting" - vhm.addToContainer(app) - obj = self.traverse( - path="/VirtualHostBase/http/localhost:8080/plone/VirtualHostRoot/" - ) # noqa - self.assertTrue(isinstance(obj, Service), "Not a service") - del app["virtual_hosting"] - def test_json_request_to_regular_view_returns_view(self): obj = self.traverse("/plone/folder_contents") self.assertTrue(IBrowserView.providedBy(obj), "IBrowserView expected") @@ -132,3 +121,30 @@ def test_json_request_to_view_namespace_returns_view(self): self.portal[self.portal.invokeFactory("Folder", id="folder1")] obj = self.traverse("/plone/folder1/@@folder_contents") self.assertTrue(IBrowserView.providedBy(obj), "IBrowserView expected") + + def setup_vhm(self): + app = self.layer["app"] + vhm = VirtualHostMonster() + vhm.id = "virtual_hosting" + vhm.addToContainer(app) + + def teardown_vhm(self): + del self.layer["app"]["virtual_hosting"] + + def test_virtual_hosting(self): + self.setup_vhm() + obj = self.traverse( + path="/VirtualHostBase/http/localhost:8080/plone/VirtualHostRoot/" + ) # noqa + self.assertTrue(isinstance(obj, Service), "Not a service") + self.teardown_vhm() + + def test_virtual_hosting_on_navroot_folder(self): + self.setup_vhm() + folder = self.portal[self.portal.invokeFactory("Folder", id="folder1")] + alsoProvides(folder, INavigationRoot) + obj = self.traverse( + path="/VirtualHostBase/http/localhost:8080/plone/folder1/VirtualHostRoot/" + ) # noqa + self.assertTrue(isinstance(obj, Service), "Not a service") + self.teardown_vhm() diff --git a/src/plone/rest/traverse.py b/src/plone/rest/traverse.py index f8d4a23..9957389 100644 --- a/src/plone/rest/traverse.py +++ b/src/plone/rest/traverse.py @@ -1,50 +1,33 @@ # -*- coding: utf-8 -*- -from Products.CMFCore.interfaces import ISiteRoot -from Products.SiteAccess.VirtualHostMonster import VirtualHostMonster -from ZPublisher.BaseRequest import DefaultPublishTraverse from plone.rest.interfaces import IAPIRequest -from plone.rest.interfaces import IService from plone.rest.events import mark_as_api_request from zope.component import adapter from zope.component import queryMultiAdapter from zope.interface import implementer from zope.publisher.interfaces.browser import IBrowserPublisher +from ZPublisher.BaseRequest import DefaultPublishTraverse from zope.traversing.interfaces import ITraversable from Products.CMFCore.interfaces import IContentish +from Products.CMFCore.interfaces import ISiteRoot -@adapter(ISiteRoot, IAPIRequest) -class RESTTraverse(DefaultPublishTraverse): +class RESTPublishTraverse(object): def publishTraverse(self, request, name): - try: - obj = super(RESTTraverse, self).publishTraverse(request, name) - if not IContentish.providedBy(obj) and not IService.providedBy(obj): - if isinstance(obj, VirtualHostMonster): - return obj - else: - raise KeyError - except KeyError: - # No object, maybe a named rest service - service = queryMultiAdapter( - (self.context, request), name=request._rest_service_id + name - ) - if service is None: - # No service, fallback to regular view - view = queryMultiAdapter((self.context, request), name=name) - if view is not None: - return view - raise + service = queryMultiAdapter( + (self.context, request), name=request._rest_service_id + name + ) + if service is not None: return service - if name.startswith(request._rest_service_id): - return obj + adapter = DefaultPublishTraverse(self.context, request) + obj = adapter.publishTraverse(request, name) - # Do not handle view namespace - if "@@" in request["PATH_INFO"] or "++view++" in request["PATH_INFO"]: - return obj + if IContentish.providedBy(obj) and not ( + "@@" in request["PATH_INFO"] or "++view++" in request["PATH_INFO"] + ): + return RESTWrapper(obj, request) - # Wrap object to ensure we handle further traversal - return RESTWrapper(obj, request) + return obj def browserDefault(self, request): # Called when we have reached the end of the path @@ -52,6 +35,11 @@ def browserDefault(self, request): return self.context, (request._rest_service_id,) +@adapter(ISiteRoot, IAPIRequest) +class RESTTraverse(RESTPublishTraverse, DefaultPublishTraverse): + """traversal object during REST requests.""" + + @implementer(ITraversable) class MarkAsRESTTraverser(object): """ @@ -69,7 +57,7 @@ def traverse(self, name_ignored, subpath_ignored): @implementer(IBrowserPublisher) -class RESTWrapper(object): +class RESTWrapper(RESTPublishTraverse): """A wrapper for objects traversed during a REST request.""" def __init__(self, context, request): @@ -93,34 +81,3 @@ def __before_publishing_traverse__(self, arg1, arg2=None): if not self._bpth_called: self._bpth_called = True bpth(arg1, arg2) - - def publishTraverse(self, request, name): - # Try to get an object using default traversal - adapter = DefaultPublishTraverse(self.context, request) - try: - obj = adapter.publishTraverse(request, name) - if not IContentish.providedBy(obj) and not IService.providedBy(obj): - raise KeyError - - # If there's no object with the given name, we get a KeyError. - # In a non-folderish context a key lookup results in an AttributeError. - except (KeyError, AttributeError): - # No object, maybe a named rest service - service = queryMultiAdapter( - (self.context, request), name=request._rest_service_id + name - ) - if service is None: - # No service, fallback to regular view - view = queryMultiAdapter((self.context, request), name=name) - if view is not None: - return view - raise - return service - else: - # Wrap object to ensure we handle further traversal - return RESTWrapper(obj, request) - - def browserDefault(self, request): - # Called when we have reached the end of the path - # In our case this means an unamed service - return self.context, (request._rest_service_id,)