20. Dexterity: Reference#

This chapter documents all types of fields, widgets, directives that you can use with dexterity.

20.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:

Default

Textline, Text, Boolean, Richtext (html), Email

Number fields

Integer, Float

Date and time fields

Datetime, Date, Time, Timedelta

Choice and Multiple Choice fields

Choice, Choice with radio widget, Choice with Select2 widget, Choice with named vocabulary, List, List with checkboxes, List with Select2 widget, List with values from a named vocabulary but open to additions, Tuple, Set, Set with checkboxes

Relation fields

Relationchoice, Relationlist

File fields

File, Image

Other fields

Uri, Sourcetext, Ascii, Bytesline, Asciiline, Pythonidentifier, Dottedname, Dict, Dict with Choice

Warning

In Volto not all field types and features are implemented yet:

  • Time, Timedelta, Dict are not supported yet.

  • Using a callable for basePath in relationfields is not supported yet.

  • The schema-hints to assign a different widget do not work yet.

  1from plone.app.multilingual.browser.interfaces import make_relation_root_path
  2from plone.app.textfield import RichText
  3from plone.app.z3cform.widget import AjaxSelectFieldWidget
  4from plone.app.z3cform.widget import RelatedItemsFieldWidget
  5from plone.app.z3cform.widget import SelectFieldWidget
  6from plone.autoform import directives
  7from plone.dexterity.content import Container
  8from plone.namedfile.field import NamedBlobFile
  9from plone.namedfile.field import NamedBlobImage
 10from plone.schema.email import Email
 11from plone.supermodel import model
 12from plone.supermodel.directives import fieldset
 13from plone.supermodel.directives import primary
 14from z3c.form.browser.checkbox import CheckBoxFieldWidget
 15from z3c.form.browser.radio import RadioFieldWidget
 16from z3c.relationfield.schema import Relation
 17from z3c.relationfield.schema import RelationChoice
 18from z3c.relationfield.schema import RelationList
 19from zope import schema
 20from zope.interface import implementer
 21
 22
 23class IExample(model.Schema):
 24    """Dexterity-Schema with all field-types."""
 25
 26    # The most used fields
 27    # textline, text, bool, richtext, email
 28
 29    fieldset(
 30        'numberfields',
 31        label='Number fields',
 32        fields=('int_field', 'float_field'),
 33    )
 34
 35    fieldset(
 36        'datetimefields',
 37        label='Date and time fields',
 38        fields=('datetime_field', 'date_field', 'time_field', 'timedelta_field'),
 39    )
 40
 41    fieldset(
 42        'choicefields',
 43        label='Choice and Multiple Choice fields',
 44        fields=(
 45            'choice_field',
 46            'choice_field_radio',
 47            'choice_field_select',
 48            'choice_field_voc',
 49            'list_field',
 50            'list_field_checkbox',
 51            'list_field_select',
 52            'list_field_voc_unconstrained',
 53            'tuple_field',
 54            'set_field',
 55            'set_field_checkbox',
 56        ),
 57    )
 58
 59    fieldset(
 60        'relationfields',
 61        label='Relation fields',
 62        fields=('relationchoice_field', 'relationlist_field'),
 63    )
 64
 65    fieldset(
 66        'filefields',
 67        label='File fields',
 68        fields=('file_field', 'image_field'),
 69    )
 70
 71    fieldset(
 72        'otherfields',
 73        label='Other fields',
 74        fields=(
 75            'uri_field',
 76            'sourcetext_field',
 77            'ascii_field',
 78            'bytesline_field',
 79            'asciiline_field',
 80            'pythonidentifier_field',
 81            'dottedname_field',
 82            'dict_field',
 83            'dict_field_with_choice',
 84        ),
 85    )
 86
 87    primary('title')
 88    title = schema.TextLine(
 89        title='Primary Field (Textline)',
 90        required=True,
 91    )
 92
 93    text_field = schema.Text(
 94        title='Text Field',
 95        required=False,
 96        missing_value='',
 97    )
 98
 99    textline_field = schema.TextLine(
100        title='Textline field',
101        description='A simple input field',
102        required=False,
103    )
104
105    bool_field = schema.Bool(
106        title='Boolean field',
107        required=False,
108    )
109
110    choice_field = schema.Choice(
111        title='Choice field',
112        values=['One', 'Two', 'Three'],
113        required=True,
114    )
115
116    directives.widget(choice_field_radio=RadioFieldWidget)
117    choice_field_radio = schema.Choice(
118        title='Choice field with radio boxes',
119        values=['One', 'Two', 'Three'],
120        required=True,
121    )
122
123    choice_field_voc = schema.Choice(
124        title='Choicefield with values from named vocabulary',
125        vocabulary='plone.app.vocabularies.PortalTypes',
126        required=False,
127    )
128
129    directives.widget(choice_field_select=SelectFieldWidget)
130    choice_field_select = schema.Choice(
131        title='Choicefield with select2 widget',
132        vocabulary='plone.app.vocabularies.PortalTypes',
133        required=False,
134    )
135
136    list_field = schema.List(
137        title='List field',
138        value_type=schema.Choice(
139            values=['Beginner', 'Advanced', 'Professional'],
140        ),
141        required=False,
142        missing_value=[],
143    )
144
145    directives.widget(list_field_checkbox=CheckBoxFieldWidget)
146    list_field_checkbox = schema.List(
147        title='List field with checkboxes',
148        value_type=schema.Choice(
149            values=['Beginner', 'Advanced', 'Professional'],
150        ),
151        required=False,
152        missing_value=[],
153    )
154
155    directives.widget(list_field_select=SelectFieldWidget)
156    list_field_select = schema.List(
157        title='List field with select widget',
158        value_type=schema.Choice(
159            values=['Beginner', 'Advanced', 'Professional'],
160        ),
161        required=False,
162        missing_value=[],
163    )
164
165    list_field_voc_unconstrained = schema.List(
166        title='List field with values from vocabulary but not constrained to them.',
167        value_type=schema.TextLine(),
168        required=False,
169        missing_value=[],
170    )
171    directives.widget(
172        'list_field_voc_unconstrained',
173        AjaxSelectFieldWidget,
174        vocabulary='plone.app.vocabularies.Users',
175    )
176
177    tuple_field = schema.Tuple(
178        title='Tuple field',
179        value_type=schema.Choice(
180            values=['Beginner', 'Advanced', 'Professional'],
181        ),
182        required=False,
183        missing_value=(),
184    )
185
186    set_field = schema.Set(
187        title='Set field',
188        value_type=schema.Choice(
189            values=['Beginner', 'Advanced', 'Professional'],
190        ),
191        required=False,
192        missing_value=set(),
193    )
194
195    directives.widget(set_field_checkbox=CheckBoxFieldWidget)
196    set_field_checkbox = schema.Set(
197        title='Set field with checkboxes',
198        value_type=schema.Choice(
199            values=['Beginner', 'Advanced', 'Professional'],
200        ),
201        required=False,
202        missing_value=set(),
203    )
204
205    # File fields
206    image_field = NamedBlobImage(
207        title='Image field',
208        description='A upload field for images',
209        required=False,
210    )
211
212    file_field = NamedBlobFile(
213        title='File field',
214        description='A upload field for files',
215        required=False,
216    )
217
218    # Date and Time fields
219    datetime_field = schema.Datetime(
220        title='Datetime field',
221        description='Uses a date and time picker',
222        required=False,
223    )
224
225    date_field = schema.Date(
226        title='Date field',
227        description='Uses a date picker',
228        required=False,
229    )
230
231    time_field = schema.Time(
232        title='Time field',
233        required=False,
234    )
235
236    timedelta_field = schema.Timedelta(
237        title='Timedelta field',
238        required=False,
239    )
240
241    # Relation Fields
242    relationchoice_field = RelationChoice(
243        title='Relationchoice field',
244        vocabulary='plone.app.vocabularies.Catalog',
245        required=False,
246    )
247    directives.widget(
248        'relationchoice_field',
249        RelatedItemsFieldWidget,
250        pattern_options={
251            'selectableTypes': ['Document'],
252            'basePath': make_relation_root_path,
253        },
254    )
255
256    relationlist_field = RelationList(
257        title='Relationlist Field',
258        default=[],
259        value_type=RelationChoice(vocabulary='plone.app.vocabularies.Catalog'),
260        required=False,
261        missing_value=[],
262    )
263    directives.widget(
264        'relationlist_field',
265        RelatedItemsFieldWidget,
266        pattern_options={
267            'selectableTypes': ['Document'],
268            'basePath': make_relation_root_path,
269        },
270    )
271
272    # Number fields
273    int_field = schema.Int(
274        title='Integer Field (e.g. 12)',
275        description='Allocated (maximum) number of objects',
276        required=False,
277    )
278
279    float_field = schema.Float(
280        title='Float field (e.g. 12.2)',
281        required=False,
282    )
283
284    # Text fields
285    email_field = Email(
286        title='Email field',
287        description='A simple input field for a email',
288        required=False,
289    )
290
291    uri_field = schema.URI(
292        title='URI field',
293        description='A simple input field for a URLs',
294        required=False,
295    )
296
297    richtext_field = RichText(
298        title='RichText field',
299        description='This uses a richtext editor.',
300        max_length=2000,
301        required=False,
302    )
303
304    sourcetext_field = schema.SourceText(
305        title='SourceText field',
306        required=False,
307    )
308
309    ascii_field = schema.ASCII(
310        title='ASCII field',
311        required=False,
312    )
313
314    bytesline_field = schema.BytesLine(
315        title='BytesLine field',
316        required=False,
317    )
318
319    asciiline_field = schema.ASCIILine(
320        title='ASCIILine field',
321        required=False,
322    )
323
324    pythonidentifier_field = schema.PythonIdentifier(
325        title='PythonIdentifier field',
326        required=False,
327    )
328
329    dottedname_field = schema.DottedName(
330        title='DottedName field',
331        required=False,
332    )
333
334    dict_field = schema.Dict(
335        title='Dict field',
336        required=False,
337        key_type=schema.TextLine(
338            title='Key',
339            required=False,
340        ),
341        value_type=schema.TextLine(
342            title='Value',
343            required=False,
344        ),
345    )
346
347    dict_field_with_choice = schema.Dict(
348        title='Dict field with key and value as choice',
349        required=False,
350        key_type=schema.Choice(
351            title='Key',
352            values=['One', 'Two', 'Three'],
353            required=False,
354        ),
355        value_type=schema.Set(
356            title='Value',
357            value_type=schema.Choice(
358                values=['Beginner', 'Advanced', 'Professional'],
359            ),
360            required=False,
361            missing_value={},
362        ),
363    )
364
365
366@implementer(IExample)
367class Example(Container):
368    """Example instance class"""

