IControlPanel, Tuples and saving to registry

Plone: 5.2.2

Hi,
I created my own control panel setting where I need a Tuple field like so:

class IControlPanel(Interface):
    resourceOrder = schema.Tuple(
        title=_("resource_order", default="Resource Order"),
        description=_("resource_order_desc", default="..."),
        unique=True,
        value_type=schema.Choice(
            source=registeredResources
        )
    )

class ControlPanelForm(RegistryEditForm):
    schema = IControlPanel
    schema_prefix = "ecode"
    label = u'Site Edit Settings'
    site = getSite()
    ...

configure.zcml

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

The new control panel renders fine and I can use the Tuple Widget. But of course it is not possible to save tuples or any other kind of lists in the registry. So I have to serialize it somehow which in general is no problem at all. But where would I do that?
Also when I try to make a quickinstall I get this error:

TypeError: There is no persistent field equivalent for the field `resourceOrder` of type `Tuple`.

It seems I can not use the Tuple Widget at all in the schema. So how can I create list-like field in the frontend but store it as a joined list or something similar? I tried to reinvent as few as possible. Do I need a completely new view which I then embed somehow into the control panel?

You can use it (I have)

You need to add it to the registry also:

I have no plone.directives available here. The latest release is from 2017. I am not sure if this is anything I should use in Plone 5.2.2. It's also only available for Python 2.6 and 2.7. I am running Python 3.8.

And in my registry.xml I only have this:

<?xml version="1.0"?>
<registry
    xmlns:i18n="http://xml.zope.org/namespaces/i18n"
    i18n:domain="my.addon">
  <records interface="my.addon.browser.controlpanel.IControlPanel" prefix="myaddon"/>

</registry>

I also tried adding the individual fields inside the registry.xml but I still get the same error. Also I want avoid redundant code. The reference to the schema should be enough here.

How about adapting to IPersistentField as described in https://pypi.org/project/plone.app.registry/#creating-records-based-on-an-interface ?

Could this work ?

registry
    xmlns:i18n="http://xml.zope.org/namespaces/i18n"
    i18n:domain="my.addon">
  <records interface="my.addon.browser.controlpanel.IControlPanel" prefix="myaddon">
     <value key="resourceOrder"></value>
  </records>

</registry>

What do you mean with adapting? Shouldn't it already be adapted when I use a field from zope.schema?

Notice how we are using standard zope.schema fields. These will be converted to persistent fields (by adapting them to IPersistentField from plone.registry) when the registry is populated. If that is not possible, an error will occur on import.

I already tried that. A quickinstall still shows this error:

Traceback (innermost last):
  Module ZPublisher.WSGIPublisher, line 162, in transaction_pubevents
  Module ZPublisher.WSGIPublisher, line 359, in publish_module
  Module ZPublisher.WSGIPublisher, line 254, in publish
  Module ZPublisher.mapply, line 85, in mapply
  Module ZPublisher.WSGIPublisher, line 63, in call_object
  Module <string>, line 6, in reinstallProducts
  Module AccessControl.requestmethod, line 88, in _curried
  Module Products.CMFQuickInstallerTool.QuickInstallerTool, line 807, in reinstallProducts
  Module <string>, line 3, in installProducts
  Module AccessControl.requestmethod, line 88, in _curried
  Module Products.CMFQuickInstallerTool.QuickInstallerTool, line 708, in installProducts
  Module Products.CMFQuickInstallerTool.QuickInstallerTool, line 627, in installProduct
   - __traceback_info__: ('my.addon',)
  Module Products.GenericSetup.tool, line 390, in runAllImportStepsFromProfile
   - __traceback_info__: profile-my.addon:default
  Module Products.GenericSetup.tool, line 1468, in _runImportStepsFromContext
  Module Products.GenericSetup.tool, line 1280, in _doRunImportStep
   - __traceback_info__: plone.app.registry
  Module plone.app.registry.exportimport.handler, line 79, in importRegistry
   - __traceback_info__: registry.xml
  Module plone.app.registry.exportimport.handler, line 127, in importDocument
  Module plone.app.registry.exportimport.handler, line 391, in importRecords
   - __traceback_info__: records name: my.addon.controlpanel.IControlPanel
  Module plone.registry.registry, line 104, in registerInterface
