Content-types with Archetypes

« Return to page index

Plone Developer Manual is a comprehensive guide to Plone programming.

1. Introduction

1.1. What is Archetypes?

Brief presentation of Archetypes.

Archetypes is a framework for developing new content types for a Plone project. Most content management projects involve introducing new types of content, which in the non-trivial case requires an informed understanding of how Zope and the CMF work. Archetypes provides a simple, extensible framework that can ease both the development and maintenance costs of CMF content types while reducing the learning curve for the simpler cases.

Compared to building content types using the stock CMF (through subclassing), Archetypes gives you the following advantages:

  1. automatically generates forms and views;
  2. provides a library of stock field types, form widgets, and field validators;
  3. allows defining custom fields, widgets, and validators;
  4. automates transformations of rich content;
  5. a built-in reference engine that gives the ability to link two objects together with a relation; such a "link" from a given object to another one is a Python object called a reference.

Since Plone 2.1, Archetypes has become the de-facto way of developing new content types, and a majority of third party products that are released these days use Archetypes.

1.2. Archetypes schemas

Introducing Archetypes-based schemas and fields.

Archetypes provides a robust framework for storing data attributes on content objects.  This framework consist of a number of Fields stored in a container called a Schema. Fields are simply specialized Python classes that allow you to store and retrieve data associated with an Archetypes object.

Fields provide a few functionalities. First, there are specialized field types for strings, lists of strings, integers, floating-point numbers, etc., that allow special handling of fields based on the type of data stored.

Some definitions

Before we go diving in, let's define some often-used terms:

  • Field: An Archetypes Field. This refers to an instance of a Field class defined in a Schema.
  • Schema: The "container" that Archetypes uses to store fields.
  • Schemata: A named grouping of fields. One Schema can have many schematas.
  • AT: Abbreviation for Archetypes.

Fields, Classes, and Objects

Archetypes Fields are Python objects contained within the Schema. A Field is defined once for an Archetypes content class. This single Field instance is used for every instance of that class.  Therefore, the relationship between Field instances and content classes is described as such: "A field instance belongs to exactly one class." A class, however, can have many different Field instances. Furthermore, every instance of an AT class uses the same set of Fields.  AT objects themselves do not contain unique Fields.

When Zope starts up, during product initialization, Archetypes reads the schema of the registered classes and "automagically" generates methods to read (the accessor) and change (the mutator) each of the fields defined.

Stock schemas

Archetypes includes three stock schemas:

  • BaseSchema: defines a normal content type,
  • BaseFolderSchema: defines a folderish content type (object can contain other objects),
  • BaseBTreeFolderSchema: for folders which need to handle hundreds or thousands of objects (even up to millions).

All three include two fields, id and title, as well as the standard Dublin Core metadata fields.

Modifying the fields of an existing schema

Modifying an existing schema field is possible using the syntax schema['<field_name>'].attribute = value. For example, to change the label of the description field widget (already available in BaseSchema), you can write (in your defined schema definition that reuses BaseSchema):

schema['description'].widget.label = u'Summary'

The fields in the schema are ordered, and normally first fields come first in "add" and "edit" forms. To rearrange a field within the schema use the moveField method:

  • Place it before a specific field: schema.moveField('<field_to_move>', before='<field_to_place_it_before>')
  • Place it after a specific field: schema.moveField('<field_to_move>', after='<field_to_place_it_after>')
  • Place it at the top of the schema: schema.moveField('<field_to_move>', pos='top')
  • Place it at the bottom: schema.moveField('<field_to_move>', pos='bottom')
  • Place it in a specific position: schema.moveField('<field_to_move>', pos=0)

 

1.3. What is ATContentTypes?

ATContentTypes is the Plone core product that provides the default content types (since Plone 2.1).

One of the major changes introduced in Plone 2.1 was that the core content types (Page, Image, etc) were changed from being based on stock CMF types, to using Archetypes. The new core types are housed in the ATContentTypes product.

ATContentTypes introduces a number of base classes and tools that provide common "Plone-ish" behaviour. This includes things like support for the "display" menu and the "more..." menu and restrictions for the "add item" menu.

You can use ATContentTypes' base classes and tools in your own products. The RichDocument tutorial covers the core techniques, and is probably a good place to go when you have finished this reference.

2. A Simple AT Product

A semi-realistic example Archetypes-based content type product.

2.1. Introduction

Introducing a sample AT Product and the contents of the tutorial.

In this part of the manual, we discuss a sample AT Product to explain CMF/Archetypes practices. We will be building a product called example.archetype, which will implement a content type (InstantMessage) that members with specific rights can use to add messages readable by other members. However, as you may have guessed, this is more a learning example than a usable product for a real website application.

What is a Product ? A product - a Zope product to be precise - is a third party add-on that can be integrated to provide additional functionality. It is a code package written using the Python language and conventions.

In order to understand this section you will need to have some prior knowledge of working on the file system and programming protocols common to Python and Zope.

The example.archetype product features the following CMF and Archetypes capabilities:

  • basic fields and widgets;
  • defining and using a vocabulary for a field with a selection widget;
  • defining specific "Add" permissions for the contents.

The code of the product can be downloaded here: http://plone.org/products/example.archetype/

2.2. Product package layout

Conventions and techniques for organizing the package for an AT product.

Following Zope, Plone and AT's conventions, the content of our example product pakage will look like this:

- __init__.py
- configure.zcml
- config.py
- interfaces.py
- content
    - __init__.py
    - message.py
- profiles
    - default
- browser
    - __init__.py
    - configure.zcml
    - instantmessage.pt
- tests
    - __init__.py
    - base.py
    - test_setup.py

What is the purpose of these files and directories?

  • __init__.py: The usual "Python package" initialization module;
  • configure.zcml: Using Zope's new Configuration Markup Language (ZCML), this file configures the services or behaviour the Zope server needs to load at startup;
  • config.py: Provides configuration variables for the product;
  • interfaces.py: Where you define interfaces describing what the packages' classes will do;
  • content: Contains the modules providing the implementation of the content types. In this case, it contains the message.py file where the 'InstantMessage' class should be defined;
  • profiles/default: Contains a set of XML files that are needed to provide the settings that will be used by Plone's Quick-Installer tool when installing the product within Plone; this is what we call an Extension Profile, an artifact of Zope CMF's GenericSetup technology. Note that this replaces the old way of doing based on the Extensions/Install. More precisely, since Plone 3.0, you do not need that old-style technique;
  • browser: The sub-package where the developer can add specific presentation code such as browser views and templates; the contained configure.zcml is used to provide these components registration.
  • tests: Contains the unit tests code for the product.

If you have ZopeSkel installed, you can use the following command to create a similar structure:

paster create -t archetype example.archetype

Now we will go through the files one by one and add what we need to produce our application.

2.3. The interfaces module

The module where you define interfaces describing what your content class(es) will do.

Why do you need interfaces?

Interfaces are useful to describe what a class will do. They are a kind of contract between a class and the components that class interact with. Starting a content management functionality package with writing interfaces is recommended practice as it helps document your code. In addition to that, Zope Component Architecture (ZCA) allows us to use interfaces as components for adapting a class (which is useful as new user requirements appear) and thus specializing its behaviour.

The interface for the Instant Message class

This is done by convention in the interfaces.py file, that you need to add at the root of the package.

First, we need an import from Zope's zope.interface module, which is included into Zope 2's distribution since version 2.8:

    from zope.interface import Interface

Following ZCA naming conventions (interface names start with an I), we define the IInstantMessage interface we need for the InstantMessage class that we will define later:

    class IInstantMessage(Interface):
        """
        Interface for the InstantMessage class.
        """

That's it!

We could add attribute definitions to it using the zope.interface.Attribute class, but this is not mandatory. When an interface is defined as above, without any function nor attribute, we call it a "marker interface" meaning that it will be used simply to "mark" the instances of the class that implements it.

More information about interfaces in the context of Archetypes can be found in the b-org tutorial - Interfaces section. For a detailed presentation of interfaces and their usage patterns, read the doctests document available from Zope's documentation site.

2.4. The configuration module

The configuration details for your content type, in config.py.

First, we have to import a class from Archetypes:

from Products.Archetypes.atapi import DisplayList

Displaylist is a data container we use when displaying pulldowns/radiobuttons/checkmarks with different choices. Let's say we wanted priorities on our instant messages, and we wanted those to be High, Normal and Low. We will specify these later in the file.

The next two lines set the project (Product in Zope) name, and point to the skin directory. PROJECTNAME should reference the name of the package: example.archetype.

PROJECTNAME = "example.archetype"

Now, we need to specify our 'Priority' pulldown. It should look like this, using the DisplayList utility class that Archetypes has provided for exactly that purpose:

MESSAGE_PRIORITIES = DisplayList((
    ('high', 'High Priority'),
    ('normal', 'Normal Priority'),
    ('low', 'Low Priority'),
    ))

Python notes:

  • The reason for double parantheses is that DisplayList is a class that you pass a tuple of tuples to.

 We also need to define the "Add" permission(s) for the content type(s):

ADD_CONTENT_PERMISSIONS = {
    'InstantMessage': 'example.archetype: Add InstantMessage',
 }

We recommend using the standard way of naming permissions: '<ProductName>: <Permission>'. This will group the related permissions together within the ZMI (Security tab), and allow the Administrator to recognize which permissions belong to which Product.

Note that, unless you have an advanced case which needs custom security settings, you don't need to define your own permissions for the "edit" and "view" of the content. In this simple case you will just reuse, in the modules where needed, the generic permissions defined in CMFCore.permissions: "View", "Modify portal content"...

2.5. The startup module

The initialization module (__init__.py) provides the script that is run when Zope is started.

Before starting the usual Zope product initialization code, we need to define a Message Factory for when this product is internationalized.

from zope.i18nmessageid import MessageFactory

exampleMessageFactory = MessageFactory('example.archetype')

The defined MessageFactory object will be imported with the special name "_" in most modules, and strings like _(u"message") will then be extracted by i18n tools for translation.

Now, we import some useful stuff from the Archetypes API:process_types is useful to get the product's content types, associated constructors, and Factory Type Information (FTI) data structures, while listTypes can be used to list the types available in the product.

We also need to import the utils module from CMFCore to be able to use its ContentInit class later.

from Products.Archetypes.atapi import process_types
from Products.Archetypes.atapi import listTypes

from Products.CMFCore import utils

Python notes:

  • Factory Type Information (FTI): Part of a CMF portal's configuration, the FTI for a content type is the data structure that holds the information needed to expose a content type within the portal. From the integrator's perspective, the FTI is the object (Factory-based Type Information object) within the portal_types component that tells CMF and Plone how to create a content from the type and how to display it.

  • How exactly does 'listTypes' work: See those registerType() calls in your content type modules? Notice how we also import those modules (but do nothing with the import) in the 'content' package's __init__.py. The registerType() call tells AT about the type so that listTypes() can find it later.

One of the important import steps : we import everything that is defined in the content sub-package, i.e. all its modules:
from content import message
Now, we import the configuration module, in order to have access to the variables it contains, such as the "Add" permission setting:
import config

Now for the real action. You define a function that is required by Zope and CMF internals to initialize our content type(s):

def initialize(context):

The first part of the code of this function generates the content types, the constructors and the Factory-based Type Informations (or FTIs) required to make your types work with the CMF:

    content_types, constructors, ftis = process_types(
        listTypes(config.PROJECTNAME),
        config.PROJECTNAME)

The second part instantiates an object of the class ContentInit (from CMFCore), and registers your types in the CMF:

    utils.ContentInit(
            "%s Content" % config.PROJECTNAME,
            content_types      = content_types,
            permission         = config.ADD_CONTENT_PERMISSIONS['InstantMessage'],
            extra_constructors = constructors,
            fti                = ftis,
            ).initialize(context)

Handling several content types

There is a better way to write the code that initializes the content type class with its "Add" permission and constructor, so that it still works if you define several content types. This is useful if you plan to later augment your product with additional types.

Here is the improved code:

def initialize(context):

    content_types, constructors, ftis = process_types(
             listTypes(config.PROJECTNAME), 
             config.PROJECTNAME)


    # We want to register each type with its own permission,
    # this will afford us greater control during system
    # configuration/deployment (credit : Ben Saller)

    allTypes = zip(content_types, constructors)
    for atype, constructor in allTypes:
        kind = "%s: %s" % (config.PROJECTNAME, atype.portal_type)
        utils.ContentInit(kind,            
                          content_types      = (atype,),
                          permission         = config.ADD_CONTENT_PERMISSIONS[atype.portal_type],
                          extra_constructors = (constructor,),            
                          fti                = ftis,
                          ).initialize(context)

Python notes:

  • We can use the "ADD_CONTENT_PERMISSIONS[atype.portal_type]" construct because ADD_CONTENT_PERMISSIONS references a dictionary in which the keys are the potential content types names.

  • The zip() function is a Python built-in that pairs up elements of two lists. In this case, "allTypes" will be a list of tuples containing a content type from "content_types" and the corresponding constructor from "constructors".

  • If you have several content types, you should not forget to import each content module, as is done for the message example discussed here !

 

2.6. The content package and its modules

Now we are ready for the core of the product, i.e. the content class definition module (content/message.py).

Since it provides a Python (sub)package, the 'content' directory contains 2 modules:

  • the usual __init__ module that initializes the package,
  • the message module (message.py) where we will define the 'InstantMessage' class.

The message module

First imports we need

We start the message module by adding the general Zope-related imports we need, such as the implements function from the zope.interface module:

from zope.interface import implements

We need to use a few classes and/or functions provided by the core of our codebase, i.e. CMF/Archetypes. It is possible to have access to all the classes and helper functions made publicly available by Archetypes, by importing its façade or API module (Products.Archetypes.atapi) this way:

from Products.Archetypes import atapi 

i18n support

It is always a good idea to have an i18n-enabled application. To start using Zope's i18n support, let's import the MessageFactory object created in the product's startup module:

from example.archetype import exampleMessageFactory as _

The MessageFactory referenced with the _ symbol can now be used to provide i18nized labels, descriptions, and all the miscellaneous text snippets that are injected in the UI, also known as "messages". For a content type implementation, this is useful for UI widgets; for example to define the label of the content title field widget, we could define label = _(u'Title'). (See later for how we make use of this tool/practice.)

ATContentTypes-based schema definition

You can base your implementation directly on these stock Archetypes schemas. But you can add better support for Plone's UI and content management policies (such as the parameters that allow showing/hiding contents in the navigation menu), by basing the implementation on ATContentTypes' base schema, ATContentTypeSchema. To be compatible with that schema, you will also need to inherit from ATContentTypes' ATCTContent base class.

Let's add the import of modules we need for that:

from Products.ATContentTypes.content import base
from Products.ATContentTypes.content import schemata
Then, we import things internal to our product package, such as our defined interface(s) and the configuration module (for access to things such as PROJECTNAME and MESSAGE_PRIORITIES):
from example.archetype.interfaces import IInstantMessage

from example.archetype import config

Now, we have everything we need to start building the schema, and then the class that will use it. We start out by copying ATContentTypes' ATContentTypeSchema, and we extend it by adding our specific fields and/or overriden field properties.

schema = schemata.ATContentTypeSchema.copy() + atapi.Schema((

  atapi.StringField('priority',
              vocabulary = config.MESSAGE_PRIORITIES,
              default = 'normal',
              widget = atapi.SelectionWidget(label = _(u'Priority')),
             ),

  atapi.TextField('body',
            searchable = 1,
            required = 1,
            allowable_content_types = ('text/plain',
                                       'text/structured',
                                       'text/html',),
            default_output_type = 'text/x-html-safe',
            widget = atapi.RichWidget(label = _(u'Message body')),
           ),

))

Notes:

  • To instantiate an Archetypes schema object, you pass a tuple of field objects to the 'Schema' class.

We define the body of the InstantMessage object using a RichWidget, so the user can use formatting with a WYSIWYG editor.

The full list of out-of-the-box available Fields and Widgets can be found in the Fields section at the end of the manual. You can find more 3rd party fields and widgets here.

Content-type class definition

The last step is to create the class for the InstantMessage content. It inherits from ATContentTypes' ATCTContent, which itself is based on AT's BaseContent, which automatically gives its 'id' and 'title' attributes, and the entire Dublin Core metadata set (Title, Description, Creator, CreationDate, etc):

class InstantMessage(base.ATCTContent):
    """An Archetype for an InstantMessage application"""

    implements(IInstantMessage)

    schema = schema

The first information we add for the class definition is saying that it implements the IInstantMessage interface that we have previously defined (in interfaces.py) and imported.

    implements(IInstantMessage)

The next thing is assigning the reference of the Archetypes schema, using the schema class attribute.

    schema = schema

The content class definition is done. Now, we are ready to activate the content type in Archetypes' internal types registry. This is done using the helper function called registerType.

atapi.registerType(InstantMessage, config.PROJECTNAME)

Congratulations! You have just created your first Archetype for Plone! It allows you to handle the content of an instant message with Zope-based persistent objects which:

  • can be added within your Plone site,
  • published by the Zope Publisher, which means you can visit them via their URLs, etc...
  • searched since they are automatically indexed,
  • etc...

But wait! You have some final packaging work to do to ease installation of the product within your Plone site.

Notes:

  • At the content class level, you could also provide the 'actions' attribute useful for defining the settings of the type's actions (for the portal_actions tool). In Plone 3, this is no more needed, since this is part of the FTI's configuration details, and should be provided using GenericSetup, in the types-related XML files (i.e. 'profiles/default/types/InstantMessage.xml'). Same for the aliases.

The __init__ module

The trick here is to simply import the message module so that all the code of that module gets interpreted as soon as the Python interpreter initializes the package.

import message

 

2.7. Adding a custom view for the content

Providing the custom presentation template for the InsantMessage, using Zope's browser layer mechanism.

The browser layer concept

A browser layer is a concept introduced by Zope Component Architecture (Zope 3), and which can be used in Plone. It is useful for registering views and resources (images, CSS, JS) for the site, in a way that they can override default elements (which are implicitly registered for the default browser layer) or be overriden when needed, even through the ZMI. A browser layer is similar in purpose to a CMF skin layer, but is implemented differently.

To add a browser layer to your product, you need 3 steps:

  • Define the marker interface for the browser layer (for example, example.archetype.interfaces.IInstantMessageSpecific.)
  • Add an XML file in your extension profile named browserlayer.xml providing the browser layer settings to the site. (This step is covered later as part of the various product setup details.)
  • Register (using ZCML) your browser views, templates and resources.
For more about browser layer techniques, check this tutorial.

Defining the browser layer interface

Add a marker interface for the browser layer (in interfaces.py):

from plone.theme.interfaces import IDefaultPloneLayer class IInstantMessageSpecific(IDefaultPloneLayer):     """Marker interface that defines a Zope 3 skin layer for this product.     """

Adding and registering the browser template

To provide a custom view template for your content type, you need a page template called instantmessage.pt in the browser/ directory, and a ZCML declaration in the configure.zcml to associate the template to the IInstantMessageSpecific Zope 3 skin layer.

<configure xmlns="http://namespaces.zope.org/zope"     xmlns:browser="http://namespaces.zope.org/browser"     i18n_domain="example.archetype" >     <browser:page         for="example.archetype.interfaces.IInstantMessage"         layer="example.archetype.interfaces.IInstantMessageSpecific"         name="instantmessage_view"         template="instantmessage.pt"         permission="zope2.View"         /> </configure>

Here is the example template code:

<html metal:use-macro="here/main_template/macros/master"
      i18n:domain="plone" >
<body>

<div metal:fill-slot="main"
     tal:define="priority here/getPriority;
                 priority_color python:(priority == 'high' and 'red') or (priority == 'low' and 'green') or ''" >

        <h1 tal:content="context/Title"
            tal:attributes="style string:color:$priority_color" >
          Title
        </h1>

        <p tal:content="structure here/getBody" />          

        <div class="documentByLine">
          Message by <span tal:content="context/Creator" /> 
          with <strong tal:content="priority" /> priority.
          -
          <span tal:replace="python:here.toLocalizedTime(context.CreationDate(),long_format=1)" />
        </div>
    
</div>

</body>
</html>

Python notes:

  • The new methods we use on the content object (getPriority, getBody, etc), called the "accessors", are generated by Archetypes as part of its internal mechanisms, based on the field definition in the content schema; so if the field is called 'priority', there is a generated method called 'getPriority' responsible to return the stored value on the object. Note that the code of the method is not available somewhere for modification ; "generated" here means it is available in the server's memory, within Archetypes engine's registries, when the Zope server has started.

After the product installation step, which we still have to discuss (see later), Plone should be able to find this template and use it as the content object's default view when you invoke the content's URL.

2.8. Installing the product

Ensuring the product elements (types, browser layers, resources) are correctly installed.

In this part, we will provide the code to be executed when the integrator "adds", i.e. installs, the InstantMessage product to the Plone site. This aspect of the product code is called the "Extension Profile" (or "Setup Profile") and is managed under the hood by a machinery called GenericSetup.

For more about GenericSetup, its possibilities, and how a developer uses it, read the GenericSetup tutorial.

The setup profile files (profiles/default)

The setup profile is composed of a set of GenericSetup XML files containing setup declarations.

Type declaration and definition

First, we provide the files needed for adding the types to CMF's types registry (portal_types): types.xml and types/InstantMessage.xml.

In types.xml, within the <object name="portal_types" ... /> element, add the setup code for the type(s) you want to install:

<?xml version="1.0"?>
<object name="portal_types" meta_type="Plone Types Tool">
 <property
    name="title">Controls the available content types in your portal</property>
 <object name="InstantMessage"
    meta_type="Factory-based Type Information with dynamic views"/>
</object> 

The name property of the <object> node constitutes the called portal type name of the content-type, a CMF concept which supports two things:

  1. Dynamic typing: objects can change their content type during their lifetime. To do this use _setPortalTypeName(<type>).
  2. You can have arbitrarily many different content types using the same base class (and having therefore the same meta_type) but differing in their Factory Type Information (FTI) settings.

The portal type name was formerly set in a content-type class attribute called portal_type, which is no longer necessary.

The name of the file inside the profiles/default/types folder must match the portal type name, with spaces converted to underscores whenever necessary. So, in types/InstantMessage.xml, add the code for the InstantMessage FTI object:

<?xml version="1.0"?>
<object name="InstantMessage"
   meta_type="Factory-based Type Information with dynamic views"
   i18n:domain="example.archetype" xmlns:i18n="http://xml.zope.org/namespaces/i18n">
  <property name="title" i18n:translate="">Example AT - InstantMessage</property>
  <property name="description"
    i18n:translate="">An example type (InstantMessage) discussed in the AT Developer Manual.</property>

In these first lines we give the content-type a title and a description.

The title property indicates the user-friendly name of the content-type. This is what's supposed to be used in the user interface, and can be accessed using the <fti>.title_or_id() or the Type() methods, which both return the content-type title if it exists or the content-type id otherwise. Like portal type, this property was formerly set in a the content-type class attribute called archetype_name, which is no longer neccessary.

  <property name="content_meta_type">InstantMessage</property>
  <property name="content_icon">document_icon.gif</property>
  <property name="product">example.archetype</property>
  <property name="factory">addInstantMessage</property>

The meta_type property of the object is a Zope concept to organize object classification or containment. For historical reasons, it is used in CMF in some places because first versions of CMF didn't have today's portal_type. Also note that Archetypes uses the content-type class name as the meta_type value, unless given explicitly.

The content_icon property specifies the icon image file which will be shown in the Plone UI for this content-type. This icon image file must be accessible from the context of the content-type, and therefore should be placed into a CMF skin layer (the CMF way) or in a browser resource directory (the Zope 3 way).

The factory property indicates the factory function which will be used to create and initialize new content objects of this type. This factory is automatically generated by the Archetypes framework, when the product is initialized (via the code in the startup module), and is always named add<content-meta-type>. The factory is also associated with a certain product by means of the product property.

  <property name="immediate_view">atct_edit</property>
  <property name="global_allow">True</property>
  <property name="filter_content_types">False</property>
  <property name="allow_discussion">False</property>

The global_allow property determines if the content-type will be available to be added from anywhere in the site.

The filter_content_types property, paired with allowed_content_types, controls what content-types will be addable inside the current one.

With allow_discussion, we specify whether or not comments will be allowed by default on this content-type.

  <property name="default_view">@@instantmessage_view</property>
  <property name="view_methods">
    <element value="@@instantmessage_view" />
  </property>
  <alias from="(Default)" to="@@instantmessage_view" />
  <alias from="edit" to="atct_edit" />
  <alias from="sharing" to="@@sharing" />
  <alias from="view" to="@@instantmessage_view" />

Here we define CMF views (templates) and aliases that map content-type methods to views.

  <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 <action> elements register type-specific actions for the content-type. The object category makes the render as tabs in the Plone UI.

  • The url_expr is a TALES expression that defines the URL from where the action will be triggered and should match one of the method aliases defined above. Hence, the edit action points to string:${object_url}/edit, which means that if you are at /path/to/object and click edit, you will go to /path/to/object/edit. /edit then gets recognized as a method alias, which points to the page template atct_edit, causing Zope to render /path/to/object/atct_edit.
  • The <permission /> element specifies a guard permission for this action. If the user's role doesn't have this permission, the action won't be available and the corresponding action tab won't be shown.
  • In addition to the former criteria, the condition_expr is a TALES expression which will be evaluated to decide if the action is available or not.
  • The visible attribute indicates wheter the action tab will be visible or hidden. If it's set to False, the tab won't appear even when the action is available, but the exposed page will still be accesible from the associated URL.
