Skip to content

Commit c97867a

Browse files
committed
rough sketch of bahiors chapter
1 parent 612ffcd commit c97867a

File tree

1 file changed

+244
-4
lines changed

1 file changed

+244
-4
lines changed

docs/backend/behaviors.md

Lines changed: 244 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,252 @@ myst:
1111

1212
# Behaviors
1313

14-
1514
```{seealso}
1615
See the chapter {ref}`training:behaviors1-label` from the Mastering Plone 6 Training.
1716
```
1817

19-
```{todo}
20-
Contribute to this documentation!
21-
See issue [Backend > Behaviors needs content](https://github.com/plone/documentation/issues/1302).
18+
## What are Behaviors in Plone
19+
20+
In Plone, behaviors are a way to add additional, reusable functionality to content objects without modifying the objects themselves.
21+
Behaviors are essentially small chunks of code that can be plugged onto content objects to provide new features or capabilities.
22+
23+
A Plone behavior could be used to
24+
25+
- Add a set of form fields (on standard add and edit forms)
26+
- Enable a particular event handler
27+
- Enable one or more views, viewlets, or other UI components
28+
- Anything else which may be expressed in code via an adapter and/or marker interface.
29+
30+
Behaviors can be added to content types on an as-needed basis, allowing for a high degree of flexibility and customization.
31+
32+
Plone already provides lots of behaviors for the built-in content types.
33+
34+
Other behaviors are implemented as add-on products, which can be installed and configured through the Plone control panel.
35+
Once a behavior has been installed, it can be applied to any content type by selecting it in the Dexterity configuration section of the Plone control panel.
36+
This allows the object to gain the additional functionality provided by the behavior.
37+
38+
Overall, behaviors are an important part of the Plone content management system and allow for powerful customization and extensibility of content objects.
39+
40+
## Built-in behaviors
41+
42+
| short name | Title | Desription |
43+
|-------------|------------------|-------------------------------|
44+
| plone.allowdiscussion | Allow discussion | Allow discussion on this item |
45+
| plone.basic | Basic metadata | Adds title and description fields. |
46+
| plone.categorization | Categorization | Adds keywords and language fields. |
47+
| plone.collection | Collection | Adds collection behavior |
48+
| plone.publication | Date range | Adds effective date and expiration date fields. |
49+
| plone.dublincore | Dublin Core metadata | Adds standard metadata fields (equals Basic metadata + Categorization + Effective range + Ownership |
50+
| plone.eventattendees | Event Attendees | Attendees extension for Events. |
51+
| plone.eventbasic | Event Basic | Basic Event schema. |
52+
| plone.eventcontact | Event Contact | Contact extension for Events. |
53+
| plone.eventlocation | Event Location | Location extension for Events. |
54+
| plone.eventrecurrence | Event Recurrence | Recurrence extension for Events. |
55+
| plone.excludefromnavigation | Exclude From navigation | Allow items to be excluded from navigation |
56+
| plone.constraintypes| Folder Addable Constrains | Restrict the content types that can be added to folderish content |
57+
| plone. textindexer | Full-Text Indexing | Enables the enhanced full-text indexing for a content type. If a field in the schema is marked with the `searchable` directive, its content gets added to the `SearchableText` index in the catalog |
58+
| plone.leadimage | Lead Image | Adds image and image caption fields |
59+
| plone.locking | Locking | Locking support for dexterity |
60+
| plone.translatable | Multilingual Support | Make this content type multilingual aware. Multilingual support must be installed. |
61+
| plone.namefromfilename | Name from file name | Automatically generate short URL name for content based on its primary field file name
62+
| plone.namefromtitle | Name from title | Automatically generate short URL name for content based on its initial title
63+
| plone.navigationroot | Navigation root | Make all items of this type a navigation root |
64+
| plone.nextpreviousenabled | Next previous navigation | Enable next previous navigation for all items of this type |
65+
| plone.nextprevioustoggle | Next previous navigation toggle | Allow items to have next previous navigation enabled |
66+
| plone.ownership | Ownership | Adds creator, contributor, and rights fields. |
67+
| plone.relateditems | Related items | Adds the ability to assign related items |
68+
| plone.richtext | RichText | Adds richtext behavior |
69+
| plone.shortname | Short name | Gives the ability to rename an item from its edit form. |
70+
| plone.tableofcontents | Table of contents | Adds a table of contents |
71+
| plone.thumb_icon | Thumbs and icon handling | Options to suppress thumbs and/or icons and to override thumb size in listings, tables etc.
72+
| plone.versioning | Versioning | Versioning support with CMFEditions |
73+
74+
## Adding or removing a behavior from a type
75+
76+
Given you already have an add-on for customizations with a setup profile...
77+
78+
## How behaviors are working
79+
80+
At the most basic level, a behavior is like a conditional adapter.
81+
For a Dexterity content type, the default condition is, "is this behavior listed in the behaviors property in the FTI?"
82+
But the condition itself is an adapter; in rare cases, this can be overruled.
83+
When a behavior is enabled for a particular object, it will be possible to adapt that object to the behavior's interface.
84+
If the behavior is disabled, adaptation will fail.
85+
86+
A behavior consists at the very least of an interface and some metadata, namely a name, title, and description.
87+
This is also called a schema-only interface
88+
89+
In other cases, there is also a factory, akin to an adapter factory, which will be invoked to get an appropriate adapter when requested.
90+
This is usually just a class that looks like any other adapter factory, although it will tend to apply to Interface, IContentish, or a similarly broad context.
91+
92+
Behaviors can specify a marker interface, which will be directly provided by instances for which the behavior is enabled.
93+
If you want to conditionally enable event handlers or view components, they can be registered for this marker interface.
94+
95+
Some behaviors have no factory.
96+
In this case, the behavior interface and the marker interface must be the same.
97+
98+
If a factory is given a marker interface different from the behavior interface must be declared!
99+
100+
Behaviors are registered globally, using the `<plone.behavior />` ZCML directive.
101+
This results in, among other things, a named utility providing `plone.behavior.interfaces.IBehavior` being registered.
102+
This utility contains various information about the behavior, such as its name, title, interface, and (optional) factory and marker interface.
103+
104+
The utility name is the name attribute of the behavior directive.
105+
Historically the full dotted name to the behavior interface is registered as name too, but usage is discouraged.
106+
107+
108+
Behaviors are named adapters.
109+
They adapt an interface, for which they are registered and they provide another interface with new functionality such as attributes and methods.
110+
111+
Dexterity types are transparently looking up behaviors registered for a type.
112+
If a behavior provides new attributes it is provided as an attribute of the adapted object.
113+
114+
```{seealso}
115+
The [README file of `plone.behavior`](https://github.com/plone/plone.behavior/blob/master/README.rst) explains the concepts and different ways to register a behavior in detail.
116+
```
117+
118+
## Custom behaviors
119+
120+
From the last section we can take away, that there are two main types of behaviors:
121+
122+
- Schema-only behaviors: These behaviors have only a schema with fields.
123+
124+
- Full behaviors: A python class containing the logic of the behavior, an interface or schema defining the contract of the behavior, and a marker interface applied to the content type.
125+
126+
### Creating a schema-only behavior
127+
128+
Given you want to add a field `subtitle` to some existing content types of your custom add-on.
129+
130+
You need to create a file `subtitle.py` in your addon like so:
131+
132+
```python
133+
from plone.autoform.interfaces import IFormFieldProvider
134+
from plone.supermodel import model
135+
from zope import schema
136+
from zope.interface import provider
137+
138+
139+
@provider(IFormFieldProvider)
140+
class ISubtitleBehavior(model.Schema):
141+
"""Subtitle behavior."""
142+
143+
subtitle = schema.Text(
144+
title="Subtitle",
145+
description="A title to be displayed below the title",
146+
default="",
147+
required=False,
148+
)
149+
```
150+
151+
You need to add a ZCML snippet to the `configure.zcml` next to `subtitle.py` like so:
152+
153+
```XML
154+
<plone:behavior
155+
name="myproject.subtitle"
156+
provides=".subtitle.ISubtitleBehavior"
157+
title="Subtitle"
158+
/>
159+
```
160+
161+
After a restart of Plone, the behavior can be added to the type in the Content Types control panel.
162+
The add and edit forms are containing a new field `Subtitle`.
163+
164+
This field is not displayed in most views.
165+
To display the data entered in this field you need to modify the page template and access the field as `context.subtitle` there.
166+
167+
### Creating a behavior with an adapter and factory
168+
169+
Given we want to display a price with different content types.
170+
The price is stored as the net value on the type as a floating point number.
171+
For display, we need at several places the VAT and the gross value.
172+
173+
We create a schema with the net value for the form and attributes for the calculated values.
174+
We create an adapter to calculate the VAT and gross values.
175+
We need a marker interface to distinguish between context and adapter.
176+
177+
The Python code in a file `price.py` looks like so:
178+
179+
```python
180+
from plone.autoform.interfaces import IFormFieldProvider
181+
from plone.supermodel import model
182+
from zope import schema
183+
from zope.interface import Attribute
184+
from zope.interface import implementer
185+
from zope.interface import Interface
186+
from zope.interface import provider
187+
188+
189+
@provider(IFormFieldProvider)
190+
class IPriceBehavior(model.Schema):
191+
"""Behavior: a price, VAT and gross."""
192+
193+
price_net = schema.Float(
194+
title="Price (net)",
195+
required=True,
196+
)
197+
price_vat = Attribute("VAT 20% of net price")
198+
price_gross = Attribute("Price gross (net + VAT 20%")
199+
200+
201+
class IPriceMarker(Interface):
202+
"""Marker for content that has a price."""
203+
204+
205+
@implementer(IPriceBehavior)
206+
class PriceAdapter:
207+
def __init__(self, context):
208+
self.context = context
209+
210+
@property
211+
def price_net(self):
212+
"""Getter, read from context and return back"""
213+
return self.context.price_net
214+
215+
@price_net.setter
216+
def price_net(self, value):
217+
"""Setter, called by the form, set on context"""
218+
self.context.price_net = value
219+
220+
@property
221+
def price_vat(self):
222+
return self.price_net * 0.2
223+
224+
@property
225+
def price_gross(self):
226+
return self.price_net + self.price_vat
227+
228+
```
229+
230+
The registration in the `configure.zcml` looks like so:
231+
232+
```XML
233+
<plone:behavior
234+
factory=".price.PriceAdapter"
235+
for=".price.IPriceMarker"
236+
marker=".price.IPriceMarker"
237+
name="myproject.price"
238+
provides=".price.IPriceBehavior"
239+
title="Price with net, VAT and gross"
240+
/>
241+
```
242+
243+
After a restart of Plone, the behavior can be added to the type in the Content Types control panel.
244+
The add and edit forms are containing a new field `Price (net)`.
245+
246+
This field is not displayed in most views.
247+
To display the data entered in this field you need to modify the page template and access the `price_net` field as `context.price_net` there.
248+
To access the `price_vat` and `price_gross` fields you need to get the adapter in your view class like so:
249+
250+
```python
251+
from .price import IPriceBehavior
252+
253+
class SomeViewClass:
254+
255+
def vat(self):
256+
adapter = IPriceBehavior(context)
257+
return adapter.price_vat
258+
259+
def gross(self):
260+
adapter = IPriceBehavior(context)
261+
return adapter.price_gross
22262
```

0 commit comments

Comments
 (0)