35. Manage Settings with Registry, Controlpanels and Vocabularies

In this part you will:

  • Store a custom setting in a registry
  • Create a controlpanel using z3c.form to allow setting that value

Topics covered:

  • plone.app.registry
  • controlpanels

35.1. The Registry

The registry is used to get and set values stored in records. Each record contains the actual value, as well as a field that describes the record in more detail. It has a nice dict-like API.

All global settings in Plone 5 are stored in the registry.

The registry itself is provided by plone.registry and the UI to interact with it by 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 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 later form is customized for usability.

35.2. A setting

Let’s store two values in the registry:

  • The date of the conference
  • Is talk submission open or closed

You cannot create values ttw, instead they need to be registered using Generic Setup.

Open the file profiles/default/registry.xml. You already registered several new settings in there:

  • You enabled self registration
  • You stored a site-logo
  • You registered additional criteria useable for Collections

Adding the following code to registry.xml. This creates a new value in the registry upon installation of the package.

<record name="ploneconf.talk_submission_open">
  <field type="plone.registry.field.Bool">
    <title>Allow talk submission</title>
    <description>Allow the submission of talks for anonymous users</description>
    <required>False</required>
  </field>
  <value>False</value>
</record>

When creating a new site a lot of settings are created in the same way. See https://github.com/plone/Products.CMFPlone/blob/master/Products/CMFPlone/profiles/dependencies/registry.xml to see how Products.CMFPlone registers values.

<record name="ploneconf.date_of_conference">
  <field type="plone.registry.field.Date">
    <title>First day of the conference</title>
    <required>False</required>
  </field>
  <value>2016-10-17</value>
</record>

35.3. Accessing and modifying values in the registry

In python you can access the registry like this:

from plone.registry.interfaces import IRegistry
from zope.component import getUtility

registry = getUtility(IRegistry)
start = registry.get('ploneconf.date_of_conference')

plone.api holds methods to make this even easier:

from plone import api
api.portal.get_registry_record('ploneconf.date_of_conference')
api.portal.set_registry_record('ploneconf.talk_submission_open', True)

35.4. Add a custom controlpanel

When you want to add a custom controlpanel it is usually more convenient to register the fields not manually like above but as field in a schema, similar to a content-types schema.

For this you define a interface for the schema and a view that auto-generates a form from the schema. In browser/configure.zcml add:

<browser:page
    name="ploneconf-controlpanel"
    for="Products.CMFPlone.interfaces.IPloneSiteRoot"
    class=".controlpanel.PloneconfControlPanelView"
    permission="cmf.ManagePortal"
    />

Add a file browser/controlpanel.py:

# -*- coding: utf-8 -*-
from datetime import date
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 IPloneconfControlPanel(Interface):

    date_of_conference = schema.Date(
        title=u'First day of the conference',
        required=False,
        default=date(2016, 10, 17),
    )

    talk_submission_open = schema.Bool(
        title=u'Allow talk submission',
        description=u'Allow the submission of talks for anonymous user',
        default=False,
        required=False,
    )


class PloneconfControlPanelForm(RegistryEditForm):
    schema = IPloneconfControlPanel
    schema_prefix = "ploneconf"
    label = u'Ploneconf Settings'


PloneconfControlPanelView = layout.wrap_form(
    PloneconfControlPanelForm, ControlPanelFormWrapper)

With this way of using fields you don’t have to register the values in registry.xml, instead you have to register the interface:

<records interface="ploneconf.site.browser.controlpanel.IPloneconfControlPanel"
         prefix="ploneconf" />

After reinstalling the package (to load the registry-entry) you can access the controlpanel at http://localhost:8080/Plone/@@ploneconf-controlpanel.

To make it show up in the general controlpanel at http://localhost:8080/Plone/@@overview-controlpanel you have to register it with GenericSetup. Add a file profiles/default/controlpanel.xml:

<?xml version="1.0"?>
<object name="portal_controlpanel">
  <configlet
      title="Ploneconf Settings"
      action_id="ploneconf-controlpanel"
      appId="ploneconf-controlpanel"
      category="Products"
      condition_expr=""
      icon_expr=""
      url_expr="string:${portal_url}/@@ploneconf-controlpanel"
      visible="True">
    <permission>Manage portal</permission>
  </configlet>
</object>

Again, after applying the profile (reinstall the package or write a upgrade-step) your controlpanel shows up in http://localhost:8080/Plone/@@overview-controlpanel.

35.5. Vocabularies

Do you remember the field rooms? We provided several options to chose from. But who says that the next conference will have the same rooms? These values should be configurable by the admin. The admin could go to the dexterity-controlpanel and change the values but we will use a different approach. We will allow the rooms to be added in the controlpanel and use these values in the talk-schema by registering a vocabulary.

Add a new field to IPloneconfControlPanel:

1
2
3
4
5
6
7
 rooms = schema.Tuple(
     title=u'Available Rooms for the conference',
     default=(u'101', u'201', u'Auditorium'),
     missing_value=None,
     required=False,
     value_type=schema.TextLine(),
 )

Create a file vocabularies.py and write the vocabulary:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 # -*- 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 IVocabularyFactory

 @implementer(IVocabularyFactory)
 def RoomsVocabularyFactory(context):
     values = api.portal.get_registry_record('ploneconf.rooms')
     return safe_simplevocabulary_from_values(values)

Note:

  • We turn the values freom the registry into a dynamic SimpleVocabulary that can be used in the schema.
  • We use the handy helper-method safe_simplevocabulary_from_values to create the vocabulary since the token of a SimpleTerm in a SimpleVocabulary needs to be bytes, not unicode.
  • You could use the context in which the vocabulary is created to further control the values in the vocabulary.

You need to register this vocabulary as a utility in configure.zcml as ploneconf.site.vocabularies.Rooms:

<utility
    name="ploneconf.site.vocabularies.Rooms"
    component="ploneconf.site.vocabularies.RoomsVocabularyFactory" />

From now on you can use this vocabulary by only referring to its name ploneconf.site.vocabularies.Rooms.

Use the new vocabulary in the talk-schema. Edit content/talk.xml

1
2
3
4
5
6
7
8
 <field name="room"
        type="zope.schema.Choice"
        form:widget="z3c.form.browser.radio.RadioFieldWidget"
        security:write-permission="cmf.ReviewPortalContent">
   <description></description>
   <title>Room</title>
   <vocabulary>ploneconf.site.vocabularies.Rooms</vocabulary>
 </field>

Note

In a python-schema that would look like this:

directives.widget(room=RadioFieldWidget)
room = schema.Choice(
    title=_(u'Room'),
    vocabulary='ploneconf.site.vocabularies.Rooms',
    required=False,
)

A admin can now configure the rooms available for the conference.

We could use the same pattern for the fields type_of_talk and audience.