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
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
1<configure
2 xmlns="http://namespaces.zope.org/zope"
3 xmlns:plone="http://namespaces.plone.org/plone"
4 i18n_domain="ploneconf.site">
5
6 <plone:behavior
7 title="Featured"
8 name="ploneconf.featured"
9 description="Control if an item is shown on the frontpage"
10 provides=".featured.IFeatured"
11 />
12
13</configure>
And a behaviors/featured.py
containing:
1from plone.autoform.interfaces import IFormFieldProvider
2from plone.supermodel import model
3from plone.supermodel.directives import fieldset
4from plone import schema
5from zope.interface import provider
6
7@provider(IFormFieldProvider)
8class IFeatured(model.Schema):
9
10 featured = schema.Bool(
11 title='Show this item on the frontpage',
12 required=False,
13 )
14 fieldset("Options", fields=["featured"])
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.
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.
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.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!We also add a
fieldset
so that our field is not mixed with the normal fields of the object.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:
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 theplone_catalog
that we want to add a new index.name
will be shown in the overview ofportal_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