Personal tools
You are here: Home Documentation How-tos Writing CMF content types using Python
Support

Get Help

Join our chat rooms or support forums if you have more specific questions.

Plone Training
Learn how to design, build, and deploy a website in Plone through one of the numerous Plone training sessions around the world.
Find Plone training…
 
Document Actions

Writing CMF content types using Python

This How-to applies to: Any version.
This How-to is intended for: Developers, Advanced Developers

How to write CMF content types using Python. We recommend using Archetypes instead, but this is here for reference, should you ever need it.

CMF Type Creation in Python

Shows the creation of new CMF object types from scratch using Python. Useful if you want to know what's going on behind the scenes in Archetypes, or can't use Archetypes for some reason.

Summary

Aims of the Tutorial

The goal of this tutorial is to easily create new CMF object types, not through the ZClass intermediary, but by using Python directly in your favorite text editor.

I'm assuming that you already know Python and CMF.

I wrote this tutorial while first using the CMF, by wanting to figure out how to write products in Python. To this end, I mainly studied the CMFCore and CMFDefault products and used the http://www.gnu.org/copyleft/fdl.html tutorial.

I hope that this will succeed in bringing missing documentation to this subject..... (notably in French!)

Don't hesitate to send me your comments via e-mail

Introduction

The CMF gives by default several different types of objects (news, links, etc...) by creating your site's container management. This is most of the time efficient and sufficient to create your portal, but it is often that you have the need to use an object type that is not already created. Fortunately, CMF allows us to create new type of objects, notably in Python

We are going to create here a new type of object: Tutorial

A Tutorial is similar to a file ... identified by its author, its contributors, its release date, its title and a short description. It can one of several types: Case Study, White Paper...

We're going to see how to create this product (though a CMF type is a product in ZOPE), how to initialize it in ZOPE and how to use it throughout a CMF site.

Product Installation

Before we see how to build the product, we're going to test it briefly.

For this, download the product archive here. Copy it into your $ZOPEPATH/directory, then uncompress it. Then go to the CMF site root where you want to use the new type and create an External Method with the following parameters:

Id: TutorialInstall 

Title: (optional)
Module Name: TutorialInstall
Function Name: install

This method allows you to declare the new object type according to the types available in your CMF site; we'll get to that later.

After this method executes, you can add new objects of the Tutorial type in your CMF Folder.

The edit/modification methods are, as you will see, similar to some of the native CMF objects, and this tutorial creates a workflow mechanism.

How does it work?

Let's focus on the Python Product Source Code before seeing how to get to the result.

Once the product is decompressed, you must put it within the directory structure:

$ZOPEPATH/lib/python/Products/CMFTutorial/ :

CMFTutorial.py

INSTALL.txt
LICENSE.txt
__init__.py
skins/
history.txt
versions.txt

As you may suspect, CMFTutorial.py contains the code of our product.

__init__.py contains the instructions that allow Zope to recognize this product when the server starts.

The skins folder contains the DTML methods necessary to display and edit our type.

At the same time, the TutorialInstall.py file adds the directory $ZOPEPATH/Extensions/

This Python script (used as an External Method) allows the automation of the recognition procedure of our type within our CMF instance.

CMFTutorial.py

This is the first file to create when you want to define a new product type, by having taken care to define the necessary attributes of this object (here: author, release date, etc...) and its actions (publication, visualization)

Like most Python scripts, this file begins with a series of imports.

ZOPE Imports:

from Globals import InitializeClass

from AccessControl import ClassSecurityInfo
import OFS.Image

Although the last line is specific to our product (import relating to the operations on files), the first two deal with a number of Zope products that call the product initialization methods and are the ones concerning security restrictions.

CMF Imports:

from Products.CMFDefault.DublinCore import DefaultDublinCoreImpl

from Products.CMFCore.PortalContent import PortalContent
from Products.CMFCore.WorkflowCore import WorkflowAction
from Products.CMFCore import CMFCorePermissions
from Products.CMFDefault.File import File

The first two lines deal with relative CMF classes that the CMFTutorial is going to come from. The third item concerns the workflow of our object; the fourth is relative to the permission of the product's security. The last is there specifically for our product (file operations)