20.2. How fields look like#

20.2.1. Backend#

This is how these fields look like when editing content in the backend:

Default fields

Default fields#

Number fields

Number fields#

Date and time fields

Date and time fields#

Choice and multiple choice fields

Choice and multiple choice fields#

File fields

File fields#

Reference fields

Reference fields#

Other fields including the dict field

Other fields including the dict field#

20.2.2. Frontend#

This is how these fields look like when editing content in Volto:

Default fields

Default fields#

Number fields

Number fields#

Date and time fields

Date and time fields#

Choice and multiple choice fields

Choice and multiple choice fields#

File fields

File fields#

Reference fields

Reference fields#

Other fields including the dict field

Other fields#

20.3. 3rd party fields#

20.4. Datagrid Field#

The datagrid field allows you to enter multiple values at once as rows in a table. Each row is a sub form defined in a separate schema.

Note

The datagrid field is for Plone Classic. See the mixedfield below, if you are working with Plone.

Here is an example:

 1from collective.z3cform.datagridfield import DataGridFieldFactory
 2from collective.z3cform.datagridfield import DictRow
 3from plone.app.z3cform.widget import SelectFieldWidget
 4from plone.autoform import directives
 5from plone.supermodel import model
 6from zope import schema
 7from zope.interface import Interface
 8
 9