Notes:
  • Defining new content-type actions this way, i.e. using GenericSetup, supersedes the old updateActions function from ATContentTypes.content.base.
  • Don't worry. You don't have to type all this XML each time you create a new content-type; since most of it is boilerplate (XML is very verbose) you can copy & paste an already working example (like the CMFPlone ones) and modify only the changing bits.

Type factory

We also need the file useful for setting the type against Plone's factory tool (portal_factory): factorytool.xml. This is needed so that when a user adds a content object and then clicks Cancel in the edit form, a stale object won't be lying around.)

<?xml version="1.0"?>
<object name="portal_factory" meta_type="Plone Factory Tool">
 <factorytypes>
  <type portal_type="InstantMessage"/>
 </factorytypes>
</object>

Roles - Permissions mapping

For our content type(s) to be usable, we need to assign the required "Add" permission to the Plone site's default roles: Contributor, Owner, and Manager. This is done using the rolemap.xml file as follows:

<?xml version="1.0"?>
<rolemap>
    <permissions>
        <permission name="example.archetype: Add InstantMessage" acquire="True">
          <role name="Manager"/>
          <role name="Owner"/>
          <role name="Contributor"/>
        </permission>
    </permissions>
</rolemap>

Browser skin layer

In order to install our browser skin layer, we also add a browserlayer.xml file with the following code:

<?xml version="1.0"?>
<layers>  
  <layer name="example.archetype"
         interface="example.archetype.interfaces.IInstantMessageSpecific" />
</layers>

Registering our setup profile

This last step ensures everything can work. We update the package's configure.zcml file with the code snippet that will load the extension profile:

<configure
    xmlns="http://namespaces.zope.org/zope"
    xmlns:five="http://namespaces.zope.org/five"
    xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
    i18n_domain="example.archetype" >

   <five:registerPackage package="." initialize=".initialize" />

   <include package=".browser" />

   <genericsetup:registerProfile
      name="default"
      title="Example Archetype content - InstantMessage"
      directory="profiles/default"
      description="Extension profile for Example AT - InstantMessage"
      provides="Products.GenericSetup.interfaces.EXTENSION"
      />

</configure>

Restarting Zope

Now that you have a first version of your product ready to be tested, and installed via your buildout, you need to (re)start Zope.

Quick-installing the product

Back in the Plone configuration (or Plone control panel), when you visit the "Add/Remove Products" interface or the portal_quickinstaller tool through the ZMI (at the root of the site), you can see the product show up under the category of "installable products".

Select and click the button to install the product. If everything goes fine, the product should be installed, and you're ready to start using it!

2.9. Basic integration tests

No product is complete without tests.

To build high-quality software, you must provide automatic tests - often known as "unit" tests (though tests for Archetypes products tend to be "integration" tests, strictly speaking).

