---
html_meta:
"description": "How to make your Plone Add-On configurable"
"property=og:description": "How to make your Plone Add-On configurable"
"property=og:title": "Vocabularies, Registry-Settings and Control Panels"
"keywords": "vocabulary, registry, controlpanel, select, options, configuration, settings"
---
(registry-label)=
# Vocabularies, Registry-Settings and Control Panels
````{sidebar} Get the code!
Code for the beginning of this chapter:
```shell
# frontend
git checkout event
```
```shell
# backend
git checkout event
```
Code for the end of this chapter:
```shell
# frontend
git checkout registry
```
```shell
# backend
git checkout registry
```
{doc}`code`
````
In this part you will:
- Store custom settings in the registry
- Create a control panel to manage custom settings
- Create options in fields as vocabularies
- Training story: Assign talks to rooms
Topics covered:
- plone.app.registry
- Vocabularies
- Control panels
If you stop by here for more tips, not attending this training: Make sure you upgrade to Volto 14 or later.
## Introduction
Do you remember the fields `audience` and `type_of_talk` in the talk content type?
We provided several options to choose from that were hard-coded in the schema.
Next we want to add a field to assign talks to a room.
Since the conference next year will have different room names, these values need to be editable.
And while we're at it: It would be much better to have the options for `audience` and `type_of_talk` editable by admins as well, e.g. to be able to add _Lightning Talks_!
By combining the registry, a control panel and vocabularies you can allow rooms to be editable options.
To be able to to so you first need to get to know the registry.
## The Registry
The registry is used to get and set values stored in records.
Each record consists of the actual value, as well as a field that describes the record in more detail.
It has a nice dict-like API.
Since Plone 5 all global settings are stored in the registry.
The registry itself is provided by [plone.registry](https://pypi.org/project/plone.registry) and the UI to interact with it by [plone.app.registry](https://pypi.org/project/plone.app.registry)
Almost all settings in `/plone_control_panel` are actually stored in the registry and can be modified using its UI directly.
Open http://localhost:8080/Plone/portal_registry and filter for `displayed_types`.
You see that you can modify the content types that should be shown in the navigation and site map.
The values are the same as in http://localhost:8080/Plone/@@navigation-controlpanel, but the latter form is customized for usability.
```{note}
This UI for the registry is not yet available in the frontend.
```
## Registry Records
In {doc}`volto_frontpage` you already added a criterion usable for Collections in {file}`profiles/default/registry/querystring.xml`.
This setting is stored in the registry.
Let's look at existing values in the registry.
Go to http://localhost:3000/controlpanel/navigation and add `talk` to the field {guilabel}`Displayed content types`.
Talks in the root will now show up in the navigation.
This setting is stored in the registry record `plone.displayed_types`.
## Accessing and modifying records in the registry
In Python you can access the registry record with the key `plone.displayed_types` via {py:mod}`plone.api`.
It holds convenience methods to `get` and `set` a record:
```{code-block} python
from plone import api
api.portal.get_registry_record('plone.displayed_types')
api.portal.set_registry_record('plone.smtp_host', 'my.mail.server')
```
The access of the registry by `zope.component.getUtility` is often seen in code from before the time of `plone.api`.
```{code-block} python
from plone.registry.interfaces import IRegistry
from zope.component import getUtility
registry = getUtility(IRegistry)
displayed_types = registry.get('plone.displayed_types')
```
The value of the record `displayed_types` is the tuple `('Image', 'File', 'Link', 'News Item', 'Folder', 'Document', 'Event', 'talk')`
## Custom registry records
Now let's add our own custom settings:
- Is talk submission open or closed?
- Which rooms are available for talks?
While we're at it we can also add new settings `types_of_talk` and `audiences` that we will use later for the fields `type_of_talk` and `audience`.
To define custom records, you write the same type of schema as you already did for content types or for behaviors:
Add a file {file}`browser/controlpanel.py`:
```{code-block} python
:linenos:
from plone.autoform import directives
from plone import schema
from zope.interface import Interface
import json
VOCABULARY_SCHEMA = json.dumps(
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"token": {"type": "string"},
"titles": {
"type": "object",
"properties": {
"lang": {"type": "string"},
"title": {"type": "string"},
},
},
},
},
}
},
}
)
class IPloneconfSettings(Interface):
talk_submission_open = schema.Bool(
title="Allow talk submission",
description="Allow the submission of talks for anonymous user",
default=False,
required=False,
)
types_of_talk = schema.JSONField(
title="Types of Talk",
description="Available types of a talk",
required=False,
schema=VOCABULARY_SCHEMA,
default={
"items": [
{
"token": "talk",
"titles": {
"en": "Talk",
"de": "Vortrag",
},
},
{
"token": "lightning-talk",
"titles": {
"en": "Lightning-Talk",
"de": "Lightning-Talk",
},
},
]
},
missing_value={"items": []},
)
directives.widget(
"types_of_talk",
frontendOptions={
"widget": "vocabularyterms",
},
)
audiences = schema.JSONField(
title="Audience",
description="Available audiences of a talk",
required=False,
schema=VOCABULARY_SCHEMA,
default={
"items": [
{
"token": "beginner",
"titles": {
"en": "Beginner",
"de": "Anfänger",
},
},
{
"token": "advanced",
"titles": {
"en": "Advanced",
"de": "Fortgeschrittene",
},
},
{
"token": "professional",
"titles": {
"en": "Professional",
"de": "Profi",
},
},
]
},
missing_value={"items": []},
)
directives.widget(
"audiences",
frontendOptions={
"widget": "vocabularyterms",
},
)
rooms = schema.JSONField(
title="Rooms",
description="Available rooms of the conference",
required=False,
schema=VOCABULARY_SCHEMA,
default={
"items": [
{
"token": "101",
"titles": {
"en": "101",
"de": "101",
},
},
{
"token": "201",
"titles": {
"en": "201",
"de": "201",
},
},
{
"token": "auditorium",
"titles": {
"en": "Auditorium",
"de": "Auditorium",
},
},
]
},
missing_value={"items": []},
)
directives.widget(
"rooms",
frontendOptions={
"widget": "vocabularyterms",
},
)
```
The motivation to use `schema.JSONField` instead of `schema.List` is described as follows.
The options for the types of a talk, the room and the audience may change.
A modification of the feeding vocabulary would mean that already used options are no longer available, which would corrupt the data of the concerned talks.
We can "future-proof" this vocabulary with JSONFields that store a vocabulary source in the registry.
This vocabulary is a list of dictionaries, with keys that never change, and values that may be modified when necessary.
See the default values to understand what is stored in the registry:
Example `types_of_talk`:
```{code-block} python
[
{
"token": "talk",
"titles": {
"en": "Talk",
"de": "Vortrag",
},
},
{
"token": "lightning-talk",
"titles": {
"en": "Lightning-Talk",
"de": "Lightning-Talk",
},
},
]
```
We now register this schema `IPloneconfSettings` for the registry.
Add the following to {file}`profiles/default/registry/main.xml`.
With this statement the registry is extended by one record per `IPloneconfSettings` schema field.
```xml
{content.room.title}
> )} ``` ````{admonition} The complete TalkView :class: toggle ```jsx import React from 'react'; import { flattenToAppURL } from '@plone/volto/helpers'; import { Container, Header, Image, Icon, Label, Segment, } from 'semantic-ui-react'; import { Helmet } from '@plone/volto/helpers'; import { When } from '@plone/volto/components/theme/View/EventDatesInfo'; const TalkView = (props) => { const { content } = props; const color_mapping = { Beginner: 'green', Advanced: 'yellow', Professional: 'red', }; return ({content.room.title}
> )} {content.audience && ({content.description}
)} {content.details && ( )} {content.speaker && ({content.company}
)} {content.email && (Email: {content.email}
)} {content.twitter && (Twitter:{' '} {content.twitter.startsWith('@') ? content.twitter : '@' + content.twitter}
)} {content.github && (Github:{' '} {content.github}
)} {content.image && (