The first things to define in our new CMF type are the attributes and base actions, all as one defines the attributes and object methods in OOP before its implementation.

These are classified through a Python list named factory_type_information.

This one is defined as:

First of all, the attributes:

factory_type_information = (

{id : Tutorial,
meta_type : CMFTutorial,
description : (Tutorials are downloadable files with information about their release.),
icon : tutorial_icon.gif,
product : CMFTutorial,
factory : addCMFTutorial,
'immediate_view': metadata_edit_form,
  • id : The object type's unique ID. This is this that will be indicated to the user in the list of CMF object types in this Folder
  • meta_type : the name of the product that tells the user see in Control_Panel / Products
  • description : as its name indicates, the object's description as such is indicated to the user
  • icon : the icon that corresponds to the product (optional). It must be put in the skins/CMFTutorial/ directory.
  • product : The product that our type belongs to (i.e., the name of the directory)
  • factory : the method that will create the object instance in our CMF site.
  • immediate_view : the DTML method that will be called after the creation of our object.

You must be very careful about the values of these attributes. In fact, if the product doesn't correspond to the product where our new type is found (i.e., the name of the directory), our product will be recognized by Zope and CMF, but it will not be possible to create an instance of this type in our CMF site...

And the actions:

actions      : (

{id : view,
name : View,
action : cmftutorial_view,
permissions : (CMFCorePermissions.View,),
},


{id : edit,
name : Edit,
action : cmftutorial_edit_form,
permissions : (CMFCorePermissions.ModifyPortalContent,),
},


{ id : metadata,
name : Metadata,
action : metadata_edit_form,
permissions : (CMFCorePermissions.ModifyPortalContent,),
},


{ id : download,
name : Download,
action : '',


permissions : (CMFCorePermissions.View,),
},
)

Our object is going to have three actions: View, Edit, and Metadata. For each action, you find:

  • id : the action's unique internet reference
  • name : its name (such that it will be attached to the management interface of our CMF object)
  • action : the associated DTML method
  • permissions : the necessary permissions to call this method

As far permissions are concerned, these are the attributes from the CMFCorePermissions class.

View allows for a method to be accessible to everyone and ModifyPortalContent uniquely to the people having the possibility to modify the content of the object (i.e., the content managers and the object's authors).

After these definitions, we pass the method to add a tutorial.

def addCMFTutorial(self,

id,
title='',
description='',
type='',
author='',
contribs='',
file='',
filetype='',
):

""" Create an empty CMFTutorial """


tutorial_object = CMFTutorial(id, title, description, type, \
author, contribs, file, filetype)
self._setObject(id, tutorial_object)

This method creates a CMFTutorial instance and adds this instance to the ZODB(_setObject), i.e. in our CMF site. We will see in a moment what this method is called.

As we have already seen, this is the DTML Document metadata_edit_form that is called implicitly after the execution of this method.

Now we turn back to the CMFTutorial class. It's a Python class, which has a constructor, attributes and methods.

class CMFTutorial(DefaultDublinCoreImpl,

PortalContent,
OFS.Image.File,
File,
):

Our class has 4 parents here. You can choose what to inherit for your product from other classes according to your needs.

DefaultDublinCore allows us to have, among other things, a workflow for the object, so as well as to have attributes for our object given to them by native CMF objects (date of last modification, author, etc...)

PortalContent is also necessary for our object to be usable by the CMF.

The inheritence of OFS.Image.File and of File is necessary for the operations concerning the attached file.

The first attribute for our class is the meta_type, which is new...(qui est a nouveau indiqu�)

This is necessary so that the object instances of the created objects are listed in your folder after their creation.

Next we find the instruction:

ClassSecurityInfo().declareObjectPublic()

which allows us to declare the object as being public.

Then comes our class constructor:

def __init__(self,

id,
title='',
description='',
type='',
author='',
contribs='',
release='',
file='',
filetype='',
):

""" Initialize an instance of the class """


## constructor parents
DefaultDublinCoreImpl.__init__(self, title, description = description)


## set info
self.id = id
self.type = type
self.author = author
self.contribs = contribs
self.release = release
self.filename = ''
self.filetype = filetype

Here we call the DefaultDublinCoreImpl constructor, which gives our instance all the properties of the base CMF objects (creation date, author of the object, etc...). The title of our tutorial and its description are stocked with attributes from DefaultDublinCoreImpl and are considered as the metadata of our object. In fact, all CMF objects have attributes like metadata, we get these for free, rather than redefining them separately. (nous en profitons donc, plutot que de les redéfinir séparement.)

Then we initialize our instance variables, which are this time specified by our product (type of tutorial, author, etc...)

Now that we have initialized the attributes, let's see how to modify them.

This is the edit method that will allow us to edit the object's properties.

def edit(self,

type='',
author='',
contribs='',
file='',
fileType=None,
):

""" Edit the instance's properties """


## update info
self.type = type
self.author = author
self.contribs = contribs


## upload file + get info
if self._isNotEmpty(file):
self.manage_upload(file)
self.filename = file.filename
self.filetype = filetype or self._getType(file.headers)

This method only consists of updating the attributes of our instance.

As mentioned in the previous section, the first part manages general information of this one and the second loads itself with the one concerning the attached file.

Note that manage_upload() is inherited from OFS.Image.File and _isNotEmpty from File.

Notice that this publishing method doesn't take the title or the description of our Tutorial. In fact, as discussed above, these attributes are the metadata of the object, and we do not have to take care of their management, which is inherited from DefaultDublinCoreImpl (and makes itself, at the user interface level, by the Metadata action and not by Edit).

The instruction
edit = WorkflowAction(edit)
takes care of managing the workflow after publishing.

The following method, SearchableText, allows users to search for different instances of this object. It must resend the desired values that this search is possible (this comes back to indexing an object in the ZCatalog according to certain properties).

def SearchableText(text):
        """ Method used by search engine """
               return "%s %s %s %s  %s" % (self.title, \
               self.description, self.type, self.author, self.contribs)

The last method, our own new type, is the altered index_html. It is not always necessary to redefine this method; however, it does allow us to force the method call index_html from OFS.Image.File (i.e. the file download) and to specify that the file name to down download is the real file name (and not its id, which would be the case without defining the header). It verifies that the file exists, in order to create a Python exception at the server level, in the case where you're attempting download a non-existant file.

def index_html(self, REQUEST, RESPONSE):

""" Force tutorial download with correct filename """

if self.filename != '': RESPONSE.setHeader(Content-Disposition, attachement; \ filename="%s" %self.filename) return OFS.Image.File.index_html(self, REQUEST, RESPONSE)

I won't go back into the following method since it's a Python method that does not concern the creation of the CMF type as such, but nothing keeps you from looking at it, either. (rien ne vous y empeche de jeter un oeil, bien au contraire.)

We have thus created our product, but we still have to make our CMF instance and ZOPE recognize each other. And that's our next step.

__init__.py

We're going to see how Zope recognizes a product. If you have already worked on Zope products in Python, you have without doubt already had to make the __init__.py method.

We must first import our product from certain CMFCore classes

import CMFTutorial

from Products.CMFCore import utils, CMFCorePermissions, DirectoryView
import sys

Then we declare the class constructor of our class and the class itself

contentConstructors = (CMFTutorial.addCMFTutorial,)
contentClasses = (CMFTutorial.CMFTutorial,)

It's this first line that is going to cause addCMFTutorial will be called during the creation of a new instance in our CMF object.

The last phase consists of the declaration of the Zope product, indicating to it its constructor and associated permissions. We also declare here the skin path, which contains the DTML methods.

z_bases = utils.initializeBasesPhase1(contentClasses, this_module)


# Make the skins available as DirectoryViews
registerDirectory(skins, globals())
registerDirectory(skins/CMFTutorial, globals())


def initialize(context):
utils.initializeBasesPhase2(z_bases, context)
utils.ContentInit(CMF CMFTutorial,
content_types = contentClasses,
permission = CMFCorePermissions.AddPortalContent, \
extra_constructors = contentConstructors,
fti = CMFTutorial.factory_type_information,
).initialize(context)

This is where you choose the name your product will have in the Zope drop down menu of types to add; here it's CMFTutorial.

Installation Method

The TutorialInstall.py is found in CMFTutorial/Extensions.

This method allows for the automation of the steps which make the CMF recognize our object, know how to add the object type to our CMF instance, the modification of the skins...steps that you already recognize if you have manually installed CMF products.

DTML Methods

Now we have a new object type usable within the CMF, recognized first by the Zope server and by the CMF site instance that we've been working on, and now we are going to see how to add and edit one in our portal.

At the beginning of this tutorial, we saw that our object had properties, actions, security of DTML. It is these DTML documents that handle the posting and publishing of each instance of our new CMF object.

The associated DTML documents must be found in the skins/Tutorial/ directory before they can be recognized by the __init__ and Installation methods (and by conforming to the code of native CMF products).

This skins directory contains 3 files:

cmftutorial_edit.py

cmftutorial_edit_form.dtml
cmftutorial_view.dtml

The DTML document metadata_edit_form is not present here, it is acquired from other CMF objects

I don't think that it is necessary to review the validation and publication DTML files, since they are fairly basic.

A few remarks:

  • The use of
      enctype="multipart/form-data"
    in the header of the edit form [dans l'entête du formulaire d'édition ], which allows us to upload the file and add it to a specific header (containing, among other things, the Mime-Type used in our script), according to the HTTP protocol spefications.

  • The use of type validation in this same form, like the authorization of DTML.

      input type="text" size="30" name="release:date" value="" />

  • and a test for the existance of the release variable on the View page, in order to avoid a formatting error for a non-specific date.

Of course, these DTML methods are modifiable as desired at the appearance level.

The script called by cmftutorial_edit_form.dtml is cmftutorial_edit, as indicated in the form. Its first lines are the specific initialization lines of the Python scripts in Zope.

Script (Python) "cmftutorial_edit"
##bind container=container
##bind context=context
##bind namespace=
##bind script=script
##bind subpath=traverse_subpath
##parameters=REQUEST, RESPONSE, type, author, contribs, 
   release, file='', filetype=''

This bit of code consists of a simple call to the edit method of the CMFTutorial class, followed by a redirection to the same publish page in case of an error or to the View page in case of success.

Conclusion

Voila, you are now ready to create new CMF object types, though basic, but that will probably respond to the majority of your needs.

If you wish to add other functionality, don't hesitate to look at the source of the native CMF products in $ZOPEPATH/lib/python/Products/CMFDefault.

Links

Created by apassant

Copyright (c) 2002 - Makina Corpus - http://www.makina-corpus.org

Document written by Alexandre Passant (mailto: apa@makina-corpus.org) under license GNU FDL (see LICENSE or http://www.gnu.org/copyleft/fdl.html).

Translated by Russell Hires 2/15/03

You have permission to copy, distribute, and/or modify this page in as long as this message appears.


mgarnsey (Jul 8, 2003 7:10 pm; Comment #1)
 > <P>At the same time, the TutorialInstall.py file adds the directory
 >   <code>$ZOPEPATH/Extensions/</code></P>
 

An alternative approach for the installer would be to place the TutorialInstall.py file into an Extensions subdirectory under the CMFTutorial folder; along with changing the Module Name parameter of the external method to "CMFTutorial.TutorialInstall". Why? then the root-level of the .gz package could be CMFTutorial, and the product would easily unpack into the Products folder as is the case with many other products.

by Alan Runyan last modified August 31, 2007 - 15:42 All content is copyright Plone Foundation and the individual contributors.

broken link

Posted by Jens W. Klein at January 2, 2005 - 09:16

The link on as explained in the ZWACK Book - Chapter 5 is broken.

Tutorial

Posted by Martin Aspeli at February 6, 2006 - 14:24
This really ought to become a tutorial. As a how-to it's rather difficult to read.

For any issues with the web site functionality, please file a ticket.

Please consult the policy on plone.org content if you want your content published on this site.

Servers and hosting by