10class IMyRowSchema(Interface):
11
12    choice_field = schema.Choice(
13        title='Choice Field',
14        vocabulary='plone.app.vocabularies.PortalTypes',
15        required=False,
16        )
17    directives.widget('objective', SelectFieldWidget)
18
19    textline_field = schema.TextLine(
20        title='Textline field',
21        required=False,
22        )
23
24    bool_field = schema.Bool(
25        title='Boolean field',
26        required=False,
27    )
28
29
30class IExampleWithDatagrid(model.Schema):
31
32    title = schema.TextLine(title='Title', required=True)
33
34    datagrid_field = schema.List(
35        title='Datagrid field',
36        value_type=DictRow(title='Table', schema=IMyRowSchema),
37        default=[],
38        required=False,
39    )
40    directives.widget('datagrid_field', DataGridFieldFactory)

The edit-form looks like this:

../_images/dexterity_reference_datagridfield_edit1.png

The output looks like this:

../_images/dexterity_reference_datagridfield_view1.png

20.5. mixedfield#

The mixedfield empowers your user to create a list of objects of mixed value types sharing the same schema. If you are familliar 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:

view mixedfield values

20.5.1. Backend#

Add a field history_field to your content type schema.

 1MIXEDFIELD_SCHEMA = json.dumps(
 2    {
 3        'type': 'object',
 4        'properties': {'items': {'type': 'array', 'items': {'type': 'object', 'properties': {}}}},
 5    }
 6)
 7
 8class IExample(model.Schema):
 9    """Dexterity-Schema"""
