16. Behaviors – Mastering Plone 6 development

16. Behaviors#

Enhance content types to be selectable for presentation on the front page.

In this part you will:

  • Add a field to talks and other content types by using a behavior

  • Make the field values available via catalog search

Tools and techniques covered:

  • Behaviors

  • Catalog indexes and catalog metadata columns

Backend chapter

Checkout ploneconf.site at tag "talks":

git checkout talks

The code at the end of the chapter:

git checkout behaviors_1

More info in The code for the training

A first approach would be to extend the functionality of a content type by writing an adapter that adapts an object of this type to add an additional attribute or feature. This would mean to write an adapter for an interface the respective content types provides.

But for which interface shall we write the adapter? Do we want to write it for the general Products.CMFCore.interfaces.IContentish which is implemented by all content types? We want to be more specific and provide the behavior only for some selected content types.

16.1. Dexterity Approach#

Dexterity has special adapters that are called and registered by the name behavior.

A behavior can be enabled for any content type through the web UI and at runtime.

All default views (for example the add and edit forms) know about the concept of behaviors. When rendering forms, the views check whether there are behaviors referenced with the current context and if these behaviors have a schema of their own, these fields get shown in addition.

16.2. Names and Theory#

The name behavior is not a standard term in software development. But it is a good idea to think of a behavior as an aspect. You are adding an aspect to your content type and you want to write your aspect in such a way that it works independently of the content type on which the aspect is applied. You should not have dependencies to specific fields of your type or to other behaviors.

Such an object allows you to apply the open/closed principle to your dexterity objects.

16.3. Practical example#

Note

We write the behavior code step by step, but you can also use the Plone Command Line Tool plonecli to initially create a behavior and edit it afterwards.

So, let us write our own small behavior.

We want some selected talks, news items or other content to be presented on the front page.

So for now, our behavior just adds a new field to store the information if an object should be listed on the front page.

We want to keep a clean structure, so we create a behaviors directory first, and include it into the ZCML declarations of our configure.zcml.

<include package=".behaviors" />

Then, we add an empty behaviors/__init__.py and a behaviors/configure.zcml containing

And a behaviors/featured.py containing:

This is exactly the same type of schema as the one in the talk content-type. The only addition is @provider(IFormFieldProvider) that makes sure that the fields in the schema are displayed in the add- and edit-forms.

Let's go through this step by step.

  1. We register a behavior in behaviors/configure.zcml. We do not say for which content type this behavior shall be enabled. You do this through the web or in the GenericSetup profile.

  2. We create an interface in behaviors/featured.py for our behavior. We make it also a schema containing the fields we want to declare. We could just define schema fields on a zope.interface class, but we use an extended form from plone.supermodel, else we could not use the fieldset features.

  3. We mark our schema as a class that also provides the IFormFieldProvider interface using a decorator. The schema class itself provides the interface, not its instance!

  4. We also add a fieldset so that our field is not mixed with the normal fields of the object.

  5. We add a normal Bool schema field to control if an item should be displayed on the front page.

Note

For simplicity we do not use the so called AnnotationStorage. The value of the field "featured" is saved on the object. Imagine an add-on that unfortunately uses the same field name "featured" for another purpose than ploneconf.site. Here the AnnotationStorage comes in. The object is equipped by a storage where behaviors do store values with a key unique per behavior.

Furthermore a marker interface is needed as soon as we want to register components for objects that do adapt this behavior, e.g. REST API endpoints.

We will see marker interfaces and AnnotationStorages in chapter Complex behaviors [voting story].

16.4. Enabling the behavior on our talk#

We could add this behavior now via the plone control panel "content types". But instead, we will do it directly and properly in a content types GenericSetup profile.

We add the behavior to profiles/default/types/talk.xml:

 1<?xml version="1.0"?>
 2<object name="talk" meta_type="Dexterity FTI" i18n:domain="plone"
 3   xmlns:i18n="http://xml.zope.org/namespaces/i18n">
 4   ...
 5 <property name="behaviors">
 6  <element value="plone.dublincore"/>
 7  <element value="plone.namefromtitle"/>
 8  <element value="ploneconf.featured"/>
 9 </property>
10 ...
11</object>

After a restart and the reinstallation of the product we now have the new field we added through the behavior:

Extended behavior field shown in Volto

16.5. Add an index for the new field#

To use this new "featured" information in searches and listings, we have to add an index to the plone_catalog. Indexing is the action to make object data searchable. Plone stores available catalog indexes in the database.

Note

You can inspect existing indexes in portal_catalog on "Index" tab http://localhost:8080/Plone/portal_catalog/manage_catalogIndexes.

First of all we have to decide which kind of index we need for our new field. Common index types are:

  • FieldIndex stores values as is

  • BooleanIndex stores boolean values as is

  • KeywordIndex allows keyword-style look-ups (query term is matched against all the values of a stored list)

  • DateIndex and DateRangeIndex store dates in searchable format. The latter provides ranged searches.

Because we have a boolean field for the featured information, it is obvious to use the BooleanIndex for this.

To add a new index we have to change the catalog.xml in the profiles/default folder of our product. Without changes the file does look like this:

1<?xml version="1.0"?>
2<object name="portal_catalog">
3  <!--<column value="my_meta_column"/>-->
4</object>

To add the new BooleanIndex to the file we have to change the file as following:

1<?xml version="1.0"?>
2<object name="portal_catalog">
3  <index name="featured" meta_type="BooleanIndex">
4    <indexed_attr value="featured"/>
5  </index>
6</object>

To understand this snippet we have to understand the tags and information we are using:

  • The index tag will tell the plone_catalog that we want to add a new index.

  • name will be shown in the overview of portal_catalog and can be used in listings and searches later on.

  • meta_type determines the type of index we want to use.

  • The indexed_attr includes the field name of the information we are going to save in the index.

After a restart and reinstallation of the product, a new index is created in the portal_catalog.

Note

Instead of de-installing and installing in the Add-Ons control panel, we can import new or altered XML files in the ZMI. To do so go to portal_setup, switch to the Import-Tab and search for the profile to import like in this case: ploneconf.site.

To see if the adding was successful, we open the ZMI of our Plone site and navigate to the portal_catalog and click the Indexes tab. The new index featured should now be listed. As soon as you edit content, you can also see the values of "featured" listed on "Browse" tab.

16.6. Add a metadata column for the new field#

The same rules and methods shown above for indexes apply for metadata columns. The difference with metadata is that it is not used as criteria for searching the catalog, but is mandatory for displaying of search results returned from the catalog.

We will see that in fact every attribute of an object can be accessed in search results by explicitly requesting objects. A way more performant search is requesting what is stored in the catalog. And this is exactly the metadata.

To add a metadata column for "featured", we have to add one more line in the catalog.xml like this:

1<?xml version="1.0"?>
2<object name="portal_catalog">
3  <index name="featured" meta_type="BooleanIndex">
4    <indexed_attr value="featured"/>
5  </index>
6  <column value="featured"/>
7</object>

After a restart and reinstallation of the product, the new metadata column can be found in the portal_catalog in your ZMI on the tab Metadata.

16.7. Exercise#

Since you now know how to add indexes to the portal_catalog, it is time for an exercise.

Add a new index for the speaker field of our content type Talk

Solution
1<?xml version="1.0"?>
2<object name="portal_catalog">
3  <index name="featured" meta_type="BooleanIndex">
4    <indexed_attr value="featured"/>
5  </index>
6  <index name="speaker" meta_type="FieldIndex">
7    <indexed_attr value="speaker"/>
8  </index>
9</object>