ploneplone-6

Is it possible to define a vocabulary based on terms saved in annotations?


Context: I have a simple way to save some json data in annotations (on portal object). This definitions sometimes changes so I just upload the new json.

I'm trying to create a behavior for my content types: a new field that saves the terms you selected from the current definitions.

The problem is that I can't access annotations from the function used to populate the vocabulary. I get:

(Pdb) from plone import api
(Pdb) api.portal.get()
*** plone.api.exc.CannotGetPortalError: Unable to get the portal object. More info on https://docs.plone.org/develop/plone.api/docs/api/exceptions.html#plone.api.exc.CannotGetPortalError

This is what I have now:

vocabulary.py

def generic_vocabulary(_terms, sort=True):
    """Returns a zope vocabulary from a dict or a list"""

    if _terms and isinstance(_terms, dict):
        _terms = _terms.items()
    elif _terms and isinstance(_terms[0], str):
        _terms = [(x, x) for x in _terms]

    if sort:
        _terms = sorted(_terms, key=lambda x: x[0])

    def factory(context):
        """Simple Vocabulary factory"""
        return SimpleVocabulary(
            [SimpleTerm(n, n.encode("utf-8"), term) for n, term in _terms]
        )

    return factory


_terms_list = (
    ("key1", "Value 1"),
    ("key2", "Value 2"),    
)


def generate_terms_list():
    import pdb

    pdb.set_trace()

    # TODO Get the list of terms from annotations
    # from plone import api
    # api.portal.get() ERROR HERE
    return _terms_list


terms_list = generic_vocabulary(generate_terms_list())
alsoProvides(terms_list, IVocabularyFactory)

configure.zcml

  <utility
    name="my.add.on.terms"
    component=".vocabulary.terms_list"
    />

behavior.py

from plone.autoform import directives
from plone.autoform.interfaces import IFormFieldProvider
from plone.supermodel import model
from zope import schema
from zope.interface import provider


@provider(IFormFieldProvider)
class ICustomTermsListBehavior(model.Schema):
    
    terms_list = schema.List(
        title="Terms List",
        description="Select Terms",
        required=False,
        value_type=schema.Choice(vocabulary="my.add.on.terms"),
    )
    directives.write_permission(terms_list="cmf.ManagePortal")

The code is working as expected if I use _terms_list. Now I just want to replace these demo terms with the real content from annotations:

def get_annot():
    annot_key = 'TEST_KEY'
    container = api.portal.get()
    annotations = IAnnotations(container)
    return annotations[annot_key]

How can I fix plone.api.exc.CannotGetPortalError: Unable to get the portal object error, and get my data from annotations?

UPDATE:

Already tried, too:

(Pdb) from zope.component import getUtility
(Pdb) from Products.CMFCore.interfaces import ISiteRoot
(Pdb) portal = getUtility(ISiteRoot)
*** zope.interface.interfaces.ComponentLookupError: (<InterfaceClass Products.CMFCore.interfaces.ISiteRoot>, '')

Same for:

(Pdb) site = api.portal.getSite()
(Pdb) site is None
True

Not working:

(Pdb) from zope.component.hooks import getSite
(Pdb) getSite() is None
True

Solution

  • The answer is: YES.

    I found it here: https://5.docs.plone.org/external/plone.app.dexterity/docs/advanced/vocabularies.html#dynamic-sources

    Solved with:

    def generate_terms_list(context):
        terms = [(str(x["id"]), str(x["value"])) for x in get_annot()]
        return terms
    
    @provider(IContextSourceBinder)
    def get_terms(context):
        return generic_vocabulary(generate_terms_list(context))(context)
    

    ...

    @provider(IFormFieldProvider)
    class ICustomTermsListBehavior(model.Schema):
        
        terms_list = schema.List(
            title="Terms List",
            description="Select Terms",
            required=False,
            value_type=schema.Choice(source=get_terms),
        )
        directives.write_permission(terms_list="cmf.ManagePortal")
    

    Note source instead of vocabulary and @provider(IContextSourceBinder) + context.