13. Content types reference#
This chapter documents common fields, widgets, directives that you can use with content types.
Note
You might see references to "Dexterity" which is the internal name of Plone's content type 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 primary("title")
22 title = schema.TextLine(
23 title="Primary Field (Textline)",
24 description="zope.schema.TextLine",
25 required=True,
26 )
27
28 description = schema.TextLine(
29 title="Description (Textline)",
30 description="zope.schema.TextLine",
31 required=False,
32 )
33
34 richtext_field = RichText(
35 title="RichText field",
36 description="This uses a richtext editor. (plone.app.textfield.RichText)",
37 max_length=2000,
38 required=False,
39 )
40
41 bool_field = schema.Bool(
42 title="Boolean field",
43 description="zope.schema.Bool",
44 required=False,
45 )
46
47 email_field = Email(
48 title="Email field",
49 description="A simple input field for a email (plone.schema.email.Email)",
50 required=False,
51 )
52
53 uri_field = schema.URI(
54 title="URI field",
55 description="A simple input field for a URLs (zope.schema.URI)",
56 required=False,
57 )
58
59 # Number fields
60 fieldset(
61 "numberfields",
62 label="Number",
63 fields=("int_field", "float_field"),
64 )
65
66 int_field = schema.Int(
67 title="Integer Field (e.g. 12)",
68 description="zope.schema.Int",
69 required=False,
70 )
71
72 float_field = schema.Float(
73 title="Float field, e.g. 12.7",
74 description="zope.schema.Float",
75 required=False,
76 )
77
78 # Date and Time fields
79 fieldset(
80 "datetimefields",
81 label="Date and time",
82 fields=(
83 "datetime_field",
84 "date_field",
85 ),
86 )
87
88 datetime_field = schema.Datetime(
89 title="Datetime field",
90 description="Uses a date and time picker (zope.schema.Datetime)",
91 required=False,
92 )
93
94 date_field = schema.Date(
95 title="Date field",
96 description="Uses a date picker (zope.schema.Date)",
97 required=False,
98 )
99
100 # Choice fields
101 fieldset(
102 "choicefields",
103 label="Choice",
104 fields=(
105 "choice_field",
106 "list_field",
107 "tuple_field",
108 "set_field",
109 ),
110 )
111
112 choice_field = schema.Choice(
113 title="Choice field",
114 description="zope.schema.Choice",
115 values=["One", "Two", "Three"],
116 required=False,
117 )
118
119 list_field = schema.List(
120 title="List field",
121 description="zope.schema.List",
122 value_type=schema.Choice(
123 values=["Beginner", "Advanced", "Professional"],
124 ),
125 required=False,
126 missing_value=[],
127 default=[],
128 )
129
130 tuple_field = schema.Tuple(
131 title="Tuple field",
132 description="zope.schema.Tuple",
133 value_type=schema.Choice(
134 values=["Beginner", "Advanced", "Professional"],
135 ),
136 required=False,
137 missing_value=(),
138 default=(),
139 )
140
141 set_field = schema.Set(
142 title="Set field",
143 description="zope.schema.Set",
144 value_type=schema.Choice(
145 values=["Beginner", "Advanced", "Professional"],
146 ),
147 required=False,
148 missing_value=set(),
149 default=set(),
150 )
151
152 """Relation fields like Volto likes it
153
154 RelationChoice and RelationList with named StaticCatalogVocabulary
155
156 StaticCatalogVocabulary registered with same name as field/relation.
157 This allowes Volto relations control panel to restrict potential targets.
158 """
159 fieldset(
160 "relationfields_volto",
161 label="Relation fields – Volto",
162 fields=(
163 "relationchoice_field_named_staticcatalogvocabulary",
164 "relationlist_field_named_staticcatalogvocabulary",
165 ),
166 )
167
168 relationchoice_field_named_staticcatalogvocabulary = RelationChoice(
169 title="RelationChoice – named StaticCatalogVocabulary – Select widget",
170 description="field/relation: relationchoice_field_named_staticcatalogvocabulary",
171 vocabulary="relationchoice_field_named_staticcatalogvocabulary",
172 required=False,
173 )
174 directives.widget(
175 "relationchoice_field_named_staticcatalogvocabulary",
176 frontendOptions={
177 "widget": "select",
178 },
179 )
180
181 relationlist_field_named_staticcatalogvocabulary = RelationList(
182 title="RelationList – named StaticCatalogVocabulary – Select widget",
183 description="field/relation: relationlist_field_named_staticcatalogvocabulary",
184 value_type=RelationChoice(
185 vocabulary="relationlist_field_named_staticcatalogvocabulary",
186 ),
187 required=False,
188 default=[],
189 missing_value=[],
190 )
191 directives.widget(
192 "relationlist_field_named_staticcatalogvocabulary",
193 frontendOptions={
194 "widget": "select",
195 },
196 )
197
198 # File and image fields
199 fieldset(
200 "filefields",
201 label="File",
202 fields=("file_field", "image_field"),
203 )
204
205 image_field = NamedBlobImage(
206 title="Image field",
207 description="A upload field for images (plone.namedfile.field.NamedBlobImage)",
208 required=False,
209 )
210
211 file_field = NamedBlobFile(
212 title="File field",
213 description="A upload field for files (plone.namedfile.field.NamedBlobFile)",
214 required=False,
215 )
216
217
218@implementer(IExample)
219class Example(Container):
220 """Example instance class"""
13.2. How fields look like#
This is how these fields look like when editing content in Volto:
Text and boolean fields#
Number fields#
Date and time fields#
Choice and multiple choice fields#
Reference fields#
File fields#
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 Volto 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, but 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.')