Skip to content

Commit f9e0ab9

Browse files
committed
Tidy custom-add-and-edit-forms.md
1 parent 60536db commit f9e0ab9

File tree

1 file changed

+91
-142
lines changed

1 file changed

+91
-142
lines changed
Lines changed: 91 additions & 142 deletions
Original file line numberDiff line numberDiff line change
@@ -1,51 +1,38 @@
11
---
22
myst:
33
html_meta:
4-
"description": ""
5-
"property=og:description": ""
6-
"property=og:title": ""
7-
"keywords": ""
4+
"description": "Custom add and edit forms in Plone"
5+
"property=og:description": "Custom add and edit forms in Plone"
6+
"property=og:title": "Custom add and edit forms in Plone"
7+
"keywords": "Plone, custom, add, edit, forms"
88
---
99

1010
# Custom add and edit forms
1111

12-
**Using \`z3c.form\`\_ to build custom forms**
12+
This chapter describes how to use `z3c.form` to build custom forms.
1313

14-
Until now, we have used Dexterity’s default content add and edit forms,
15-
supplying form hints in our schemata to influence how the forms are
16-
built.
17-
For most types, that is all that’s ever needed.
18-
In some cases, however, we want to build custom forms, or supply additional
19-
forms.
14+
Until now, we have used Dexterity's default content add and edit forms, supplying form hints in our schemata to influence how the forms are built.
15+
For most types, that is all that's ever needed.
16+
In some cases, however, we want to build custom forms, or supply additional forms.
2017

21-
Dexterity uses the [z3c.form] library to build its forms, via the
22-
[plone.z3cform] integration package.
18+
Dexterity uses the [`z3c.form`](https://z3cform.readthedocs.io/en/latest/) library to build its forms, via the [`plone.z3cform`](https://pypi.org/project/plone.z3cform/) integration package.
2319

24-
Dexterity also relies on [plone.autoform], in particular its
25-
`AutoExtensibleForm` base class, which is responsible for processing
26-
form hints and setting up [z3c.form] widgets and groups (fieldsets).
27-
A custom form, therefore, is simply a view that uses these libraries,
28-
although Dexterity provides some helpful base classes that make it
29-
easier to construct forms based on the schema and behaviors of a
30-
Dexterity type.
20+
Dexterity also relies on [`plone.autoform`](https://pypi.org/project/plone.autoform/), in particular its `AutoExtensibleForm` base class, which is responsible for processing form hints and setting up `z3c.form` widgets and groups (fieldsets).
21+
A custom form, therefore, is simply a view that uses these libraries, although Dexterity provides some helpful base classes that make it easier to construct forms based on the schema and behaviors of a Dexterity type.
22+
23+
```{note}
24+
If you want to build standalone forms not related to content objects, see the [`z3c.form` documentation](https://z3cform.readthedocs.io/en/latest/).
25+
```
3126

32-
:::{note}
33-
If you want to build standalone forms not related to content objects,
34-
see the [z3c.form] documentation.
35-
:::
3627

3728
## Edit forms
3829

39-
An edit form is just a form that is registered for a particular type of
40-
content and knows how to register its fields.
41-
If the form is named `edit`, it will replace the default edit form,
42-
which is registered with that name for the more general
43-
`IDexterityContent` interface.
30+
An edit form is just a form that is registered for a particular type of content and knows how to register its fields.
31+
If the form is named `edit`, it will replace the default edit form, which is registered with that name for the more general `IDexterityContent` interface.
4432

45-
Dexterity provides a standard edit form base class that provides
46-
sensible defaults for buttons, labels and so on.
33+
Dexterity provides a standard edit form base class that provides sensible defaults for buttons, labels, and so on.
4734
This should be registered for a type schema (not a class).
48-
To create an edit form that is identical to the default, we could do:
35+
To create an edit form that is identical to the default, we could do the following.
4936

5037
```python
5138
from plone.dexterity.browser import edit
@@ -54,7 +41,7 @@ class EditForm(edit.DefaultEditForm):
5441
pass
5542
```
5643

57-
and register it in configure.zcml:
44+
And register it in {file}`configure.zcml`.
5845

5946
```xml
6047
<browser:page
@@ -65,105 +52,80 @@ and register it in configure.zcml:
6552
/>
6653
```
6754

68-
This form is of course not terribly interesting, since it is identical
69-
to the default. However, we can now start changing fields and values.
70-
For example, we could:
71-
72-
- Override the `schema` property to tell [plone.autoform] to use a
73-
different schema interface (with different form hints) than the
74-
content type schema.
75-
- Override the `additionalSchemata` property to tell [plone.autoform]
76-
to use different supplemental schema interfaces.
77-
The default is to use all behavior interfaces that provide the
78-
`IFormFieldProvider` marker from [plone.autoform].
79-
- Override the `label` and `description` properties to provide
80-
different a different title and description for the form.
81-
- Set the [z3c.form] `fields` and `groups` attributes directly.
82-
- Override the `updateWidgets()` method to modify widget properties,
83-
or one of the other `update()` methods,
84-
to perform additional processing on the fields.
85-
In most cases, these require us to call the `super` version at the
86-
beginning.
87-
See the [plone.autoform] and [z3c.form] documentation
88-
to learn more about the sequence of calls that emanate from the form
89-
`update()` method in the `z3c.form.form.BaseForm` class.
90-
- Override the `template` attribute to specify a custom template.
55+
This form is of course not terribly interesting, since it is identical to the default.
56+
However, we can now start changing fields and values.
57+
For example, we could do any of the following.
58+
59+
- Override the `schema` property to tell `plone.autoform` to use a different schema interface (with different form hints) than the content type schema.
60+
- Override the `additionalSchemata` property to tell `plone.autoform` to use different supplemental schema interfaces.
61+
The default is to use all behavior interfaces that provide the `IFormFieldProvider` marker from `plone.autoform`.
62+
- Override the `label` and `description` properties to provide a different title and description for the form.
63+
- Set the `z3c.form` `fields` and `groups` attributes directly.
64+
- Override the `updateWidgets()` method to modify widget properties, or one of the other `update()` methods, to perform additional processing on the fields.
65+
In most cases, these require us to call the `super` version at the beginning.
66+
See the [`plone.autoform`](https://pypi.org/project/plone.autoform/#introduction) and [`z3c.form` documentation](https://z3cform.readthedocs.io/en/latest/) to learn more about the sequence of calls that emanate from the form `update()` method in the `z3c.form.form.BaseForm` class.
67+
- Override the `template` attribute to specify a custom template.
68+
9169

9270
## Content add sequence
9371

94-
Add forms are similar to edit forms in that they are built from a type’s
95-
schema and the schemata of its behaviors.
96-
However, for an add form to be able to construct a content object,
97-
it needs to know which `portal_type` to use.
72+
Add forms are similar to edit forms in that they are built from a type's schema and the schemata of its behaviors.
73+
However, for an add form to be able to construct a content object, it needs to know which `portal_type` to use.
9874

99-
You should realise that the FTIs in the `portal_types` tool can be
100-
modified through the web.
101-
It is even possible to create new types through the web that re-use existing
102-
classes and factories.
75+
You should realize that the FTIs in the `portal_types` tool can be modified through the web.
76+
It is even possible to create new types through the web that reuse existing classes and factories.
10377

104-
For this reason, add forms are looked up via a namespace traversal
105-
adapter called `++add++`.
78+
For this reason, add forms are looked up via a namespace traversal adapter called `++add++`.
10679
You may have noticed this in the URLs to add forms already.
107-
What actually happens is this:
108-
109-
- Plone renders the {guilabel}`add` menu.
110-
111-
- To do so, it looks, among other places, for actions in the *folder/add* category. This category is provided by the `portal_types` tool.
112-
- The *folder/add* action category is constructed by looking up the `add_view_expr` property on the FTIs of all addable types. This is a TALES expression telling the add menu which URL to use.
113-
- The default `add_view_expr` in Dexterity (and CMF 2.2) is `string:${folder_url}/++add++${fti/getId}`. That is, it uses the `++add++` traversal namespace with an argument containing the FTI name.
114-
115-
- A user clicks on an entry in the menu and is taken to a URL like `/path/to/folder/++add++my.type`.
116-
117-
> - The `++add++` namespace adapter looks up the FTI with the given name, and gets its `factory` property.
118-
> - The `factory` property of an FTI gives the name of a particular `zope.component.interfaces.IFactory` utility, which is used later to construct an instance of the content object. Dexterity automatically registers a factory instance for each type, with a name that matches the type name, although it is possible to use an existing factory name in a new type.
119-
> This allows administrators to create new “logical” types that are
120-
> functionally identical to an existing type.
121-
> - The `++add++` namespace adapter looks up the actual form to render as
122-
> a multi-adapter from `(context, request, fti`) to `Interface` with
123-
> a name matching the `factory` property.
124-
> Recall that a standard view is a multi-adapter from
125-
> `(context, request)` to `Interface` with a name matching the URL
126-
> segment for which the view is looked up.
127-
> As such, add forms are not standard views, because they get the
128-
> additional `fti` parameter when constructed.
129-
> - If this fails, there is no custom add form for this factory (as is
130-
> normally the case).
131-
> The fallback is an unnamed adapter from `(context, request, fti)`.
132-
> The default Dexterity add form is registered as such an adapter,
133-
> specific to the `IDexterityFTI` interface.
134-
135-
- The form is rendered like any other `z3c.form` form instance,
136-
and is subject to validation,
137-
which may cause it to be loaded several times.
138-
139-
- Eventually, the form is successfully submitted. At this point:
140-
141-
> - The standard `AddForm` base class will look up the factory from the FTI reference it holds and call it to create an instance.
142-
> - The default Dexterity factory looks at the `klass` [^id2] attribute of the FTI to determine the actual content class to use, creates an object and initialises it.
143-
> - The `portal_type` attribute of the newly created instance is set to
144-
> the name of the FTI.
145-
> Thus, if the FTI is a “logical type” created through the web, but
146-
> using an existing factory, the new instance’s `portal_type` will be
147-
> set to the “logical type”.
148-
> - The object is initialised with the values submitted in the form.
149-
> - An `IObjectCreatedEvent` is fired.
150-
> - The object is added to its container.
151-
> - The user is redirected to the view specified in the `immediate_view`
152-
> property of the FTI.
80+
What actually happens is the following.
81+
82+
- Plone renders the {guilabel}`add` menu.
83+
84+
- To do so, it looks, among other places, for actions in the `folder/add` category.
85+
This category is provided by the `portal_types` tool.
86+
- The `folder/add` action category is constructed by looking up the `add_view_expr` property on the FTIs of all addable types.
87+
This is a TALES expression telling the add menu which URL to use.
88+
- The default `add_view_expr` in Dexterity (and CMF 2.2) is `string:${folder_url}/++add++${fti/getId}`.
89+
That is, it uses the `++add++` traversal namespace with an argument containing the FTI name.
90+
91+
- A user clicks on an entry in the menu, and is taken to a URL using the parttern `/path/to/folder/++add++my.type`.
92+
93+
- The `++add++` namespace adapter looks up the FTI with the given name, and gets its `factory` property.
94+
- The `factory` property of an FTI gives the name of a particular `zope.component.interfaces.IFactory` utility, which is used later to construct an instance of the content object.
95+
Dexterity automatically registers a factory instance for each type, with a name that matches the type name, although it is possible to use an existing factory name in a new type.
96+
This allows administrators to create new "logical" types that are functionally identical to an existing type.
97+
- The `++add++` namespace adapter looks up the actual form to render as a multi-adapter from `(context, request, fti)` to `Interface` with a name matching the `factory` property.
98+
Recall that a standard view is a multi-adapter from `(context, request)` to `Interface` with a name matching the URL segment for which the view is looked up.
99+
As such, add forms are not standard views, because they get the additional `fti` parameter when constructed.
100+
- If this fails, there is no custom add form for this factory, as is normally the case.
101+
The fallback is an unnamed adapter from `(context, request, fti)`.
102+
The default Dexterity add form is registered as such an adapter, specific to the `IDexterityFTI` interface.
103+
104+
- The form is rendered like any other `z3c.form` form instance, and is subject to validation, which may cause it to be loaded several times.
105+
106+
- Eventually, the form is successfully submitted.
107+
At this point:
108+
109+
- The standard `AddForm` base class will look up the factory from the FTI reference it holds and call it to create an instance.
110+
- The default Dexterity factory looks at the `klass` [^id2] attribute of the FTI to determine the actual content class to use, creates an object and initializes it.
111+
- The `portal_type` attribute of the newly created instance is set to the name of the FTI.
112+
Thus, if the FTI is a "logical type" created through the web, but using an existing factory, the new instance's `portal_type` will be set to the "logical type".
113+
- The object is initialized with the values submitted in the form.
114+
- An `IObjectCreatedEvent` is fired.
115+
- The object is added to its container.
116+
- The user is redirected to the view specified in the `immediate_view` property of the FTI.
153117

154118
[^id2]: `class` is a reserved word in Python, so we use `klass`.
155119

156-
This sequence is pretty long, but thankfully we rarely have to worry
157-
about it. In most cases, we can use the default add form, and when we
158-
can’t, creating a custom add form is only a bit more difficult than
159-
creating a custom edit form.
120+
This sequence is pretty long, but thankfully we rarely have to worry about it.
121+
In most cases, we can use the default add form, and when we can't, creating a custom add form is only a bit more difficult than creating a custom edit form.
122+
160123

161124
## Custom add forms
162125

163-
As with edit forms, Dexterity provides a sensible base class for add
164-
forms that knows how to deal with the Dexterity FTI and factory.
126+
As with edit forms, Dexterity provides a sensible base class for add forms that knows how to deal with the Dexterity FTI and factory.
165127

166-
A custom form replicating the default would look like this:
128+
A custom form replicating the default would be the following.
167129

168130
```python
169131
from plone.dexterity.browser import add
@@ -175,7 +137,7 @@ class AddView(add.DefaultAddView):
175137
form = AddForm
176138
```
177139

178-
and be registered in ZCML like this:
140+
And be registered in ZCML as follows.
179141

180142
```xml
181143
<adapter
@@ -196,29 +158,16 @@ and be registered in ZCML like this:
196158

197159
The name here should match the *factory* name.
198160
By default, Dexterity types have a factory called the same as the FTI name.
199-
If no such factory exists
200-
(i.e. you have not registered a custom `IFactory` utility),
201-
a local factory utility will be created and managed by Dexterity when the
202-
FTI is installed.
161+
If no such factory exists (in other words, you have not registered a custom `IFactory` utility), a local factory utility will be created and managed by Dexterity when the FTI is installed.
203162

204163
Also note that we do not specify a context here.
205164
Add forms are always registered for any `IFolderish` context.
206165

207-
:::{note}
208-
If the permission used for the add form is different to the
209-
`add_permission` set in the FTI, the user needs to have *both*
210-
permissions to be able to see the form and add content.
211-
For this reason, most add forms will use the generic
212-
`cmf.AddPortalContent` permission.
213-
The {guilabel}`add` menu will not render links to types where the user
214-
does not have the add permission stated in the FTI,
215-
even if this is different to `cmf.AddPortalContent`.
216-
:::
217-
218-
As with edit forms, we can customise this form by overriding [z3c.form]
219-
and [plone.autoform] properties and methods.
220-
See the [z3c.form] documentation on add forms for more details.
221-
222-
[plone.autoform]: http://pypi.python.org/pypi/plone.autoform
223-
[plone.z3cform]: http://pypi.python.org/pypi/plone.z3cform
224-
[z3c.form]: http://pythonhosted.org/z3c.form
166+
```{note}
167+
If the permission used for the add form is different from the `add_permission` set in the FTI, the user needs to have *both* permissions to be able to see the form and add content.
168+
For this reason, most add forms will use the generic `cmf.AddPortalContent` permission.
169+
The {guilabel}`add` menu will not render links to types where the user does not have the add permission stated in the FTI, even if this is different to `cmf.AddPortalContent`.
170+
```
171+
172+
As with edit forms, we can customize this form by overriding `z3c.form` and `plone.autoform` properties and methods.
173+
See the [`z3c.form` documentation](https://z3cform.readthedocs.io/en/latest/) on add forms for more details.

0 commit comments

Comments
 (0)