Use inspect to get the name of a named utility

While developing a small application with a lot of vocabularies I found a pattern how to use registry-values as a vocabulary. I'd like to know if you think this is a good idea.

It is based on https://training.plone.org/5/mastering-plone/registry.html#vocabularies

Basically it goes like this:

Create a controlpanel that stores settings in the registry as usual in controlpanel.py:

# -*- coding: utf-8 -*-
from plone.app.registry.browser.controlpanel import ControlPanelFormWrapper
from plone.app.registry.browser.controlpanel import RegistryEditForm
from plone.z3cform import layout
from zope import schema
from zope.interface import Interface


class IProjectControlPanel(Interface):

    foo = schema.Tuple(
        title=u'Foo',
        default=(
            u'One',
            u'Two',
        ),
        missing_value=None,
        required=False,
        value_type=schema.TextLine(),
    )

    bar = schema.Tuple(
        title=u'Bar',
        default=(
            u'Three',
            u'Four',
        ),
        missing_value=None,
        required=False,
        value_type=schema.TextLine(),
    )


class ProjectControlPanelForm(RegistryEditForm):
    schema = IProjectControlPanel
    schema_prefix = 'project'
    label = u'Project Settings'


ProjectControlPanelView = layout.wrap_form(
    ProjectControlPanelForm, ControlPanelFormWrapper)

Register that in registry.xml:

<records interface="my.project.browser.controlpanel.IProjectControlPanel"
         prefix="project" />

Now the trick. Instead of having many vocabularies that di the same with different values I reuse the same factory and get the registry-value in question from the stack. Register named vocabularies with the same name as the registry-entry and point to the same factory:

<utility
    name="project.foo"
    component="my.project.vocabularies.RegistryValueVocabularyFactory" />

<utility
    name="project.bar"
    component="my.project.vocabularies.RegistryValueVocabularyFactory" />

In vocabularies.py get the name and reuse it to get the registry-value:

# -*- coding: utf-8 -*-
from inspect import currentframe
from plone import api
from plone.app.vocabularies.terms import safe_simplevocabulary_from_values
from zope.interface import provider
from zope.schema.interfaces import IVocabularyFactory


@provider(IVocabularyFactory)
def RegistryValueVocabularyFactory(context):
    # get the name of this utility by inspecting the previous stack
    name = currentframe().f_back.f_locals['name']
    values = api.portal.get_registry_record(name)
    return safe_simplevocabulary_from_values(values)

Now you can use it in your schema:

class ISomeType(model.Schema):

    foo = schema.Choice(
        title=u'Foo',
        vocabulary='project.foo',
        required=False,
    )

    bar = schema.Choice(
        title=u'Bar',
        vocabulary='project.bar',
        required=False,
    )
1 Like

Check https://docs.plone.org/external/plone.app.dexterity/docs/advanced/vocabularies.html#parameterised-sources on how to pass in the name explicitly

2 Likes

Nice, since I do not really need a named vocabulary I would also save the utility-registration. I'll give that a try.

That works fine.

This is now my vocabularies.py:

# -*- coding: utf-8 -*-
from plone import api
from plone.app.vocabularies.terms import safe_simplevocabulary_from_values
from zope.interface import implementer
from zope.schema.interfaces import IContextSourceBinder


@implementer(IContextSourceBinder)
class RegistryValueVocabulary(object):

    def __init__(self, value_name):
        self.value_name = value_name

    def __call__(self, context):
        values = api.portal.get_registry_record(self.value_name)
        return safe_simplevocabulary_from_values(values)

and here is the schema:

class ISomeType(model.Schema):

    foo = schema.Choice(
        title=u'Foo',
        source=RegistryValueVocabulary('project.foo'),
        required=False,
    )

    bar = schema.Choice(
        title=u'Bar',
        source=RegistryValueVocabulary('project.bar'),
        required=False,
    )

Now the vocabularies.zcml can go away and I do not need to use inspect any more.

3 Likes

Thanks for the great tip! I used this to eliminate a huge chunk of ZCML and vocabulary factory code that was simply looking up registry vocabulary values: https://github.com/uwosh/uwosh.oie.studyabroadstudent/commit/af79ebf9443b91703ca605a43d5eb37774066c31

1 Like