Chapter 3. Developing for Plone
Table of Contents
In this section we develop a basic Archetypes content type.
Plone extends Zope's Content Management Framework ( CMF). Developing for Plone and for the CMF is one and the same thing. CMF classifies its domain objects as Portal Content. In this section, we will explore the traditional style of developing Content Types, as well as using Archetypes.
Developing new content types for Plone requires an understanding of Factory Type Information ( FTI).
FTI refers to all the information required to register a content type with CMF. Let's look at the FTI of a Document:
factory_type_information = (
{'id' : 'Document',
'meta_type' : 'Document',
'description' : """Documents contain text that can be
formatted using 'Structured Text.' They may also contain
HTML, or "plain" text.
""",
'icon' : 'document_icon.gif',
'product' : 'CMFDefault',
'factory' : 'addDocument',
'immediate_view' : 'metadata_edit_form',
'actions' : (
{'id' : 'view',
'name' : 'View',
'action': 'string:${object_url}/document_view',
'permissions' : (View,)
},
{'id' : 'edit',
'name' : 'Edit',
'action': 'string:${object_url}/document_edit_form',
'permissions' : (ModifyPortalContent,)
},
{'id' : 'metadata',
'name' : 'Metadata',
'action': 'string:${object_url}/metadata_edit_form',
'permissions' : (ModifyPortalContent,)
}
)
},)
The id, meta_type, description and icon keys should be self explanatory.
product is the name of the Zope Product where the content type is defined.
factory is the name of the factory (also known as constructor) method that creates an instance of the content type. The factory method must be defined in the same module as the class.
immediate_view is the name of the view method that be displayed immediately after creation.
actions is a list of actions provided by the content type and are usually visible as tabs for instances of the content type. Each action is a dictionary with values for id, name, action and permissions keys:
Keys of the action dictionary
- id
a unique value that can be distiguished from other actions
- name
a human readable name for the action
- action
a TAL expression that resolves to a path for a method
- permissions
guards the access to an action.
Other keys not listed in Document's FTI are:
Factory Type information keys
- filter_content_types
determines whether the content of a folderish content type should be filtered or not. Valid values are 0 or 1.
- allowed_content_types
a sequence of content types that are allowed as content if the content type is folderish and if filter_content_types is set to 1.
- global_allow
enables the creation of instances anywhere in the portal.
- allow_discussion
determines if discussion is allowed on instances of the content type.
Archetypes make creating new content types for Plone really easy. The distinguishing feature of an Archetype is its Schema. A Schema defines the fields of an Archetype. A field, in simple terms, is similar to a column definition in a relational database, in that it defines the datatype that should be associated with a particular name in Schema. Fields do much more though. Their features include:
the widget that should be used to render field values,
the validators to be used for validation,
where field values should be stored, e.g. ZODB, RDMBS,
whether the value should be searchable.
The following field types are supported:
Supported Field types
- StringField
Used for storing simple strings
- FileField
Used for storing files
- TextField
Used for storing text which can be used in transformations
- DateTimeField
Used for storing date/time
- LinesField
Used for storing text which can be used in transformations
- IntegerField
Used for storing integer values
- FloatField
Used for storing float values
- FixedPointField
Used for storing fixed point values
- ReferenceField
Used for storing references to other Archetypes Objects
- ComputedField
Read-only field, which value is computed from a python expression
- BooleanField
Used for storing boolean values
- CMFObjectField
Used for storing a value inside a CMF Object, which can have workflow. Can only be used for BaseFolder-based content objects
- ImageField
Used for storing images.
- PhotoField
Used for storing images. Based on CMFPhoto.
Most fields have default widgets associated with them. The widget for a field can be modified, so its important to know what widgets are available. They are:
Default widgets
- StringWidget
Renders a HTML text input box which accepts a single line of text
- DecimalWidget
Renders a HTML text input box which accepts a fixed point value
- IntegerWidget
Renders a HTML text input box which accepts an integer value
- ReferenceWidget
Renders a HTML text input box which accepts a reference value
- ComputedWidget
Renders the computed value as HTML
- TextAreaWidget
Renders a HTML textarea for typing a few lines of text
- LinesWidget
Renders a HTML textarea for a list of values, one per line
- BooleanWidget
Renders a HTML checkbox
- CalendarWidget
Renders a HTML input box with a helper popup box for choosing dates
- SelectionWidget
Renders a HTML selection widget, which can be represented as a dropdown, or as a group of radio buttons
- MultiSelectionWidget
Renders a HTML selection widget, where you can be choose more than one value
- KeywordWidget
Renders a HTML widget for choosing keywords
- RichWidget
Renders a HTML widget that allows you to type some content, choose formatting and/or upload a file
- FileWidget
Renders a HTML widget upload a file
- IdWidget
Renders a HTML widget for typing an Id
- ImageWidget
Renders a HTML widget for uploading/displaying an image
- LabelWidget
Renders a HTML widget that only displays the label
- PasswordWidget
Renders a HTML password widget
- VisualWidget
Renders a HTML visual editing widget
- EpozWidget
Renders a HTML Epoz widget
- InAndOutWidget
Renders a widget for moving items from one list to another. Items are removed from the first list.
- PicklistWidget
Render a widget to pick from one list to populate another. Items stay in the first list.
Create the following directory structure in the Products directory for the Archetypes-based leave application:
ATLeaveApplication
|
+--Extensions
|
+--skins
|
+--leaveapp
Next, create the LeaveApplication.py module inside ATLeaveApplication with the following content:
1 from AccessControl import ClassSecurityInfo
2 from Products.Archetypes.public import BaseContent, registerType
3 from Products.Archetypes.public import BaseSchema, Schema
4 from Products.Archetypes.public import StringField, \
5 TextField, DateTimeField, ComputedField
6 from Products.Archetypes.public import StringWidget, \
7 SelectionWidget, TextAreaWidget, CalendarWidget, \
8 MultiSelectionWidget, ComputedWidget
9 from Products.ATLeaveApplication.config import LEAVE_TYPES
10 schema = BaseSchema.copy() + Schema((
11 DateTimeField('start_date',
12 required=1,
13 widget=CalendarWidget(
14 label='Start date',
15 label_msgid='label_start_date',
16 description='The date when your leave period starts',
17 description_msgid='help_start_date',
18 i18n_domain='leaveapp'
19 ),
20 ),
21 DateTimeField('end_date',
22 required=1,
23 widget=CalendarWidget(
24 label='End date',
25 label_msgid='label_end_date',
26 description='The date when your leave period ends',
27 description_msgid='help_end_date',
28 i18n_domain='leaveapp'
29 ),
30 ),
31 TextField('reason',
32 required=1,
33 searchable=1,
34 widget=TextAreaWidget(
35 label='Reason',
36 label_msgid='label_reason',
37 description='The reason why you require leave',
38 description_msgid='help_reason',
39 i18n_domain='leaveapp'
40 ),
41 ),
42 StringField('leave_type',
43 required=1,
44 vocabulary=LEAVE_TYPES,
45 widget=MultiSelectionWidget(
46 label='Type of leave',
47 label_msgid='label_leave_type',
48 description='Indicate the type of leave you require',
49 description_msgid='help_leave_type',
50 i18n_domain='leaveapp'
51 ),
52 ),
53 StringField('employee_name',
54 required=1,
55 widget=StringWidget(
56 label='Name',
57 label_msgid='label_employee_name',
58 description='Enter your firstname',
59 description_msgid='help_employee_name',
60 i18n_domain='leaveapp'
61 ),
62 ),
63 StringField('employee_surname',
64 required=1,
65 widget=StringWidget(
66 label='Surname',
67 label_msgid='label_employee_surname',
68 description='Enter your surname',
69 description_msgid='help_employee_name',
70 i18n_domain='leaveapp'
71 ),
72 ),
73 ComputedField('employee_fullname',
74 searchable=1,
75 expression='context.getFullname()',
76 widget=ComputedWidget(
77 label='Fullname',
78 label_msgid='label_employee_surname',
79 i18n_domain='leaveapp'
80 ),
81 ),
82 StringField('employee_email',
83 required=1,
84 widget=StringWidget(
85 label='Email address',
86 label_msgid='label_employee_email',
87 i18n_domain='leaveapp'
88 ),
89 ),
90 StringField('employee_phone',
91 required=1,
92 widget=StringWidget(
93 label='Phone number',
94 label_msgid='label_employee_phone',
95 i18n_domain='leaveapp'
96 ),
97 ),
98 ))
99 class LeaveApplication(BaseContent):
100 """ Leave Application """
101 security = ClassSecurityInfo()
102 archetype_name = 'Leave Application'
104 schema = schema
105 security.declarePublic('getFullname')
106 def getFullname(self):
107 """ return Person's Fullname """
108 return '%s %s' % (self.getEmployee_name(),
109 self.getEmployee_surname())
110 immediate_view = 'base_view'
111 allowed_content_types = ()
112 global_allow = 1
113 filter_content_types = 0
114
115 actions = ()
116 registerType(LeaveApplication)
Lines 1-8 show the imports that are necessary for our Archetype. Let's look at them one at a time.:
1 from AccessControl import ClassSecurityInfo
ClassSecurityInfo defines an API for making declarative security assertions about instances of our class and for individual methods in our class.
In the following lines, BaseContent should be subclassed by non-container types. If your content type is folderish you can subclass BaseFolder or BaseBTreeFolder.
registerType registers a type with the Archetype tool.
BaseSchema is a Schema instance that defines common fields like id and title, as well as all the fields required by the Dublin Core metadata interface. Schema is imported for the creation of schema for our leave application later on:
2 from Products.Archetypes.public import BaseContent, registerType 3 from Products.Archetypes.public import BaseSchema, Schema
Line 4-5 imports fields; and lines 6-8 imports the widgets that we require for our schema definition:
4 from Products.Archetypes.public import StringField, \ 5 TextField, DateTimeField, ComputedField 6 from Products.Archetypes.public import StringWidget, \ 7 SelectionWidget, TextAreaWidget, CalendarWidget, \ 8 MultiSelectionWidget, ComputedWidget
LEAVE_TYPES is an instance of a DisplayList that we define in our product's config module. We'll discuss this later on:
9 from Products.ATLeaveApplication.config import LEAVE_TYPES
Schema takes a sequence of field instances as parameter. The first field in our schema is start_date:
11 DateTimeField('start_date',
12 required=1,
13 widget=CalendarWidget(
14 label='Start date',
15 label_msgid='label_start_date',
16 description='The date when your leave period starts',
17 description_msgid='help_start_date',
18 i18n_domain='leaveapp'
19 ),
20 ),
Field takes the name of the field as the first parameter, followed by keyword arguments that set properties on the field. For start_date we set the required and widget properties. The complete list of field properties (as listed in the Archetypes developers guide) follows:
Field properties
- required
Makes the field required upon validation. Defaults to 0 (not required).
- widget
One of the Widgets to be used for displaying and editing the content of the given field.
- default
Sets the default value of the field upon initialization.
- vocabulary
This parameter specifies a vocabulary. It can be given either as a static instance of DisplayList or as a method name (it has to be the name as a string). The method is called and the result is taken as the vocabulary. The method should return a DisplayList.
The contents of the vocabulary are used as the values which can be choosen from to fill this field.
An example for a DisplayList usage can be found in the ArchExample directory, in config.py.
- enforceVocabulary
If set, checks if the value is within the range of vocabulary upon validation
- multiValued
If set, allows the field to have multiple values (e.g. a list) instead of a single value
- isMetadata
If set, the field is considered metadata
- accessor
Name of the method that will be used for getting data out of the field. If the method already exists, nothing is done. If the method doesn't exist, Archetypes will generate a basic method for you.
- edit_accessor
Name of the method that will be used for getting data out of the field just before edition. Unlike the standard accessor method which could apply some transformation to the accessed data, this method should return the raw data without any transformation. If the method already exists, nothing is done. If the method doesn't exist, Archetypes will generate a basic method for you.
- mutator
Name of the method that will be used for changing the value of the field. If the method already exists, nothing is done. If the method doesn't exist, Archetypes will generate a basic method for you.
- mode
One of r, w or rw. If r, only the accessor is generated. If w only the mutator and the edit accessor are generated. If rw, accessor and mutator and edit accessor are generated.
- read_permission
Permission needed to view the field. Defaults to CMFCorePermissions.View. Is checked when the view is being auto-generated.
- write_permission
Permission needed to modify the field. Defaults to CMFCorePermissions.ModifyPortalContent. Is checked when the submitted form is being processed..
- storage
One of the Storage options. Defaults to AttributeStorage, which just sets a simple attribute on the instance.
- validators
One of the Validators. You can also create your own validator.
- index
A string specifying the kind of index to create on portal_catalog for this field. To include in catalog metadata, append :schema, as in FieldIndex:schema. You can specify another field type to try if the first isn't available by using the | character. Both combinations can be used together, as in:
... index="TextIndex|FieldIndex:schema", ...
- schemata
Schemata is used for grouping fields into fieldsets. Defaults to default on normal fields and metadata on metadata fields.
Like Field, Widget also has a number of properties that determines how it is rendered.
Widget properties
- label
The visual label for a widget when rendering the form.
- label_msgid
The i18n id of the label.
- description
Descriptive text to guide the end user when enter a value for a field.
- description_msgid
The i18n id of the description.
- i18n_domain
The i18n message catalog where internationalised strings should be looked up.
- visible
Defaults to 1. Use 0 to render a hidden field, and -1 to skip rendering.
Our class definition spans lines 99-115. Most attributes that we set here should be familiar to by now, since they correspond in name and meaning to factory type information. Additionally we define archetype_name and schema. archetype_name uniquely identifies an Archetype. All Archetypes must have a schema attribute which must be set to a Schema instance.
Notice that actions is an empty tuple. This does not mean LeaveApplication has no actions. When an Archetype is registered, it acquires common actions from the Archetypes base factory information. When defined, actions either extends the acquired actions, or overrides base actions that have the same id as newly defined ones. The actions defined in base_factory_type_information are:
'actions': ({'id': 'view',
'name': 'View',
'action': 'string:${object_url}/base_view',
'permissions': (CMFCorePermissions.View,),
},
{'id': 'edit',
'name': 'Edit',
'action': 'string:${object_url}/base_edit',
'permissions': (CMFCorePermissions.ModifyPortalContent,),
},
{'id': 'metadata',
'name': 'Properties',
'action': 'string:${object_url}/base_metadata',
'permissions': (CMFCorePermissions.ModifyPortalContent,),
},
{'id': 'references',
'name': 'References',
'action': 'string:${object_url}/reference_graph',
'condition': 'object/archetype_tool/has_graphviz',
'permissions': (CMFCorePermissions.View,),
'visible' : 1,
},
)
On lines 105-109 we define getFullname and declare it to be a public method. getFullname simply computes the fullname of an employee. This is the method refered to in the expression of the employee_fullname ComputedField.
Finally, on line 116 we register LeaveApplication as an Archetype.
An Archetype holds its configuration data in a module named config.py. Here it is:
1 from Products.CMFCore.CMFCorePermissions import AddPortalContent
2 from Products.Archetypes.public import DisplayList
3 ADD_CONTENT_PERMISSION = AddPortalContent
4 PROJECTNAME = "ATLeaveApplication"
5 SKINS_DIR = 'skins'
6 GLOBALS = globals()
7 LEAVE_TYPES = DisplayList((
8 ('sick', 'Sick leave'),
9 ('normal', 'Normal leave'),
10 ('discretionary', 'Discretionary leave'),
11 ))
config.py mostly defines constants for our application. The constants listed below must be defined --- they are used by the registration process and installer:
Constant to be defined for a new Archetype
- ADD_CONTENT_PERMISSION
the permission required to add instance of your Archetype.
- PROJECTNAME
The name of the package
- SKINS_DIR
The directory name where the project's skins reside.
- GLOBALS
The current scope's variables
Other application constants, such as LEAVE_TYPES in our case, should also be defined here. LEAVE_TYPES is a DisplayList instance. As mentioned above, DisplayList should be used for field vocabularies.
To enable installation of LeaveApplication into a Plone site, we need to create an install script in the Extensions directory of our Product. This should be named Install.py and contains the following code:
1 from Products.Archetypes.public import listTypes
2 from Products.Archetypes.Extensions.utils import installTypes, install_subskin
3 from Products.ATLeaveApplication.config import PROJECTNAME, GLOBALS
4 from StringIO import StringIO
5 def install(self):
6 out = StringIO()
7 installTypes(self, out, listTypes(PROJECTNAME), PROJECTNAME)
8 install_subskin(self, out, GLOBALS)
9 out.write("Successfully installed %s." % PROJECTNAME)
10 return out.getvalue()
listTypes, imported on line 1, conveniently lists all the Archetypes defined in a project and saves you the trouble of enumerating them.
installTypes installs all types defined by your product into a Plone site.
install_subskin registers your skins directory with the portal_skins tool.
Like all Zope Products, we have to register our content type with Zope's product registry as well. This is done in the package contstructor, __init__.py:
1 from Products.Archetypes.public import process_types, listTypes 2 from Products.CMFCore import utils 3 from Products.CMFCore.DirectoryView import registerDirectory 4 from config import SKINS_DIR, GLOBALS, PROJECTNAME 5 from config import ADD_CONTENT_PERMISSION 6 registerDirectory(SKINS_DIR, GLOBALS) 7 def initialize(context): 8 import LeaveApplication 9 content_types, constructors, ftis = process_types( 10 listTypes(PROJECTNAME), 11 PROJECTNAME) 12 utils.ContentInit( 13 PROJECTNAME + ' Content', 14 content_types = content_types, 15 permission = ADD_CONTENT_PERMISSION, 16 extra_constructors = constructors, 17 fti = ftis, 18 ).initialize(context)
Following the necessary imports, registerDirectory (line 6) registers our skins directory in a global registry of DirectoryViews. This step is required if you want to install skins provided by your product into a Plone site.
initialize (line 7) is called by Zope with a ProductContext as parameter. All Archetypes defined by your product should be imported inside this method. Finally, process_types (line 9) introspects all types in a product and prepares a list of content_types, constructors, and ftis (factory type information) for intialisation in utils.ContentInit (line 12).
At this point you should be able to start Zope and install the ATLeaveApplication using the Portal Quickinstaller tool.
We can define a custom view for our class by setting the actions attribute of our class. Modify line 115 in LeaveApplication.py to read:
actions = (
{ 'id': 'view',
'name': 'View',
'action': 'string:${object_url}/leaveapp_view',
'permissions': (CMFCorePermissions.View,),
},
)
To use CMFCorePermissions.View we need to import it first. Add the import statement to the lists of imports
1 from AccessControl import ClassSecurityInfo 2 from CMFCore import CMFCorePermissions ...
Next, we need to create leaveapp_view in our skins directory. Create a PageTemplate named leaveapp_view.pt in skins/leaveapp:
1 <html xmlns="http://www.w3.org/1999/xhtml" 2 xml:lang="en" 3 lang="en" 4 xmlns:tal="http://xml.zope.org/namespaces/tal" 5 xmlns:metal="http://xml.zope.org/namespaces/metal" 6 xmlns:i18n="http://xml.zope.org/namespaces/i18n" 7 metal:use-macro="here/main_template/macros/master" 8 i18n:domain="leaveapp"> 9 <head><title></title></head> 10 <body> 11 <metal:fill fill-slot="main"> 12 <p> 13 <span tal:replace="here/getFullname">Pete Smith</span>, requested 14 <span tal:replace="here/getLeave_type">Normal leave</span>, 15 from <span tal:replace="here/getStart_date"> 16 to <span tal:replace="here/getEnd_date">. 17 </p> 18 </metal:fill> 19 </body> 20 </html>
Our template presents a narrative view of a LeaveApplication. We use the generated field accessor to render a field's value. For all fields in a Schema, Archetypes automatically generates accessors. Their names are derived by prefixing the field name with get, and capitalising the first letter of the field name. For example, a field named leave_type will have a generated accessor named getLeave_type.