TypeError: There is no persistent field equivalent for the field `resourceOrder` of type `Tuple`.

Every time I have seen this error it has been because of that.
Note: You will need to uninstall and install the add on ( in the control panel, not in /portal_quickinstaller)

What happens if you skip the schema_prefix (I dont know what it is for ?)

@ngoeddel there is a generic adapter for zope.schema.IField yes, but it is a compromise, not supporting all use cases. It may fail. So you might be able to solve this with a custom adapter.

I haven't dug into the IField adapter in detail. But, given that for plone registry Choice fields, only named vocabularies are supported, and your value_type is a Choice that refers to a source instead, maybe that's causing the adapter to fail?

This is not related to your question, but the use of getSite() as a class variable here does not seem like a good idea. It might work if you have only one Plone site on a Zope server, but if you are trying to get the portal root I think you'd be better off having a @property method named "site" and having that call plone.api.portal.get()

(Also looks like a typo in schema_prefix)

From the error given, I would find it very strange if it has something to do with Choice etc.

What happens if you add just a field with schema.TextLine ?

Hi, sorry for the delay.

In reality there are already other fields in that schema which work fine. All the other fields are schema.TextLine. Out of curiosity I changed the resourceOrder field to schema.TextLine too, removed the value_type and then there is no problem at all. I can reinstall the addon without an issue, I can open the custom control panel, I can change the value of the field and save it. When I go back to schema.Tuple or schema.List or schema.Set the error reappers. This the TextLine version of it:

    resourceOrder = schema.TextLine(
        title=_("resource_order", default="Resource Order"),
        description=_("resource_order_desc", default="..."),
        required=False
    )

'Cause you are curious about the used vocabulary. It's this: How to enumerate all content types implementing a given interface - #4 by ngoeddel

The prefix is used for the name in the registry. Every field in the schema gets that prefix. So if you try to read the value directly from the registry you have to write myaddon.resourceOrder.

Oh, that's the real name of the addon. I missed that before posting it here. It should be myaddon to be compatible to the code snippets I posted before.
And you are right about site. I already fixed that error because it caused an exception when canceling the control panel form.

I did a test yesterday in Plone 5.2

Tuple gave me no errors, but the weird thing was that if I used value_type= schema.TextLine, it worked but if I changed to value_type = schema.Int it gives error (something like Int is not callable).

This has worked before, so strange things are happening here.

My code was something like this:

# -*- coding: utf-8 -*-
"""Module where all interfaces, events and exceptions live."""

from z3c.form import interfaces
from zope import schema
from zope.interface import alsoProvides
from plone.supermodel import model
from plone.supermodel.directives import fieldset




from medialog.controlpanel.interfaces import IMedialogControlpanelSettingsProvider

from zope.i18nmessageid import MessageFactory
_ = MessageFactory('medialog.datatablebehavior')


class ITableSettings(model.Schema):
    """Adds settings to medialog.controlpanel """

    fieldset(
        'extra',
        label=_(u'Datatable'),
        fields=[
            'page_size',
            'page_sizes',
            ],
    )

    page_size = schema.Int(
        title=_(u'Default Page Length'),
        default=25,
        min=5,
    )

    # schema.Int gives error for unknown reasons (Int is not callable)
    page_sizes = schema.Tuple(
        title=_(u"Page lenghts to show in menu"),
        value_type=schema.TextLine(
            title=_(u'Page Length'),
        ),
        required = False,
    )

alsoProvides(ITableSettings, IMedialogControlpanelSettingsProvider)

PS: Alsoprovides should be changed to @provider (for the future)

I just found out I had to use the fields from plone.registry.field.* and not from zope.schema.*. All these fields implement the IPersistentField interface. Also you can find that the Choice field is a very special case that disallows the source attribute. And now I get the correct exception too:

ValueError: Persistent fields do not support sources, only named vocabularies or vocabularies based on simple value sets.

But I still do not understand why this is disallowed in the first place. In the end the values were just stored in a tuple, set or a list. And every element is a well typed SimpleTerm object.
Well, it seems I have to create redundant code then.

1 Like