Vocabularies#
Vocabularies are lists of value/title pairs that provide options for selection fields.
They are commonly used with zope.schema.Choice fields to populate dropdown menus, checkboxes, and radio buttons in forms.
The zope.schema package provides tools to programmatically construct vocabularies using SimpleVocabulary and SimpleTerm objects.
Vocabulary Terms#
A vocabulary consists of terms. Each term represents one selectable option and has three key attributes:
SimpleTerm.tokenMust be an ASCII string. This is the value passed with the request when the form is submitted. A token must uniquely identify a term.
SimpleTerm.valueThe actual value stored on the object. This is not passed to the browser or used in the form. The value is often a unicode string, but can be any type of object.
SimpleTerm.titleA unicode string or translatable message. It is used for display in the form.
Note
If you need internationalized texts, only the title should be translated.
The value and token must always carry the same value.
Creating a Static Vocabulary#
From a List of Tuples#
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
items = [
('value1', 'This is label for item'),
('value2', 'This is label for value 2'),
]
terms = [
SimpleTerm(value=pair[0], token=pair[0], title=pair[1])
for pair in items
]
my_vocabulary = SimpleVocabulary(terms)
From a List of Values#
For simple cases where value, token, and title are the same:
from zope.schema.vocabulary import SimpleVocabulary
values = ['foo', 'bar', 'baz']
my_vocabulary = SimpleVocabulary.fromValues(values)
Using the Vocabulary in a Schema#
from plone.supermodel import model
from zope import schema
class ISampleSchema(model.Schema):
content_type = schema.Choice(
title='Content Type',
vocabulary=my_vocabulary,
required=True,
)
Registering a Named Vocabulary#
To make a vocabulary reusable across your application, register it as a named utility.
Creating a Vocabulary Factory#
from zope.interface import provider
from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
@provider(IVocabularyFactory)
def priority_vocabulary_factory(context):
"""A vocabulary of priority levels."""
items = [
('low', 'Low Priority'),
('normal', 'Normal Priority'),
('high', 'High Priority'),
('urgent', 'Urgent'),
]
terms = [
SimpleTerm(value=pair[0], token=pair[0], title=pair[1])
for pair in items
]
return SimpleVocabulary(terms)
Registering in ZCML#
Register the vocabulary factory in your configure.zcml:
<utility
provides="zope.schema.interfaces.IVocabularyFactory"
component=".vocabularies.priority_vocabulary_factory"
name="example.vocabularies.priority"
/>
Using a Named Vocabulary in a Schema#
Reference the vocabulary by its registered name:
from plone.supermodel import model
from zope import schema
class ITask(model.Schema):
priority = schema.Choice(
title='Priority',
vocabulary='example.vocabularies.priority',
required=True,
)
Retrieving a Vocabulary Programmatically#
from zope.component import getUtility
from zope.schema.interfaces import IVocabularyFactory
factory = getUtility(IVocabularyFactory, 'example.vocabularies.priority')
vocabulary = factory(context)
Working with Vocabulary Terms#
Getting a Term by Value#
term = vocabulary.getTerm('high')
print(term.value) # 'high'
print(term.token) # 'high'
print(term.title) # 'High Priority'
Getting a Term by Token#
term = vocabulary.getTermByToken('high')
Iterating Over a Vocabulary#
for term in vocabulary:
print(f"Value: {term.value}, Token: {term.token}, Title: {term.title}")
Checking if a Value Exists#
if 'high' in vocabulary:
print("Value exists in vocabulary")
Dynamic Vocabularies#
Dynamic vocabularies generate their values at runtime based on context data, such as catalog queries or registry settings.
Context Source Binder#
Use IContextSourceBinder when your vocabulary depends on the current context:
from zope.interface import provider
from zope.schema.interfaces import IContextSourceBinder
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
@provider(IContextSourceBinder)
def available_documents_source(context):
"""Return a vocabulary of documents in the current folder."""
# Get the portal catalog
catalog = context.portal_catalog
# Query for documents
brains = catalog.searchResults(
portal_type='Document',
path={
'query': '/'.join(context.getPhysicalPath()),
'depth': 1,
},
)
# Build vocabulary terms
terms = [
SimpleTerm(
value=brain.UID,
token=brain.UID,
title=brain.Title,
)
for brain in brains
]
return SimpleVocabulary(terms)
Using a Source in a Schema#
Use source instead of vocabulary when using a context source binder:
from plone.supermodel import model
from zope import schema
class IMyContent(model.Schema):
related_document = schema.Choice(
title='Related Document',
source=available_documents_source,
required=False,
)
Dynamic Vocabulary from Registry#
A common pattern is to populate vocabulary options from the Plone registry:
from plone import api
from zope.interface import provider
from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
@provider(IVocabularyFactory)
def talk_types_vocabulary_factory(context):
"""Vocabulary of talk types from registry."""
types = api.portal.get_registry_record(
'example.conference.talk_types',
default=['Talk', 'Keynote', 'Workshop'],
)
terms = [
SimpleTerm(value=t, token=t, title=t)
for t in types
]
return SimpleVocabulary(terms)
Built-in Plone Vocabularies#
Plone provides many useful vocabularies in the plone.app.vocabularies package.
Common Vocabularies#
Vocabulary Name |
Description |
|---|---|
|
All portal types installed |
|
User-friendly content types |
|
Types filtered by the Types Tool |
|
All installed workflows |
|
All workflow states |
|
All workflow transitions |
|
Available content languages |
|
Supported content languages |
|
User roles available in the site |
|
All groups in the site |
|
All users in the site |
|
Available skins/themes |
|
All action category ids |
|
Common timezones from pytz |
|
Configured WYSIWYG editors |
|
All available image scales |
|
All available permissions |
|
All catalog indexes |
Using Built-in Vocabularies#
from plone.supermodel import model
from zope import schema
class IMyContent(model.Schema):
allowed_types = schema.List(
title='Allowed Content Types',
value_type=schema.Choice(
vocabulary='plone.app.vocabularies.ReallyUserFriendlyTypes',
),
required=False,
)
workflow = schema.Choice(
title='Workflow',
vocabulary='plone.app.vocabularies.Workflows',
required=False,
)
Creating a Vocabulary with plonecli#
The easiest way to create a vocabulary in a Plone add-on is using plonecli with bobtemplates.plone.
Installation#
UV tool install plonecli
Creating an Add-on with a Vocabulary#
First, create a new Plone add-on:
plonecli create addon src/collective.myaddon
Then, navigate to the package directory and add a vocabulary:
cd src/collective.myaddon
plonecli add vocabulary
The CLI will prompt you for the vocabulary class name. Enter a name like MyCustomVocabulary.
Generated File Structure#
The plonecli add vocabulary command creates the following structure:
src/collective.myaddon/
└── src/
└── collective/
└── myaddon/
├── configure.zcml # Updated with vocabulary registration
└── vocabularies/
├── __init__.py
├── configure.zcml # Vocabulary ZCML configuration
└── my_custom_vocabulary.py # Your vocabulary implementation
Generated Vocabulary Code#
The generated vocabulary file looks like this:
# -*- coding: utf-8 -*-
from collective.myaddon import _
from plone import api
from zope.interface import provider
from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
@provider(IVocabularyFactory)
def MyCustomVocabularyFactory(context):
"""Vocabulary factory for my custom vocabulary."""
# TODO: Replace with your vocabulary terms
values = [
('value1', _('Title 1')),
('value2', _('Title 2')),
('value3', _('Title 3')),
]
terms = [
SimpleTerm(value=pair[0], token=pair[0], title=pair[1])
for pair in values
]
return SimpleVocabulary(terms)
Generated ZCML Registration#
The vocabulary is automatically registered in vocabularies/configure.zcml:
<configure
xmlns="http://namespaces.zope.org/zope"
i18n_domain="collective.myaddon">
<utility
component=".my_custom_vocabulary.MyCustomVocabularyFactory"
name="collective.myaddon.MyCustomVocabulary"
provides="zope.schema.interfaces.IVocabularyFactory"
/>
</configure>
Using the Generated Vocabulary#
After building and installing your add-on, use the vocabulary in your schemas:
from plone.supermodel import model
from zope import schema
class IMyContent(model.Schema):
my_field = schema.Choice(
title='My Field',
vocabulary='collective.myaddon.MyCustomVocabulary',
required=True,
)
The vocabulary will also appear in the Dexterity schema editor in your browser.
Multiple Choice Fields#
For fields that allow multiple selections, use schema.List or schema.Set with a Choice value type:
List with Multiple Choices#
from plone.supermodel import model
from zope import schema
class IArticle(model.Schema):
categories = schema.List(
title='Categories',
description='Select one or more categories',
value_type=schema.Choice(
vocabulary='example.vocabularies.categories',
),
required=False,
)
Set with Multiple Choices (No Duplicates)#
from plone.supermodel import model
from zope import schema
class IArticle(model.Schema):
tags = schema.Set(
title='Tags',
description='Select one or more tags',
value_type=schema.Choice(
vocabulary='example.vocabularies.tags',
),
required=False,
)
Customizing Vocabulary Widgets#
Using Checkboxes for Multiple Choice#
from plone.autoform import directives
from plone.supermodel import model
from z3c.form.browser.checkbox import CheckBoxFieldWidget
from zope import schema
class IMyContent(model.Schema):
directives.widget('options', CheckBoxFieldWidget)
options = schema.List(
title='Options',
value_type=schema.Choice(
vocabulary='example.vocabularies.options',
),
required=False,
)
Translated Vocabulary Titles#
For internationalized vocabulary titles, use message factories:
from collective.myaddon import _
from zope.interface import provider
from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
@provider(IVocabularyFactory)
def status_vocabulary_factory(context):
"""Vocabulary with translated titles."""
items = [
('draft', _('Draft')),
('pending', _('Pending Review')),
('published', _('Published')),
('archived', _('Archived')),
]
terms = [
SimpleTerm(value=pair[0], token=pair[0], title=pair[1])
for pair in items
]
return SimpleVocabulary(terms)
Don't forget to add translations to your .po files for each supported language.
Vocabulary with Catalog Query#
A complete example showing a vocabulary populated from a catalog query:
from plone import api
from zope.interface import provider
from zope.schema.interfaces import IVocabularyFactory
from zope.schema.vocabulary import SimpleTerm
from zope.schema.vocabulary import SimpleVocabulary
@provider(IVocabularyFactory)
def authors_vocabulary_factory(context):
"""Vocabulary of authors from existing content."""
catalog = api.portal.get_tool('portal_catalog')
# Get unique creators from all content
brains = catalog.searchResults(
portal_type=['Document', 'News Item', 'Event'],
)
# Collect unique authors
authors = set()
for brain in brains:
if brain.Creator:
authors.add(brain.Creator)
# Build terms sorted alphabetically
terms = []
for author_id in sorted(authors):
# Try to get the user's full name
user = api.user.get(author_id)
if user:
fullname = user.getProperty('fullname') or author_id
else:
fullname = author_id
terms.append(
SimpleTerm(
value=author_id,
token=author_id,
title=fullname,
)
)
return SimpleVocabulary(terms)
REST API Access#
Vocabularies are accessible via the Plone REST API at the @vocabularies endpoint.
List All Vocabularies#
curl -X GET http://localhost:8080/Plone/@vocabularies \
-H "Accept: application/json" \
--user admin:secret
Get Vocabulary Terms#
curl -X GET http://localhost:8080/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes \
-H "Accept: application/json" \
--user admin:secret
The response includes the token and title for each term:
{
"@id": "http://localhost:8080/Plone/@vocabularies/plone.app.vocabularies.ReallyUserFriendlyTypes",
"items": [
{"token": "Document", "title": "Page"},
{"token": "News Item", "title": "News Item"},
{"token": "Event", "title": "Event"}
],
"items_total": 3
}
Best Practices#
Use named vocabularies for reusable options that appear in multiple schemas.
Use context source binders when the vocabulary values depend on the current context.
Keep tokens ASCII-safe since they are passed via HTTP requests.
Make titles translatable using message factories for internationalization.
Cache expensive vocabularies if they query the catalog or external services.
Use plonecli to scaffold vocabularies in your add-ons to follow best practices.
Document your vocabularies with docstrings explaining what they contain and when to use them.
See Also#
fields-label for field types that use vocabularies
../forms/index for form handling