Custom add and edit forms¶
Using `z3c.form`_ to build custom forms
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. For most types, that is all that’s ever needed. In some cases, however, we want to build custom forms, or supply additional forms.
Dexterity uses the z3c.form library to build its forms, via the plone.z3cform integration package.
                    Dexterity also relies on
                    plone.autoform, in particular its
                    AutoExtensibleForm
                    base class, which is responsible for processing form hints
                    and setting up
                    z3c.form
                    widgets and groups (fieldsets). 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.
                  
Note
If you want to build standalone forms not related to content objects, see the z3c.form documentation.
Edit forms¶
                      An edit form is just a form that is registered for a
                      particular type of content and knows how to register its
                      fields. 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.
                    
Dexterity provides a standard edit form base class that provides sensible defaults for buttons, labels and so on. This should be registered for a type schema (not a class). To create an edit form that is identical to the default, we could do:
from plone.dexterity.browser import edit
class EditForm(edit.DefaultEditForm):
    pass
                      and register it in configure.zcml:
<browser:page
    for=".fs_page.IFSPage"
    name="edit"
    class=".fs_page.EditForm"
    permission="cmf.ModifyPortalContent"
    />
                      This form is of course not terribly interesting, since it is identical to the default. However, we can now start changing fields and values. For example, we could:
- 
                        Override the
                        
schemaproperty to tell plone.autoform to use a different schema interface (with different form hints) than the content type schema. - 
                        Override the
                        
additionalSchemataproperty to tell plone.autoform to use different supplemental schema interfaces. The default is to use all behavior interfaces that provide theIFormFieldProvidermarker from plone.autoform. - 
                        Override the
                        
labelanddescriptionproperties to provide different a different title and description for the form. - 
                        Set the
                        z3c.form
                        
fieldsandgroupsattributes directly. - 
                        Override the
                        
updateWidgets()method to modify widget properties, or one of the otherupdate()methods, to perform additional processing on the fields. In most cases, these require us to call thesuperversion at the beginning. See the plone.autoform and z3c.form documentation to learn more about the sequence of calls that emanate from the formupdate()method in thez3c.form.form.BaseFormclass. - 
                        Override the
                        
templateattribute to specify a custom template. 
Content add sequence¶
                      Add forms are similar to edit forms in that they are built
                      from a type’s schema and the schemata of its behaviors.
                      However, for an add form to be able to construct a content
                      object, it needs to know which
                      portal_type
                      to use.
                    
                      You should realise that the FTIs in the
                      portal_types
                      tool can be modified through the web. It is even possible
                      to create new types through the web that re-use existing
                      classes and factories.
                    
                      For this reason, add forms are looked up via a namespace
                      traversal adapter alled
                      ++add++. You may have noticed this in the URLs to add forms
                      already. What actually happens is this:
                    
- 
                        
Plone renders the add menu. - To do so, it looks, among other places, for actions in the folder/add category. This category is provided by the
portal_typestool. - The folder/add action category is constructed by looking up theadd_view_exprproperty on the FTIs of all addable types. This is a TALES expression telling the add menu which URL to use. - The defaultadd_view_exprin Dexterity (and CMF 2.2) isstring:${folder_url}/++add++${fti/getId}. That is, it uses the++add++traversal namespace with an argument containing the FTI name. - 
                        
- 
                            A user clicks on an entry in the menu and is taken
                            to a URL like
                            
/path/to/folder/++add++my.type. - 
                            
- 
                                The
                                
++add++namespace adapter looks up the FTI with the given name, and gets itsfactoryproperty. - 
                                The
                                
factoryproperty of an FTI gives the name of a particularzope.component.interfaces.IFactoryutility, 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. This allows administrators to create new “logical” types that are functionally identical to an existing type. - 
                                The
                                
++add++namespace adapter looks up the actual form to render as a multi-adapter from(context, request, fti) toInterfacewith a name matching thefactoryproperty. Recall that a standard view is a multi-adapter from(context, request)toInterfacewith a name matching the URL segment for which the view is looked up. As such, add forms are not standard views, because they get the additionalftiparameter when constructed. - 
                                If this fails, there is no custom add form for
                                this factory (as is normally the case). The
                                fallback is an unnamed adapter from
                                
(context, request, fti). The default Dexterity add form is registered as such an adapter, specific to theIDexterityFTIinterface. 
 - 
                                The
                                
 
 - 
                            A user clicks on an entry in the menu and is taken
                            to a URL like
                            
 - 
                        
The form is rendered like any other
z3c.formform instance, and is subject to validation, which may cause it to be loaded several times. - 
                        
- Eventually, the form is successfully submitted. At this point:
 - 
                            
- 
                                The standard
                                
AddFormbase class will look up the factory from the FTI reference it holds and call it to create an instance. - 
                                The default Dexterity factory looks at the
                                
klass[*] attribute of the FTI to determine the actual content class to use, creates an object and initialises it. - 
                                The
                                
portal_typeattribute of the newly created instance is set to the name of the FTI. Thus, if the FTI is a “logical type” created through the web, but using an existing factory, the new instance’sportal_typewill be set to the “logical type”. - The object is initialised with the values submitted in the form.
 - 
                                An
                                
IObjectCreatedEventis fired. - The object is added to its container.
 - 
                                The user is redirected to the view specified in
                                the
                                
immediate_viewproperty of the FTI. 
 - 
                                The standard
                                
 
 
| [*] | 
                            class
                            is a reserved word in Python, so we use
                            klass.
                           | 
                        
This sequence is pretty long, but thankfully we rarely have to worry about it. 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.
Custom add forms¶
As with edit forms, Dexterity provides a sensible base class for add forms that knows how to deal with the Dexterity FTI and factory.
A custom form replicating the default would look like this:
from plone.dexterity.browser import add
class AddForm(add.DefaultAddForm):
    portal_type = 'example.fspage'
class AddView(add.DefaultAddView):
    form = AddForm
                      and be registered in ZCML like this:
<adapter
    for="Products.CMFCore.interfaces.IFolderish
         zope.publisher.interfaces.browser.IDefaultBrowserLayer
         plone.dexterity.interfaces.IDexterityFTI"
    provides="zope.publisher.interfaces.browser.IBrowserPage"
    factory=".fs_page.AddView"
    name="example.fspage"
    />
<class class=".fs_page.AddView">
    <require
        permission="cmf.AddPortalContent"
        interface="zope.publisher.interfaces.browser.IBrowserPage"
        />
</class>
                      
                      The name here should match the factory name. By
                      default, Dexterity types have a factory called the same as
                      the FTI name. If no such factory exists (i.e. you have not
                      registered a custom
                      IFactory
                      utility), a local factory utility will be created and
                      managed by Dexterity when the FTI is installed.
                    
                      Also note that we do not specify a context here. Add forms
                      are always registered for any
                      IFolderish
                      context.
                    
Note
                        If the permission used for the add form is different to
                        the
                        add_permission
                        set in the FTI, the user needs to have both
                        permissions to be able to see the form and add content.
                        For this reason, most add forms will use the generic
                        cmf.AddPortalContent
                        permission. The 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.
                      
As with edit forms, we can customise this form by overriding z3c.form and plone.autoform properties and methods. See the z3c.form documentation on add forms for more details.