The FTI
With the schema in place, we just need to make our types installable. We do this with GenericSetup.
First, we add a types.xml file to profiles/default:
<object name="portal_types"> <object name="example.conference.presenter" meta_type="Dexterity FTI" /> <object name="example.conference.program" meta_type="Dexterity FTI" /> <object name="example.conference.session" meta_type="Dexterity FTI" /> </object>
We use the package name as a prefix and the type name in lowercase to create a unique name. It is important that the meta_type is Dexterity FTI.
We then need to add an XML file for each of the types, where the file name matches the type name. First, we add a directory profiles/default/types, and then add the following:
For the Presenter type, we have example.conference.presenter.xml:
<?xml version="1.0"?>
<object name="example.conference.presenter" meta_type="Dexterity FTI"
i18n:domain="example.conference" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<!-- Basic metadata -->
<property name="title" i18n:translate="">Presenter</property>
<property name="description" i18n:translate="">A person presenting sessions</property>
<property name="content_icon">user.gif</property>
<property name="allow_discussion">True</property>
<property name="global_allow">True</property>
<property name="filter_content_types">True</property>
<property name="allowed_content_types" />
<!-- schema interface -->
<property name="schema">example.conference.presenter.IPresenter</property>
<!-- class used for content items -->
<property name="klass">plone.dexterity.content.Item</property>
<!-- add permission -->
<property name="add_permission">cmf.AddPortalContent</property>
<!-- enabled behaviors -->
<property name="behaviors">
<element value="plone.app.content.interfaces.INameFromTitle" />
</property>
<!-- View information -->
<property name="default_view">view</property>
<property name="default_view_fallback">False</property>
<property name="view_methods">
<element value="view"/>
</property>
<!-- Method aliases -->
<alias from="(Default)" to="(dynamic view)"/>
<alias from="edit" to="@@edit"/>
<alias from="sharing" to="@@sharing"/>
<alias from="view" to="(selected layout)"/>
<!-- Actions -->
<action title="View" action_id="view" category="object" condition_expr=""
url_expr="string:${object_url}" visible="True">
<permission value="View"/>
</action>
<action title="Edit" action_id="edit" category="object" condition_expr=""
url_expr="string:${object_url}/edit" visible="True">
<permission value="Modify portal content"/>
</action>
</object>
There is a fair amount of boilerplate here which could actually be omitted, because the Dexterity FTI defaults will take care of most of this. However, it is useful to see the options available so that you know what you can change.
The important lines here are:
- The name attribute on the root element must match the name in types.xml and the filename
- We use the package name as the translation domain again, via i18n:domain
- We set a title and description for the type
- We also specify an icon. Here, we use a standard icon from Plone's plone_images skin layer. You'll learn more about static resources later.
- We set global_allow to True. This means that the type will be addable in standard folders.
- The schema interface is referenced by the schema property.
- We set the klass property to the standard plone.dexterity.content.Item. There is also plone.dexterity.content.Container.
- We specify the name of an add permission. The default cmf.AddPortalContent should be used unless you configure a custom permission. Custom permissions are convered later in this manual.
- We add a behavior. Behaviors are re-usable aspects providing semantics and/or schema fields. Here, we add the INameFromTitle behavior, which will give our content object a readable id based on the title property. We'll cover other behaviors later.
The Session type, in example.conference.session.xml, is very similar:
<?xml version="1.0"?>
<object name="example.conference.session" meta_type="Dexterity FTI"
i18n:domain="example.conference" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<!-- Basic metadata -->
<property name="title" i18n:translate="">Session</property>
<property name="description" i18n:translate="">A session on the program</property>
<property name="content_icon">document_icon.gif</property>
<property name="allow_discussion">True</property>
<property name="global_allow">False</property>
<property name="filter_content_types">True</property>
<property name="allowed_content_types" />
<!-- schema interface -->
<property name="schema">example.conference.session.ISession</property>
<!-- class used for content items -->
<property name="klass">plone.dexterity.content.Item</property>
<!-- add permission -->
<property name="add_permission">cmf.AddPortalContent</property>
<!-- enabled behaviors -->
<property name="behaviors">
<element value="plone.app.content.interfaces.INameFromTitle" />
</property>
<!-- View information -->
<property name="default_view">view</property>
<property name="default_view_fallback">False</property>
<property name="view_methods">
<element value="view"/>
</property>
<!-- Method aliases -->
<alias from="(Default)" to="(dynamic view)"/>
<alias from="edit" to="@@edit"/>
<alias from="sharing" to="@@sharing"/>
<alias from="view" to="(selected layout)"/>
<!-- Actions -->
<action title="View" action_id="view" category="object" condition_expr=""
url_expr="string:${object_url}" visible="True">
<permission value="View"/>
</action>
<action title="Edit" action_id="edit" category="object" condition_expr=""
url_expr="string:${object_url}/edit" visible="True">
<permission value="Modify portal content"/>
</action>
</object>
Again, this is an Item. Here, we have set global_allow to False, since these objects should only be addable inside a Program.
The Program, in example.conference.program.xml, looks like this:
<?xml version="1.0"?>
<object name="example.conference.program" meta_type="Dexterity FTI"
i18n:domain="example.conference" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
<!-- Basic metadata -->
<property name="title" i18n:translate="">Program</property>
<property name="description" i18n:translate="">A conference program</property>
<property name="content_icon">folder_icon.gif</property>
<property name="allow_discussion">True</property>
<property name="global_allow">True</property>
<property name="filter_content_types">True</property>
<property name="allowed_content_types">
<element value="example.conference.session" />
</property>
<!-- schema interface -->
<property name="schema">example.conference.program.IProgram</property>
<!-- class used for content items -->
<property name="klass">plone.dexterity.content.Container</property>
<!-- add permission -->
<property name="add_permission">cmf.AddPortalContent</property>
<!-- enabled behaviors -->
<property name="behaviors">
<element value="plone.app.content.interfaces.INameFromTitle" />
</property>
<!-- View information -->
<property name="default_view">view</property>
<property name="default_view_fallback">False</property>
<property name="view_methods">
<element value="view"/>
</property>
<!-- Method aliases -->
<alias from="(Default)" to="(dynamic view)"/>
<alias from="edit" to="@@edit"/>
<alias from="sharing" to="@@sharing"/>
<alias from="view" to="(selected layout)"/>
<!-- Actions -->
<action title="View" action_id="view" category="object" condition_expr=""
url_expr="string:${object_url}" visible="True">
<permission value="View"/>
</action>
<action title="Edit" action_id="edit" category="object" condition_expr=""
url_expr="string:${object_url}/edit" visible="True">
<permission value="Modify portal content"/>
</action>
</object>
The difference here is that we use the Container class, and we filter the containable types (filter_content_types and allowed_content_types) to allow only Sessions to be added inside this folder.
