13. Content types: Reference#
This chapter documents common fields, widgets, directives that you can use with content types. Content types are often called dexterity types which refers to the rework of the content type concept by dexterity and abandoning the Archetypes system.
13.1. Fields included in Plone#
This is a schema with examples for all field-types that are shipped with Plone by default. They are arranged in fieldsets:
- Text, boolean, email
Textline, RichText, Boolean, Email, URI
- Number fields
Integer, Float
- Date and time fields
Datetime, Date
- Choice and Multiple Choice fields
Choice, List, Tuple, Set
- Relation fields
Relationchoice, Relationlist
- File fields
File, Image
See also
See the code in example.contenttype branch training
1from plone.app.textfield import RichText
2from plone.autoform import directives
3from plone.dexterity.content import Container
4
5from plone.namedfile.field import NamedBlobFile
6from plone.namedfile.field import NamedBlobImage
7from plone.schema import Email
8
9from plone.supermodel import model
10from plone.supermodel.directives import fieldset
11from plone.supermodel.directives import primary
12from z3c.relationfield.schema import RelationChoice
13from z3c.relationfield.schema import RelationList
14from zope import schema
15from zope.interface import implementer
16
17
18class IExample(model.Schema):
19 """Dexterity-Schema with common field-types."""
20
21 # fieldset(
22 # "default",
23 # label="Text, Boolean, Email",
24 # fields=(
25 # "title",
26 # "description",
27 # "richtext_field",
28 # "bool_field",
29 # "email_field",
30 # "uri_field",
31 # ),
32 # )
33
34 fieldset(
35 "numberfields",
36 label="Number",
37 fields=("int_field", "float_field"),
38 )
39
40 fieldset(
41 "datetimefields",
42 label="Date and time",
43 fields=(
44 "datetime_field",
45 "date_field",
46 ),
47 )
48
49 fieldset(
50 "choicefields",
51 label="Choice",
52 fields=(
53 "choice_field",
54 "list_field",
55 "tuple_field",
56 "set_field",
57 ),
58 )
59
60 fieldset(
61 "relationfields_volto",
62 label="Relation fields – Volto",
63 fields=(
64 "relationchoice_field_named_staticcatalogvocabulary",
65 "relationlist_field_named_staticcatalogvocabulary",
66 ),
67 )
68
69 fieldset(
70 "filefields",
71 label="File",
72 fields=("file_field", "image_field"),
73 )
74
75 # Default fields
76 primary("title")
77 title = schema.TextLine(
78 title="Primary Field (Textline)",
79 description="zope.schema.TextLine",
80 required=True,
81 )
82
83 description = schema.TextLine(
84 title="Description (Textline)",
85 description="zope.schema.TextLine",
86 required=False,
87 )
88
89 # text_field = schema.Text(
90 # title="Text Field",
91 # description="zope.schema.Text",
92 # required=False,
93 # missing_value="",
94 # default="",
95 # )
96
97 # textline_field = schema.TextLine(
98 # title="Textline field",
99 # description="A simple input field (zope.schema.TextLine)",
100 # required=False,
101 # )
102
103 richtext_field = RichText(
104 title="RichText field",
105 description="This uses a richtext editor. (plone.app.textfield.RichText)",
106 max_length=2000,
107 required=False,
108 )
109
110 bool_field = schema.Bool(
111 title="Boolean field",
112 description="zope.schema.Bool",
113 required=False,
114 )
115
116 email_field = Email(
117 title="Email field",
118 description="A simple input field for a email (plone.schema.email.Email)",
119 required=False,
120 )
121
122 uri_field = schema.URI(
123 title="URI field",
124 description="A simple input field for a URLs (zope.schema.URI)",
125 required=False,
126 )
127
128 # Choice fields
129 choice_field = schema.Choice(
130 title="Choice field",
131 description="zope.schema.Choice",
132 values=["One", "Two", "Three"],
133 required=False,
134 )
135
136 list_field = schema.List(
137 title="List field",
138 description="zope.schema.List",
139 value_type=schema.Choice(
140 values=["Beginner", "Advanced", "Professional"],
141 ),
142 required=False,
143 missing_value=[],
144 default=[],
145 )
146
147 tuple_field = schema.Tuple(
148 title="Tuple field",
149 description="zope.schema.Tuple",
150 value_type=schema.Choice(
151 values=["Beginner", "Advanced", "Professional"],
152 ),
153 required=False,
154 missing_value=(),
155 default=(),
156 )
157
158 set_field = schema.Set(
159 title="Set field",
160 description="zope.schema.Set",
161 value_type=schema.Choice(
162 values=["Beginner", "Advanced", "Professional"],
163 ),
164 required=False,
165 missing_value=set(),
166 default=set(),
167 )
168
169 # File and image fields
170 image_field = NamedBlobImage(
171 title="Image field",
172 description="A upload field for images (plone.namedfile.field.NamedBlobImage)",
173 required=False,
174 )
175
176 file_field = NamedBlobFile(
177 title="File field",
178 description="A upload field for files (plone.namedfile.field.NamedBlobFile)",
179 required=False,
180 )
181
182 # Date and Time fields
183 datetime_field = schema.Datetime(
184 title="Datetime field",
185 description="Uses a date and time picker (zope.schema.Datetime)",
186 required=False,
187 )
188
189 date_field = schema.Date(
190 title="Date field",
191 description="Uses a date picker (zope.schema.Date)",
192 required=False,
193 )
194
195 """Relation fields like Volto likes it
196
197 RelationChoice and RelationList with named StaticCatalogVocabulary
198
199 StaticCatalogVocabulary registered with same name as field/relation.
200 This allowes Volto relations control panel to restrict potential targets.
201 """
202
203 relationchoice_field_named_staticcatalogvocabulary = RelationChoice(
204 title="RelationChoice – named StaticCatalogVocabulary – Select widget",
205 description="field/relation: relationchoice_field_named_staticcatalogvocabulary",
206 vocabulary="relationchoice_field_named_staticcatalogvocabulary",
207 required=False,
208 )
209 directives.widget(
210 "relationchoice_field_named_staticcatalogvocabulary",
211 frontendOptions={
212 "widget": "select",
213 },
214 )
215
216 relationlist_field_named_staticcatalogvocabulary = RelationList(
217 title="RelationList – named StaticCatalogVocabulary – Select widget",
218 description="field/relation: relationlist_field_named_staticcatalogvocabulary",
219 value_type=RelationChoice(
220 vocabulary="relationlist_field_named_staticcatalogvocabulary",
221 ),
222 required=False,
223 default=[],
224 missing_value=[],
225 )
226 directives.widget(
227 "relationlist_field_named_staticcatalogvocabulary",
228 frontendOptions={
229 "widget": "select",
230 },
231 )
232
233 # Number fields
234 int_field = schema.Int(
235 title="Integer Field (e.g. 12)",
236 description="zope.schema.Int",
237 required=False,
238 )
239
240 float_field = schema.Float(
241 title="Float field, e.g. 12.7",
242 description="zope.schema.Float",
243 required=False,
244 )
245
246
247@implementer(IExample)
248class Example(Container):
249 """Example instance class"""
13.2. How fields look like#
This is how these fields look like when editing content in Volto:
13.3. mixedfield (datagrid field)#
The mixedfield empowers your user to create a list of objects of mixed value types sharing the same schema. If you are familiar with the Plone Classic datagrid field this is the complementary field / widget combo for Plone. mixedfield is a combination of a Plone Classic JSONField and a widget for Plone. Nothing new, just a term to talk about linking backend and frontend.
Example is a custom history:
Backend#
Add a JSONField
field to your content type schema.
1from plone.schema import JSONField
2
3MIXEDFIELD_SCHEMA = json.dumps(
4 {
5 'type': 'object',
6 'properties': {'items': {'type': 'array', 'items': {'type': 'object', 'properties': {}}}},
7 }
8)
9
10class IExample(model.Schema):
11 """Dexterity-Schema"""
12
13 fieldset(
14 'datagrid',
15 label='Datagrid field',
16 fields=(
17 # 'datagrid_field',
18 'mixed_field',
19 ),
20 )
21
22 primary('title')
23 title = schema.TextLine(
24 title='Primary Field (Textline)',
25 description='zope.schema.TextLine',
26 required=True,
27 )
28
29 description = schema.TextLine(
30 title='Description (Textline)',
31 description='zope.schema.TextLine',
32 required=False,
33 )
34
35 history_field = JSONField(
36 title='Mixedfield: datagrid field for Plone',
37 required=False,
38 schema=MIXEDFIELD_SCHEMA,
39 widget='history_widget',
40 default={'items': []},
41 missing_value={'items': []},
42 )
Frontend#
Provide a widget in your favorite add-on with a schema of elementary fields you need.
1import ObjectListWidget from '@plone/volto/components/manage/Widgets/ObjectListWidget';
2
3const ItemSchema = {
4 title: 'History-Entry',
5 properties: {
6 historydate: {
7 title: 'Date',
8 widget: 'date',
9 },
10 historytopic: {
11 title: 'What',
12 },
13 historyversion: {
14 title: 'Version',
15 },
16 historyauthor: {
17 title: 'Who',
18 },
19 },
20 fieldsets: [
21 {
22 id: 'default',
23 title: 'History-Entry',
24 fields: [
25 'historydate',
26 'historytopic',
27 'historyversion',
28 'historyauthor',
29 ],
30 },
31 ],
32 required: [],
33};
34
35const HistoryWidget = (props) => {
36 return (
37 <ObjectListWidget
38 schema={ItemSchema}
39 {...props}
40 value={props.value?.items || props.default?.items || []}
41 onChange={(id, value) => props.onChange(id, { items: value })}
42 />
43 );
44};
45
46export default HistoryWidget;
Keeping this example as simple as possible we skipped the localization. Please see Volto documentation for details.
Register this widget for the backend field of your choice in your apps configuration config.js
.
The following config code registers the custom Plone HistoryWidget for Plone Classic fields with widget "history_widget".
1import { HistoryWidget } from '@rohberg/voltotestsomevoltothings/components';
2
3// All your imports required for the config here BEFORE this line
4import '@plone/volto/config';
5
6export default function applyConfig(config) {
7 config.settings = {
8 ...config.settings,
9 supportedLanguages: ['en', 'de', 'it'],
10 defaultLanguage: 'en',
11 };
12 config.widgets.widget.history_widget = HistoryWidget;
13
14 return config;
15}
Please be sure to use plone.restapi
version >= 7.3.0. If you cannot upgrade plone.restapi
then a registration per field id instead of a registration per field widget name is needed.
export default function applyConfig(config) {
config.widgets.id.history_field = HistoryWidget;
return config;
}
The user can now edit the values of the new field history_field.
That's what you did to accomplish this:
You added a new field of type JSONField with widget "history_widget" and default schema to your content type schema.
You registered the custom Plone widget for widget name "history_widget".
A view (ExampleView
) of the content type integrates a component to display the values of the field history_field.
1import React from 'react';
2import moment from 'moment';
3import { Container, Table } from 'semantic-ui-react';
4
5const MyHistory = ({ history }) => {
6 return (
7 _CLIENT__ && (
8 <Table celled className="history_list">
9 <Table.Header>
10 <Table.Row>
11 <Table.HeaderCell>Date</Table.HeaderCell>
12 <Table.HeaderCell>What</Table.HeaderCell>
13 <Table.HeaderCell>Version</Table.HeaderCell>
14 <Table.HeaderCell>Who</Table.HeaderCell>
15 </Table.Row>
16 </Table.Header>
17
18 <Table.Body>
19 {history?.items?.map((item) => (
20 <Table.Row>
21 <Table.Cell>
22 {item.historydate && moment(item.historydate).format('L')}
23 </Table.Cell>
24 <Table.Cell>{item.historytopic}</Table.Cell>
25 <Table.Cell>{item.historyversion}</Table.Cell>
26 <Table.Cell>{item.historyauthor}</Table.Cell>
27 </Table.Row>
28 ))}
29 </Table.Body>
30 </Table>
31 )
32 );
33};
34
35const ExampleView = ({ content }) => {
36 return (
37 <Container>
38 <h2>I am an ExampleView</h2>
39 <h3>History</h3>
40 <MyHistory history={content.history_field} />
41 </Container>
42 );
43 };
44
45 export default ExampleView;
Et voilà.
13.4. Widgets#
Volto makes suggestions which widget to use, based on the fields type, backend widget and id.
All widgets are listed here: frontend widgets
Determine frontend widget#
If you want to register a frontend widget for your field, you can define your field such as:
directives.widget(
"specialfield",
frontendOptions={
"widget": "specialwidget"
})
specialfield = schema.TextLine(title="Field with special frontend widget")
Then register your frontend widget in your apps configuration.
import { MySpecialWidget } from './components';
const applyConfig = (config) => {
config.widgets.widget.specialwidget = MySpecialWidget;
return config;
}
You can also pass additional props to the frontend widget using the widgetProps
key:
directives.widget(
"specialfield",
frontendOptions={
"widget": "specialwidget",
"widgetProps": {"isLarge": True, "color": "red"}
})
specialfield = schema.TextLine(title="Field with special frontend widget")
The props will be injected into the corresponding widget component, configuring it as specified.
13.5. Directives#
Directives can be placed anywhere in the class body (annotations are made directly on the class). By convention they are kept next to the fields they apply to.
For example, here is a schema that omits a field:
from plone.autoform import directives
from plone.supermodel import model
from zope import schema
class ISampleSchema(model.Schema):
title = schema.TextLine(title='Title')
directives.omitted('additionalInfo')
additionalInfo = schema.Bytes()
You can also handle multiple fields with one directive:
directives.omitted('field_1', 'field_2')
With the directive "mode" you can set fields to 'input', 'display' or 'hidden'.
directives.mode(additionalInfo='hidden')
You can apply directives to certain forms only. Here we drop a field from the add-form, it will still show up in the edit-form.
from z3c.form.interfaces import IAddForm
class ITask(model.Schema):
title = schema.TextLine(title='Title')
directives.omitted(IAddForm, 'done')
done = schema.Bool(
title='Done',
required=False,
)
The same works for custom forms.
With the directive widget()
you can not only change the widget used for a field. With pattern_options
you can pass additional parameters to the widget. Here, we configure the datetime widget powered by the JavaScript library pickadate by adding options that are used by it. Plone passes the options to the library.
class IMeeting(model.Schema):
meeting_date = schema.Datetime(
title='Date and Time',
required=False,
)
directives.widget(
'meeting_date',
DatetimeFieldWidget,
pattern_options={
'time': {'interval': 60, 'min': [7, 0], 'max': [19, 0]}},
)
13.6. Validation and default values#
In the following example we add a validator and a default value.
from zope.interface import Invalid
import datetime
def future_date(value):
if value and not value.date() >= datetime.date.today():
raise Invalid('Meeting date can not be before today.')
return True
def meeting_date_default_value():
return datetime.datetime.today() + datetime.timedelta(7)
class IMeeting(model.Schema):
meeting_date = schema.Datetime(
title='Date and Time',
required=False,
constraint=future_date,
defaultFactory=meeting_date_default_value,
)
Validators and defaults can also be made aware of the context (i.e. to check against the values of other fields).
For context aware defaults you need to use a IContextAwareDefaultFactory
. It will be passed the container for which the add form is being displayed:
from zope.interface import provider
from zope.schema.interfaces import IContextAwareDefaultFactory
@provider(IContextAwareDefaultFactory)
def get_container_id(context):
return context.id.upper()
class IMySchema(model.Schema):
parent_id = schema.TextLine(
title='Parent ID',
required=False,
defaultFactory=get_container_id,
)
For context-aware validators you need to use invariant()
:
from zope.interface import Invalid
from zope.interface import invariant
from zope.schema.interfaces import IContextAwareDefaultFactory
class IMyEvent(model.Schema):
start = schema.Datetime(
title='Start date',
required=False,
)
end = schema.Datetime(
title='End date',
required=False,
)
@invariant
def validate_start_end(data):
if data.start is not None and data.end is not None:
if data.start > data.end:
raise Invalid('Start must be before the end.')