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 #533: Implement API get_vocabulary & get_vocabularies_name #557

Merged
merged 15 commits into from
Feb 18, 2025
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions docs/content.md
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ not_found = api.content.get(UID='notfound')

## Find content objects

You can use the {meth}`api.content.find` function to search for content.
You can use the {func}`api.content.find` function to search for content.

Finding all Documents:

Expand Down Expand Up @@ -460,7 +460,6 @@ portal = api.portal.get()
api.content.transition(obj=portal['about'], transition='reject', comment='You had a typo on your page.')
```


(content-disable-roles-acquisition-example)=

## Disable local roles acquisition
Expand Down
2 changes: 1 addition & 1 deletion docs/contribute.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ def foo(path=None, UID=None):
% InvalidParameterError,
% lambda: foo("/plone/blog", "abcd001")
% )
%
%
% # Make it available for testing below
% from plone import api
% api.content.foo = foo
Expand Down
43 changes: 43 additions & 0 deletions docs/portal.md
Original file line number Diff line number Diff line change
Expand Up @@ -416,6 +416,49 @@ api.portal.set_registry_record('field_one', 'new value', interface=IMyRegistrySe
% 'new value'
% )

(portal-get-vocabulary-example)=

## Get vocabulary

To get a vocabulary by name, use {meth}`api.portal.get_vocabulary`.

```python
from plone import api

# Get vocabulary using default portal context
vocabulary = api.portal.get_vocabulary(name='plone.app.vocabularies.PortalTypes')

# Get vocabulary with specific context
context = api.portal.get()
states_vocabulary = api.portal.get_vocabulary(
name='plone.app.vocabularies.WorkflowStates',
context=context
)
```

(portal-get-all-vocabulary-names-example)=

## Get all vocabulary names

To get a list of all available vocabulary names in your Plone site, use {meth}`api.portal.get_vocabulary_names`.

```python
from plone import api

# Get all vocabulary names
vocabulary_names = api.portal.get_vocabulary_names()

# Common vocabularies that should be available
common_vocabularies = [
'plone.app.vocabularies.PortalTypes',
'plone.app.vocabularies.WorkflowStates',
'plone.app.vocabularies.WorkflowTransitions'
]

for vocabulary_name in common_vocabularies:
assert vocabulary_name in vocabulary_names
```

## Further reading

For more information on possible flags and usage options please see the full {ref}`plone-api-portal` specification.
4 changes: 4 additions & 0 deletions news/533.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Added two new portal API functions:
- ``api.portal.get_vocabulary``: Get a vocabulary by name
- ``api.portal.get_vocabulary_names``: Get a list of all available vocabulary names
@ujsquared
41 changes: 41 additions & 0 deletions src/plone/api/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
from Products.CMFCore.interfaces import ISiteRoot
from Products.CMFCore.utils import getToolByName
from Products.statusmessages.interfaces import IStatusMessage
from zope.component import ComponentLookupError
from zope.component import getUtilitiesFor
from zope.component import getUtility
from zope.component import providedBy
from zope.component.hooks import getSite
from zope.globalrequest import getRequest
from zope.interface.interfaces import IInterface
from zope.schema.interfaces import IVocabularyFactory

import datetime as dtime
import re
Expand Down Expand Up @@ -431,3 +434,41 @@ def translate(msgid, domain="plone", lang=None):
# Pass the request, so zope.i18n.translate can negotiate the language.
query["context"] = getRequest()
return translation_service.utranslate(**query)


@required_parameters("name")
def get_vocabulary(name=None, context=None):
"""Return a vocabulary object with the given name.

:param name: Name of the vocabulary.
:type name: str
:param context: Context to be applied to the vocabulary. Default: portal root.
:type context: object
:returns: A SimpleVocabulary instance that implements IVocabularyTokenized.
:rtype: zope.schema.vocabulary.SimpleVocabulary
:Example: :ref:`portal-get-vocabulary-example`
"""
if context is None:
context = get()
try:
vocabulary = getUtility(IVocabularyFactory, name)
except ComponentLookupError:
raise InvalidParameterError(
"Cannot find a vocabulary with name '{name}'.\n"
"Available vocabularies are:\n"
"{vocabularies}".format(
name=name,
vocabularies="\n".join(get_vocabulary_names()),
),
)
return vocabulary(context)


def get_vocabulary_names():
"""Return a list of vocabulary names.

:returns: A sorted list of vocabulary names.
:rtype: list[str]
:Example: :ref:`portal-get-all-vocabulary-names-example`
"""
return sorted([name for name, vocabulary in getUtilitiesFor(IVocabularyFactory)])
69 changes: 69 additions & 0 deletions src/plone/api/tests/test_portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from zope.component import getUtility
from zope.component.hooks import setSite
from zope.interface import Interface
from zope.schema.vocabulary import SimpleVocabulary
from zope.site import LocalSiteManager

import DateTime
Expand Down Expand Up @@ -894,3 +895,71 @@ def test_translate_with_country_codes(self):
),
"Página",
)

def test_get_vocabulary(self):
"""Test getting a vocabulary by name."""
from plone.api.exc import InvalidParameterError
from plone.api.exc import MissingParameterError

# The vocabulary name must be given as parameter
with self.assertRaises(MissingParameterError):
portal.get_vocabulary()

# Test getting a commonly available vocabulary
vocabulary = portal.get_vocabulary(name="plone.app.vocabularies.PortalTypes")
self.assertIsInstance(vocabulary, SimpleVocabulary)

# Test with invalid vocabulary name
with self.assertRaises(InvalidParameterError) as cm:
portal.get_vocabulary(name="non.existing.vocabulary")

expected_msg = (
"Cannot find a vocabulary with name 'non.existing.vocabulary'.\n"
"Available vocabularies are:\n"
)
self.assertTrue(str(cm.exception).startswith(expected_msg))

# Test with context
vocabulary_with_context = portal.get_vocabulary(
name="plone.app.vocabularies.PortalTypes", context=self.portal
)
self.assertIsInstance(vocabulary_with_context, SimpleVocabulary)

def test_get_vocabulary_names(self):
"""Test getting list of vocabulary names."""
names = portal.get_vocabulary_names()

# Test we get a list of strings
self.assertIsInstance(names, list)
self.assertTrue(len(names) > 0)
self.assertIsInstance(names[0], str)

# Test that common vocabularies are included
common_vocabularies = [
"plone.app.vocabularies.PortalTypes",
"plone.app.vocabularies.WorkflowStates",
"plone.app.vocabularies.WorkflowTransitions",
]

for vocabulary_name in common_vocabularies:
self.assertIn(vocabulary_name, names)

def test_vocabulary_terms(self):
"""Test the actual content of retrieved vocabularies."""
# Get portal types vocabulary
types_vocabulary = portal.get_vocabulary("plone.app.vocabularies.PortalTypes")

# Check that we have some common content types
types = [term.value for term in types_vocabulary]
self.assertIn("Document", types)
self.assertIn("Folder", types)

# Get workflow states vocabulary
states_vocabulary = portal.get_vocabulary(
"plone.app.vocabularies.WorkflowStates"
)

# Check that we have some common workflow states
states = [term.value for term in states_vocabulary]
self.assertIn("private", states)
self.assertIn("published", states)