The tutorial on testing and test-driven development is essential reading if you want to write high-quality software (and you don't know the techniques it advocates already). Please refer to it for details.

The example.archetype product contains basic tests that prove that the product is properly installed, that it registers its types, and that an InstantMessage object can actually be instantiated. If it contained more functionality, there would have been more tests, but even simple integration tests like this can be surprisingly useful - if you accidentally broke the content type with some change, you'd notice that it failed to install or instantiate.

The tests are in the "tests" directory. The file "base.py" contains some base classes that are used for tests, to ensure the site is properly set up:

import unittest

from zope.testing import doctestunit
from zope.component import testing
from Testing import ZopeTestCase as ztc

from Products.Five import zcml
from Products.Five import fiveconfigure
from Products.PloneTestCase import PloneTestCase as ptc
from Products.PloneTestCase.layer import PloneSite
from Products.PloneTestCase.layer import onsetup

@onsetup
def setup_product():
    """Set up the package and its dependencies.
    
    The @onsetup decorator causes the execution of this body to be deferred
    until the setup of the Plone site testing layer. We could have created our
    own layer, but this is the easiest way for Plone integration tests.
    """
    
    fiveconfigure.debug_mode = True
    import example.archetype
    zcml.load_config('configure.zcml', example.archetype)
    fiveconfigure.debug_mode = False
        
    ztc.installPackage('example.archetype')
    

setup_product()
ptc.setupPloneSite(products=['example.archetype'])


class InstantMessageTestCase(ptc.PloneTestCase):
    """Base class for integration tests.

    This may provide specific set-up and tear-down operations, or provide
    convenience methods.
    """

The actual tests are in "test_setup.py":

from base import InstantMessageTestCase
from example.archetype.interfaces import IInstantMessage

class TestProductInstall(InstantMessageTestCase):

    def afterSetUp(self):
        self.types = ('InstantMessage',)

    def testTypesInstalled(self):
        for t in self.types:
            self.failUnless(t in self.portal.portal_types.objectIds(),
                            '%s content type not installed' % t)

    def testPortalFactoryEnabled(self):
        for t in self.types:
            self.failUnless(t in self.portal.portal_factory.getFactoryTypes().keys(),
                            '%s content type not installed' % t)

class TestInstantiation(InstantMessageTestCase):

    def afterSetUp(self):
        # Adding an InstantMessage anywhere - can only be done by a Manager or Portal Owner
        self.setRoles(['Manager'])
        self.portal.invokeFactory('InstantMessage', 'im1')

    def testCreateInstantMessage(self):
        self.failUnless('im1' in self.portal.objectIds())

    def testInstantMessageInterface(self):
        im = self.portal.im1
        self.failUnless(IInstantMessage.providedBy(im))

def test_suite():
    from unittest import TestSuite, makeSuite
    suite = TestSuite()
    suite.addTest(makeSuite(TestProductInstall))
    suite.addTest(makeSuite(TestInstantiation))
    return suite

To run these tests within your buildout environment:

./bin/instance test -s example.archetype

You may see output like:

 Ran 4 tests with 0 failures and 0 errors in 0.119 seconds.

If there was an error with one or more of the tests, you'd be told here!

Please refer to the testing tutorial for more about writing tests - and writing good tests - and how to run them.

2.10. Troubleshooting

When creating new content types, many factors can silently fail due to human errors in the complex content type setup chain and security limitations. The effect is that you don't see your content type in Add drop down menu. Here are some tips for debugging.

1. Is your product broken due to Python import time errors? Check the *Zope Management Interface (ZMI from now on) → Control Panel → Products*. Turn on Zope debugging mode to trace import errors. To see error messages directly in the console with buildout, use *bin/instance fg*.

2. Have you rerun the quick installer (GenericSetup) after creating/modifying the content type? If not, (re)install the product from the *Plone Control Panel → Add-on Products* or from the *ZMI → portal_quickinstaller*.

3. Do you have a correct Add permission for the product? Check the call of the ``ContentInit()`` method inside the *__init__.py* file. See `The startup module <http://plone.org/documentation/manual/archetypes-developer-manual/a-semi-realistic-example/the-startup-module>`_.

4. Does it show up in the portal factory? Check *ZMI → portal_factory* and *factorytool.xml*.

5. Is it correctly registered as a portal type and implictly addable? Check *ZMI → portal_types*. Check *default/profiles/type/yourtype.xml*.

6. Does it have a correct product name defined? Check *ZMI → portal_types*.

7. Does it have a proper factory method? Check *ZMI → types_tool*. Check Zope logs for ``_queryFactory`` and import errors.

8. Does it register itself with Archetypes? Check *ZMI → archetypes_tool*. Make sure that you have ``ContentInit`` properly run in your *__init__.py*. Make sure that all modules having Archetypes content types defined and ``registerType()`` calls are imported in *__init__py*.

3. Fields

Fields for Archetypes

3.1. Fields Reference

Attributes of standard Archetypes fields.

Topics

Common Field Attributes

BooleanField

ComputedField

CMFObjectField

DateTimeField

FileField

FixedPointField

FloatField

ImageField

IntegerField

LinesField

ReferenceField

StringField

TextField

 

 

    Common Field Attributes

    These attributes are common to nearly all fields. Field-specific attributes follow, and are listed by field. Particular fields have different defaults, types, and some other specialized attributes.

    Name Description Possible Values Default
    accessor The name of a class method that will return the value of the field. Use this to change how the field is retrieved. If you don't provide a custom method name here, a default accessor, named getYourFieldName, is going to be created that just returns the value of the Field.
    A class method name; for example, specialGetMethod None
    default
    The default value for the field.
    Type should be appropriate to the field.
    None
    default_method
    The name of a class method returning a value for the field. A class method name; for example, getSpecialDescription.
    None
    edit_accessor The name of a class method that returns the raw value of a field. Any method name (for example, rawGetMethod).
    None
    enforceVocabulary Determines whether or not values outside the vocabulary will be accepted. If True, Archetypes will validate input for the field against the vocabulary. Only values already in the vocabulary will be accepted.
    True or False.
    False
    index
    (Plone < 3 only)
    If you want this field to be placed in its own catalog index, then specify the type of index here as a string. If you append :schema onto the end of the schema, then this will also be added as a metadata column. (The actual index will be on the field accessor, typically "getFieldName".)
    Ignored in Plone 3+; use GenericSetup profile for similar functionality.
    The name of any index, such as KeywordIndex or KeywordIndex:schema.
    None
    index_method May be used to specify the method called when indexing a field. Use '_at_accessor' to use the default accessor, '_at_edit_accessor' to use the edit accessor, or the name of a method returning the value to be indexed. _at_accessor, _at_edit_accessor, getIndexAccessor and getIndexAccessorName
    _at_accessor
    languageIndependent
    Flag for Fields that are independent of the language, such as dates. True tells LinguaPlone that no translation is necessary for this field. True or False False
    isMetadata
    Marks metadata fields. This is currently only needed as a convenience for the filterFields method of Schema. Fields marked as metadata are not displayed in the uncustomized base view. True or False False
    mode
    The read/write mode of field, as a string; the default is to be read and write. Accessors will not be created without the read mode, and Mutators will not be created without the write mode.
    For read only: r, for write only: w, for read and write: rw.
    rw
    multiValued
    Set this to True if the field can have multiple values. This is the case for fields like multiple-selection lists that allow the selection of multiple values. True or False.
    False
    mutator
    The string name of a class method that changes the value of the Field. If you don't provide a special method name here, a default mutator is generated with the name 'setYourFieldName' to simply store the value. A class method name; for example, specialSetMethod.
    None
    name A unique name for this field. Usually specified as the first item in the field definition.
    Any string. Strongly recommended: lowercase, no punctuation or spaces, conforming to standard Python identifier rules. For example, description, user_name, or coffee_bag_6.
    No default.
    primary If True, this will be the field that used for File Transfer Protocol (FTP) and WebDAV requests. There can be only field that does this; if multiple are defined, the first one in the schema will be used. You normally set this for the main body attribute. Only used for TextField and FileField field types. True or False False
    read_permission
    The permission required for the current user to allowed to view or access the field. Only useful if the read mode is activated. This read permission is checked when rendering the widget in read mode. A permission identifier imported from Products.CMFCore.permissions View
    required
    Specifies that some value for this field required. True or False. False
    schemata
    Use named schematas to organize fields into grouped views. A short string that labels the group.
    default
    searchable
    Specifies whether or not the field value will be indexed as part of the SearchableText for the content object. SearchableText is what is checked by the portal's main search. True or False. False
    storage
    The storage mechanism for the field. The default is Attribute Storage, which stores the field as an attribute of the object. Any valid storage object such as AttributeStorage or SQLStorage. You can find these in the Archetypes Application Programming Interface (API).
    AttributeStorage
    type
    Provided by the field class.. Should never be changed in a Schema. None
    None
    validators
    A list or tuple of strings naming validators that will check field input. If you only have one validator, you may specify it as a string.
    Validators may also be instances of a class implementing the IValidator interface from from Products.validation.interfaces.IValidator. Providing a class instance allows you more flexibility as you may set additional parameters.
    Validators are checked in order specified.
    The names of validators registered via Products.validation; for example, isEmail. ()
    vocabulary
    Provides the values shown in selection and multi-selection inputs. This may be specified as a static list or as the name of a class method returning the choice list.
    A list of strings (in which case keys and values will be the same); a list of 2-tuples of strings [("key1","value 1"),("key 2","value 2"),...]; a Products.Archetypes.utils.DisplayList. Or, the name of a class method returning any of the above. ()
    vocabulary_factory Like the vocabulary attribute, in Plone 3 provides the values shown in selection and multi-selection inputs. A string name of a Zope 3 style vocabulary factory (a named utility providing zope.schema.interfaces.IVocabularyFactory) None
    widget The widget that will be used to render the field for viewing and editing. See the widget reference for a list of available widgets.
    An instance of a widget; for example, StringWidget().
    StringWidget()
    write_permission
    The permission required for the current user to edit the field value. Only interesting if the write mode is activated. The write permission is checked when rendering the widget in write mode. A permission identifier imported from Products.CMFCore.permissions ModifyPortalContent

    Standard Fields

     

    BooleanField

    Simple storage of True or False for a field.

    Standard properties
    Name Type Default Description example values
    widget widget BooleanWidget Implemented as a check box.
    default boolean False

    type
    boolean

    Note: The required attribute for the boolean field is often confusing. It does not require that the box be checked. Use a validator if you need to require the box be checked.

    ComputedField

    Read-only field, whose content cannot be edited directly by users, but is computed instead from a Python expression. For example, it can be the result of an operation on the contents from some other fields in the same schema, e.g. calculating the sum of two or more currency amounts, or composing a full name from first name and surname.
    This field is usually not stored in the database, because its content is calculated on the fly when the object is viewed.

    Standard properties
    Name Type Default description some possible values
    widget widget ComputedWidget
    storage storage ReadOnlyStorage

    type
    computed

    mode string r

    Special properties
    Name Type Default Description some possible values
    expression

    Evaluated on the object to compute a value.

     

    CMFObjectField

    Used for storing values inside a CMF Object, which can have workflow. Can only be used for BaseFolder-based content objects.

    Standard properties
    Name Type Default description some possible values
    widget widget FileWidget
    storage storage ObjectManagedStorage

    type
    object

    Special properties
    Name Type Default Description some possible values
    portal_type
    File

    workflowable
    True

    default_mime_type
    application/octet-stream

    DateTimeField

    Used for storing dates and times.

    Standard properties
    Name Type Default Description some possible values
    widget widget CalendarWidget
    default DateTime


    type
    datetime

    Note: The default for the DateTimeField needs to be specified as a DateTime object. If you need to set the current date/time as the default, you'll need to use the default_method attribute to specify a class method returning the current date/time as a DateTime object.

    Example:

    from DateTime.DateTime import DateTime
    
    ...
    
    # inside the schema definiton
        DateTimeField('dateAdded',
            searchable = 1,
            required = 0,
            default_method = 'getDefaultTime',
            widget = CalendarWidget(
                label = 'Date Added'
            ),
        ),
    
    ...
    
    #inside the content class definition
        def getDefaultTime(self):  # function to return the current date and time
            return DateTime()


    FileField

    Storage for large chunks of data such as plain-text files, office-automation documents, and so on. If you're using Plone 4 or newer, consider using plone.app.blob.field.BlobField instead, that stores the file data outside of the ZODB and accepts the same parameters as atapi.FileField. See this page for info about migration.

    Standard properties
    Name Type Default Description some possible values
    widget widget FileWidget
    default string


    type
    file

    Special properties
    Name Type Default Description some possible values
    primary
    False

    default_content_type
    application/octet

    primary boolean False Set this True to mark the field as primary for FTP or WebDAV.

    Note: File field values are stored as strings. It's a common practice to use streams to read/write the values as if they were files.

     

    FixedPointField

    For storing numerical data with fixed points.

    Standard properties
    Name Type Default Description some possible values
    widget widget DecimalWidget
    validators validators isDecimal

    default string 0.00

    type
    fixedpoint

    Special properties
    Name Type Default Description some possible values
    precision
    2

     

    FloatField

    For storing numerical data with floating points.

    Standard properties
    Name Type Default Description some possible values
    default string 0.0

    type
    float

     

    ImageField

    Stores an image and allows dynamic resizing of the image. If you're using Plone 4 or newer, consider using plone.app.blob.field.ImageField instead, that stores the image data outside of the ZODB, and accepts the same parameters as atapi.ImageField. See this page for info about migration.

    Standard properties
    Name Type Default Description some possible values
    widget widget ImageWidget
    default string


    type
    image

    allowable_content_types tuple of MIME strings Specifies the types of images that will be allowed. ('image/gif','image/jpeg','image/png') ('image/jpeg','image/png')

    Note: Archetypes Image field values are stored as strings. It's a common practice to use streams to read/write the values as if they were files.

    Special properties
    Name Type Default Description some possible values
    original_size tuple (w,h) None The size to which the original image will be scaled. If it's None, then no scaling will take place; the original size will be retained. Caution: the aspect ratio of the image may be changed. (640,480)
    max_size tuple (w,h) None If specified then the image is scaled to be no bigger than either of the given values of width or height. Aspect ratio is preserved. Useful to prevent storage of megabytes of unnecessary image data. (1024,768)
    sizes dict {'thumb':(80,80)} A dictionary specifying any additional scales in which the image will be available. Dictionary entries should be of the form 'scaleName':(width,height). The scaled versions will be accessible as object/<imagename>_<scalename>, e.g. object/image_mini.
    { 'mini' : (80,80), 'normal' : (200,200), 'big' : (300,300), 'maxi' : (500,500)}
    pil_quality integer 88 A JPEG quality setting (range 0 to 100). Lower numbers yield high compression and low image quality. High numbers yield low compression and better quality. 50 (a medium quality)

    Using Image Scales

    To display the original image (possibly rescaled if you used original_size or max_size attributes), you may use a URL like "http://url_of_content_object/imageFieldName" as the SRC attribute of an IMG tag where url_of_content_object is the URL of the content object and imageFieldName is the name of the image field.

    To display one of the scales, use a URL like "http://url_of_content_object/imageFieldName_scale",
    where scale is one of the keys of the sizes dictionary.

    Attention: The direct attribute access as shown above works only together with AttributeStorage, which will be used by default. To avoid heavy memory consumption on sites with many images it is recommended to use AnnotationStorage for the ImageField.

    You may also generate a ready-to-insert IMG tag with the python code:

    obj.getField('image').tag(obj, scale='mini')

    if obj is your content object, image the name of your image field, and mini the name of your scale.

    You may rescale to other sizes than those in the sizes field attribute with code like:

    obj.getField('image').tag(obj, height=480, width=640, alt='alt text',
                css_class='css_class_selector', title='html title attribute')

    From Plone 4 on, the plone.app.imaging package introduces a new way to control image scales, factoring this functionality out of Archetypes for reutilization. For example:

    <img tal:define="scales context/@@images;
                     thumbnail python: scales.scale('image', width=64, height=64);"
         tal:condition="thumbnail"
         tal:attributes="src thumbnail/url;
                         width thumbnail/width;
                         height thumbnail/height" />

    Would create an up to 64 by 64 pixel scaled down version of the image stored in the "image" field of the context. For further info, check the plone.app.imaging README file.

    IntegerField

    For storing numerical data as integers.

    Standard properties
    Name Type Default Description some possible values
    widget widget IntegerWidget
    default integer 0

    type
    integer

    Special properties
    Name Type Default Description some possible values
    size
    10 Sets the size of the input field.

     

    LinesField

    Used for storing text as a list, for example a list of data such as keywords.

    Standard properties
    Name Type Default Description some possible values
    widget widget LinesWidget
    default string ()

    type
    lines

     

    ReferenceField

    Used for storing references to other Archetypes Objects.

    Standard properties
    Name Type Default Description some possible values
    widget widget ReferenceWidget
    index_method
    _at_edit_accessor

    type
    reference

    multiValued boolean False Set multiValued True to allow multiple references (one-to-many), or False to allow only a single reference (one-to-one).
    Special properties
    Name Type Default Description some possible values
    relationship

    Specifes an identifier for the type of relationships associated with the field. This should be unique within your content type, but has no larger meaning. A ReferenceField allows you to edit the set of references with a particular relationship identifier from the current content object to other objects. 'KnowsAbout', 'Owns', 'WorksWith'
    allowed_types tuple of portal types () Determines all the portal types that will be searched to find objects that the user can make a reference to. It also specifies the Types that should be allowed to be added directly from the reference widget. This is only activated if the addable property is set. An empty list or tuple will allow references to all portal types. ('Document', 'File')
    allowed_types_method string None A string containing the name of a class method that will return a list of portal types to which references are allowed.
    vocabulary_display_path_bound integer 5 Sets a limit for presentation of reference items. Up to this limit, only titles are displayed. Above the limit, the path to the referenced object is also displayed. The idea is that if there are a large number of referenced items, the user will need help to differentiate them.
    vocabulary_custom_label string None A string containing a python expression that will be evaluated to get the displayed text for a referenced item. Your expression may use the variable "b" which will be a reference to the catalog brain returned by the reference lookup. "b.getObject().title_or_id()"

     

    More about References

    Archetypes References work with any object providing the IReferenceable interface. They are mantained in the uid_catalog and reference_catalog catalogs. You can find both at the root of your Plone site. Check them to see their indexes and metadata.

    Althought you could use the ZCatalogs API to manage Archetypes references, these catalogs are rarely used directly. A ReferenceField and its API is used instead.

    To set a reference, you can use the setter method with either a list of UIDs or one UID string, or one object or a list of objects (in the case the ReferenceField is multi-valued) to which you want to add a reference to. Note that None and [] are equal.
    For example, to set a reference from the myct1 object to the areferenceableobject object using the MyReferenceField field:

        >>> myct1.setMyReferenceField(areferenceableobject)

    To get the referenced object(s), just use the getter method. Note that what you get are
    the objects themselves, not their catalog brains.

        >>> myct1.getMyReferenceField()


    StringField

    A field for plain-text, unformatted strings.

    Standard properties
    Name Type Default Description some possible values
    default string


    type
    string

    widget widget StringWidget
    Special properties
    Name Type Default Description some possible values
    default_content_type string MIME type text/plain
    Rarely changed.

     

    TextField

    A string field typically used for longer, multi-line strings. The string may also be transformed into alternative formats.

    Standard properties
    Name Type Default Description some possible values
    default string


    type
    text

    widget widget StringWidget
    Special properties
    Name Type Default Description some possible values
    primary boolean False Set this True to mark the field as primary for FTP or WebDAV.

    default_content_type string MIME type text/plain A string designating MIME the default input type for the field. text/plain, text/html
    allowable_content_types tuple of MIME-type strings ('text/plain',) Used in the TextArea and Rich widgets to let the user choose between different text formats in which the content is entered. ('text/plain', 'text/html',)
    default_output_type string MIME type text/plain This is used by the accessor (get) method to and decides which MIME-Type the content should be transformed into if no special MIME-Type is demanded. 'text/html', 'text/x-html-safe'

     

    3.2. Widgets Reference

    This page is a syntax reference and general guide for defining and using Widgets.

    Widget Attribute Topics

    Common Widget Attributes

    BooleanWidget

    CalendarWidget

    ComputedWidget

    DecimalWidget

    FileWidget

    ImageWidget

    InAndOutWidget

    IntegerWidget

    KeywordWidget

    LabelWidget

    LinesWidget

    MultiSelectionWidget

    PasswordWidget

    PicklistWidget

    ReferenceWidget

    ReferenceBrowserWidget

    RichWidget

    SelectionWidget

    StringWidget

    TextAreaWidget

    Common Widget Attributes

    The table below describes attributes common to nearly all widgets. Illustrations and special attributes listings for each of the standard widgets follows.

    Name Description Possible Values
    condition
    A string containing a TALES expression to determine whether or not a field/widget is included on a view or edit page. This does not distinguish between view and edit mode.
    Your TALES expression may referenc the current context as 'object' and the Plone site root as 'portal'
    description
    Help or explanatory text for the field. Usually shown on the edit form under the label and above the input field.
    description_msgid The i18n identifier for the description message. Used to translate the message. Should be unique within your product's i18n domain. 'help_type_field'
    label The label that will appear in the field. Any string, for example, Start Date for a field start_date. Also label_msgid (takes string message ids for i18n.)
    label_msgid The i18n identifier for the label message. Should be unique within your product's i18n domain. 'label_type_field'
    i18n_domain The i18n domain specifier for your product. This should be unique for your product, and will be used to find the translation catalogs for your product. 'productname'
    modes The modes that this widget will be shown in; by default there are two modes: view and edit. A list of modes as strings; by default ("view", "edit").
    populate If this is enabled, the view and edit fields will be populated. Usually this is enabled, but for fields such as a password field, this shouldn't be the case. Usually this is true by default. True or False
    postback If this is enabled, then when an error is raised, the field is repopulated; for fields such as a password field, this shouldn't be the case. Usually this is True by default. True or False
    visible Determines whether or not the field is visible view and edit mode. This is a dictionary mapping the view mode to a string describing the visibility. Choices are visible, hidden (rendered in an HTML hidden form value), invisible (not rendered at all). For example, {'view': 'visible', 'edit': 'hidden' } means that the view will show, but the edit page will hide the value.

     

    Standard Widgets

     

    BooleanWidget

    Renders an HTML checkbox, from which users can choose between two values such as on/off, true/false, yes/no.

    booleanwidget.png 

    CalendarWidget

    Renders a HTML input box with a helper popup box for choosing dates.

    datetimewidget.png

    Special Properties

    Name Type Default Description
    format string
    Defines the date/time format using strftime, e.g. '%d.%m.%Y', for the view. (See the strftime section of the Python time documentation.
    If this is not specified, the long form of the portal's local time format is used.
    future_years integer 5 Specifies the number of future years offered by the year drop-down portion of the date widget. Do not use both future_year and end_year. (Plone 2.5+)
    starting_year integer 1999 The first year offered by the year drop-down. (Plone 2.5+)
    ending_year integer None The final year offered by the year drop-down. Do not use both future_years and end_year. (Plone 2.5+)
    show_hm boolean True Should the widget ask for a time as well as a date? (Plone 2.5+)

     

    ComputedWidget

    Generally used for ComputedField field type, it renders the computed value. Note that if your field has a vocabulary, and the field value is a key in that vocabulary, the widget will lookup the key in the vocabulary and show the result.

    Standard Properties

    Name Type Default Description
    modes tuple ('view', 'edit') As ComputedField is a read-only field, this property can be used to prevent the widget from appearing in edit templates, by setting it to just ('view',).

     

    DecimalWidget

    In edit mode, renders an HTML text input box which accepts a fixed point value.

    Special Properties

    Name Type Default Description
    thousands_commas boolean False In view mode, formats the value to shows commas for thousands. For example, when thousands_commas is True, "7632654849635.02" is displayed as "7,632,654,849,635.02". (Note: this feature is not localized; it uses commas independent of locale.
    whole_dollars boolean False Shows whole dollars in view, leaving out the cents. Enter "1.123", and "$1" is shown. (Note: this feature is not localized; it uses the dollar sign independent of locale.)
    maxlength
    255 Maximum input size; sets the HTML input tag's maxlength attribute.
    dollars_and_cents boolean False In view mode, shows dollars and cents. Enter "123.123" and "$123.12" is shown. (Note: this feature is not localized; it always uses the dollar sign, period, and two digits precision.)
    size
    5 Size of the input field; sets the HTML input tag's size attribute.

     

    FileWidget

    Renders an HTML widget so a user can upload a file.

    filewidget.png

     

    ImageWidget

    Renders an HTML widget that can be used to upload, display, delete, and replace images. You can provide a display_threshold that allows you to set the size of an image; if it's below this size, the image will display in the Web page.

    imagewidget.png

     

    Special Properties

    Name Type Default Description
    display_threshold integer 102400 Only display the image inline if img.getSize() <= display_threshold

     

    InAndOutWidget

    In edit mode, renders a widget for moving items from one list to another. Items are removed from the source list. This can be used to choose multiple values from a list. This provides a good alternative to the MultiSelectionWidget when the vocabulary is too long for checkboxes.

    inandoutwidget.png

     

    Special Properties

     

    IntegerWidget

    A simple HTML input box for a string.

    Special Properties

    Name Type Default Description
    size
    6 Size of the select widget; sets the HTML select tag's size attribute.
    Name Type Default Description
    maxlength
    255 Maximum input size; sets the HTML input tag's maxlength attribute
    size
    5 Size of the input field; sets the HTML input tag's size attribute.

     

    KeywordWidget

    This widget allows the user to select keywords or categories from a list. It is used for the Categories field in the Categorization Schema (Plone 3+) or the equivalent Keywords field on the Properties Tab (Plone < 3) of a content object.
    Keywords are drawn from the field vocabulary and/or the unique values for the field in a specified catalog.
    Additional keywords may be added unless the enforceVocabulary property of the field is True.

    Special Properties

    Name Type Default Description
    vocab_source
    portal_catalog Sets the catalog to search for additional vocabulary to be combined with the vocabulary defined for the field. Additional keywords from existing content are found using catalog.uniqueValuesFor(fieldName).
    roleBasedAdd
    True Only shows the "New keywords" input for adding keywords if the current user has one of the roles stored in the allowRolesToAddKeywords property in the site_properties property sheet in portal_properties

     

    LabelWidget

    Used to display labels on forms -- without values or form input elements.

     

    LinesWidget

    Displays a text area so that users can enter a list of values, one per line.

    lineswidget.png

     

    Special Properties

    Name Type Default Description
    rows integer 5 Rows of the lines widget; sets the HTML textarea tag's rows attribute.
    cols integer 40 Columns of the lines widget; sets the HTML textarea tag's cols attribute.

     

    MultiSelectionWidget

    A selection widget; by default it's an HTML select widget which can be used to choose multiple values. As a checkbox users can choose one or more values from a list (useful if the list is short).

    multiselectionwidget-listbox.png

     

    multiselectionwidget-checkbox.png

     

    Special Properties

    Name Type Default Description
    format string select Possible values: 'select' or 'checkbox'. Uses a either a series of checkboxes or a multi-selection list. Note that checkboxes have much better usability for short vocabularies. Consider using the InAndOutWidget for longer vocabularies.
    size
    5 Defines the size of the multi-select list. Does not apply for checkboxes.

     

    PasswordWidget

    Renders an HTML password input.

    Special Properties

    Name Type Default Description
    maxlength
    255 Maximum input size; sets the HTML input tag's maxlength attribute.
    size
    20 Size of the input field; sets the HTML input tag's size attribute.

    Standard Properties

    Name Type Default
    populate boolean False
    postback boolean False
    modes
    ('edit',)

     

    PicklistWidget

    Similar to the InAndOutWidget, but the values stay in the source list after selection.

    picklistwidget.png

     

    Special Properties

    Name Type Default Description
    size integer 6 Size of the selection box; sets the HTML select tag's size attribute.

     

    ReferenceWidget

    Renders an HTML text input box which accepts a list of possible reference values. Used in combination with the Reference Field.
    Note: In Plone 2.5 and above, the ReferenceBrowserWidget is a usually a better choice for a reference widget due to its ability to browse for content referenceable objects.

    referencewidget.png

     

    Special Properties

    Name Type Default Description
    checkbox_bound
    5 When the number of items exceeds this value, multi-selection lists are used. Otherwise, radio buttons or checkboxes are used.
    destination
    None May be:
    • ".", context object;
    • None, any place where Field.allowed_types can be added;
    • string path;
    • name of method on instance (it can be a combination list);
    • a list, combining all item above;
    • a dict, where {portal_type:} destination is relative to portal root
    addable
    False Create createObject link for every addable type
    destination_types
    None Either a single type given as a string, or a list of types given as a string, defining what types we allow adding to. Only applies when addable is set on the widget.

     

    ReferenceBrowserWidget

    A sophisticated widget for browsing, adding and deleting references.
    Standard in Plone 2.5+, available for earlier versions as an add-on product.
    Import from Products.ATReferenceBrowserWidget.ATReferenceBrowserWidget in Plone 2.5 and 3. In Plone 4, this widget has been improved and now lives inarchetypes.referencebrowserwidget.ReferenceBrowserWidget.

    Special Properties

    Name Type Default Description
    size integer
    Size of the field if not multiValued; sets the HTML input tag's size attribute.
    default_search_index string SearchableText when a user searches in the popup, this index is used by default
    show_indexes boolean False If True, a drop-down list is shown in the popup to select the index used for searching. If set to False, default_search_index will be used.
    available_indexes dict {} Optional dictionary containing all the indexes that can be used for searching along with their friendly names. Format: {'catalogindex':'Friendly Name of Index', ... } The friendly names are shown in the widget.
    Caution: If you set show_indexes True, but do not use this property to specify indexes, then all the indexes will be shown.
    allow_search boolean True If True, a search form is included in the popup.
    allow_browse True Allows the user to browse content to find referenceable content.
    startup_directory string '' Directory shown when the popup opens. Optional. When empty, the current folder is used. See the ATReferenceBrowser readme.txt for advanced usage.
    base_query dict or name of method
    Defines query terms that will apply to all searches, mainly useful to create specific restrictions when allow_browse=0. Can be either a dictonary with query parameters, or the name of a method or callable available in cotext that will return such a dictionary.
    force_close_on_insert boolean False If true, closes the popup when the user choses insert. This overrides the default behavior in multiselect mode.
    search_catalog string 'portal_catalog' Specifies the catalog used for searches
    allow_sorting boolean False Allows changing the order of referenced objects (requires multiValued).
    show_review_state boolean False If True, popup will display the workflow state for objects.
    show_path boolean False If True, display the relative path (relative to the portal object) of referenced objects.
    only_for_review_states
    None If set, content items are only referenceable if their workflow state matches one of the specified states. If None there will be no filtering by workflow state.
    image_portal_types sequence () Use to specify a list of image portal_types. Instances of these portal types are previewed within the popup widget
    image_method string None Specifies the name of a method that is added to the image URL to preview the image in a particular resolution (e.g. 'mini' for thumbnails).
    history_length integer 0 If not zero, enables a history feature that show the paths of the last N visited folders.
    restrict_browsing_to_startup_directory boolean False If True, the user will not be able to browse above the starting directory.

    The cited Plone 4 implementation of this widget includes the following additional properties:

    Special Properties

    Name Type Default Description
    startup_directory_method string ''
    The name of a method or variable that, if available at the instance, will be used to obtain the path of the startup directory. If present, 'startup_directory' will be ignored.
    show_results_without_query bool
    False
    Don't ignore empty queries, but display results.
    hide_inaccessible bool
    False
    Don't show inaccessible objects (no permission) in view mode.
    popup_width integer
    500
    Width of popup window in pixels.
    popup_height integer
    550
    Height of popup window in pixels
    popup_name string
    'popup'
    Name of template to be used for popup. To use another template you have to register a named adapter for this template.

    Example of registering a popup in ZCML:

    <zope:adapter
    for="Products.Five.BrowserView"
    factory=".view.default_popup_template"
    name="popup"
    provides="zope.formlib.namedtemplate.INamedTemplate" />

    RichWidget

    Allows the input of text, or upload of a file, in multiple formats that are then transformed as necessary for display. For example, allows you to type some content, choose formatting and/or upload a file. If available, the visual editor set in personal preferences is used for editing and formatting.

    richwidget.png

     

    Special Properties

    Name Type Default Description
    rows integer 5 Number of rows. (Since the visual mode of the RichWidget is controlled by JavaScript, this is not very useful.)
    cols integer 40 Number of columns. (Since the visual mode of the RichWidget is controlled by JavaScript, this is not very useful.)
    allow_file_upload boolean True If True, a file upload option is included with the field.

     

    SelectionWidget

    Renders an HTML selection widget, which can be represented as a dropdown, or as a group of radio buttons.

    selectionwidget-dropdown.png

     

    selectionwidget-radio.png

     

    Special Properties

    Name Type Default Description
    format string 'flex' Possible values: 'flex', 'select', 'radio'. Uses radio buttons when set to radio, and a single-selection list when set to select. Using flex will automatically use single-selection lists for more than three settings at a time, and a single-select list for up to three settings.

     

    StringWidget

    Renders an HTML text input box which accepts a single line of text. For simple text lines such as author.

    stringwidget.png

     

    Special Properties
    Name Type Default Description
    maxlength integer 255 Maximum input length in characters; sets the HTML input tag's maxlength attribute.
    size
    30 Size of the input widget; sets the HTML input tag's size attribute.

     

    TextAreaWidget

    Renders an HTML text area for typing a few lines of text. Also provides for the entry of the content in multiple formats when allowed_content_types in the enclosing TextField allows it.

    textareawidget.png

     

    Special Properties

    Name Type Default Description
    rows integer 5 Number of rows for the edit widget; sets the HTML textarea tag's rows attribute.
    cols integer 40 Column width of the edit widget; sets the HTML textarea tag's cols attribute.
    append_only boolean False Set this attribute to True to make an append-only TextArea widget. New text gets added to the top of the existing text, dividing the new text from the existing text using the divider property. The existing text is shown below the TextArea, and is not editable. This currently works with TextArea widgets and using plain text format.
    divider string ======================== Divider text marker to use for append only text areas. Only used then the append_only property is True.
    maxlength integer False If non-zero, sets a maximum input length in characters. Since the HTML textarea tag has no maxlength property, this is enforced via a JavaScript snippet. So, it is is not applicable when JavaScript is unavailable.

    Add-on Widgets

    To find all available add-on widgets contributed by the community, follow this link.

    3.3. Validator Reference

    A quick reference to the built-in Archetypes validators.

    Using Validators

    Archetypes fields may have validators specified in the Field schema. For example, the schema for the basic page type includes the stanza:

    ATDocumentSchema = ATContentTypeSchema.copy() + Schema((
        TextField('text',
    ...
                  validators = ('isTidyHtmlWithCleanup',),
    ...
        ),
    

    This specifies that the isTidyHtmlWithCleanup test will be applied to validate form input.

    You may specify a sequence of validators:

    validators = ('isMaxSize', 'isTidyHtmlWithCleanup',),
    

    and the validators will tested in order.

    The validators sequence may contain two kinds of entries:

    • The string names of validators registered with the validation service (see Products.validation);
    • Instances of classes implementing the IValidator interface (Products.validation.interfaces.IValidator.IValidator).

    A validation specification using a validator class instance can look like:

    validators = ( ExpressionValidator('python: int(value) == 5'), )
    

     

    Registered Validators

    These are validators pre-registered with the validation service. They may be specified by name.

    Name Use Details
    isDecimal Is the input a decimal number. Allows exponent notation.
    isInt Is the input an integer.
    isPrintable Does not contain unprintable characters r'[a-zA-Z0-9\s]+$'
    isSSN Is a well-formed social-security number Very naive: r'^\d{9}$'
    isUSPhoneNumber Is a valid US phone number Looks for 10 digits, ignores spaces, parens and dashes
    isInternationalPhoneNumber Is a valid international phone number Looks for any number of digits, ignores spaces, parens and dashes
    isZipCode Very naive: is five or nine digits
    isURL Is a valid URL Recognizes most protocols
    isEmail Is a valid e-mail address A pretty good regular expression test
    isMailTo Is an e-mail address preceded by "mailto:"
    isUnixLikeName Passes the basic test to be a Unix-style name r"^[A-Za-z][\w\d\-\_]{0,7}$"
    isMaxSize Tests if an upload, file or something supporting len() is smaller than a given max size value. Tests against a maxsize attribute on the field
    isValidDate Tests whether or not input value can be converted to a DateTime object.  
    isEmpty Input value must be empty.  
    isEmptyNoError Input value must be empty. Validation will fail if input value is not empty; but no error will show.
    isValidId Input value is a valid identifier.  
    isTidyHtml Uses mx.Tidy to validate HTML input. Fails on errors and warnings.  
    isTidyHtmlWithCleanup Uses mx.Tidy to validate HTML input. Fails only on errors; cleans up.  
    isNonEmptyFile The uploaded file is not empty.  
    isTAL Validates as Template Attribute Language  

     

    Useful Validation Classes

    These classes are useful for creating your own validation class instances. Imports and prototypes are shown. See source for details.

    ExpressionValidator

    Evaluates an expression to test the input value.

    from Products.validation.validators.ExpressionValidator import ExpressionValidator
    
    def ExpressionValidator(expression=None, errormsg=None)
    

    RegexValidator

    Tests value against a regular expression after removing ignore

    characters.

    from Products.validation.validators.RegexValidator import RegexValidator
    
    def RegexValidator(name, regex, title=name, description='',
     errmsg='fails tests of %s' % name, ignore=None)
    

    RangeValidator

    Tests to see if specified minval <= input_value < maxval

    from Products.validation.validators.RangeValidator import RangeValidator
    
    def RangeValidator(name, minval=0.0, maxval=0.0, title='', description='')
    

    4. Other Useful Archetypes Features

    Complementary features you'd be pleased to know about.

    4.1. How to use events to hook the Archetypes creation process

    Times have changed since the days of at_post_create_script(). Here is the way to hook into Zope3 (or Five's) event system in order to execute code during the Archetypes content creation and or editing process.

    In the old days the only way to execute code during the object creation process for Archetypes was to add a method to your content type called at_post_create_script. In this script you would add any code that should execute after Archetypes was done creating the object.

    The new method for hooking the Archetypes object creation and editing process is to use Zope3 style events, like Products.Archetypes.interfaces.IObjectInitializedEvent.

    Prerequisites

    Have a content type handy so we can add a post creation hook to it. To learn how to create a content type, check previous sections of this manual.

    We're going to use a content type called ExampleContent with the interface IExampleContent for this how to. The code structure will look like this:

    tutorial/configure.zcml
    tutorial/interfaces.py
    tutorial/content/examplecontent.py

    Step by step

    First let's create the interface for our ExampleContent type. In interfaces.py, add:

    from zope.interfaces import Interface
    
    class IExampleContent(Interface):
        ''' Interface for the ExampleContent type
        '''

    You can store the implementation for your event handlers anywhere but for the purpose of this example we're going to put it in the same module as the ExampleContent type:

    from zope.interface import implements
    from Products.ATContentTypes import atct
    
    def addSubFolder(obj, event):
        obj.invokeFactory(type_name='Folder', id='subfolder')
    
    class ExampleContent(atct.ATFolder):
        implements(IExampleContent)
        portal_type = archetype_name = 'ExampleContent'  # <-- this is no longer needed with GenericSetup.

    All we need to do now is register the addSubFolder method as a handler for Products.Archetypes.interfaces.IObjectInitializedEvent and for anything implementing the IExampleContent interface. We do this in a configure.zcml file:

    <subscriber for=".interfaces.IExampleContent
                     Products.Archetypes.interfaces.IObjectInitializedEvent"
                handler=".content.examplecontent.addSubFolder" />

    Notice that there are two interfaces in the "for" attribute. This is because we are registering a multi-adapter. Now when you add an ExampleContent type the addSubFolder method will be executed after Archetypes has created the object. The object itself will be passed to the handler and we can use the object reference to make additional modifications, in this case adding a sub folder.

    You can register as many handlers as you need.

    Warnings from your future

    Having implemented all of your content type's event hooks you might then run off and try using invokeFactory somewhere in your code only to realize that your IObjectInitializedEvent handlers are not being executed. This is because invokeFactory does not notify Zope's event system that new objects are being created. You have to provide these notifications yourself. So here is an example:

    import zope.event
    from Products.Archetypes.event import ObjectInitializedEvent
    some_folder.invokeFactory(type_name='ExampleContent', id='foobar')
    obj = getattr(some_folder, 'foobar')
    zope.event.notify(ObjectInitializedEvent(obj))

    This will both create your object and invoke any IObjectInitializedEvent handlers you have registered. Notice that we are importing ObjectInitializedEvent, not the interface IObjectInitializedEvent. We want to actually instantiate an event passing it our newly created object as the single parameter and then pass the event to zope.event.notify. From there, Zope takes care of figuring out which handlers need to execute.

    So for example, in our addSubFolder method above, any events registered for the folder we created would not fire. To complete our hook in this case we should provide a notification for our newly created folder. Archetypes or other products may be expecting notifications so when using invokeFactory, always send an IObjectInitializedEvent for the object. The complete method looks like this:

    def addSubFolder(obj, event):
        obj.invokeFactory(type_name='Folder', id='subfolder')
        folder = getattr(obj, 'subfolder')
        zope.event.notify(ObjectInitializedEvent(folder))
    

    Further information

    The IObjectInitializedEvent is fired once during the objects creation process. To hook the editing process for an object use IObjectEditedEvent.

    The Sending and handling events tutorial is a little out of date but provides a broader explanation of the underlying mechanics. Walking through Five to Zope 3 - Events is another great introduction to events handling. 

     

    5. Appendix: Practicals

    5.1. How-To Extend A Basic Archetype Content Type

    This How-To describes what to do next after you've gotten the basic Archetype examples working. You can add functions, views, actions, and edit-page validation.

    Ok, so you've got the Archetype examples to work, and now you'd like to know how to flesh out the basic example into something useful. (That's how I started; I knew I needed to make new Content Types, but didn't know how they worked. I got the examples going, then tried to figure out how to modify them to do what I wanted.) I found learning Plone/Zope very frustrating until I got to this point. Then, once I figured out how to make Content Types do what I wanted, it all made sense.

    Adding Functions

    This is probably one of the first things on your mind. Where do I add functions to my Content Type? How do I call them? What syntax do I use? Well, these were the things that I wondered about. I figured out how to do them after some trial and error. I never made a real website before, where I had to write scripts (a blog doesn't count). So even though I had a lot of python experience, I was confused at first. [First, know that the Zope server converts a URL path into an object path, to find the object that will render your page. This is covered in the Zope Developer's guide. The key point you need to know is that there's a parallel between the URL and your object hierarchy, but it's not exactly the same.]

    To cut to the chase, Zope figures out which object/function the URL is pointing to, and it takes the query string (?arg=val,arg2=val2, etc.) and uses it to figure out all the right arguments for the function call. So, you define your function in the ususal way, i.e.,

    class MyExample (BaseContent):
      """ My example Archetype Content Type. """
    
      # define the schema
      # override the default actions
    
      def my_function (self, foo1, foo2):
        """ You need a doc string here!! I lost a lot of time finding this out.
            Archetypes needs this when registering the function in the framework.
            You'll get a 404 error if you forget the doc string. """
        temp1 = "foo"  # this is not persisted in the ZODB
        self.this_is_a_persisted_member_in_the_ZODB = "I'm here to stay %s" % (foo1,)
        # if you don't return anything, then the Zope server will not re-render a page
        # anything you return will be rendered
        # return "got here" will show up as text
        # return context.index_html() will return the default page (should be reasonable in any content; people won't get lost)
        # return context.base_edit() has the effect of "jumping" to the edit page
    Adding Actions

    I was confused when I saw the description of actions in the Plone manual. Here's how I think of them: they're just the hyper-linked tabs along the top of the Content Type (content actions) or horizontal site navagation (site actions). The links are typically to a Content Type function (that returns a page), or to a page template (I only know how to make .pt and .cpt types so far). The actions for the Content Type are defined (overriden) using the Factory Type Information format, and the process well described in the Archetype tutorials. I'll just add that you can make most of the tabs (actions) visible=True or visible=False. You can append your own actions that show up as tabs for your content type.

    Changing Page Views

    You can change the various views of your content type by defining new page templates to display your data. Typically, these page templates (.pt) are placed within the skins/ directory of your product. I can't fill you in on exactly how Zope maps the URL (http://.../myArchExample/my_view) to the my_view.pt, but the details are taken care of (by the Install.py script?) and you should put your page templates in the skins/ directory. [This section needs updating, as soon as I learn how it's done.]

    Validating the Edit Form

    You probably have a need to validate the data that users enter on the edit page. This process is called validation, and the scripts that implement the rules are called validators. There's a clean way to do this in Archetypes using built-in field validators and your own post_validation() function for the Content Type. You don't have to write any (.cpt,.cpy,.vpy) form templates, or controller scripts. Of course, validation is optional, so you can skip either step.

    1. Use field validators on individual entries (see the validator = (,) field attribute). This is the first-line of validation.
    2. Define a post_validation() function. This allows you to validate fields in the context of the entire class, and set error (re-do) flags for individual fields.

    After the user hits the 'submit' button on the edit page, the field validators are run first. If any validators fail, the input field is highlighted, and the user is sent back to the edit page to fix the errors.

    If all the field validators pass, then your post_validate(self, REQUEST, errors) is called. The form keys and data are passed to you in the REQUEST dictionary. Your code will validate the edit form values in the REQUEST dictionary. If you see errors that require fixing, you'll set them in the errors dictionary (using the corresponding key in the REQUEST). For example, here's a

    class MyExample (BaseContent):
      """ My example Archetype Content Type. """
    
      # define the schema
      # override the default actions
    
      def post_validate (self, REQUEST, errors):
        """ This function checks the edit form values in context.
            It's called after the field validation passes.  """
        if REQUEST['type'] == 'buy' and REQUEST['quantity'] == 0:
          error['quantity'] = "Quantity must be non-zero."
    Adding Child Members

    If your content type is a folder-like object, you can write functions that will add child objects. This may be useful, for example, if your Content Type is a ledger, and you need to add new transactions when the user hits an action. The following example code shows how to do this.

    class MyExample (BaseFolder):
      """ My example Archetype Content Type. """
    
      # define the schema
      # override the default actions
    
      def addTransaction (self, type, quantity):
        """ This function creates a new MyTransaction object in the folder. """
        # create a unique id for this transaction
        newId = self.generateUniqueId('MyTransaction')
        # create a new MyTransaction object
        self.invokeFactory(id=newId, type_name='MyTransaction')
        myTransaction = getattr(self, newId)
        return myTransaction.base_edit()     # send the user to the edit page

    5.2. Implement Archetypes ComputedField and ComputedWidget on your Product and reference other Fields

    A simple use of ComputedField and ComputedWidget referencing other fields, built-in or 3rd party, in the same Plone product

    Motivation

    There are many reasons why this how-to exists:

    • Almost no Archetypes examples using ComputedField and ComputedWidget
    • We want our product to process some data on itself, but reload isn't a matter of concern
    • We love PZP (Python-Zope-Plone)!

    What do we need?

    • A Plone installation
    • A nice text editor (my wintel box runs SciTE)
    • Some product (for real dummies like me, try http://plone.org/documentation/tutorial/anonymously-adding-custom-content-types-with-argouml-and-archgenxml/view)

    What we are going to achieve?

    • Make a page process it's own information


    Let's say you created a product, maybe using ArgoUML (an UML editor) and ArchGenXML. One thing you might realize it's missing on creating UML's is that: we create classes (Plone products), their types are also classes (Archetypes' fields and widgets) and Attributes (Fields and Widgets' properties) as TD's (tagged data) for Archetypes' types, but we have no methods!

    But we could do more if we inserted code: classes are made of attributes and methods (code). But as UML editors are not that Python friendly, we do that by hand.

    So, how?

    If you already have navigated the path of a product, you've stumbled on some source files (.py) inside, so take some time to read their source (Read the source, Luke!). Probably you've seen some like this (for example, MyOrder.py):

    from AccessControl import ClassSecurityInfo
    from Products.Archetypes.atapi import *
    from Products.Laborde.config import *
    
    from Products.DataGridField import DataGridField, DataGridWidget # we talk about this later
    from Products.DataGridField.Column import Column #really!
    
    schema = Schema((
        StringField(
            name='PurchaseOrderID',
            widget=StringWidget(
                label="PurchaseOrderID",
                description="Enter this purchase order unique identification number.",
                label_msgid='MyOrder_label_PurchaseOrderID',
                description_msgid='MyOrder_help_PurchaseOrderID',
                i18n_domain='MyOrder',
            ),
            required=True,
            searchable= True
        ),
        DataGridField(
            name='PurchaseOrderItems',
            required=True,
            searchable=True,
            widget=DataGridWidget(),
            allow_empty_rows = False,
            columns=(
                "Maker",
                "Model",
                "Description",
                "UnitaryCost",
                "Quantity"
            ),
        ),
    
        ComputedField(
            name='TotalCost',
            searchable=True,
            expression="context.calculateTotal()",
            widget=ComputedWidget(
                label="Total",
                modes=('view')
            ),
        ),
    
    ),
    )
    
    PurchaseOrder_schema = BaseSchema.copy() + \
        schema.copy()
    
    class PurchaseOrder(BaseContent):
        """
        """
    
        # some class defitnitions
    
        # a function that calculates total
        # but it doesn't even check (try-except) data it uses
    
        def calculateTotal(self):
            Total = 0.0
            for n in self.PurchaseOrderItems:
                Quantity = float(n['Quantity'])
                UnitaryCost = float(n['UnitaryCost'])
                Total = Total + Quantity * UnitaryCost
            Total = '%1.2f' % Total # this makes our total have 2 decimals for display
            return Total
    
    registerType(PurchaseOrder, PROJECTNAME)
    

     

    Aargh! I've just core dumped and almost killed 30!

    The above code can be divided in two parts: Schema and Class (Product). We have declared 3 different fields in the schema: the first is a bultin trivial Archetype field; the second is imported from the Product DataGridField (you need it installled on your Plone instance to work); the third is our the field we want to change as someone changes values on the form.

    expression="dir()" # useful to check avaible objects

    ,

    expression="1+1" # 10 if you have two neurons, like me. Otherwise, 2.
    

    ,

    expression="dir(context)" # avaible context child objects

    or

    expression="context.calculateTotal()" # Voilá! Reference to some real(?) code!


    We've just called something (a function, in fact) named calculateTotal.

    But smart as we are, we realized that expresssions called this way must be somewhere in our context scope. I mean, inside our class definition.

    The function definition itself isn't that simple: it adds up the total and returns its value. What isn't simple? Our generous DataGridField returns a tuple of dictionaries like:

    (
        {"Maker":"HP","Model":"scanjet 4670","Description":"scanner","UnitaryCost":"99.00","Quantity":"1"},
        {"Maker":"LG","Model":"L173SA","Description":"17 LCD monitor","UnitaryCost":"299.95","Quantity":"2"},
        {"Maker":"Seagate","Model":"SA32300","Description":"Hard drive","UnitaryCost":"134.50","Quantity":"2"}
    )
    

     

    The for loop iterates over every item on the tuple and searches for two dictionary items. Other field are rather simple to retrieve data: just use field's name attribute.

    The widget=SomeWidget(modes='view',...) realizes the feat of showing this field only on the view mode: not when adding the item and editting, nor when editting an existing item.

    What's next?

    • What could we do with PhotoField (ImageWidget)?
    • try-except is always recomended
    • Could this better than mutate?
    • Can we make a file avaible for download with some strange mime type based on the information of this product?

    5.3. Making the view page of a content type use your schemata declarations

    How to make the schemata declarations in a Archetypes schema be used in the view page of a content type.

    Introduction

    Declaring schematas in your Archetype schema has the nice effect of displaying the fields of the different schemas on different edit pages (very much like a "wizard" for adding a new content type instance). Often times you might like to also have the view page be divided according to the different schemas you have declared. This is not done automatically by Archetypes so in this document I'll show you how to do it yourself. Don't worry! It's really quite easy.

    Python class and schema

    I'll be using a simple article content type I have constructed for this how-to to show you how the schematas can be used on your content type's view page. The example type is really not very usable, but just complex enough to show you how to do this. It has a schema of four fields in addition to the default id and title fields: abstract, body, firstname, lastname. The abstract and body fields are in a schemata named article and the firstname and lastname field in a schemata named author.

    I have also defined the title and id fields to be in schemata article. This was done so I won't have an extra schemata called default and so I can use the title field for the title of the article. (Remember to use BaseSchema.copy()!)

    The class itself has just the schema declaration and a new view action definition. I have defined the view action to use a template called article_view that we'll be getting to shortly.

    Here is the file in it's entirety:

     

    from Products.Archetypes.public import *
    
    from Products.CMFCore import CMFCorePermissions
    
    
    
    from config import PKG_NAME
    
    
    
    schema = BaseSchema.copy() + Schema((
    
            TextField('abstract',
    
                    required=1,
    
                    searchable=1,
    
                    widget = TextAreaWidget(description="Abstract", label="Abstract"),
    
                    schemata = 'article'),
    
            TextField('body',
    
                    required=1,
    
                    searchable=1,
    
                    widget = TextAreaWidget(description="Body", label="Body"),
    
                    schemata = 'article'),
    
            TextField('firstname',
    
                    required=1,
    
                    searchable=1,
    
                    widget = StringWidget(description="First name", label="First name"),
    
                    schemata = 'author'),
    
            TextField('lastname',
    
                    required=1,
    
                    searchable=1,
    
                    widget = StringWidget(description="Last name", label="Last name"),
    
                    schemata = 'author'),
    
            ))
    
    
    
    schema['title'].schemata = 'article'
    
    schema['id'].schemata = 'article'
    
    
    
    class Article(BaseContent):
    
            schema = schema
    
    
    
            actions = (
    
                            {'id': 'view',
    
                            'name': 'View',
    
                            'action': 'string:${object_url}/article_view',
    
                            'permissions': (CMFCorePermissions.View,)
    
                            },
    
            )
    
    
    
    registerType(Article, PKG_NAME)
    
    

    View template

    The view template article_view is the main part of this how-to. It has the page template code to generate the different pages for the different schematas.

    First you should copy the base.pt file from the Archetypes skins folder (on my Debian GNU/Linux unstable it's in /usr/share/zope/Products/Archetypes:1.3/skins/archetypes) to your product's skins folder. It has most of the template code you'll need ready, so you'll only need to make some minor changes to make this work. Also it uses all the default macros and such, so you'll view page will look like a real plone page.

    The base.pt template just goes through all the fields of your content type and shows their widgets. What we want to do is to have it only go through the fields of one schemata at a time and give us links to see the others. This will be done using REQUEST parameters to the scripts.

    I'll go though the changes from the top of the file downwards so you'll have a easier time keeping up and making the changes to your own template.

    Links to the different schematas

    We'll want the list of different schematas to be at the top of the page, so that'll go in first. Find the line that says '<metal:main_macro define-macro="main">'. This is where the body of the template starts. After this line is the header with the title and the little icons for edit, print and such, and I want to have my links to show up above that. So after the beginning of the body and above the header add the following code:

     

            <div style="margin-bottom: 1em">
    
                    <span tal:repeat="schemata python: here.Schemata().keys()">
    
                            <b tal:condition="python: schemata != 'metadata'">[<a tal:attributes="href string:?page=${schemata}"><span tal:replace="schemata" /></a>]</b>
    
                    </span>
    
            </div>
    
    

    This just repeats over our schematas' names (we get them with here.Schemata().keys()) and prints all of them on one line as links, each one in square brackets. The links are to the same view page, but they all set a parameter in REQUEST called page that points to the schemata we are linking to. This isn't very pretty so you'll probably want to make them look nicer if you like. The 'schemata != 'metadata'' part is because there's a schemata called metadata created automatically for your content type to support default standard metadata which can be set via the properties tab and that we do not want to include here.

    Showing only the schemata we want

    In the next part we'll be diving deeper into the code. You're looking for a part that says 'tal:repeat="field python:here.Schema().filterFields(isMetadata=0)"'. This repeats through the fields of your content type and the following parts show their widgets. What we want to do here is to have it repeat through the fields of the schemata we want instead of all of them. In the previous part we set a parameter in REQUEST called page that points to the schemata we want to see, and here we want to use that to pick which schemata's fields to loop over. So just go ahead and replace the part with 'tal:repeat="field python:here.Schemata()[here.REQUEST.get('page', here.Schemata().keys()[0])].filterFields(isMetadata=0)"'. This just gets the page parameter from REQUEST (if page is not found, ie. the template is called with no parameters, then first schemata, in this case article, is used) and loops through the fields of the schemata with that name.

    The completed article_view.pt looks like this:

     

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
    
          lang="en"
    
          xmlns:tal="http://xml.zope.org/namespaces/tal"
    
          xmlns:metal="http://xml.zope.org/namespaces/metal"
    
          xmlns:i18n="http://xml.zope.org/namespaces/i18n"
    
          metal:use-macro="here/main_template/macros/master">
    
    
    
      <head><title></title></head>
    
    
    
      <body>
    
    
    
        <div metal:fill-slot="main">
    
    
    
          <metal:main_macro define-macro="main">
    
    
    
            <div style="margin-bottom: 1em">
    
                    <span tal:repeat="schemata python: here.Schemata().keys()">
    
                            <b tal:condition="python: schemata != 'metadata'">[<a tal:attributes="href string:?page=${schemata}"><span tal:replace="schemata" /></a>]</b>
    
                    </span>
    
            </div>
    
    
    
            <metal:header_macro define-macro="header">
    
              <div metal:use-macro="here/document_actions/macros/document_actions">
    
                Document actions (print, rss, etc)
    
              </div>
    
              <h1 tal:content="title_string | here/title_or_id" />
    
              <tal:has_document_byline tal:condition="exists:here/document_byline">
    
                <div metal:use-macro="here/document_byline/macros/byline">
    
                  Get the byline - contains details about author and modification date.
    
                </div>
    
              </tal:has_document_byline>
    
            </metal:header_macro>
    
    
    
            <metal:body_macro metal:define-macro="body"
    
                              tal:define="field_macro field_macro | here/widgets/field/macros/view;"
    
                              tal:repeat="field  python:here.Schemata()[here.REQUEST.get('page', here.Schemata().keys()[0])].filterFields(isMetadata=0)">
    
              <tal:if_visible define="mode string:view;
    
                                      visState python:field.widget.isVisible(here, mode);
    
                                      visCondition python:field.widget.testCondition(here, portal, template);"
    
                              condition="python:visState == 'visible' and visCondition">
    
                <metal:use_field use-macro="field_macro" />
    
              </tal:if_visible>
    
            </metal:body_macro>
    
    
    
            <metal:folderlisting_macro metal:define-macro="folderlisting"
    
                                       tal:define="fl_macro here/folder_listing/macros/listing | nothing;
    
                                                   folderish here/isPrincipiaFolderish | nothing;">
    
                <tal:if_folderlisting condition="python:folderish and fl_macro">
    
                    <metal:use_macro use-macro="fl_macro" />
    
                </tal:if_folderlisting>
    
            </metal:folderlisting_macro>
    
    
    
            <metal:footer_macro define-macro="footer">
    
            </metal:footer_macro>
    
    
    
          </metal:main_macro>
    
    
    
        </div>
    
    
    
      </body>
    
    
    
    </html>
    
    

    Conclusion

    So that was it. I told you it was going to be easy!

    Happy hacking!

    5.4. Enabling versioning on your custom content-types

    Plone 3 includes a robust versioning system as well as a tool for viewing diffs, which allows you to easily see the changes between two revisions. This document explains how to integrate versioning and diff functionality with your custom Archetypes-based content-types.

    Prerequisites

    You'll need a Plone 3 instance and a custom product which contains at least one Archetypes-based content-type on which you want to enable versioning.  

    You'll also need to have the Working Copy Support (Iterate) product installed.  This product is part of the Plone core so to install it, all you need to do it visit the Add-on Products section (a.k.a. Quickinstaller) of the Plone control panel and select it for installation.

    Creating a setup handler script for GenericSetup

    The integration code we'll be writing here is best run as a setup handler using GenericSetup. If your product doesn't already have a GenericSetup profile and a custom setup handler, this tutorial provides instructions on how to create those.

    Declaring versionable types in your setup handler

    The portal_repository tool stores a list of content-types on which version is enabled.  With the following code we create a list of the custom types on we which we want to activate versioning and then notify the repository tool to start versioning the types in this list.

    If you copy the code below, make sure to edit the TYPES_TO_VERSION setting so that it contains a list of the types on which you want to activate versioning.

    from Products.CMFCore.utils import getToolByName
    from Products.CMFEditions.setuphandlers import DEFAULT_POLICIES
    
    # put your custom types in this list
    TYPES_TO_VERSION = ('Scientist', 'Article', 'Presentation')
    
    def setVersionedTypes(portal):
        portal_repository = getToolByName(portal, 'portal_repository')
        versionable_types = list(portal_repository.getVersionableContentTypes())
        for type_id in TYPES_TO_VERSION:
            if type_id not in versionable_types:
                # use append() to make sure we don't overwrite any
                # content-types which may already be under version control
                versionable_types.append(type_id)
                # Add default versioning policies to the versioned type
                for policy_id in DEFAULT_POLICIES:
                    portal_repository.addPolicyForContentType(type_id, policy_id)
        portal_repository.setVersionableContentTypes(versionable_types)

    Now we call this function from the importVarious() function in our setup handler script.  Make sure to pass the portal object as a parameter.

    def importVarious(context):
        """Miscellanous steps import handle"""
        portal = context.getSite()
        setVersionedTypes(portal)

    Enabling visual diffs on your versioned types

    Now that you've enabled versioning, you'll probably want to enable visual diffs so you can compare the changes made between different versions of an object.

    Starting in Plone 3.2 the diff tool can be configured via a GenericSetup configuration file.  You'll want to create or edit the diff_tool.xml file in the /profiles directory of your product.  Here's an example confirmation file that enables compound diffs on the 3 content-types used in the example above.

    <?xml version="1.0"?>
    <object>
      <difftypes>
        <type portal_type="Scientist">
          <field name="any" difftype="Compound Diff for AT types"/>
        </type>
    
        <type portal_type="Article">
          <field name="any" difftype="Compound Diff for AT types"/>
        </type>
    
        <type portal_type="Presentation">
          <field name="any" difftype="Compound Diff for AT types"/>
        </type>
      </difftypes>
    </object>

    For Plone versions earlier than 3.2, there is not a GenericSetup handler for configuring the diff tool, but you can create these settings through the ZMI using the portal_diff tool.  In the Portal Type drop-down menu select the content-type on which you want to enable diffs.  In the Field name box type "any".  For the Diff Type select "Compound Diff for AT Types".  Finally click the Add Field button.  Repeat these steps for each content-type.

    Deploying your new versioning and diffs policy

    To deploy these changes you'll need to re-run you product's GenericSetup policy.  If your instance is not running in debug mode, you'll first need to restart your Zope instance to make your new filesystem code available. 

    Assuming you've used paster to create your product package, reinstalling your product in the Add-on Products section of the Plone control panel should be sufficient to re-run your GenericSetup policy.  If your product's install method does not run your GenericSetup policy, you may need to visit the portal_setup tool in the ZMI and run it manually.

    Verifying that versioning and visual diffs are now enabled

    Visit an instance of one of the types on which you've enabled versioning.  Make some edits to one or more of the fields save those changes.  Next, visit the history tab for the item you just edited. You should see a list showing two versions.  Click the link to compare versions you should see a diff showing you what has been changed between the two revisions.

    5.5. b-org: Creating content types the Plone 2.5 way

    Plone 2.5 brings us closer to the promised land of Zope 3. Zope 3 brings us a new way of working. This tutorial will show how to marry the old and the new, to make Plone products that are more extensible, better tested and easier to maintain.

    5.5.1. Introduction

    What is b-org, and what will you learn here?

    b-org stands for "base-organisation". The name had nothing whatsoever to do with my desire to get an svn URL of http://svn.plone.org/svn/collective/borg. Promise. In fact, it used to be called company, which some people rightly pointed out is a bit too generic and opens up the possibility of conflicts with other people's code. It just proves that naming generic components is difficult.

    Generic is the key word here. Functionally, b-org provides infrastructure to help you manage Departments, Employees and Projects in a natural way. Departments are containers for employees, employees are linked to projects by references. Using membrane, these objects become sources for users and groups, so that a department is a group for all the employees in it, and employees become real users of the system, with usernames and passwords. Projects manage local roles, so that employees that have been associated with the project are able to add and modify content in it. Other users may or may not be able to view content in a project, depending on its workflow state.

    However, b-org makes no assumptions about which metadata you want to associate with departments, employees or projects. For that, it expects you to plug in your own content schema. It also delegates almost all its functionality to smaller components, so that if you, for example, want to store authentication details via LDAP or change the way in which users are employees to projects, you can do so by implementing small, isolated components rather than sub-classing and re-implementing large chunks of the three basic content types.

    That's all well and good, but you're probably not going to want to read a lengthy tutorial just about how great b-org is. As the title promises, this tutorial is about leveraging new technologies available in Plone 2.5 to write better content types and other software in Plone. Hopefully, you will find the techniques described here useful whether you are writing a member management module using membrane (mmmm), or other code. I for one, want to go and rewrite several of my products (like Poi) to make them more extensible and flexible after having adopted these techniques. Hopefully, you will also learn something about the development process, in particular test-driven development, that I followed, and how the future of Plone is entangled in Zope 3.

    This tutorial should be viewed as complementary to, rather than superceding, my earlier tutorial entitled RichDocument - Creating Content Types the Plone 2.1 way. The techniques of RichDocument, in particular relating to extending ATContentTypes, are still valid in Plone 2.5. What Plone 2.5 allows us to do, however, is to achieve better separation of concerns between content storage, business logic and view logic, due to the added spices of Zope 3. For RichDocument, the gain wouldn't be that great since it's relatively simple (and focuses on doing as little as possible by re-using as much as possible from ATContentTypes). Hence, I didn't update the RichDocument tutorial, nor do I feel as compelled to update RichDocument itself (yet). b-org is a more ambitious example which allows us to illustrate the new techniques more fully.

    One thing to note is that this tutorial is still centered on Archetypes, and assumes you know the basics of Archetypes development on the filesystem. Archetypes is rooted in a pre-Zope 3 world, and there are times when we have to accommodate it in ways that make our clean patterns a bit messier - luckily, not too often. There are ways of managing content in Zope 3 that can be applied to Plone, for example by way of zope.formlib, but these are generally not quite ready to replace what we can do today with Archetypes. In the future, they may be, but more likely Archetypes will converge a bit more with its Zope 3 equivalents and blur the lines between the two approaches. The upshot is that what you know about Archetypes today continues to be relevant, and is augmented by the Zope 3-inspired techniques you will find here.

    5.5.2. A whirlwind tour of Zope 3

    Zope 3 is still fairly new. After reading this tutorial, it should hopefully start to feel a bit more familiar. In this section, we will give a brief overview of what is different in Zope 3 and how it fits into Plone.

    The name Zope 3 is a lie. True - it is brought to you by many of the same clever people who built Zope 2, one of the most advanced open source app servers of its day. True, it is still Python, it still publishes things over the web, and there are still Zope Page Templates. However, Zope 3 is about small, re-usable components orchestrated into a flexible framework. It is this flexibility that allows us to use Zope 3 technologies in Zope 2 applications like Plone.

    A piece of wizardry called Five (Zope 2 + Zope 3 = Five, geddit?) makes a number of Zope 3 components directly available in Zope 2, and since Zope 2.8, almost all of Zope 3 has shipped with Zope 2 as a python library. Plone 2.5's primary purpose was to lay the foundations for taking advantage of Zope 3 technologies in Plone.

    Zope 3 may seem a bit alien at first, because it uses strange concepts such as adapters and utilities. Luckily, these are not so difficult to understand, and once you do, you will find that they help you focus your development on smaller and more manageable components. You will also find that these basic concepts underpin most of the innovative parts of Zope 3.

    Interfaces

    Everything in Zope 3 starts with interfaces. Unlike Java or C#, say, Python does not have a native type for an interface, so an interface in Zope 3 is basically a class that contains only empty methods and attributes, and inherits from Interface. Here is a basic example:

    from zope.interface import Interface, Attribute
    
    class IShoe(Interface):
        """A shoe
        """
    
        color = Attribute("Color of the shoe")
        size = Attribute("Shoe size")
    
    class IShoeWearing(Interface):
        """An object that may wear shoes
        """
    
        def wear(left, right):
            """Wear the given pair of shoes
            """
    

    Interfaces are primarily documentation - everything has docstrings. Also note that the wear() method lacks a body (there is not even a pass statement - the docstring is enough to keep the syntax valid), and does not take a self parameter. That is because you will never instantiate or use an interface directly, only use it to specify the behaviour of an object.

    An object can be associated with an interface in a few different ways. The most common way is via its class. We say that the class implements an interface, and objects of that class provide that interface:

    from zope.interface import implements
    
    class Shoe(object):
        """A regular shoe
        """
    
        implements(IShoe)
        
        color = u''
        size = 0
    

    The implements(IShoe) line means that objects of this class will provide IShoe. Further, we fulfill the interface by setting the two attributes (we could have implemented them as properties or used a an __init__() method as well). The IShoeWearing interface will be implemented in the section on adapters below.

    We use interfaces to model components. Interfaces are normally the first stage of design, in that you should define clear interfaces and write actual classes to fulfill those interfaces. This formalism makes for great documentation - interfaces are conventionally found in an interfaces module, and this is typically the first place you look after browsing a package's documentation. It also underpins the adapter and utility system - otherwise known as the Component Architecture - as described below.

    Note that you can use common OOP techniques in designing interfaces. If one interface describes a component that has an "is-a" or "has-a" relationship to another component, you can let interfaces subclass or reference each other. An object will provide the interfaces of its class, and all its base-classes, and all base-interfaces of those interfaces. Don't worry about untangling that - it works the way you would expect.

    You can also apply interfaces directly to an object. Of course, if that interface has methods and attributes, they must be provided by the object, and unless you resort to crazy dynamic programming, the object will get those from its class, which means that you may as well have applied the interface to the class. However, some interfaces don't have methods or attributes, but are used as markers to distinguish a particular feature of an object. Such marker interfaces may be used as follows:

    class IDamaged(Interface):
        """A shoe that is damaged
        """
    
    >>> from zope.interface import alsoProvides
    >>> boot = Shoe()
    >>> IDamaged.providedBy(boot)
    False
    >>> alsoProvides(boot, IDamaged)
    >>> IDamaged.providedBy(boot)
    True
    

    Marker interfaces are very useful for things that change at run-time in response to some event (e.g. some user action), and thus cannot be determined in advance. In a moment, you see that what you will learn about adapters and adapter factories below also applies to marker interfaces - it is possible to alter which adapter factory is invoked by applying a different marker interface.

    It's also possible to apply interfaces directly to classes (that is the class itself provides the interface, as opposed to the more usual case where the class implements the interface so that objects of that class provides it - this is useful because it allows you to group those classes together and describe the type of class they are) and to modules (where you want to describe the public methods and variables of a module). These constructs are less common, so don't worry about them for now. Look at the documentation and interfaces (!) in the zope.interface package for more.

    Adapters

    The most important thing that Zope 3 promises is separation of concerns. In Zope 2, almost everything has a base class that pulls in a number of mix-in classes, such as SimpleItem (surely, the most ironically named class in Zope 2) and its plethora of base classes that include RoleManager, Acquisition.Implicit and many others. This means that a class written for Zope 2 is nearly impossible to re-use outside of Zope.

    Furthermore, in Zope 2 we are tightly wedded to the context (aka here) because it is so convenient to use in page templates, workflow scripts etc. For example, people often write an Archetypes class that contains a schema (storage logic), methods for providing various operations (business logic) and methods for preparing things to display in a page template (view logic). Often, people do this simply because they can't think of a better place to put things, but it does mean that re-using any part of the functionality becomes impossible without importing the whole class - and its base classes, which include Archetypes' BaseObject, CMF's DynamicType, and Zope's SimpleItem - to name a few!

    Think about the example above. The Shoe class is well-contained and only concerned with one thing - storing the attributes of shoes. It can be used as an abstraction of shoe anywhere, and is very lightweight. Now let's consider that we may want to wear shoes as well. We can create a pair of shoes easily enough:

    >>> left = Shoe()
    >>> right = Shoe()
    >>> left.size = right.size = 10
    >>> left.color = right.color = u"brown"

    Now we want someone to wear these shoes. Let's say we have a person:

    class IPerson(Interface):
        """A person
        """
    
        name = Attribute("The person's name")
        apparel = Attribute("A list of things this person is wearing")
    
    class Person(object):
    
        implements(IPerson)
    
        name = u''
        apparel = ()
    

    In a Zope 2 world, we may have required Person to mix in some ShoeWearingMixin class that specified exactly how shoes should be worn. That makes for fat interfaces that are difficult to understand. In a Zope 3 world, we would more likely use an adapter.

    An adapter is a glue component that can adapt an object providing one interface (or a particular combination of interfaces, in the case of a multi-adapter) to another interface. We already have a specification for something that wears shoes, in the form of IShoeWearing. Here is a snippet of code that may use this interface:

    >>> wearing = ...
    >>> wearing.wear(left, right)

    The question is what to do with the '...' - how do we obtain an object that provides IShoeWearing? Code like this is normally operating on some context, which in this case may be a Person. If that Person implemented IShoeWearing (or at least the wear() method), it would work, but then we are making undue demands on Person. What we need is a way to adapt this IPerson to something that is IShoeWearing. To do that, we need to write an adapter:

    from zope.interface import implements
    from zope.component import adapts
    
    class PersonWearingShoes(object):
        """Adapter allowing a person to wear shoes
        """
        implements(IShoeWearing)
        adapts(IPerson)
    
        def __init__(self, context):
            self.context = context
    
        def wear(self, left, right):
            self.context.apparel += (left, right)
    

    Here, we implement the IShoeWearing interface. Note how the wear() method now has a self parameter, since this is a real object. Also note the __init__() method, which takes a parameter conventionally called context. This is the thing that is being adapted, in this case an object providing IPerson. We store this as an instance variable and then reference it later. Note that adapters are almost always transient objects that are created on the fly (we will see how in a second).

    We could now do something like this:

    >>> wearing = PersonWearingShoes(person)
    >>> wearing.wear(left, right)
    

    However, this still requires that we know exactly which adapter to invoke for the particular object (person in this case), effectively creating a tight coupling between the adapter, the thing being adapted, and the code using the adapter.

    Luckily, the Zope 3 Component Architecture knows how to find the right adapter if you only tell it about the available adapters. We do that using ZCML, the Zope Configuration Markup Language. This is an XML dialect that is used to configure many aspects of Zope 3 code, such as permissions and component registration. You can do what ZCML does in Python code as well, but typically it's more convenient to use ZCML because it allows you to separate your logic from your configuration.

    ZCML directives are stored in file called configure.zcml, which itself may include other files. A configure.zcml file in your product directory (Products/myproduct/configure.zcml) will be picked up automatically by Five. Here is a snippet that will register the above adapter:

    <adapter factory=".shoes.PersonWearingShoes" />

    You will sometimes see a fuller form of this directive, like:

    <adapter
        factory=".shoes.PersonWearingShoes"
        for=".interfaces.IPerson"
        provides=".interfaces.IShoeWearing"
        />

    Here, we are specifying full dotted names to interfaces in the for or provides attributes. These are equivalent to the adapts() and implements() calls we used when defining the adapter. Note that adapts() did not work prior to Zope 2.9 (so the ZCML for attribute is mandatory), and that if your adapter class for some reason implements more than one interface (e.g. because it's inheriting another adapter that has its own implements() call), you may need to specify provides to let Zope 3 know which interface you're really adapting to.

    Notice here that the dotted names begin with dot. This means "relative to the current package". You can write "..foo.bar" to reference the parent package as well. You could specify an absolute path instead, e.g. Products.Archetypes.interfaces.IBaseObject or zope.app.annotation.interfaces.IAttributeAnnotatable. Typically, you use the full dotted name for things in other packages and the relative name for things in your own package.

    The factory attribute normally references a class. In Python, a class is just a callable (taking the parameters specified in its __init__() method) that returns an instance of itself. You can reference another callable as well if you need to, such as a function that takes the same parameters (only context in this case - obviously there is no self for functions), finds or constructs and object (which must provide IShoeWearing) and then returns it. This is rarely used, but can be very powerful (for example, it could find an object providing the given interface in the adapted object's annotations - but don't worry if you don't understand that for now).

    With this wiring in place, we can now find an adapter for an IPerson to IShoeWearing. The Component Architecture will ensure that we find the correct adapter:

    >>> wearing = IShoeWearing(person)
    >>> wearing.wear(left, right)
    >>> person.apparel == (left, right,)
    True
    

    We are "calling" the interface, which is a convenience syntax for an adapter lookup. If an adapter cold not be found, you will get a ComponentLookupError. There are plenty of functions in zope.component to discover adapters and other components - see zope.component.interfaces for the full story.

    It is important to realise that the adapter lookup is essentially a search. The Component Architecture will look at the interfaces provided by person and look for a suitable adapter to IShoeWearing. As mentioned before, it's possible for an object to provide many interfaces, e.g. inherited from its base classes, implemented explicitly by the object (by declaring implements(IFoo, IBar)), via ZCML or because an object directly provides an interface. It is therefore possible that there are multiple adapters that could be applicable. In this case, Zope 3 will use the interface resolution order (IRO) to find the most specific adapter. The IRO is much like you would expect of polymorphism in traditional OOP:

    • an interface directly provided by the object is more specific than one provided by its class
    • an object provided by an object's class is more specific than that provided by a base class
    • if an object has multiple base classes, interfaces are inherited in the same order as methods are inherited
    • if a class implements multiple interfaces, the first one specified is more specific than the second one, and so on

    Remember marker interfaces? One use of marker interfaces is to imply a particular adapter. Think about the case where you may have  specific adapter to IShoeWearing for some marker interface IAmputee. If you mark a person as an IAmputee due to some unforunate accident, the IShoeWearing adapter may raise a warning rather than modify the apparel list.

    All of this may seem a little roundabout and unfamiliar, but you'll get to grips with it soon enough. Let's re-cap how we arrived at this:

    1. We modelled our application domain with some interfaces - IPerson, IShoe
    2. We modelled an aspect of a person (or other object) for wearing shoes - IShoeWearing
    3. We wrote some simple classes that implemented the domain interfaces IPerson and IShoe
    4. We wrote and registered a simple adapter that could adapt an IPerson to IShoeWearing

    Then we showed how this could be used by some hypothetical client code. The upshot is that the client code only needed to know about IPerson and IShoeWearing, not how the aspect of a person that involves wearing shoes is implemented. The Component Architecture will ensure that the appropriate adapter is found, regardless of whether the person is a vanilla IPerson, a sub-class with a more specific sub-interface, or an instance with a marker interface applied.

     

    Multi-adapters, named adapters and views

    In the example above, we used an adapter with a single context. That is the most common form of adapter, but sometimes there is more than one object that forms the context of an adapter. As a rule of thumb, if you find yourself passing a particular parameter into every method of an adapter, it should probably be a multi-adapter.

    The most common example of a multi-adapter that you will come across is that of a view, which incidentally is also how Zope 3 solves the "where do I put my view logic" code. We will cover views in detail later, but for now think of them as a python class that is automatically instantiated and bound to a page template when it's rendered. In the template, the variable view refers to the view instance and can be used in TAL expressions to gain things to render or loop on.

    When dealing with a view, there are two things that make up its context - the context content object (conventionally called context) and the current request (conventionally called request). Thus, a view class is a multi-adapter from the tuple (context, request) to IBrowserView. As it happens, there are ZCML directives called browser:page and browser:view that make it easier to register a view and bind a page template to it, handle security etc. However, abstractly a view looks like this:

    class PersonView(object):
        implements(IBrowserView)
        adapts(IPerson, IHttpRequest)
    
        def __init__(self, context, request):
            self.context = context
            self.request = request
    
        def name(self):
            return self.context.name
    
        def requested_shoes(self):
            return self.request.get('requested_shoes', [])
    

    Notice how this adapts both IPerson and IHttpRequest, and thus takes two parameters in its __init__() method. As you will learn later, views typically inherit the BrowserView base class for convenience, but the principle is the same.

    To obtain a multi-adapter, you can't use the "calling an interface" syntax that you use for a regular adapter. Instead, you must use the getMultiAdapter() method:

    >>> from zope.component import getMultiAdapter
        ...
    >>> personView = getMultiAdapter((person, request,), IBrowserView)
    

    You could use queryMultiAdapter() instead if you wanted it to return None instead of raise a ComponentLookupError when it fails to find the adapter.

    The above code has a problem, however (apart from being an incomplete example) - what if you have more than one view on the same object, say for two different tabs? To resolve this ambiguity, views are actually named multi-adapters. The names correspond to the names used as part a URL, and are registered using the name attribute in ZCML. This is used in browser:page and browser:view directives, but can also be used in the standard adapter directive:

    <adapter factory=".sampleviews.PersonView" name="index.html" />

    To get this particular view, we can write:

    >>> personView = getMultiAdapter((person, request,), name=u'index.html')

    conventionally, we leave off the required interface when we used named adapters, although you can supply it if necessary.

    Multi-adapters are useful for other things as well. If you have an adapter and find that every method takes at a common parameter, it's a good candidate for a multi-adapter. Also observe that in the case above, we could register a different adapter for a different type of request as well as for a different type of object. Again, the Component Arhictecture will find the most specific one looking at both interfaces.

    Named adapters do not have to be multi-adapters, of course. They are typically used in cases where something (e.g. the user) is making a selection from a set of possible choices (such as choosing the particular view among many possible views).

    Utilities

    In the CMF, we have tools, which are essentially singletons. They contain various methods and attributes and may be found using the ubiquitous getToolByName() function. The main problem with tools is that they live in content space, as objects in the ZODB, and require a lot of Zope 2 specific things.

    Let's say we had a shoe locating service (very useful when you can't find your shoes):

    class IShoeLocator(Interface):
        """A service for finding your shoes
        """
    
        def findShoes(owner):
            """Find all shoes for the given owner.
            """
    
    class DefaultShoeLocator(object):
        implements(IShoeLocator)
        
        def findShoes(self, owner):
            return ... 
    

    The Component Architecture contains a very flexible utility registry, which lets you look up things by interface and possibly by name. Unlike adapters, utilities do not have context, and they are instantiated only once, when Zope starts up. Global utilities are not persistent (but local utilities are - see below).

    As with adapters, we register utilities with ZCML:

    <utility factory=".locator.DefaultShoeLocator" />
    

    Alternatively, you could skip the implements() call on the factory and set it in ZCML. This may also be necessary in order to disambiguate if you have more than one interface being provided by the utility component:

    <utility 
        factory=".locator.DefaultShoeLocator" 
        provides=".interfaces.IShoeLocator
        />

    Now you can find the utility using getUtility():

    >>> from zope.component import getUtility
    >>> locator = getUtility(IShoeLocator)
    >>> locator.findShoes(u"optilude")
        ...
    

    The utility registry turns out to be a very useful generic registry, because like the adapter registry, it can manage named utilities. Let's say that you had a few different shoes you wanted to keep around:

    >>> left = Shoe()
    >>> right = Shoe()
        ...
    
    >>> from zope.component import provideUtility
    >>> provideUtility(left, name=u'left-shoe')
    >>> provideUtility(right, name=u'right-shoe')
    

    We can now find these utilities again using the name argument to getUtility().

    >>> to_put_on = getUtility(IShoe, name=u'left-shoe')
    

    Of course, we are still using the transient global utility registry, so these will diseappear when Zope is restarted. We could use local components instead (see below), or we could register them using ZCML. If we had defined the shoes left and right in a module shoes.py, we could write:

    <utility
        component=".shoes.left"
        name="left"
        />
    
    <utility
        component=".shoes.right"
        name="right"
        />
    

    An alternative would have been to define two classes LeftShoe and RightShoe and use the factory attribute of the directive instead of component (which refers to an instance, rather than a class/factory).

    Local components

    The examples above all use global, transient registries that are reloaded each time Zope is restarted. That is certainly what you want for code and functionality. Sometimes, you would like for utilities to be a bit more like their CMF cousins and also manage persistent state. To achieve that you need to use local components, which are stored in the ZODB.

    Prior to Zope 3.3, which is included in Zope 2.10, local components were a bit of a black art. Then came the jim-adapter branch and everything was greatly simplified. The theory is still the same, the API is just much more sane. Each time Zope executes a request (or if you implicitly invoke zope.component.setSite(), for example in a test), it discovers which is the nearest site to the context. In Plone, the site is normally the root of the Plone instance, but in theory any folder could be turned into a site.

    A site has a local component registry, where local utilities and adapters may be defined. This means that a particular utility or adapter can be specific to a particular Plone site, not affecting other Plone instances in the same Zope instance. You cannot use ZCML to register local components, since ZCML is inherently global (at least for now) - it does not know anything about your particular sites. However, you can register them with Python code, e.g. in an Install.py or a GenericSetup profile, using calls like provideUtility() (and its equivalent, provideAdapter()) called on a local site manager instance:

    >>> from zope.component import getSiteManager
    
    >>> getUtility(IShoe, name=u'left-shoe) is left
    True
    
    >>> sm = getSiteManager(context)
    >>> sm.provideUtility(myShoe, name=u'left-shoe')
    >>> getUtility(IShoe, name=u'left-shoe) is myShoe
    True
    

    Unfortunately, Plone 2.5 does not run on Zope 2.10. We won't cover local components here, because, well, I never learnt how to do it the Zope 2.9 way, and what I saw of it scared me. I'm told it's not that bad, and there is documentation in Five and in Zope 3 itself. Local components will become more important in Plone 3.0, where Zope 2.10 or later will be required and more things that use local components will be part of the core.

    b-org does not use local components yet, and we will see how the extension mechanism would benefit from local components so that you could have one b-org extension installed in one Plone instance and another extension installed in another Plone instance, without the two interfering. Luckily, to code that uses adapters and utilities, it is completely transparent whether they are global or local.

    Conclusion

    That's it! If you can master the concepts of interfaces, adapters and utilities you will go far in a Zope 3 world. They will become much more natural as you use them a few times, and you'll probably wonder how you ever managed without them. Hopefully, that point will come before the end of this tutorial, which is largely focused on showing how the principle of separation of concerns can be imposed upon your Archetypes and Plone code.

    5.5.3. Overview of b-org

    The big picture

    To the user, b-org presents itself as three content types:

    Department
    A container for employees, and a source of groups. That is, each department becomes a group, and the employees within that department become group members.
    Employee
    Information about employees, and a source of users. That is, each active employee object becomes a user who can log in and interact with the portal.
    Project
    A project workspace - a folder where employees can collaborate on content. Content inside the project folder has a custom workflow, and employees who are related to the project (by reference) have elevated permissions over this content.
    Out of the box, these are not terribly interesting, because they have only the minimum of metadata required to function. The task of providing actual schema fields, view templates, content type names (if Department, Employee and Project are not appropriate) and other application-specific facets is left up to simpler third-party products that plug into b-org. One example of such a product is included, which models a hypothetical charity use case and is called charity.

    This seemingly innocuous orchestration of functionality is achieved by a variety of means:
    Archetypes
    Used to build the actual content types and their schemata.
    The Zope 3 Component Architecture
    Is used to make all this exensibility possible - you will see lots of examples of interfaces, adapters and utilities.
    Membrane
    The content types are registered with membrane to be able to act as groups and users
    PAS and PlonePAS
    The Pluggable Authentication Service is used by membrane to actually provide user sources. A custom PAS plug-in is also used to manage local roles for members and managers within projects and departments.
    GenericSetup
    The next-generation set-up and installation framework is used to install and configure b-org. charity demonstrates how GenericSetup XML profiles can be used directly, without depending on the actual GenericSetup import mechanism.
    Zope 3 events
    Zope 3's event dispatch mechanism is used to ensure employee users actually own their own Employee objects, among other things.
    Zope 3 views
    The charity demo uses views for its display templates.
    Annotations
    Employees' passwords are hashed and stored in an annotation
    Placeful workflow
    To let content inside projects have a different workflow to that of the rest of the site, each project uses a CMFPlacefulWorkflow policy.

    On the following pages, you will learn about each of these components and how it fits together. Meanwhile, you can follow along the code by looking in the subversion repository, or getting b-org from its product page.

    5.5.4. To Archetype or not to Archetype

    Archetypes is still the most complete framework for building content types quickly. With the advent of Zope 3, there is an alternative in Zope 3 schemas. Here's why b-org doesn't use them.

    There is a growing consensus that Archetypes has grown a little too organically. On the one hand, Archetypes has given us a lot of flexibility, and made many of us more productive than we would ever have thought possible (for those who remember the heady days of plain Zope 2, and then plain CMF development). On the other hand, Archetypes has become fairly monolithic. The reference engine, for example, is woven tightly into the field type machinery, and the way that views are composed from widgets makes these almost impossible to re-use outside of Archetypes.

    In practical terms, the biggest headache that arises from Archetypes' evolution is the very same problem we identified when introducing Zope 3 concepts - it's hard to re-use Archetypes-based components without sub-classing and repeating a large portion of a type's configuration. Take the Poi issue tracker, for example - I frequently get requests from people who want to add a few use-case specific fields to each issue, or add some new functionality such as having private issues or issues submitted on behalf of someone else. The problem is that I don't want to put all this functionality in Poi itself, because this would increase the complexity of the product and thus the maintenance burden and probably impact the intuitiveness of the UI, when in reality not everyone would benefit from such new features.

    Ideally, someone would be able to plug in their own schema fields and add some logic in well-defined places without having to re-invent all of Poi. However, this is difficult, because, for example, the "add issue" button assumes you are adding a PoiIssue object, which has a schema defined wholly in Products/Poi/content/PoiIssue.py. There are custom form controller scripts to handle saving of issues, and a lot of methods are found in the various content classes to do things like send mail notifications or perform issue searches for various lists. Again, changing the logic of who gets an email notification or how a particular list of open issues is calculated may involve subclassing one or all of Poi's content types, re-registering view templates and other content type information, and possibly customise a number of templates and scripts to reference the new subclassed types. Of course, when Poi itself changes, keeping these customisations up-to-date becomes difficult.

    Zope 3 has, in keeping with its philosophy, approached these problems by promising separation of concerns. In Zope 3, you would typically define an interface that specifies the schema of a content type, and then create a class that is only concerned with holding and persisting the data for this schema:

    from zope.interface import Interface
    from zope import schema

    class IIssue(Interface):
    """A tracker issue
    """

    title = schema.TextLine(title=u"The short title of this issue", required=True)
    severity = schema.Int(title=u"The severity of this issue", required=True, default=3)

    ...

    from persistent import Persistent
    from zope.interface import implements
    class Issue(Persistent):
    implementS(IIssue)

    title = u""
    severity = 0

    The actual functionality for sending notifications etc would be in various adapters (e.g to INotifying), the view logic in views. Forms can be created from schema interfaces like IIssue above, using zope.formlib. This can handle proper add forms (so the object is not created until the form has been filled in, which is another headache with CMF content types and therefore also Archetypes), validation, edit forms etc. Each form, adapter and menu entry (for the "add" menu, say) is registered separately, meaning that they can also be overridden and customised separately. Rocky Burt has written an excellent tutorial on how to use formlib in a Plone context that may be enlightening.

    There are voices that say we should dump Archetypes entirely in favour of Zope 3-style content objects. Other voices (including my own) say that this may be a bit premature. Certainly, Zope 3 schemas and content objects are not yet fully integrated into CMF and Plone, so you end up depending on some CMF base classes at the very least. Moreover, the number and richness of widgets available for Zope 3 forms does not yet match that of Archetypes. Fundamentally, Archetypes has been around for a long time and has grown to meet a wide variety of use cases, whereas in the context of Plone at least, Zope 3 schemas are a new kid on the block.

    The point is - Archetypes is not going to go away, not for a long time anyway, and are still the right choice for many types of applications. Almost all of Plone's add-on products use Archetypes, and it is well-understood by our developer community. The more likely scenario is that Archetypes will evolve in the same way that Zope 2 is evolving, by seeing its internals refactored piecemeal and pragmatically to take advantage of Zope 3 equivalents and concepts, until theoretically an Archetypes schema and content object is just a different spelling for what Zope 3 is doing, and Zope 3's content type story offers the same richness as Archetypes does (and more).

    In the meantime, Archetypes is the right choice for b-org (and for other membrane-based systems). What we will try to do, however, is to alleviate the aforementioned problems by making use of Zope 3 design techniques, in order to make b-org extensible and flexible.

    5.5.5. The extension story

    One of the main drivers behind the componentisation of b-org is that it should be easy to extend and customise for third party developers. We'll take a look at how such customisations may look, before considering how we made it possible.

    b-org ships with an example called charity, found in the examples/charity directory, which demonstrates one use-case specific implementation of b-org. This is quite simple, consisting of the following top-level files and directories:

    configure.zcml
    Registers the schema extension adapters (see below) and references the browser package
    Extensions/
    Contains an Install.py script that configures the Factory Type Information for the Department, Employee and Project content types. It does so by using GenericSetup XML files, but invokes the import handlers explicitly rather than through a GenericSetup profile.
     browser/
    Contains Zope 3 views for the charity department, employee and project content types, and a configure.zcml to register these. More on views in a later section.
    schema/
    Contains adapters that extend the schemas for Departments, Employees and Projects with use-case specific fields.

    To use charity you should copy or symlink it from Products/borg/examples/charity to Products/charity. It can be installed as normal, but you must install b-org first. See borg/README.txt for the full install instructions!

    A key aim is to make it possible to meaningfully extend b-org without needing to subclass all its types. Of course, you can do that, but in most cases it's not necessary. Unfortunately, the mechanisms and techniques described here will be "global" in nature. That is, you will not be able to have two different modes of customisation for two different Plone instances in the same Zope instance. This is because prior to Zope 2.10 (which Plone 2.5 does not support - it wasn't out until several months after Plone 2.5 was released), the "local" components story in Zope 3 was not fully developed. There is also a specific problem with the way the schema extension mechanism works which makes it inherently global.

    When Plone 3.0 rolls around, it will support local components much better, and Archetypes 1.5, in conjunction with a third-party product called ContentFlavors (or possibly another similar tool), will enable the kind of extension story described here to work on almost any type. At that point, the forerunner you see in b-org now will be obsolete.

    Of course, if you don't need two different b-org customisations for two different Plone sites in the same Zope instance (which I suspect most people can work around - having two separate Zope instances of course isolates you from all of this), you should be fine.

    The schemas extenders

    If you look at charity/configure.zcml you will see the following registrations:

      <adapter factory=".schema.department.DepartmentSchemaExtender" />
      <adapter factory=".schema.employee.EmployeeSchemaExtender" />
      <adapter factory=".schema.project.ProjectSchemaExtender" />

    These schema extenders are adapters that hook into a specific part of b-org. We will describe this in more detail later, but here is how they look from the point of view of the extending product:

    from zope.interface import implements
    from zope.component import adapts

    from Products.Archetypes.atapi import *

    from Products.borg.interfaces import IEmployeeContent
    from Products.borg.interfaces import ISchemaExtender

    CharityEmployeeSchema = Schema((

    StringField('title',
    accessor='Title',
    required=True,
    user_property='fullname',
    widget=StringWidget(
    label=u"Full name",
    description=u"Full name of this employee",
    ),

    ),

    StringField('email',
    validators=('isEmail',),
    required=True,
    searchable=True,
    user_property=True,
    widget=StringWidget(
    label=u"Email address",
    description=u"Enter the employee's email address",
    ),
    ),

    StringField('phone',
    required=False,
    searchable=True,
    user_property=True,
    widget=StringWidget(
    label=u"Phone number",
    description=u"Enter the employee's phone number",
    ),
    ),

    StringField('mobilePhone',
    required=False,
    searchable=True,
    user_property=True,
    widget=StringWidget(
    label=u"Mobile phone number",
    description=u"Enter the employee's mobile phone number",
    ),
    ),

    StringField('location',
    searchable=True,
    user_property=True,
    widget=StringWidget(
    label=u"Location",
    description=u"Your location - either city and country - or in a company setting, where your office is located.",
    ),
    ),

    StringField('language',
    user_property=True,
    vocabulary="availableLanguages",
    widget=SelectionWidget(
    label=u"Language",
    description=u"Your preferred language.",
    ),
    ),

    TextField('description',
    required=True,
    searchable=True,
    user_property=True,
    default_content_type='text/html',
    default_output_type = 'text/x-html-safe',
    allowable_content_types = ('text/html', 'text/structured', 'text/x-web-intelligent',),
    widget=RichWidget(
    label=u"Biography",
    description=u"Enter a short biography of the employee",
    ),
    ),

    ))

    class EmployeeSchemaExtender(object):
    """Extend the schema of an employee to include additional fields.
    """
    implements(ISchemaExtender)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    def extend(self, schema):
    schema = schema + CharityEmployeeSchema
    # Reorder some fields
    schema.moveField('description', after='mobilePhone')
    schema.moveField('location', before='description')
    schema.moveField('language', before='description')
    schema.moveField('roles_', after='description')
    return schema


    This example is employee.py. The other extensions are simpler, and work on the exact same principle. When calculating the schema of a content type, the b-org types (by virtue of Products.borg.content.schema.ExtensibleSchemaSupport, a mix-in class that all the b-org types uses, and which the aforementioned changes to Archetypes should make obsolete) will look up an adapter from the content object (which is marked with IEmployeeContent, in this case), to ISchemaExtender. This will be given the chance to extend (and modify) the schema of the type.

    The returned value is cached (to avoid an expensive re-calculation each time the schema is used). This cache can be invalidated upon an event, which you will see in charity/Extensions/Install.py:

    from zope.event import notify
    from Products.borg.content.schema import SchemaInvalidatedEvent
    from Products.borg.content.employee import Employee

    ...

    def install(self, reinstall=False):
    ...
    notify(SchemaInvalidatedEvent(Employee))
    The event is an instance of a class that implements ISchemaInvalidatedEvent, and takes a class as an argument to know which class the schema is being invalidated for.

    Defining new views and type information

    We have now managed to add new schema fields to Department, Employee and Project. The auto-generated edit form will pick these up for editing, but we probably also want some custom views. We may also want to change other aspects of the Factory Type Information (FTI) which controls how the type is presented within Plone's UI (an FTI is an object in portal_types).

    First, we define some views in the browser package. These are described in a later section, but lookin at charity/configure.zcml, you will see:
    <include package=".browser" />
    This will bring in charity/browser/configure.zcml, which contains several directives like:
      <page  
    name="charity_employee_view"
    for="Products.borg.interfaces.IEmployeeContent"
    class=".employee.EmployeeView"
    template="employee.pt"
    permission="zope2.View"
    />
    This, along with the class Products.charity.browser.employee.EmployeeView and the template charity/browser/employee.pt will make a view @@charity_employee_view (the @@ is optional, but serves to disambiguate views from content objects, for example) available on any employee (or rather, any object providing IEmployeeContent).

    We then need to tell Plone that this view should be invoked when you view an Employee object or click its 'View' tab. This is done by setting the (Default) and view method aliases for the Employee type. See this page of the RichDocument tutorial for some background.

    To achieve this, we could modify portal_types/Employee in Python during the Install.py script. However, to make it easier to define the FTI, we use a GenericSetup XML file instead. Take a look at charity/Extensions/setup/types/Employee.py, for example:
    <?xml version="1.0"?>
    <object name="Employee"
    meta_type="Factory-based Type Information"
    xmlns:i18n="http://xml.zope.org/namespaces/i18n">

    <property name="title">Employee</property>
    <property name="description">A charity employee or volunteer.</property>
    <property name="content_icon">employee.gif</property>
    <property name="content_meta_type">Employee</property>
    <property name="product">borg</property>
    <property name="factory">addEmployee</property>
    <property name="immediate_view">base_edit</property>
    <property name="global_allow">False</property>
    <property name="filter_content_types">False</property>
    <property name="allowed_content_types" />
    <property name="allow_discussion">False</property>

    <alias from="(Default)" to="@@charity_employee_view"/>
    <alias from="view" to="@@charity_employee_view"/>
    <alias from="edit" to="base_edit"/>
    <alias from="properties" to="base_metadata"/>
    <alias from="sharing" to="folder_localrole_form"/>

    <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>

    <action title="Properties" action_id="metadata" category="object" condition_expr=""
    url_expr="string:${object_url}/properties" visible="True">
    <permission value="Modify portal content"/>
    </action>

    <action title="Sharing" action_id="local_roles" category="object" condition_expr=""
    url_expr="string:${object_url}/sharing" visible="True">
    <permission value="Modify portal content"/>
    </action>

    </object>
    This defines the various aspects of the FTI, and is basically a modified copy of the equivalent file from the b-org extension profile. You'll learn more about these in the section on GenericSetup, but for now observe that we invoke this explicitly in Install.py, via some boilerplate utility code:
    from Products.charity.Extensions.utils import updateFTI

    def install(self, reinstall=False):
    ...
    if not reinstall:
    updateFTI(self, charity, 'Department')
    updateFTI(self, charity, 'Employee')
    updateFTI(self, charity, 'Project')
    This will update the FTIs by examing Products/charity/Extensions/setup/types. Each file there is named corresponding to the name of the FTI it modifies.

    Adding new functionality

    Extending the schema and modifying the FTI to support different views is probably enough for a large number of use cases. If you find yourself thinking "I wish I could add a method to the Employee class to support ...", take your left hand, hold it out, raise you right hand and slap your left wrist sternly, then read the section on adapters again.

    For example, let's say you wanted to send an email to administrators when a particular button in the view was clicked. You could do that in an adapter. For examples, in your interfaces module, you could could have:
    from zope.interface import Interface

    class IAdministratorNagging(Interface):
    """Someone who will nag the admin
    """

    def nag(message):
    """Send nagging email
    """

    Then, an adapter from IEmployee in module nag.py:

    from zope.interface import implements
    from zope.component import adapts

    from interfaces import IAdministratorNagging
    from Products.borg.interfaces import IEmployeeContent

    from Products.CMFCore.utils import getToolByName

    class NaggingEmployee(object):
    implements(IAdministratorNagging)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    def nag(self, message):
    mailHost = getToolByName(self.context, 'MailHost')
    ...

    And finally, in your configure.zcml:

    <adapter factory=".nag.NaggingEmployee" />

    Then, in the form handler that is about to nag the employee, you would do:

    from Products.myproduct.interfaces import IAdministratorNagging
    nagger = IAdministratorNagging(employee)
    nagger.nag("Give me more disk space!")

    Obviously, this is a somewhat contrived example, but hopefully you get the gist.

    Modifying workflow and other configuration

    The b-org workflows are not special. In your Install.py, you could modify them or change the workflow assignments as you would any other content type. You can also use CMFPlacefulWorkflow to assign different workflows depending on context, if need be.

    Similarly, if you need to modify the behaviour of the Department, Employee and Project types in other ways, for example by modifying settings in portal_properties, you are of course free to do so. The intended pattern is that your b-org customisation product encapsulates the various settings and extensions that describe your use case.

    Changing fundamental b-org behaviour

    Lastly, as you learn about b-org you will see how it uses adapters to hook into membrane. If you need to override its behaviour, you can add an overrides.zcml to your product, which is otherwise identical to a configure.zcml in format, but is able to override earlier registrations (such s those in b-org). For example, you could override the adapter from IEmployeeContent to IUseRelated to change the way in which user ids is assigned, or the adapter to IUserAuthentication to change the way in which authentication is performed.

    5.5.6. Filesystem organisation

    b-org attempts to adhere to modern ideal about how code should be laid out on the filesystem.

    In the Zope 3 world, the Products pseudo-namespace is frowned upon. In Zope 2, every extension module lives in the Products/ folder. This raises some obvious namespace clash concerns, but also separates Zope modules further from plain-Python modules. In Zope 3, you can install a module anywhere in your PYTHONPATH. For example, in Plone 3.0, there will be a module called plone.portlets, normally installed in lib/python/plone/portlets.

    For modules that need to act like Zope products (i.e. they need an initialize() method, they install content types, they register a GenericSetup profile or CMF skins or use an Extensions/Install.py method, say), this works in Zope 2.10 and later. It can also be made to work in earlier version of Zope using a product (ironically) called pythonproducts.

    For the purposes of borg, we stick with the traditional Products/ installation. It's nice to have imports like from borg import ..., but fundamentally, b-org is very closely tied to Zope (2) and Plone, so the re-use argument goes away, and that nice import syntax is not really worth the extra dependency and configuration.

    One thing you may notice, though, is that the borg product is named in lowercase, in keeping with Zope 3 and Python naming conventions. Looking inside it, you will see the following key files and directories:

     

    __init__.py
    Initialises the Zope 2 product machinery, registers content types, the skin layer and the GenericSetup extension profile that is used to install b-org.
    config.py
    Holds various constants
    configure.zcml
    Starts the Zope 3 snowball going. This references other packages with their own configure.zcml files.
    content/
    Contains the Archetypes content types for Department, Employee and Project. Also contains some utilities, like EmployeeLocator, an adapter to find employees, two utilities used to provide vocabularies AddableTypesProvider and ValidRolesProvider, and the the schema extension mechanism in schema.py.
    events/
    Contains event subscribers which modify ownership of an Employee object so that the employee user owns it (and can thus edit their own profiles, for example), as well as to set up the local workflow when a Project is created.
    interfaces/
    Contains all the interfaces that b-org defines, in various sub-modules like interfaces/employee.py for the Employee-related interfaces. All of these are imported into interfaces/__init__.py, so that you can write from Products.borg.interfaces import ....
    membership/
    Contains various adapters for plugging into membrane which enable b-orgs user-and-group functionality.
    pas/
    Contains a custom PAS plug-in which is used to manage the local roles for Project members
    permissions.py
    Contains custom add-content permissions, so that the ability to add Department, Employee and Project content objects can be controlled by different permissions.
    profiles/
    Contains the GenericSetup extension profile that sets up b-org. This is registered in the borg/__init__.py.
    setuphandlers.py
    Defines a custom GenericSetup "import step" which configures aspects of b-org that cannot be expressed in the existing GenericSetup XML formats.
    skins/
    Contains the borg skin layer, which is registered in borg/__init__.py. This contains only the b-org icons. These could potentially have been defined in a browser package using Zope 3 resources, but are included in a traditional skin layer to make them easier to customise using conventional methods. See the section on Zope 3 views for more details.
     tests/
    Contains unit and integration tests.
    zmi/
    Defines a ZMI page for adding the PAS plug-in, for completeness' sake.

    You will notice that there are many directories, and many of these directories contain the same set of files - employee.py, department.py and project.py. This is a side-effect of the finer-grained components and increased separation of concerns that stem from Zope 3 design concepts. For products that act less as framework, the degree of separation may be lower, and thus the product may appear smaller. However, as you browse b-org's source code, it should become obvious why things are placed where they are, and how code is grouped together by logical functionality rather than a tight coupling to Archetypes content types.

     

    5.5.7. Interfaces

    In Zope 3, everything is connected to an interface in some way. Sure enough, b-org has a slew of them. Getting the interface design right is often more than half the battle, so pay attention to this part.

    If you were trying to understand b-org without a comprehensive tutorial to hand, you would do well to look at the interfaces package. You will notice that this is subdivided into various files

    interfaces/department.py
    Contains a description of a department (IDepartment) and a marker interface for the content object that stores the department (IDepartmentContent).
    interfaces/employee.py
    Contains the equivalent interfaces, IEmployee and IEmployeeContent, as well as the definition of a specific event interface, IEmployeeModified.
    interfaces/project.py
    Again contains IProject and IProjectContent, as well ILocalWorkflowSelection, which is used to denote a utility that defines the placeful workflow policy that projects will use.
    interfaces/workspace.py
    Holds the interface IWorkspace, which is used by the local-role PAS plug-in to extract which users should have which local roles in a project.
    interfaces/schema.py
    Contains interfaces relevant to the custom schema extension mechanism - ISchemaExtender, IExtensibleSchemaProvider and ISchemaInvalidatedEvent.
    interfaces/utils.py
    Defines interfaces that are used as input to various vocabularies - IEmployeeLocator, IAddableTypesProvider and IValidRolesProvider.

    In order to understand what each of these interfaces describes in more detail, look at the files above. Recall that interfaces are mainly documentation - these interfaces are accompanied by docstrings and generally self-documenting code.

    The various interfaces intended for public consumption are imported to interfaces/__init__.py, so that client code can write, e.g.:

    from Products.borg.interfaces import IEmployee
    This is a common idiom. If you find yourself with too many interfaces to manage in interfaces/__init__.py, you don't necessarily need to do this, but it's probably a sign that you should be breaking your code into smaller packages!

    Remember that unless you have a particular need to depend on Zope 2, then you don't need to pollute the Products namespace with such components! (and even if you do, with PythonProducts or Zope 2.10, you can do without the Products/ namespace too). For example, we could have placed the employee functionality in a package borg.employee, found in lib/python/borg/employee as a plain-python library, possibly depending on Zope 3 components (i.e. packages in the zope.* namespace).

    Conversely, if you have relatively few interfaces, you can simply have an interfaces.py module without a directory.

    Separating Archetypes from real components

    One thing you may notice is that we have split the interface describing the concept of e.g. an employee (IEmployee) from the interface that describes the employee content object in the ZODB (IEmployeeContent). Whether this is always the right thing to do is debatable, but the reasoning goes something like this:

    Archetypes objects contain a very large API. Archetypes schemas and the infamous ClassGen generate methods on the content objects corresponding to schema fields, so that a field name gets an accessor called getName() and a mutator called setName(). This is all rather Archetypes-specific, and in Zope 3 schemas, we typically prefer simple properties (a name attribute) to pairs of methods. To avoid being constrained by the Archetypes when defining interfaces (Archetypes is just one implementation choice), we created IEmployee as follows:
    class IEmployee(Interface):
    """An employee, which is also a user.
    """

    id = schema.TextLine(title=u'Identifier',
    description=u'An identifier for the employee',
    required=True,
    readonly=True)

    fullname = schema.TextLine(title=u'Full name',
    description=u"The employee's full name for display purposes",
    required=True,
    readonly=True)
    To support this, we could put the relevant properties into the Archetypes content object, but this is cumbersome, since the property() declaration normally used to convert methods to properties will only work when those methods actually exist, not when they are created by ClassGen.

    Instead, we mark the content object with a marker interface, IEmployeeContent and then register an adapter to IEmployee. Strictly speaking, this is cheating, since the adapter makes assumptions about its context (such as which methods are available, and the fact that it uses Archetypes) that are not formally defined in the interface. To save excessive typing and retain some sanity in the interface definitions, it's not a terrible compromise though. Here's the adapter, from membership/employee.py:
    class Employee(object):
    """Provide department information.
    """
    implements(IEmployee)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    @property
    def id(self):
    return self.context.getId()

    @property
    def fullname(self):
    return self.context.Title()

    Now, you can write:

    emp = IEmployee(some_employee_content_object)
    print emp.fullname

    Another side-effect of this pattern is that we can separate things that are Archetypes-dependent from things that operate on the more general notion of an employee. For example, membrane generally makes assumptions about operating on Archetypes content objects, so the various membrane adapters adapt IEmployeeContent, whereas the view for charity employees is only concerned with "real" employees and so adapts the context to IEmployee.

    This pattern is repeated for Departments and Projects as well.

    Interfaces intended for utilities and adapters

    Although interface design should generally not be too concerned with how those interfaces are implemented, you will often think "this is going to be used a a utility" or "this will most likely be an adapter". In this case, you may want to make some reference in the doc-string at least. For example, the ILocalWorkflowSelection interface states:
    class ILocalWorkflowSelection(Interface):
    """A selection of a local workflow for projects.

    This will normally be looked up as a utility.
    """

    workflowPolicy = schema.TextLine(title=u'Workflow policy identifier',
    description=u'The id of the placeful workflow policy to use',
    required=True,
    readonly=True)
    Conversely, many interfaces are context-dependent, which means that most likely they will either be directly provided by a particular object or adaptable to it. Take the IAddableTypesProvider:
    class IAddableTypesProvider(Interface):
    """A component capable of finding addable types in a given context.
    """

    availableTypes = schema.Tuple(title=u'Available types',
    description=u'A list of all addable types',
    value_type=schema.Object(ITypeInformation))

    defaultAddableTypes = schema.Tuple(title=u'Default addable types',
    description=u'A list of types to be addable by default',
    value_type=schema.Object(ITypeInformation))
    The implication here is that client code will do something like:
    from Products.borg.interfaces import IAddableTypesProvider
    addableTypes = IAddableTypesProvider(context).availableTypes
    Whether IAddableTypesProvider was provided directly by the context or (more likely) provided via an adapter is not important. The only time this distinction is really useful is in the case of marker interfaces, such as IEmployeeContent:
    class IEmployeeContent(Interface):
    """Marker interface for employee content objects"""
    These are often checked with providedBy():
    assert IEmployeeContent.providedBy(employeeContentObject)
    # we've got an employee, good
    Again, the guiding principle here is separation of concerns. The aspect of a component that can provide a list of addable types (IAddableTypesProvider) is logically distinct from (and could be varied independently of) the aspect of a component that specifies it represents a project (IProject), even though it so happens that at present projects are the only time we concern ourselves with restricting addable types.

    In the olden days, we would probably have put methods like getAvailableProjectAddableTypes() into the Project content type. Hopefully, you'll see why this is less optimal than having it in a separate component (hint: what if you in your customisation of b-org wanted to be much more particular about which types were addable?). You will hopefully start to pick up "fat" interfaces during interface design - if you had a neat IProject interface that described attributes of a project that were to be saved alongside the project object, and then found a couple of methods about defining addable types that were related to one another but not so much to the data of a project in general, you would hopefully reach for a new interface. If so - well done, you're getting there.

    5.5.8. Test-driven development

    Testing should come first, not last, when doing development.

    One of the greatest things that Zope 3 has established is a culture of test-driven development. Because Zope 3 components tend to be small and not dependent on a large framework or (typically) a running application server, tests are easier to write and execute faster. Most Zope 3 testing happens in the form of testable documentation - DocTests - which tell the story of how a component should be used along with testable examples.

    The testing tutorial explains the philosophy behind test-driven development and the tools and techniques available in Zope. It is required reading if you are not familiar with testing in Zope, and probably quite useful even if you are.

    Testing strategy

    Tests were (largely) written against interfaces and stub implementations, before the actual functionality was written. One of the first test cases to be created was test_adapters.py, which simply verifies that the various adapter registrations are in effect. This is obviously an integration test (using PloneTestCase), since it is verifying what happens on a "normal" Zope start-up.

    You will also notice tests named after the three content types, test_department.py, test_employee.py and test_project.py. Each of these contains tests that verify the given type is available and can be instantiated and edited. This catches errors in Archetypes registrations or schemas. There are then further tests for the membrane integration and for the adapters to the canonical interfaces IDepartment, IEmployee and IProject. Lastly, non-trivial methods in content types and relevant adapters are given their own test fixtures.

    By being systematic and diligent with tests, many, many bugs were caught and dealt with before they ever hit a live system. Of course, this does not replace in-browser acceptance testing, which was also performed regularly.

    At the time of writing, there are no zope.testbrowser based functional tests for the user interface. That is regrettable - and this is an open source project after all, so feel free to contribute some!

    Test set-up

    You will find b-org's tests in the tests module. Most of these use are DocTest integration tests, using PloneTestCase. Make sure you use a recent version of PloneTestCase (or svn trunk) since there have been some recent changes in how Zope 3 components (or rather, ZCML registrations) are loaded for test runs. The upshot is that with PloneTestCase, things should "just work" for integration testing - components you have defined in ZCML in your products will be loaded as they would when Zope is started.

    The file base.py contains an insulating base class for b-org tests, called BorgTestCase and its sister-class BorgFunctionalTesetCase. When imported, this file will trigger the setup of a Plone site with the membrane and borg extension profiles installed, as such:

    from Testing import ZopeTestCase

    # Let Zope know about the two products we require above-and-beyond a basic
    # Plone install (PloneTestCase takes care of these).
    ZopeTestCase.installProduct('membrane')
    ZopeTestCase.installProduct('borg')

    # Import PloneTestCase - this registers more products with Zope as a side effect
    from Products.PloneTestCase.PloneTestCase import PloneTestCase
    from Products.PloneTestCase.PloneTestCase import FunctionalTestCase
    from Products.PloneTestCase.PloneTestCase import setupPloneSite

    # Set up a Plone site, and apply the membrane and borg extension profiles
    # to make sure they are installed.
    setupPloneSite(extension_profiles=('membrane:default', 'borg:default'))

    Integration and unit tests

    Most of the tests are integration test that are set up like so:

    import unittest
    from Testing.ZopeTestCase import ZopeDocTestSuite

    from base import BorgTestCase
    from utils import optionflags

    def test_creation():
    """Test that departments can be created an initiated.

    >>> self.setRoles(('Manager',))
    >>> id = self.portal.invokeFactory('Department', 'dept')
    >>> dept = self.portal.dept

    Set roles.

    >>> dept.setRoles(('Reviewer',))
    >>> tuple(dept.getRoles())
    ('Reviewer',)

    Add an employee and set it as a manager.

    >>> id = dept.invokeFactory('Employee', 'emp')
    >>> dept.setManagers((dept.emp.UID(),))
    >>> tuple(dept.getManagers())
    (<Employee at ...>,)
    """

    ...

    def test_suite():
    return unittest.TestSuite((
    ZopeDocTestSuite(test_class=BorgTestCase,
    optionflags=optionflags),
    ))

    There is also a plain-python (no loading of Zope necessary, which is much faster) unit test for the password digest in test_passwords.py. This is appropriate because the functionality under test does not depend on the Zope application server or database being loaded. Use plain-python (or perhaps rather, plain Zope 3) tests whenever you can to reduce interdependencies and test load times:

    import unittest

    from zope.testing.doctestunit import DocTestSuite
    from utils import configurationSetUp, configurationTearDown, optionflags

    def test_passwords_hashed():
    """Check that passwords are hashed

    We expect that the password will be saved as a SHA-1 digest.

    >>> import sha
    >>> digest = sha.sha('secret').digest()

    Set a password.

    >>> from Products.borg.content.employee import Employee
    >>> e = Employee('emp')
    >>> e.setPassword('secret')

    The value is stored in an annotation, and there is no direct way to
    access it (deliberately). Thus, check the annotation directly.

    >>> from zope.app.annotation.interfaces import IAnnotations
    >>> from Products.borg.config import PASSWORD_KEY
    >>> annotations = IAnnotations(e)
    >>> password = annotations[PASSWORD_KEY]

    Ensure it is what we expected:

    >>> password == digest
    True
    """

    ...

    def test_suite():
    return unittest.TestSuite((
    DocTestSuite(setUp=configurationSetUp,
    tearDown=configurationTearDown,
    optionflags=optionflags),
    ))

    The functions configurationSetUp() and configurationTearDown() are defined in utils.py and are used to load specific ZCML files that enable the test environment to function. This is necessary because without PloneTestCase's integration test layer in effect, there will be no compnent registrations when the tests are run! This may be more cumbersome (though in reality, the same set of components tend to be used), but also allows better control over the environment in which test are run, in addition to (much) faster test execution times.

    From utils.py:

    import doctest
    from zope.app.tests import placelesssetup
    from zope.configuration.xmlconfig import XMLConfig

    # Standard options for DocTests
    optionflags = (doctest.ELLIPSIS |
    doctest.NORMALIZE_WHITESPACE |
    doctest.REPORT_ONLY_FIRST_FAILURE)


    def configurationSetUp(self):
    """Set up Zope 3 test environment
    """

    placelesssetup.setUp()

    # Ensure that the ZCML registrations in membrane and borg are in effect
    # Also ensure the Five directives and permissions are available

    import Products.Five
    import Products.membrane
    import Products.borg

    XMLConfig('configure.zcml', Products.Five)()
    XMLConfig('meta.zcml', Products.Five)()

    XMLConfig('configure.zcml', Products.membrane)()
    XMLConfig('configure.zcml', Products.borg)()

    def configurationTearDown(self):
    """Tear down Zope 3 test environment
    """

    placelesssetup.tearDown()

    You will also find a regular unit test in test_setup.py, simply because this was quicker to write:

    from base import BorgTestCase

    from Products.membrane.interfaces import ICategoryMapper
    from Products.membrane.config import ACTIVE_STATUS_CATEGORY
    from Products.membrane.utils import generateCategorySetIdForType

    from Products.borg.config import LOCALROLES_PLUGIN_NAME, PLACEFUL_WORKFLOW_POLICY

    class TestProductInstall(BorgTestCase):

    def afterSetUp(self):
    self.types = ('Department', 'Employee', 'Project',)

    def testTypesInstalled(self):
    for t in self.types:
    self.failUnless(t in self.portal.portal_types.objectIds(),
    '%s content type not installed' % t)

    ...

    def test_suite():
    from unittest import TestSuite, makeSuite
    suite = TestSuite()
    suite.addTest(makeSuite(TestProductInstall))
    return suite

    Finally, there is an docstring DocTest for the ExtensibleSchemaSupport class. This is because this class if largely standalone (it probably shouldn't be b-org at all, but in a more general module, except Archetypes will gain similar functionality of its own for Plone 3.0) and the test provided important documentation in the class' docstring.

    The class looks like this:

    class ExtensibleSchemaSupport(Base):
    """Mixin class to support instance-based schemas.

    Note: you must mix this in before BaseFolder or BaseContent, e.g.:

    class Foo(ExtensibleSchemaSupport, BaseContent):
    ...

    This is based on Archetype's VariableSchemaSupport.

    Define a content type with a marker interface:

    >>> from zope.interface import Interface, implements
    >>> class IMyType(Interface):
    ... pass

    >>> from Products.Archetypes.atapi import *
    >>> from Products.borg.content.schema import ExtensibleSchemaSupport
    >>> class MyType(ExtensibleSchemaSupport, BaseObject):
    ... implements(IMyType)
    ... schema = BaseSchema.copy() + Schema((StringField('foo'),))
    >>> registerType(MyType, 'testing')

    Create a schema extender:

    ...

    """
    implements(IExtensibleSchemaProvider)

    ...

    And the test runner, in test_schema.py, contains:

    import unittest
    from Testing.ZopeTestCase import ZopeDocTestSuite

    from base import BorgTestCase
    from utils import optionflags

    def test_suite():
    return unittest.TestSuite((
    ZopeDocTestSuite('Products.borg.content.schema',
    test_class=BorgTestCase,
    optionflags=optionflags),
    ))

    5.5.9. Setup using GenericSetup

    b-org uses GenericSetup to impose itself on your Plone instance. Here's how it works.

    Hands up if you have ever written a workflow definition in Python and tried to figure out how to install it in your Extensions.py and thought, this is the least useful API I have ever had to deal with. Actually, the API is not that bad, it's just not very good for performing set-up. Similarly, it may start to make your separation-of-concerns-brainwashed mind a little uneasy that we tend to define aspects of the type's configuration as class attributes in an Archetypes class (though of course it's better than using a CMF FTI dict or mangling portal_types directly).

    The fine folks who gave us the CMF came up with another way, called GenericSetup (after a few name changes - you may see the names CMFSetup and ContentSetup as well, which refer to predecessors of what is not GenericSetup). This is based on a declarative XML syntax that can represent site configuration. The configuration of an entire site is called a profile and can be imported and exported to replicate state across multiple Plone (or CMF) sites. There is a smaller version of a profile called an extension profile which can be used to extend a base profile. Both membrane and b-org use extension profiles to install themselves.

    GenericSetup is described a tutorial by Rob Miller, cheif GenericSetup protagonist, so we won't repeat too much of the detail here. However, you should be aware that in Plone 2.5, GenericSetup has a slightly awkward user experience and does not have any well-defined way of performing uninstall, which stems from the fact that it was originally designed for the use case of taking a snapshot of the configuration of an entire site, not for installing and uninstalling products and extensions!

    The other main shortcoming at the moment is that there is no way to specify interdependencies between profiles. It is important that membrane is installed before b-org, but if you're not careful it will happen the other way around. When you create a Plone site, you will be able to choose a number of extension profiles to apply (including meaningless ones like Archetypes - meaningless because Plone already invokes those when you set up a site). In this list, "Base organisation" comes before "membrane" by virtue of alphabetical sorting. Therefore, you can't just choose both and click "Add". Instead, you should select "membrane" first, and then add "Base organisation" via portal_setup, as described in the b-org README.txt:

    1. Go to portal_setup in the ZMI
    2. Click the Properties tab
    3. Select "Base organisation" as the active profile (since this is an extension profile, it won't override the base profile that set up your Plone site) and click Update.
    4. Go to the Import tab and click Import all steps at the bottom. Note that although it seems like this will re-install a whole bunch of stuff, it will only execute those steps that are actually listed in the import_steps.xml for the active profile, which in this case is b-org's.

    If you didn't already set up membrane and you created a Plone site without the membrane extension profile, follow the same steps to install membrane before you install b-org.

    So why did we do all this? Firt of all, both membrane and b-org are really infrastructure that fundamentally influence how you build your site, so the lack of uninstall isn't as bad as it would have been for more user-facing products. Secondly, with Plone 3.0, this will become easier, as the QuckInstaller (and hence the Add/Remove Products control panel page) becomes Extension Profile aware and gives some uninstall support.

    At the end of this section, you will see how you can use a traditional QuickInstaller Install.py method and still get the nice XML syntax, with a bit of extra work.

    Import steps

    To GenericSetup, the installation of a third party product via an extension profile is considered to be the importing of that profile. A file import_steps.xml is used to determine which actual import steps will be executed. First, we need to tell GenericSetup where the import steps are defined, though, by registering the extension profile. This is done in the product's __init__.py:

    from Products.CMFPlone.interfaces import IPloneSiteRoot
    from Products.GenericSetup import EXTENSION, profile_registry

    ...

    def initialize(context):
    ...
    profile_registry.registerProfile('default',
    'Base organisation',
    'Organisation and project infrastructure',
    'profiles/default',
    'borg',
    EXTENSION,
    for_=IPloneSiteRoot)

    This references the directory profiles/default, which contains various files:

    import_steps.xml
    Lists the steps to be performed during import (set-up)
    export_steps.xml
    Lists the steps to be performed during export - that is, if the configuration is changed in the ZODB and the site admin wishes to export the configuration to a file, these steps will be performed.
    membrane_tool.xml
    Configuration for membrane tools
    skins.xml
    Sets up skins in portal_types
    types.xml
    Configures FTIs (Factory Type Information settings) for the content types that b-org ships with. Each of the types listed here has a corresponding file in profiles/default/types (the name of the type and the name of the file should match). This file contains all the various FTI settings, such as friendly name, meta type, actions and aliases.
    workflows.xml
    Configures workflows. This works in the same way as types.xml - the main file configures the names of the workflows and the bindings of workflows to content types. The actual workflow definitions, including states and transitions, are found in profiles/default/workflows.
    The import_steps.xml which orchistrates all this looks like follows:

    <?xml version="1.0"?>
    <import-steps>
    <import-step id="borg_various" version="20060803-01"
    handler="Products.borg.setuphandlers.importVarious"
    title="Various base-org Settings">
    <dependency step="typeinfo"/>
    <dependency step="skins"/>
    <dependency step="workflow"/>
    </import-step>
    </import-steps>
    Note that we don't actually specify most of the files - they are referenced by the base profile that was used to set up Plone or the extension profile for membrane. GenericSetup knows all the registered profiles' steps, and looks for the corresponding files.

    Various setup handlers

    The one setup handler you do see is the "various" handler. This is dependent on the set-up of type info, skins and workflow. Ordinarily, setup handlers will utilise GenericSetup base classes, adapters and utility functions to parse XML files. However, it's not always convenient to invent a generic XML syntax for all types of configuration. The importVarious pattern is used by many products that need to perform some custom set-up in Python. It is invoked as if it were a handler for an XML file, but it just happens to have different side-effects. The main caveat with this type of set-up, of course, is that it cannot symmetrically export (and then re-import) the configuration, and it is more difficult to re-use.

    importVarious looks as follows:

    from StringIO import StringIO

    ...

    def setupPlugins(portal, out):
    """Install and prioritize the project local-role PAS plug-in.
    """

    ...

    def setupPortalFactory(portal, out):
    """Add borg types to portal_factory
    """

    ...

    def addProjectPlacefulWorkflowPolicy(portal, out):
    """Add the placeful workflow policy used by project spaces.
    """

    ....

    def importVarious(context):
    """
    Import various settings.

    Provisional handler that does initialization that is not yet taken
    care of by other handlers.
    """
    site = context.getSite()
    out = StringIO()

    setupPlugins(site, out)
    setupPortalFactory(site, out)
    addProjectPlacefulWorkflowPolicy(site, out)

    logger = context.getLogger("borg")
    logger.info(out.getvalue())

    We set up the PAS plugins, register our types with portal_factory and add a placeful workflow policy. The exact code to perform each of these steps is not listed here to save space, but they use the same techniques you would use in an Install.py file. Note that the portal_factory setup is available in a more friendly XML format in Plone 2.5.1 and later, which was released after b-org.

    GenericSetup without portal_setup

    When Plone 3.0 arrives, it will make the Add/Remove Products control panel aware of extension profiles, and thus provide a more user friendly way of performing install using GenericSetup. It will also support uninstall. Until that time, however, it is possible to re-use the GenericSetup XML handlers to parse files like types.xml  and workflow.xml from a regular Install.py installation. We do this in the charity example.

    When importing, GenericSetup requires a setup environment, and usually an object to work on. A simple SetupEnviron is found in charity/Extensions/utils.py, along with a method called updateFTI() which can take an FTI object and update its settings based on a types.xml-like file. This method takes a module and the id of an FTI to update, and finds the corresponding file.

    It is used in charity/Extensions/Install.py as follows:
    from Products import charity

    from Products.charity.Extensions.utils import updateFTI

    def install(self, reinstall=False):

    ...

    if not reinstall:
    updateFTI(self, charity, 'Department')
    updateFTI(self, charity, 'Employee')
    updateFTI(self, charity, 'Project')

    The relevant files may be found in charity/Extensions/setup/types/.

    5.5.10. Using membrane to provide membership behaviour

    How b-org uses membrane to let employees be users and departments be groups

    Since version 2.5, the user management infrastructure in Plone has been replaced by PAS, the Zope Pluggable Authentication Service, and PlonePAS, a Plone integration layer for this. PAS offers several advantages over plain user folders, mainly in terms of flexibility. Unfortunately, it is also more difficult to work with through-the-web and has a very decentralised API, based on the notion of plugin components, that can be difficult to understand at first.

    Membrane (or rather, membrane with a lowercase m) is a component first developed by Plone Solutions and later improved by Rob Miller and others. It is similar to CMFMember in that it can turn content objects into users, although it is less concerned with replicating existing Plone functionality and more concerned with making a thin integration layer to plug into. It therefore fits b-org very well.

    Membrane works on Archetypes objects (though theoretically it could be used with other objects as well). It adds a tool called membrane_tool which contains a registry of content types that are member- or group-sources, as well as a special catalog. Using the Archetypes catalog multiplex, it is able to catalog objects (which may also be cataloged in portal_catalog) and find them again based on various interfaces (that is, it catalogs the interfaces provided by an object). membrane provides a number of PAS plug-ins that will search this catalog when looking for users and delegate to the content objects (or rather, adapters on the content object) for obtaining user information, performing authentication and so on.

    Registering with membrane

    membrane_tool contains an API for registering content types as membership providers, but the easiest option is to use a GenericSetup profile (see the section on GenericSetup for the full story). In profiles/default/membrane_tool.xml, you will find:
    <?xml version="1.0"?>
    <object name="membrane_tool" meta_type="MembraneTool">
    <membrane-type name="Department">
    <active-workflow-state name="active" />
    </membrane-type>
    <membrane-type name="Employee">
    <active-workflow-state name="active" />
    </membrane-type>
    <membrane-type name="Project">
    <active-workflow-state name="published" />
    <active-workflow-state name="private" />
    </membrane-type>
    </object>
    This registers the three content types (by their portal type), and specifies the workflow states in which they are "active" as member and group sources.

    Applying marker interfaces

    When looking for content objects that provide group and member information, membrane will use a number of marker interfaces that indicate support for various types of behaviour. These are implemented by the three content type classes.

    In content/department.py, you will find:
    from Products.membrane.interfaces import IPropertiesProvider

    ...

    class Department(ExtensibleSchemaSupport, BaseFolder):
    """A borg department.

    Departments can contain other employees.
    """

    implements(IDepartmentContent, IPropertiesProvider)
    All this means is that the Department's schema is capable of providing properties to PAS. Properties (normally related to users, but groups can have properties as well) are just metadata about the user or group. Membrane supports as PAS properties plugin that will look for Archetypes schema fields with member_property=True set and report these back as user properties. Although Department does not use any such properties at the moment, we add this marker so that extension modules that use the schema extension mechanism can benefit from this.

    The equivalent setup for Employees, in content/employee.py, is a little more interesting.
    from Products.membrane.interfaces import IUserAuthProvider
    from Products.membrane.interfaces import IPropertiesProvider
    from Products.membrane.interfaces import IGroupsProvider
    from Products.membrane.interfaces import IGroupAwareRolesProvider

    ...

    class Employee(ExtensibleSchemaSupport, BaseContent):
    """A borg employee.

    Employees are also users.
    """

    implements(IEmployeeContent,
    IUserAuthProvider,
    IPropertiesProvider,
    IGroupsProvider,
    IGroupAwareRolesProvider,
    IAttributeAnnotatable)
    Here, we are saying that:
    • An Employee can be used as a source of authentication (i.e. as a user), since it is marked with IUserAuthProvider. Note that the actual authentication is performed by a different adapter.
    • An Employee can provide user properties to PAS via membrane, following IPropertiesProvider.
    • An Employee can be part of a group, because of IGroupsProvider.
    • An employee can be given roles. There is an IRolesProvider interface that we cold use for basic role awareness. The IGroupAwareRolesProvider is a sub-interface that will cause membrane to also look at the user's groups.
    The IAttributeAnnotatable interface is part of Zope 3's annotations framework, discussed in a later section.

    Projects does not require any particular marker interfaces.

    Providing membership behaviour

    When membrane looks for objects to provide membership-related behaviour, it will not only look for objects directly providing a particular interface, but also for objects that can be adapted to that interface. For example, the presence of the interface IGroup informs membrane that an object can act as a group, and contains methods that describe the members of that group.

    Of course, we could have declared that Department implemented IGroup and written these methods directly in the Department content object. Hopefully you'll agree now that this would not be optimal, since it mixes the content-object aspect and the group-behaviour aspect of Department into a single monolithic object. Instead, we will use an adapter, which also means that if you require different behaviour in an extension to b-org, you have only to override the adapter, leaving the core content object alone.

    In membership/department.py, you will see:
    class Group(object):
    """Allow departments to act as groups for contained employees
    """
    implements(IGroup)
    adapts(IDepartmentContent)

    def __init__(self, context):
    self.context = context

    def Title(self):
    return self.context.Title()

    def getRoles(self):
    """Get roles for this department-group.

    Return an empty list of roles if the department is in a workflow state
    that is not active in membrane_tool.
    """
    mb = getToolByName(self.context, MEMBRANE_TOOL)
    wf = getToolByName(self.context, 'portal_workflow')

    reviewState = wf.getInfoFor(self.context, 'review_state')
    wfmapper = ICategoryMapper(mb)
    categories = generateCategorySetIdForType(self.context.portal_type)
    if wfmapper.isInCategory(categories, ACTIVE_STATUS_CATEGORY, reviewState):
    return self.context.getRoles()
    else:
    return ()

    def getGroupId(self):
    return self.context.getId()

    def getGroupMembers(self):
    mt = getToolByName(self.context, MEMBRANE_TOOL)
    usr = mt.unrestrictedSearchResults
    members = {}
    for m in usr(object_implements=IMembraneUserAuth.__identifier__,
    path='/'.join(self.context.getPhysicalPath())):
    members[m.getUserId] = 1
    return tuple(members.keys())
    Mostly, this is about examining the Department content object to find roles (which are listed in an Archetypes field, editable by the Manager role). When calculating roles, we make sure that we don't give roles if the Department group-source is actually disabled (by virtue of its workflow state and the settings in membrane_tool). The group title and id are taken from the object as well.

    The most interesting method is getGroupMembers(). Here, we perform a search in the membrane_tool catalog for objects adaptable to IMembraneUserAuth. This interface is the basic interface in membrane describing things that can act as users - there is an adapter from IUserAuthProvider to IMembraneUserAuth. We restrict this to objects inside the Department object. The net effect is that all Employee objects inside the Department are returned.

    Now, let's say you had a need for a Department which in addition to acting as a group for all members inside it, also allowed some members from other departments to be in that group. In this case, you could use a schema extender to add a ReferenceField to the schema of Department that allowed the Department owner to reference other Employees. You would then provide an override adapter, perhaps subclassing Products.borg.membership.department.Group but overriding getGroupMembers() to append the ids of the referenced users as well as the contained ones ... or instead of, depending on your needs.

    As it happens, Projects also act as groups, with members being assigned by reference, using two reference fields - one for project members, and one for project manangers. Here is the equivalent adapter from membership/project.py:
    class Group(object):
    """Allow projects to be groups for related members and managers
    """
    implements(IGroup)
    adapts(IProjectContent)

    def __init__(self, context):
    self.context = context

    def Title(self):
    return self.context.Title()

    def getRoles(self):
    # The project does not imply any special roles *globally*, although
    # the IWorkspace adapter above enables some local roles
    return ()

    def getGroupId(self):
    return self.context.getId()

    def getGroupMembers(self):
    return [IUserRelated(m).getUserId() for m in
    self.context.getRefs(PROJECT_RELATIONSHIP) +
    self.context.getRefs(PROJECT_MANAGER_RELATIONSHIP)]
    As may be expected, the membrane adapters for Employee are a bit more involved. They consist of the following:
    IUserRelated adapter
    Provides a user id for employees. Note that user ids and user names are possibly different when PAS is used: the user id must be globally unique; the user name is the named used for logging in.
    IUserAuthentication adapter
    Used to perform actual authentication by validating a supplied username and password.
    IUserRoles adapter
    Used to determine which roles the particular user is given.
    IMembraneUserManagement
    Used by membrane and Plone's UI to deal with changes to the user, such as the adding of a new user (not implemented here, since we
    All these adapters are found in membership/employee.py.

    The IUserRelated adapter is the simplest, as it simply invokes the user name. Note that by default, membrane will use the Archetypes UID() function as the user id. This is sensible, but unfortunately Plone's UI (and that of third party products) is not always aware of the distinction between user id and user name. Ideally, only the user name would ever be displayed, the user id being an internal concept, but in practice you may end up with things like member folder names that are long, unfriendly UID strings. Sometimes this may even be unavoidable in the general case, because it's possible that two different sources of users could use the same user name for two different user ids! For the purposes of b-org, however, we assume user names are unique and well-defined. The adapter is therefore quite trivial:
    class UserRelated(object):
    """Provide a user id for employees.

    The user id will simply be the id of the member object. This overrides the
    use of UIDs
    """
    implements(IUserRelated)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    def getUserId(self):
    return self.context.getId()
    The id of the content object that represents the employee is used as the user id. This is also used as the user name, as defined in the IUserAuthentication adapter:
    class UserAuthentication(object):
    """Provide authentication against employees.
    """
    implements(IUserAuthentication)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    def getUserName(self):
    return self.context.getId()

    def verifyCredentials(self, credentials):
    login = credentials.get('login', None)
    password = credentials.get('password', None)

    if login is None or password is None:
    return False

    digest = sha(password).digest()

    annotations = IAnnotations(self.context)
    passwordDigest = annotations.get(PASSWORD_KEY, None)

    return (login == self.getUserName() and digest == passwordDigest)
    In the verifyCredentials() method, the adapter is passed the login and password as entered by the user in a dict (credentials) and then compares those to the values stored on its context (the Employee content object). The password is stored as a SHA1 digest in an annotation to make sure it cannot be read back by examining the content object - more on this in the section on annotations. Be aware also that the IUserAuthentication adapter is called on every request after a user is logged in and can deny access for whatever reason by returning non-True. This means that it is important that the method is as efficient as possible - expensive database lookups, for example, are probably not a good idea here!

    The IUserRoles adapter is trivial. Roles are stored on the content object in a field that is editable only by managers. Of course, we could have picked roles from some other rule if necessary:
    class UserRoles(object):
    """Provide roles for employee users.

    Roles may be set (by sufficiently privilged users) on the user object.
    """
    implements(IUserRoles)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    def getRoles(self):
    return self.context.getRoles()
    The getRoles() method returns an iterable of strings representing applicable roles. Note that depending on group membership (and the IGroupAwareRolesProvider marker as described above) and local roles the user may in fact have more roles than what this method returns! The IUserRoles interface is concerned only with global roles intrinsic to the user.

    Finally, we have the IMembraneUserManagement adapter. This lets membrane know what to do when it is asked by Plone's UI to add, edit or remove users. In particular, the doChangeUser() method enables the PasswordResetTool to do its magic. Note that we have not implemented doAddUser(), because there is no well-defined global policy for where the actual Employee content object should be added! Recently membrane has gained some functionality whereby a site-local utility providing IUserAdder from membrane can be queried for this policy. That may be useful for b-org extension products, but b-org is still not in a position to make a general policy for this, so it is not implemented out of the box.
    class UserManagement(object):
    """Provides methods for adding deleting and changing users

    This is an implementation of IUserManagement from PlonePAS
    """
    implements(IMembraneUserManagement)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    def doAddUser(self, login, password):
    """This can't be done unless we have a canonical place to store users
    some implementations may wish to define one and implement this.
    """
    raise NotImplementedError

    def doChangeUser(self, login, password, **kw):
    self.context.setPassword(password)
    if kw:
    self.context.edit(**kw)

    def doDeleteUser(self, login):
    parent = aq_parent(aq_inner(self.context))
    parent.manage_delObjects([self.context.getId()])

    That's it! Through these adapters, the three b-org content types are able to act as sources of groups and users. Hopefully, you will appreciate the flexibility of the separation of concerns into adapters for things like editing user properties, determining user id, calculating roles and performing authentication. If you extend b-org, you can provide a more specific adapter to any of the above interfaces to customise the membership behaviour.

    5.5.11. Writing a custom PAS plug-in

    Projects require that members are given particular local roles within a project space. This is achieved using a custom PAS plug-in.

    PAS was introduced in the previous section on membrane. Truth be told, it can be a bit of a jungle of plug-ins and delegation because it is so very generic. Luckily, Plone (and membrane) takes care of most of the complexity for us. Sometimes, however, it is desirable to influence the authentication and role management at a lower level.

    Workspace adapters

    b-org ships with a bit of framework, adapted from some similar code in an unreleased version of teamspace by Wichert Akkerman, which can provide local roles in a "workspace" - in this case a Project. It relies on an adapter to the IWorkspace interface to determine the mapping of users and roles in the particular context. Before showing how this plug-in is written and registered, however, let's look at how it is used by a Project.

    In membership/project.py you will find:
    class LocalRoles(object):
    """Provide a local role manager for projects
    """
    implements(IWorkspace)
    adapts(IProjectContent)

    def __init__(self, context):
    self.context=context

    def getLocalRoles(self):
    project = IProject(self.context)
    roles = {}
    for m in project.getManagers():
    roles[m.id] = ('Manager',)
    for m in project.getMembers():
    if m.id in roles:
    roles[m.id] += ('TeamMember',)
    else:
    roles[m.id] = ('TeamMember',)
    return roles

    def getLocalRolesForPrincipal(self, principal):
    r = self.getLocalRoles()
    return r.get(principal, ())
    This queries the lists of managers and members assigned (by reference) to the project and specifies that both managers and members should get the role TeamMember and managers should also get the role Manager.

    As it turns out, this behaviour is also useful in Departments, which can be given one or more department managers by reference. The idea is that department managers should be allowed to add and remove Employees within that Department (recall that Department is a folderish container for Employee objects). The analogous adapter in membership/department.py reads:
    class LocalRoles(object):
    """Provide a local role manager for departments
    """
    implements(IWorkspace)
    adapts(IDepartmentContent)

    def __init__(self, context):
    self.context = context

    def getLocalRoles(self):
    project = IDepartment(self.context)
    roles = {}
    for m in project.getManagers():
    roles[m.id] = ('Manager',)
    return roles

    def getLocalRolesForPrincipal(self, principal):
    r = self.getLocalRoles()
    return r.get(principal, ())
    Thus, a container wanting to use the PAS plug-in we're about to see to manage local roles only need to be adaptable to IWorkspace. In fact, this whole machinery ought to be factored out into a separate component, possibly sharing code to teamspace, another product which provides similar functionality. Mostly, this is down to laziness - creating another product (with all its boilerplate) and managing another dependency in the Products folder seemed too onerous when b-org was being developed. Hopefully, with Zope 2.10/Plone 3.0 and a growing preference for plain-Python packages and "eggs", it will seem a little less of an obstacle to split products up into multiple smaller pieces. So much for making excuses.

    The plug-in

    The PAS plug-in that uses the IWorkspace interface can be found in pas/localrole.py. It looks like this:
    # Borrowed from Project pasification branch - written primarily by
    # Wichert Akkerman and Copyright Amaze Internet Services

    # This module is releasd under the Zope Public License

    from sets import Set

    from Globals import InitializeClass
    from Acquisition import aq_inner, aq_chain, aq_parent
    from AccessControl import ClassSecurityInfo
    from Products.PageTemplates.PageTemplateFile import PageTemplateFile

    from Products.PluggableAuthService.utils import classImplements
    from Products.PluggableAuthService.plugins.BasePlugin import BasePlugin
    from Products.PlonePAS.interfaces.plugins import ILocalRolesPlugin

    from Products.borg.interfaces import IWorkspace

    manage_addWorkspaceLocalRoleManagerForm = PageTemplateFile(
    "../zmi/WorkspaceLocalRoleManagerForm.pt", globals(),
    __name__="manage_addProjectRoleManagerForm")

    def manage_addWorkspaceLocalRoleManager(dispatcher, id, title=None, REQUEST=None):
    """Add a WorkspaceLocalRoleManager to a Pluggable Authentication Services."""
    plrm = WorkspaceLocalRoleManager(id, title)
    dispatcher._setObject(plrm.getId(), plrm)

    if REQUEST is not None:
    REQUEST.RESPONSE.redirect(
    '%s/manage_workspace?manage_tabs_message=WorkspaceLocalRoleManager+added.'
    % dispatcher.absolute_url())


    class WorkspaceLocalRoleManager(BasePlugin):
    meta_type = "Workspace Roles Manager"
    security = ClassSecurityInfo()

    def __init__(self, id, title=None):
    self.id = id
    self.title = title

    #
    # ILocalRolesPlugin implementation
    #

    security.declarePrivate("getRolesInContext")
    def getRolesInContext(self, user, object):
    roles = []
    uid = user.getId()

    obj, workspace = self._findWorkspace(object)
    if workspace is not None:
    if user._check_context(obj):
    roles.extend(workspace.getLocalRolesForPrincipal(uid))

    return roles

    security.declarePrivate("checkLocalRolesAllowed")
    def checkLocalRolesAllowed(self, user, object, object_roles):
    roles = []
    uid = user.getId()

    obj, workspace = self._findWorkspace(object)
    if workspace is not None:
    if not user._check_context(obj):
    return 0
    roles = workspace.getLocalRolesForPrincipal(uid)
    for role in roles:
    if role in object_roles:
    return 1

    return None

    security.declarePrivate("getAllLocalRolesInContext")
    def getAllLocalRolesInContext(self, object):
    rolemap = {}

    obj, workspace = self._findWorkspace(object)
    if workspace is not None:
    localRoleMap = workspace.getLocalRoles()
    for (principal, roles) in localRoleMap.items():
    rolemap.setdefault(principal, Set()).update(roles)

    return rolemap

    # Helper methods

    security.declarePrivate("_findWorkspace")
    def _findWorkspace(self, object):
    """Find the first workspace, if any, in the acquistion chain of this
    object. Returns a tuple obj, workspace where workspace is the adapted
    IWorkspace.
    """

    for obj in self._chain(object):
    workspace = IWorkspace(obj, None)
    if workspace is not None:
    return obj, workspace
    return None, None

    security.declarePrivate("_chain")
    def _chain(self, object):
    """Generator to walk the acquistion chain of object, considering that it
    could be a function.
    """

    # Walk up the acquisition chain of the object, to be able to check
    # each one for IWorkspace.

    # If the thing we are accessing is actually a bound method on an
    # instance, then after we've checked the method itself, get the
    # instance it's bound to using im_self, so that we can continue to
    # walk up the acquistion chain from it (incidentally, this is why we
    # can't juse use aq_chain()).

    context = aq_inner(object)

    while context is not None:
    yield context

    funcObject = getattr(context, 'im_self', None)
    if funcObject is not None:
    context = aq_inner(funcObject)
    else:
    # Don't use aq_inner() since portal_factory (and probably other)
    # things, depends on being able to wrap itself in a fake context.
    context = aq_parent(context)

    classImplements(WorkspaceLocalRoleManager, ILocalRolesPlugin)
    InitializeClass(WorkspaceLocalRoleManager)
    On first glance, there is quite a lot going on here, but it is not so hard to understand. First, we define a good old-fashioned Zope 2 factory and ZMI add form. This is good practice, because PAS plug-ins can be managed via acl_users in the ZMI. If you find yourself wandering there, however, remember to bring a torch and keep a trail of breadcrumbs to find your way out. A backup wouldn't hurt either if you try to change things. It is, unfortunately, not the most intuitive of interfaces.

    We will see how the plug-in is registered and activated in a moment, but first notice that the plug-in implements an interface, ILocalRolesPlugin,  which is defined by PlonePAS, the PAS-in-Plone integration layer. This defines methods that will be called by the PAS machinery to determine, in this case, local roles. Note that this is not an adapter (perhaps it would have been if PAS had been invented in Zope 3, though Zope 3 has its own authentication machinery that is evolved from PAS and works slightly differently). When created, the ProjectLocalRoleManager is an Zope 2 object that lives in the ZODB in acl_users.

    The methods of the ILocalRolesPlugin interface are fairly self-explanatory in purpose. They allow PAS to extract the local roles for a particular user in a particular context (getRolesInContext()), to check whether a user in fact has one of the roles required to access a particular method attribute in a particular context (checkLocalRolesAllowed()), and to get a map of users-to-roles in a particular context.

    The complex parts are, as often is the case, concerned with acquistion. The helper method _findWorkspace() attempts to walk up the object hierarchy to find the first possible IWorkspace (it will only consider one) to get hold of the appropriate IWorkspace adapter that is then used to determine the actual roles that apply, as above. Without walking up the content hierarchy, it would not be possible to let the local roles of a particular project apply when in the context of a piece of content inside that project (i.e. a sub-object of the folderish Project object). There is some reasonably hairy acqusition-juggling going on in the _chain() method to return this chain as a generator. The hairiness comes from the fact that the thing that is being checked may in fact be a method that is being accessed, and aqusition chains can get themselves in all kinds of knots, especially when Five is in the mix.

    Lastly, we need to declare a ClassSecurityInfo and call InitializeClass to get Zope 2 to play ball.

    Registering the plug-in

    To be able to use this plug-in, we must first register it with PAS. This is done when the product is loaded, in borg/__init__.py:
    from Products.PluggableAuthService import registerMultiPlugin
    ...
    from pas import localrole
    ...
    registerMultiPlugin(localrole.WorkspaceLocalRoleManager.meta_type)

    def initialize(context):
    context.registerClass(localrole.WorkspaceLocalRoleManager,
    permission = AddUserFolders,
    constructors = (localrole.manage_addWorkspaceLocalRoleManagerForm,
    localrole.manage_addWorkspaceLocalRoleManager),
    visibility = None)
    ...
    This is similar to how CMF content types are initialised with ContentInit().initialize() and context.registerClass(). In other words, copy-and-paste and the less you know the happier you will be.

    By registering the plug-in, we could now ask our users to instantiate a Workspace Roles Manager within acl_users.... er... somwhere. Like we said - not necessarily obvious. Better to do it once, in the setup code for b-org. Please refer to the section on GenericSetup to learn how b-org is actually installed, but notice that the relevant code is in setuphandlers.py:
    from Products.CMFCore.utils import getToolByName
    from Products.PlonePAS.Extensions.Install import activatePluginInterfaces
    from config import LOCALROLES_PLUGIN_NAME
    ...
    def setupPlugins(portal, out):
    """Install and prioritize the project local-role PAS plug-in.
    """
    uf = getToolByName(portal, 'acl_users')

    borg = uf.manage_addProduct['borg']
    existing = uf.objectIds()

    if LOCALROLES_PLUGIN_NAME not in existing:
    borg.manage_addWorkspaceLocalRoleManager(LOCALROLES_PLUGIN_NAME)
    print >> out, "Added Local Roles Manager."
    activatePluginInterfaces(portal, LOCALROLES_PLUGIN_NAME, out)
    All we do here is get hold of the factory dispatcher for the user folder (from manage_addProduct, which has something to do with that registerClass call for the WorkspaceLocalRoleManager seen in the previous code example, but like we said, it's dont-ask, don't-tell) and if it is not there already, we create an instance of the plugin using the factory. We then need to activate it so that it actually takes effect. out is a StringIO output stream used for logging.

    5.5.12. Placeful workflow

    b-org uses CMFPlacefulWorkflow, which ships with Plone 2.5, to manage the workflow of content objects inside a project.

    Placeful workflows are based on the concept of policies. You can think of a policy as a mapping of workflows to types, in the same way as you could control from the portal_workflow tool. Policies are created, normally by copying an existing policy (possibly the default, global policy), and then applied to a context. In Plone, this can be done using the policy option in the state menu.

    Placeful workflows are used in b-org Projects. Inside a project, project members should have elevated view and modify permissions over content. This is achieved using the following technique:

    • A new role TeamMember is made available within any Project.
    • A custom workflow, borg_project_default_workflow is a customisation of the default Plone workflow that has a simplified set of states and actions, and is aware of the TeamMember role.
    • A placeful workflow policy sets the default workflow, as well as the workflow for folders, to this one.
    • When a Project is created, this placeful workflow policy is enabled for the project.

    The custom workflow is defined using GenericSetup, in profiles/default/workflows/borg_project_default_workflow/definition.xml. You can of course install your own workflow if necessary. The workflow policy is set up in the importVarious setup step, in setuphandlers.py:

    from Products.CMFCore.utils import getToolByName
    from config import LOCALROLES_PLUGIN_NAME, PLACEFUL_WORKFLOW_POLICY

    ...

    def addProjectPlacefulWorkflowPolicy(portal, out):
    """Add the placeful workflow policy used by project spaces.
    """
    placeful_workflow = getToolByName(portal, 'portal_placeful_workflow')
    if PLACEFUL_WORKFLOW_POLICY not in placeful_workflow.objectIds():
    placeful_workflow.manage_addWorkflowPolicy(PLACEFUL_WORKFLOW_POLICY,
    duplicate_id='portal_workflow')
    policy = placeful_workflow.getWorkflowPolicyById(PLACEFUL_WORKFLOW_POLICY)
    policy.setTitle('[borg] Project content workflows')
    policy.setDefaultChain(('borg_project_default_workflow',))
    policy.setChainForPortalTypes(('Folder', 'Large Plone Folder',),
    ('borg_project_default_workflow',))

    Again, you could add a different policy if you needed different settings.

    Finally, we apply the policy when a project is created. We will see how this is set up when events are covered in the next section, but the relevant code is in events/project.py:

    from zope.interface import implements
    from zope.component import getUtility

    from Products.CMFCore.utils import getToolByName

    from Products.borg.config import PLACEFUL_WORKFLOW_POLICY
    from Products.borg.interfaces import ILocalWorkflowSelection

    class DefaultLocalWorkflowSelection(object):
    """Select the default local workflow policy.

    Local adapters or overrides may supercede this.
    """

    implements(ILocalWorkflowSelection)

    workflowPolicy = PLACEFUL_WORKFLOW_POLICY


    def addLocalProjectWorkflow(ob, event):
    """Apply the local workflow for project spaces when a project is added.
    """

    # Add the TeamMember role if necessary
    if 'TeamMember' not in ob.validRoles():
    # Note: API sucks :-(
    ob.manage_defined_roles(submit='Add Role',
    REQUEST={'role': 'TeamMember'})


    # Find out which workflow to use - this is looked up as a utility so
    # that other components can override it.
    workflowSelection = getUtility(ILocalWorkflowSelection, context=ob)

    # Set the placeful (local) workflow
    placeful_workflow = getToolByName(ob, 'portal_placeful_workflow')
    ob.manage_addProduct['CMFPlacefulWorkflow'].manage_addWorkflowPolicyConfig()
    config = placeful_workflow.getWorkflowPolicyConfig(ob)
    config.setPolicyBelow(policy=workflowSelection.workflowPolicy)

    Here, the local role is added to the newly created project instance (it is not made global so as not to pollute the global roles list), and the policy is associated with the contents of the (folderish) project object.

    Note that we do not hard-code the name of the workflow policy! Instead, we ask a utility called ILocalWorkflowSelection. This could be overridden using a local utility, but the global one references the policy created above, as defined in DefaultLocalWorkflowSelection. This utility is registered in events/configure.zcml as follows:

      <utility provides="..interfaces.ILocalWorkflowSelection"
    factory=".project.DefaultLocalWorkflowSelection" />

    5.5.13. Sending and handling events

    Events is undoubtedly one of the most useful things that Zope 3 brings to the Zope 2 world. Here's how b-org uses them.

    In the previous section, you saw how an event handler was used to apply a placeful workflow policy to newly created projects. This pattern is quite powerful - instead of needing to subclass Project just to add something to at_post_create_script() or initializeArchetype(), say, you simply register an appropriate event handler. This pattern can of course apply to other situations, such as when objects are modified, deleted, added to a container, or on any other type of event that may occur in your system. Events are synchronous, so when code emits an event, it will block until all event handlers are finished.

    Recall the event handler for adding projects. It can be found in events/project.py and has the following signature:

    def addLocalProjectWorkflow(ob, event):
    ...

    The first argument is the object the event was fired on, the second is an instance of the event itself. In fact, this two-part event dispatcher is a special case of events described with IObjectEvent and its sub-interfaces. Internally, Zope 3 catches all IObjectEvents and re-dispatches the event based on the object that is passed along the event instance. The registration for the event handler in events/configure.zcml looks like this:

    <subscriber for="..interfaces.IProjectContent
    zope.app.container.interfaces.IObjectAddedEvent"
    handler=".project.addLocalProjectWorkflow" />

    Note that there are two interfaces the subscriber is registered for - the object type and the event type. These must be separated by whitespace, though a newline like above is customary. This is the same syntax that is used to explicitly define multi-adapters (if you are not using the adapts() syntax in an adapter class) - in fact, the events machinery uses the adapter registry internally to map subscribers to events when they are fired.

    A more general-case event can be found in events/employee.py, which takes care of assigning ownership of an Employee object to the user that is tied to that employee. The code is borrowed and adapted from PloneTool, but notice the signature which only includes the event:

    def modifyEmployeeOwnership(event):
    """Let employees own their own objects.

    Stolen from Plone and CMF core, but made less picky about where users are
    found.
    """

    The registration in events/onfigure.zcml is similar to the one above, but only uses one for interface:

     <subscriber for="..interfaces.IEmployeeModifiedEvent"
    handler=".employee.modifyEmployeeOwnership" />

    Sending custom events

    You will notice that the IEmployeeModifiedEvent is a custom event. In Plone 3.0 (or rather, Archetypes 1.5) this won't be necessary, because Archetypes will take care of sending an event derived from IObjectModifiedEvent, which in turn derives from IObjectEvent and thus is subject to the same registration as the IObjectAddedEvent that includes the object type and the event type. For now, though, we need to send the event ourselves.

    The event is described by an interface in interfaces/employee.py:

    from zope.interface import Interface, Attribute

    ...

    class IEmployeeModifiedEvent(Interface):
    """An event fired when an employee object is saved.
    """

    context = Attribute("The content object that was saved.")
    The implementation is trivial, and can be found in content/employee.py:
    from zope.interface import implements

    ...

    from Products.borg.interfaces import IEmployeeModifiedEvent

    ...

    class EmployeeModifiedEvent(object):
    """Event to notify that employees have been saved.
    """
    implements(IEmployeeModifiedEvent)

    def __init__(self, context):
    self.context = context
    It is of course the event class that we instantiate and send, whilst we register the event handler for the event interface. This means that we could provide alternative implementations for the same event interface, if need be. It also means that event handlers subscribed for a parent interface will be invoked for events that provide a sub-interface.

    Sending the event is very simple. In the definition of Employee in content/employee.py, we have:

    from zope.event import notify

    ...

    class Employee(ExtensibleSchemaSupport, BaseContent):

    ...
     
    security.declarePrivate(permissions.View, 'at_post_create_script')
    def at_post_create_script(self):
    """Notify that the employee has been saved.
    """
    notify(EmployeeModifiedEvent(self))

    security.declarePrivate(permissions.View, 'at_post_edit_script')
    def at_post_edit_script(self):
    """Notify that the employee has been saved.
    """
    notify(EmployeeModifiedEvent(self))

    We construct an event instance and parameterise it with the right object (i.e. self) before sending it with notify(), all on one line.

    5.5.14. Annotations

    Annotations are an elegant solution to the "where do I store this?" problem, and are used in many Zope 3 applications.

    It is often useful to be able to attach information to an object even if you don't have control over that object's type and schema. For example, a tagging solution may attach a list of tags to an object, or a notification tool may want to add a list of subscribers on a per-object basis. This is known in Zope 3 as "annotations".

    Annotations work like this:

    • A marker interface, normally IAttributeAnnotatable is applied to the class or object that is to be annotated. This particular marker means that annotations are stored in a persistent dict called __annotations__ that is added to the object, though this should be considered an implementation detail.
    • An adapter exists from IAttributeAnnotable to IAnnotations. If you need a different annotation regime (e.g. one that stores the values keyed by object id in some local utility) you could provide a different adapter to IAnnotations.
    • The code that wishes to annotate an object will adapt it to IAnnotations. The annotations adapter acts like a dict. Conventionally, each package that uses annotations will store all its (arbitrary) information under a particular key in that dict. The key name is normally the same as the name of the package. This is mainly to avoid conflicts between different packages annotating a particular object.

    In b-org, we don't have quite the same need for annotating objects from other parts of Plone, but we use annotations to store users' passwords. This ensures that they cannot be accessed through-the-web (since Zope 2 won't publish the __annotations__ dict, as it begins with an underscore) and keeps passwords out of the way. Strictly speaking, this is probably overkill since the password is also hashed using the SHA1 one-way hasing algorithm, but that never stopped anyone before.

    First, look at the definition of the Employee class in content/employee.py:

    from zope.app.annotation.interfaces import IAttributeAnnotatable, IAnnotations

    ...

    class Employee(ExtensibleSchemaSupport, BaseContent):

    ...

    implements(IEmployeeContent,
    IUserAuthProvider,
    IPropertiesProvider,
    IGroupsProvider,
    IGroupAwareRolesProvider,
    IAttributeAnnotatable)

    Here, we explicitly say that Employee is attribute annotatable. Of course, this requires control over the class. If you are trying to annotate another type that isn't already marked as annotatable, you may be able to add the marker interface using classProvides() or directlyProvides() from zope.interface, or use the ZCML <implements /> directive. You need to be a bit careful, though, since the thing you are annotating should probably be persistent. You should also be polite - you're stuffing your own information onto someone else's object. Try not to break it.

    Further down in content/employee.py, you will see the annotation being set:

        security.declareProtected(permissions.SetPassword, 'setPassword')
    def setPassword(self, value):
    if value:
    annotations = IAnnotations(self)
    annotations[PASSWORD_KEY] = sha(value).digest()

    PASSWORD_KEY comes from config.py, and is simply a string. The digest is verified in membership/employee.py, in the IUserAuthentication adapter:

    class UserAuthentication(object):
    """Provide authentication against employees.
    """
    implements(IUserAuthentication)
    adapts(IEmployeeContent)

    def __init__(self, context):
    self.context = context

    def getUserName(self):
    return self.context.getId()

    def verifyCredentials(self, credentials):
    login = credentials.get('login', None)
    password = credentials.get('password', None)

    if login is None or password is None:
    return False

    digest = sha(password).digest()

    annotations = IAnnotations(self.context)
    passwordDigest = annotations.get(PASSWORD_KEY, None)

    return (login == self.getUserName() and digest == passwordDigest)

    That's all there is to it. We get an IAnnotations adapter, and then look up the PASSWORD_KEY to find the digest. The annotations adapter has the same contract as a Python dict, so we can use functions like get() and setdefault().

    5.5.15. Zope 3 Views

    One of the nicest things that Zope 3 brough us is a way to manage view logic.

    In Zope 2, a view (be that a view of a content object, or a more standalone template) typically consists of a Zope Page Template that pulls in data from its context. The problem is that non-trivial templates usually require some kind of "view logic" or "display logic". People tend to put these in a few places:

    • Complex python: expressions in the ZPT. This is bad because it makes your templates hard to understand, and because there is a limit to what you can do with one-line Python expressions.
    • External Python Scripts in a skin layer that get acquired in the page template, e.g. here/calculateDate. This is bad because it is cumbersome to create a new file for something which may be quite trivial, because all such scripts are part of a global namespace (and thus there may be conflicts between two different scripts with the same name), and also because Python scripts in the skin layers (and python: expressions) are slower than filesystem Python code and more restricted.
    • A custom tool that provides some necessary functionality. This is bad because a tool is a singleton, so you will probably need to explicitly pass around a context. Tools are also part of that same global namespace (by way of acquisition from the portal root), and are a hassle to create and install.
    • Methods on the context content object (where applicable). This is bad because it mixes presentation logic and the model (the schema) and storage logic. This often leads to an explosion of methods on each content type that are highly specific to a particular template. This pattern also requiers that you have the ability to add new methods to the content type class, even if you are just adding a new view template for it.

    As usual, these problems indicate a lack of separation of concerns. Zope 3's answer is a view - a class (typically) which may be associated with a template.

    Views are multi-adapters

    You will often hear that views are named multi-adapters of a context and a request. In fact, the concept of a multi-adapter originated in the need for views. For most practical purposes, you can forget about this - it is an implementation detail. However, you may sometimes need to look up views yourself, which can be done using:

    from zope.component import getMultiAdapter
    myView = getMultiAdapter((context, request), name='my_view')
    

    More importantly, you need to know that to access the context the view is operating on inside that view, you can use self.context, and to access the request (including form variables submitted as part of that request, if applicable), using self.request.

    Explicitly acquiring views

    One of the easiest ways of using views with existing code is to make page templates in a skin layer as you normally would, and then acquire a view object that is used for rendering logic. One of the main reasons for using this approach is that it allows page templates to be customised using the normal skin layer mechanism. This is approach is used extensively in Plone 2.5. Here's an example from the "recent" portlet, starting with portlet_recent.pt:

    ...
    
    <tal:recentlist tal:define="view context/@@recent_view;
                                results view/results;">
    
        ...
    
        <tal:items tal:repeat="obj results">
    
            ...    
    
        </tal:items>
    
        ...
    
    </tal:recentlist>
    

    The important line here is context/@@recent_view. This will look up a view named recent_view relative to the current context (context in page templates is a now-preferred alias for the here variable that was used before - here still works in Zope 2 templates, but is gone in Zope 3).

    This view is defined by a class and a ZCML directive. The ZCML directive looks like this:

    <browser:view
          for="*"
          name="recent_view"
          class=".portlets.recent.RecentPortlet"
          permission="zope.Public"
          allowed_attributes="results"
          />
    

    Actually, this is not exactly what's in the file in Plone, since Plone is working around a few Zope 2.8 issues, but basically, this says that the view is available on all types of contexts (for="*" - this could specify a dotted name to an interface if needed, more on that below), has the name recent_view, is public (because of the magic permission zope.Public) and that when acquired, the attribute (method) results is allowed - more attributes could be specified separated by whitespace. The class that is referenced contains the view implementation. Here it is, again slightly modernised:

    from Products.Five.browser import BrowserView
    from Products.CMFCore.utils import getToolByName
    
    from Acquisition import aq_inner
    
    class RecentPortlet(BrowserView):
        """The recent portlet
        """
    
        def results(self):
            """Get the search results
            """
            context = aq_inner(self.context)
            putils = getToolByName(context, 'plone_utils')
            portal_catalog = getToolByName(context, 'portal_catalog')
            typesToShow = putils.getUserFriendlyTypes()
            return self.request.get(
                'items',
                portal_catalog.searchResults(sort_on='modified',
                                             portal_type=typesToShow,
                                             sort_order='reverse',
                                             sort_limit=5)[:5])
    

    The use of aq_inner() on self.context is not strictly necessary always, but is a useful rule of thumb to make acquisition do what you expect it to do (this is because the BrowserView base class extends Acquisition.Explicit, which causes self.context to gain an acquistion wrapper that can mess with its acqusition chain).

    Views with templates

    Zope 3 does not use views in this way. Instead, you would bind the template to the browser view explicitly. The main drawback of this technique is that the template is not present in the portal_skins tool, and so cannot be customised through-the-web. This may be possible in future versions of Zope and CMF, but for now the full-blown view technique is best used when it is not necessary to customise views through-the-web. Of course, you can still override view registrations using ZCML on more specific interfaces or an overrides.zcml.

    Here is a view for departments in the charity example product, under charity/browser/configure.zcml. Notice how this entire XML file is in the browser namespace, and thus it is unnecessary to prefix each directive with browser:

    <configure xmlns="http://namespaces.zope.org/browser"
               i18n_domain="charity">
    
      <page
          name="charity_department_view"
          for="Products.borg.interfaces.IDepartmentContent"
          class=".department.DepartmentView"
          template="department.pt"
          permission="zope2.View"
          />
          
      ...
      
    </configure>
    

    Here, we explicitly state that this view is only available for IDepartmentContent objects. This means that if you try to invoke @@charity_department_view on anything that does not provide this interface, you will get a lookup error. The view is protected by the Zope 2 View permission. Also note that there is no allowed_attributes (or allowed_interface) attribute here. This is because the view is not intended to be used by other templates (if they tried, they would get an Unauthorized error when trying to access any attribute of the view) - all the logic is in the department.pt template.

    The department.pt template is found in charity/browser, the same directory as the configure.zcml file above. You can use relative paths like ./templates/... if necessary to point to the template file on the filesystem. Here is the class:

    from Products.Five.browser import BrowserView
    from Products.borg.interfaces import IDepartment
    
    class DepartmentView(BrowserView):
        """A view of a charity department"""
    
        def __init__(self, context, request):
            self.context = context
            self.request = request
        
        def name(self):
            return self.context.Title()
            
        def managers(self):
            return self.context.getManagers()
            
        def details(self):
            return self.context.Description()
    

    And here is the template that uses these methods:

    <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"
          lang="en"
          metal:use-macro="here/main_template/macros/master"
          i18n:domain="charity">
    <body>
    
    <metal:main fill-slot="main">
        
        <div metal:use-macro="here/document_actions/macros/document_actions">
            Document actions (print, rss etc)
        </div>
        
        <h1 class="documentFirstHeading" tal:content="view/name" />
        
        <table class="listing vertical" style="float:right" tal:condition="view/managers">
          <tr>
            <th>Manager(s)</th>
            <td>
              <div tal:repeat="obj view/managers">
                <a href="#" tal:attributes="href obj/absolute_url" tal:content="obj/Title" />
             </div>
            </td>
          </tr>
        </table>
    
        <div tal:content="structure view/details" />
    
        <metal:listing use-macro="here/folder_listing/macros/listing" />
        
        <div class="visualClear"><!----></div>
        
    </metal:main>
    
    </body>
    </html>
    

    Now, you can go to a hypothetical URL /mydept/@@charity_department_view to see this view rendered. In fact, this is set as the view and (Default) aliases for the Department content type when charity is installed, so the user will never see this URL.

    Views without templates

    It is also possible to make views without templates. This is useful if you need a URL to submit that does some processing. That processing would normally be done in the __call__() method, as in the hypothetical example below:

      <browser:view
          name="modify_customer"
          for=".interfaces.ICustomer"
          class=".customer.ModifyCustomerView"
          permission="cmf.ModifyPortalContent"
          />

    Now, we could write a form that has action="@@modify_customer", which would result in this being called:

    class ModifyCustomerView(BrowserView):
        """Modify a customer from a form
        """
    
        def __call__(self):
            name = self.request.form.get('name', None)
            dog = self.request.form.get('dog', None)
    
            self.context.name = name
            self.context.dog = dog
    
            self.request.response.redirect('@@customer_view')
    

    This is obviously a simplified example, but the important thing to realise is that the view will tend to use self.context and self.request to interact with the rest of the portal.