Home Company Services Portfolio Contact us nav spacer

Chapter 3. Developing for Plone

Chapter 3. Developing for Plone

Roché Compaan

Edited by

Jean Jordaan

In this section we develop a basic Archetypes content type.

Developing Plone Products

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.

Factory Type Information

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

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.

Our First Archetype

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.

Configuration variables

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.

Archetype Install script

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.

Archetype initialisation

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.

Custom actions

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.