10
11    fieldset(
12        'datagrid',
13        label='Datagrid field',
14        fields=(
15            # 'datagrid_field',
16            'mixed_field',
17            ),
18    )
19
20    primary('title')
21    title = schema.TextLine(
22        title='Primary Field (Textline)',
23        description='zope.schema.TextLine',
24        required=True,
25        )
26
27    description = schema.TextLine(
28        title='Description (Textline)',
29        description='zope.schema.TextLine',
30        required=False,
31        )
32
33    history_field = JSONField(
34        title='Mixedfield: datagrid field for Plone',
35        required=False,
36        schema=MIXEDFIELD_SCHEMA,
37        widget='history_widget',
38        default={'items': []},
39        missing_value={'items': []},
40        )

20.5.2. Frontend#

Provide a widget in your favorite add-on with a schema of elementary fields you need.

 1import React from 'react';
 2
 3import ObjectListWidget from '@plone/volto/components/manage/Widgets/ObjectListWidget';
 4
 5const ItemSchema = {
 6    title: 'History-Entry',
 7    properties: {
 8        historydate: {
 9            title: 'Date',
10            widget: 'date',
11        },
12        historytopic: {
13            title: 'What',
14        },
15        historyversion: {
16            title: 'Version',
17        },
18        historyauthor: {
19            title: 'Who',
20        },
21    },
22    fieldsets: [
23        {
24            id: 'default',
25            title: 'History-Entry',
26            fields: [
27                'historydate',
28                'historytopic',
29                'historyversion',
30                'historyauthor',
31            ],
32        },
33    ],
34    required: [],
35};
36
37const HistoryWidget = (props) => {
38    return (
39        <ObjectListWidget
40            schema={ItemSchema}
41            {...props}
42            value={props.value?.items || props.default?.items || []}
43            onChange={(id, value) => props.onChange(id, { items: value })}
44        />
45    );
46};
47
48export 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.

Thats 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".

edit mixedfield values

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à.

view mixedfield values

20.6. Widgets#

Todo

Document all available widgets

20.7. 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]}},
    )

20.7.1. 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 be 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.')

See also

To learn more about directives, validators and default values, refer to the following: