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
Catalog indexes and catalog metadata columns
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.
23.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 (e.g. 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.
23.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.
23.3. Practical example#
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
<include package=".behaviors" />
Then, we add an empty
behaviors/__init__.py and a
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>
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
IFormFieldProviderinterface using a decorator. The schema class itself provides the interface, not its instance!
We also add a
fieldsetso 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 frontpage.
For simplicity we do not use the so called
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
Here the AnnotationStorage comes in.
The object is equipped by a storage where behaviors do store values with a key unique per behavior.
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.
23.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
We add the behavior to
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:
23.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
Indexing is the action to make object data searchable.
Plone stores available catalog indexes in the database.
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:
indextag will tell the
plone_catalogthat we want to add a new index.
namewill be shown in the overview of
portal_catalogand can be used in listings and searches later on.
meta_typedetermines the type of index we want to use.
indexed_attrincludes 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
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:
To see if the adding was successful, we open the ZMI of our Plone site and navigate to the
portal_catalog and click the
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.
23.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 criterions 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 another restart and another import of the xml-profile, the new metadata column can be found in the
portal_catalog in your
ZMI on the tab
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
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>