How to change existing dexterity types and behaviors

I want to document to best way to modify existing types and behaviors in Plone. I know sometimes it might be the better to write a new type but often you want to change something small in the default types and not write a whole new thing.

Say you want to make the subject-field in the Event-type shipped in Plone 5 to be required.
or...
... you want to omit a field or changing its visibility-mode or edit-permission
... you want to change the order of fields (or move a field to another fieldset)
... you want to change the widget for some field

All this for existing type-schemas or a existing behaviors. What is the way to go?

This will go in the documentation of plone.app.contenttypes and dexterity.

2 Likes

It seems to me that this is a very good question. And for types set up the
way they are in plone.app.contenttypes, I don't have an answer.

Schema in p.a.contentttypes are currently registered with GS code
like plone.app.contenttypes.schema:event.xml. I
guess that might construct a schema interface on the fly, but I don't know
if we can get at it.

Perhaps we should have declared schema interfaces for those types? We could
use plone.supermodel.model.load() to register the XML for the schema. Then
there would be an explicit schema interface that would provide a handle.
Though that's still going to require z3c.form expertise, which is an
awfully high hurdle for integrators.

Even that, though, wouldn't help with fields brought in by behaviors.
(Please someone, tell me if I'm missing something obvious.) Maybe we should
be avoiding behavior-added fields in p.a.contenttypes? Add things like
title and description explicitly to make it easier to change them?

We really do need good answers for these questions!

Steve

The question that should be asked is how solve these usecases in the most easy way. Easy in the sense of minimal amount of files and configurations and regarding the level of complexity exposed to developers and integrators. Schemaextenders in AT were easy to implement and to configure.

-aj

On 20/07/14 20:32, smcmahon wrote:

Even that, though, wouldn't help with fields brought in by behaviors.
(Please someone, tell me if I'm missing something obvious.)

I seem to remember that all interesting fields in p.a.c reside in
behaviors. And the most interesting behaviors, like DublinCore, are
bundles of behaviors....

The big hurdle I encountered in customizing the DublinCore behavior, and
I guess this is generic, is that form directives need to be placed in
the same module that defines the schema. I.e. you cannot in your own
code do a form.order_before on a field that is defined elsewhere.

So you have to redefine that behavior schema. But that results in a
different behavior - it's in a different source module now. So you have
to disable the upstream behavior and activate your custom behavior.

In the end this resulted in me copying all of DublinCore into my custom
project, renaming it to CustomCore and making my tweaks there as needed.
Less than elegant, but hey it works.

:*CU#

 Guido Stevens  |  +31.43.3618933  |  http://cosent.nl

 s o c i a l   k n o w l e d g e   t e c h n o l o g y

I recently tried modifying the tagged values to no effect. I think that used to work but does not work now.

from plone.autoform.interfaces import ORDER_KEY
from paragon.site.content.addon import IAddon
IAddon.setTaggedValue(ORDER_KEY, [('IBasic.description', 'before', 'ILeadImage.image')])

This will throw:
    
zope.configuration.config.ConfigurationExecutionError: <type 'exceptions.ValueError'>: The directive plone.autoform.order applied to interface paragon.site.content.addon.IAddon refers to unknown field name IBasic.description

I think, that Guidos way is the most successful one. We should no try to be too clever:

The easiest way to customize (field only) behavior TTW, I think, is really to disable the behavior and re-define the fields TTW. This should be especially ok for dublincore fields, because all dexterity objects provide dublincore interface. The current exception for TTW customization are timezone aware datetime fields, because timezone awareness is hardcoded into the behavior (and would need more work).

The easiest eay to customize behavior in python code, I think, is Guido's way of inheriting the original behavior schema and adding your own requirements on top of it. This would require some extra GS or setuphandlers to swap the behaviors and would bloat the behavior selection UI, but those steps can be made easier later.

2 Likes

i dont see subclassing as a nice way to do that.

after really struggling with how to change stuff from a different behavior/schema i came to the solution of using
IABehavior.setTaggedValue(...)

the ability to rearrange fields, hide them, change their modes and so on is really not pleasing and easy.

How do I add a constraint to a File field that's defined in a xml schema? Doing https://docs.plone.org/develop/addons/schema-driven-forms/customising-form-behaviour/validation.html#field-widget-validators doesn't work. I had to customize the form to add the validation.

Sorry to resurrect a really old thread, but there seems to be no consensus or answer here. Nor has documentation appeared yet.

My specific case is a third party add-on that adds a new field with a behavior. However, it defines that field a required field: I want it optional in my implementation.

My first hunch is to subclass the schema and change it, so I'm siding with @datakurre here.

I will look at IABehavior.setTaggedValue() tho.

I have done exactly what you are trying to do, and I am pretty sure that I kept the behaviour.
I was just theming the Movie and Audio content type, so I dont have the code, but I think I just 're-added' the same field (in python code or Dexterity Control panel… can't remember) and set required to false.

I have no idea if this is a 'bad way to do it'

1 Like

I don't think this is a bad way to do it at all, as long as you are writing code for "A leaf" - basically, a product that is designed to be the final customization for a customer's site - and no one is going to re-adapt or from what you just built.

I, however, am working on a 'base install' for my enterprise. I have ten, no wait, 15 different departments that all want one-off's from the base. By saying all of these subsites would never want a required audio field would guarantee that one of them will want it - under a certain folder only - based on Murphy's law.

I really don't want separate content products for each install.

But I guess we've already gone down that road, because some subsites have different "analytics requirements" which conditionally show fields. In my ATContentTypes install, I implemented this as an add on with schemaextender, then added "toggle the fields on and off" with add-on configuration. So I'm already using a registry.xml / add-on config approach.

Applying a conditional behavior (a condiditional, condittional adapter?) based on both the content type and browser layer would be a sweet option for me. I haven't tried adding a layer attribute to a plone:behavior zcml element yet. I bet it won't work. At best, it will generate an xml validation error. Most likely, it will simply be quietly ignored.

This is one case where I think a model driven (xml) behavior would work better, because then we can use GS profiles to play with the knobs and switches, and that fits in nicely with the current model.


So, today (I will change my mind tomorrow):
I think behaviors should optionally be implemented with xml models - like dexterity types - and we can do this with GS profiles.
I also think behaviors can become layer aware. Not sure how hard that's going to be, or how much coupling that creates. I'm skeptical.

I'm untangling this suggestion now. It's pretty deep into the architecture and I will have a better understanding after I dig into the docs and come up for air.

Anyone wanting to follow me down the rabbit hole:
https://docs.plone.org/external/plone.app.dexterity/docs/reference/misc.html
http://plone.293351.n2.nabble.com/plone-autoform-why-use-tagged-values-td7560956.html

I have to read @optilude threads very slowly and carefully, but I always come out the other side quite enlightened.

Wild guess, but would it be possible to register the behavior (or maybe just the field) for an interface:

  <plone:behavior
    for="my.addon.interfaces.IMyInterface"

and then add this behaviour for the content type

and then do the toggle off (and on) by removing this interface (make an action that calls a browser view or something that nolongerProvides(self.context, IMyInterface and alsoProvides (?))

1 Like

Somehow I keep returning to this post with my Google searches, so it might help some future reader of this post to add a possible solution for pbauer's problem. It seems that the tagged value is dependent on the plugin that processes the values. In my case, this thing worked, to hide some fields.

    from zope.interface import Interface
    from plone.autoform.interfaces import OMITTED_KEY
    from plone.restapi import behaviors

    behaviors.IBlocks.setTaggedValue(OMITTED_KEY,
       [
        (Interface, 'blocks', 'true'),
        (Interface, 'blocks_layout', 'true'),
    ])

I think, for pbauer's example to work, the Interface class should be used directly, not its dotted name. I wonder if, in my case, the Interface stands for the context, the request or the view.

This, of course, later I've found that this is already documented here: https://docs.plone.org/external/plone.app.dexterity/docs/reference/misc.html

And another way that I've discovered can be used to tweak schemas. I have the need to add some description to the IRichtext.text field, so I've created this schema plugin:

from zope.component import adapter
from zope.interface import implementer

from plone.autoform.interfaces import IFormFieldProvider
from plone.supermodel import interfaces


@implementer(interfaces.ISchemaPlugin)
@adapter(IFormFieldProvider)
class SchemaTweaks(object):
    """
    """

    order = 999999

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

    def __call__(self):

        if self.schema.getName() == 'IRichTextBehavior':
            field = self.schema['text']
            field.description = u'Rich text, double click for toolbar.'

        if self.schema.getName() == 'IBlocks':
            del self.schema._Element__tagged_values[
                'plone.supermodel.fieldsets'
            ]

And I've registered this as a simple adaptor with:

 <adapter factory=".patches.SchemaTweaks" name="schema.tweaks" />

Probably a generic mechanism to allow registration of named adapters (with the name being the schema name) to customize schemas could be created, but I'm not sure there would be much to be gained over the example above.

4 Likes

@tiberiuichim thanks for the update. I recently used the patters with the tagged-values to change the interval of the time in the datetime-widget in events like this:

from plone.app.event.dx.behaviors import IEventBasic
from plone.autoform.interfaces import WIDGETS_KEY

widgets = IEventBasic.getTaggedValue(WIDGETS_KEY)
widgets["start"].params["pattern_options"] = {"time": {"interval": 15}}
widgets["end"].params["pattern_options"] = {"time": {"interval": 15}}
4 Likes

I'm looking at this:

it extends an existing dexterity content type but to finalize it, five.grok is used (" otherwise the plone.directives.form form hints won’t be activated"). Is it possible to do the same without five.grok?

I would like to add a new field to the existing standard Folder content type. I can do it TTW but I would like to make it programmatically in a file system product.

Since this serves as reference (at least for me) here is an example for how to use the ISchemaPlugin-approach to change a field-setting, the fieldset and the sorting of a field from a default-behavior:

from plone.app.dexterity.behaviors.id import IShortName
from plone.autoform.interfaces import IFormFieldProvider
from plone.autoform.interfaces import ORDER_KEY
from plone.supermodel.interfaces import FIELDSETS_KEY
from plone.supermodel.interfaces import ISchemaPlugin
from zope.component import adapter
from zope.interface import implementer


@implementer(ISchemaPlugin)
@adapter(IFormFieldProvider)
class SchemaTweaks(object):
    """Make id required, move to default fieldset after title."""

    order = 999999

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

    def __call__(self):
        if self.schema.getName() == 'IShortName':
            field = self.schema['id']
            field.required = True
            self.schema.setTaggedValue(FIELDSETS_KEY, [])
            order = [('id', 'after', 'IDublinCore.title')]
            IShortName.setTaggedValue(ORDER_KEY, order)

6 Likes

This is super useful!

Have you also tested it in tests?
Because when i run the site, i have all the tweaks working, but when i run tests, plone.supermodel (https://github.com/plone/plone.supermodel/blob/master/plone/supermodel/model.py#L75) does not see my adapter and i can't test tweaks.

No, I have not tests for this. Did you add alsoProvides(self.request, IPloneFormLayer) to your test-setup?