Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Issue #532: Implement get_path helper API #559

Merged
merged 12 commits into from
Feb 21, 2025
52 changes: 52 additions & 0 deletions docs/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -536,6 +536,58 @@ view = api.content.get_view(
%
% self.assertEqual(view.__name__, u'plone')

(content-get-path-example)=

## Get content path

To get the path of a content object, use {meth}`api.content.get_path`.
This method returns either an absolute path from the Zope root or a relative path from the portal root.

The following example shows how to get the absolute path from the Zope root

```python
from plone import api
portal = api.portal.get()

folder = portal['events']['training']
path = api.content.get_path(obj=folder)
assert path == '/plone/events/training'
```

The following example shows how to get the portal-relative path.

```python
rel_path = api.content.get_path(obj=folder, relative_to_portal=True)
assert rel_path == '/events/training'
```

If the API is used to fetch an object outside the portal with the {meth}`relative_to_portal` parameter set as `True`, it throws a `InvalidParameterError`.

% invisible-code-block: python
%
% # Setup an object outside portal for testing error case
% app = portal.aq_parent
% app.manage_addFolder('outside_folder')
%
% # Test that getting relative path for object outside portal raises error
% from plone.api.exc import InvalidParameterError
% with self.assertRaises(InvalidParameterError):
% api.content.get_path(obj=app.outside_folder, relative_to_portal=True)

```python
from plone.api.exc import InvalidParameterError

# Getting path of an object outside portal raises InvalidParameterError
try:
outside_path = api.content.get_path(
obj=app.outside_folder,
relative_to_portal=True
)
assert False, "Should raise InvalidParameterError & not reach this code"
except InvalidParameterError as e:
assert "Object not in portal path" in str(e)
```

## Further reading

For more information on possible flags and usage options please see the full {ref}`plone-api-content` specification.
1 change: 1 addition & 0 deletions news/532.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added the content API helper function ``api.content.get_path``, which gets either the relative or absolute path of an object. @ujsquared
30 changes: 30 additions & 0 deletions src/plone/api/content.py
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,36 @@ def get_uuid(obj=None):
return IUUID(obj)


@required_parameters("obj")
def get_path(obj=None, relative_to_portal=False):
"""Get the path of an object.

:param obj: [required] Object for which to get its path
:type obj: Content object
:param relative_to_portal: Return a relative path from the portal root
:type relative_to_portal: boolean
:returns: Path to the object
:rtype: string
:raises:
InvalidParameterError
:Example: :ref:`content-get-path-example`
"""
if not hasattr(obj, "getPhysicalPath"):
raise InvalidParameterError(f"Cannot get path of object of type {type(obj)}")

if relative_to_portal:
site = portal.get()
site_path = site.getPhysicalPath()
obj_path = obj.getPhysicalPath()
if obj_path[: len(site_path)] != site_path:
raise InvalidParameterError(
"Object not in portal path. Object path: {}".format("/".join(obj_path))
)
rel_path = obj_path[len(site_path) :]
return "/" + "/".join(rel_path) if rel_path else "/"
return "/" + "/".join(obj.getPhysicalPath()[1:])


def _parse_object_provides_query(query):
"""Create a query for the object_provides index.

Expand Down
50 changes: 50 additions & 0 deletions src/plone/api/tests/test_content.py
Original file line number Diff line number Diff line change
Expand Up @@ -1447,3 +1447,53 @@ def test_get_view_view_not_found(self):

for should_be_there in should_be_theres:
self.assertIn((should_be_there + "\n"), str(cm.exception))

def test_get_path(self):
"""Test getting the path of a content object."""
from plone.api.exc import InvalidParameterError

portal = self.layer["portal"]

# Test portal root
self.assertEqual(
api.content.get_path(portal), "/plone" # This assumes default Plone site id
)
self.assertEqual(api.content.get_path(portal, relative_to_portal=True), "/")

# Test folder structure
folder = api.content.create(container=portal, type="Folder", id="test-folder")
self.assertEqual(api.content.get_path(folder), "/plone/test-folder")
self.assertEqual(
api.content.get_path(folder, relative_to_portal=True), "/test-folder"
)

# Test nested content
document = api.content.create(
container=folder, type="Document", id="test-document"
)
self.assertEqual(
api.content.get_path(document), "/plone/test-folder/test-document"
)
self.assertEqual(
api.content.get_path(document, relative_to_portal=True),
"/test-folder/test-document",
)

# Test invalid object
invalid_obj = object()
with self.assertRaises(InvalidParameterError) as cm:
api.content.get_path(invalid_obj)
self.assertEqual(
str(cm.exception), "Cannot get path of object of type <class 'object'>"
)

# Test object outside portal

class FauxObject:
def getPhysicalPath(self):
return ("", "foo", "bar")

outside_obj = FauxObject()
with self.assertRaises(InvalidParameterError) as cm:
api.content.get_path(outside_obj, relative_to_portal=True)
self.assertIn("Object not in portal path", str(cm.exception))