Every content management system requires extensive customization. Start with one that has the power to make your web site work the way your organization does.
Over the last few months, we have discussed content management systems (CMS) in general and Zope's content management framework (CMF) in particular. Zope's CMF is designed to give developers the tools they need to create their own content management systems. Of course, anyone who has worked with a CMS knows that even the most proprietary of the bunch requires extensive modification, reworking and customization before it can be used. Zope thus not only reduces the price of the base software, but provides a rich environment that makes it relatively easy to develop and customize the CMS.
When you create a CMF site, you (as the site manager) can add, modify and delete documents. Click on the folder contents link, click on the New... button, indicate which type of document you want to add and what ID it should have and then click on the add button. Enter the metadata (that is, title, description, subject and content type), click on the change & edit button, add some content and you're off and running.
However, although the existing content types are sufficient for simple sites, more sophisticated sites will want to create their own, custom types. CMF provides several ways to do exactly this. This month, we look at types in CMF—how we can work with them, customize their behavior, install new ones and even create new types to handle custom content.
The simplest way to create a new type is to use CMF's built-in, Web-based type extension system. It allows you to create a new type that shares its methods, properties, actions, presentation templates and icons with another type by default. When you create a new type using the Web-based extension system, you can modify any of these items, except for the methods and properties. In other words, the new type you create can have a different look and feel from its parent type, but it continues to behave much as the parent did.
For example, let's go to what is known as the types tool, available by clicking on portal_types within the management interface for a CMF site. If you don't have a CMF site already defined in Zope, you can create one by choosing CMF Site from the Add... menu in the upper-right corner of the Web-based Zope management interface. Once you have created the site, clicking on its icon from within the management interface displays a number of different customization tools, each with an icon that looks like a wrench.
When you first enter the types tool, you see a list of the currently defined CMF types, including folders, documents, news items, links and topics. You can examine and modify the properties and actions associated with these types by clicking on the name of the type you want to change. For example, if you want to examine or change the way the File content type does things, click on File. This brings up a new set of management tabs at the top of the page, with properties (the default) and actions being the only ones not standard to other parts of Zope. Actually, properties is a standard Zope tab, but CMF types have a number of unusual property names.
In addition to the standard properties you expect to see, each type has the following properties that affect what it does:
Icon: a string that describes which icon should be displayed for items of this type.
Product metatype: describes the Zope product meta-name. Meta-names are used in the Add... menu in the Zope Management Interface. This also is the name used in the similar Add... menu in the CMF.
Product name: indicates the Zope product in which the CMF type was defined. Because both the File and News item types were defined in the default CMF installation, they are listed as being in the CMFDefault product. And indeed, if you look in /lib/python/products/CMFDefault, which is a symbolic link to CMF-1.3/CMFDefault in CMF 1.3, you should see both File.py and NewsItem.py, Python modules that define the content types. To see how the initial values for properties are set, look at the factory_type_information variable in any module for any defined CMF type.
Product factory method: describes the method CMF should invoke to create a new instance of the type.
Filter content types and Allowed content types: these work together, even though they are separate properties. Although both of these properties exist for all CMF types, they are relevant only for folder-like objects, such as Folders and Topics. The first, Filter content types, is a boolean value that indicates whether Allowed content types is active. The second, Allow content types, lets you specify which types may be contained within the current type. So if you were interested in creating a folder that would contain only News items, you could do so by clicking yes and then indicating which types may be included.
The easiest way to create a new CMF type is to base it on an existing type with the Web-based CMF type creation tool. This method does not allow you to modify the fields or methods associated with a type, but it does let you change the permissions associated with the type's actions, whether the type can be discussed and even the way in which this data type is displayed.
For example, go to the portal_types tool and choose Factory-based type information from the Select type to add... menu in the top-right corner. You are prompted for two pieces of information, the ID or name of the new type and the existing type on which it should be based. We are creating the ATFDocument, which means we are basing ourselves on CMF Default: Document.
Once you create the new type, it is available and visible from all of the type listings, including the types tool and the contents view in which you create a new instance of a type. Indeed, anyone with administrative privileges on the portal can now see your new ATFDocument type in the menu of options from which they can choose a new type to create.
What's the point of doing this, if ATFDocument and Document are the same? Well, they're not exactly the same; rather, they share methods and an overall class definition. Other information about this type, such as properties, permissions and skins, default to be the same as Document, but they can be made to look quite different. This means that if you want instances of Document to be displayed in black-on-white text without discussions and ATFDocument to be displayed in yellow-on-maroon text with discussions, you can do that quickly and easily with this method. And, if and when you upgrade your copy of CMF, ATFDocument will be updated automatically, along with Document.
Of course, there will be times when you want to create a type that has fields or behavior significantly different from an existing type. Several options exist for doing this, but the most flexible (and challenging and poorly documented) method is to create a new Zope product that adheres to CMF rules. For example, all Python packages must contain an __init__.py file in the package's root directory. This file may be empty, or it may contain statements that are evaluated when the package is first loaded into memory. In the case of a product, __init__.py is where the class is first registered into Zope by use of the initialize() method, which takes a single argument commonly called context. A bare-bones Zope product thus has an __init__.py that looks something like the following mythical MyProduct:
import MyProduct def initialize(context): context.registerClass( MyProduct.MyProduct, constructors=(MyProduct.manage_addMyProductForm, MyProduct.manage_addMyProduct) )
When Zope starts up, it looks through the products and invokes the initialize() method with an appropriate context. Context is part of Zope's system of acquisition, in which an object's attributes are defined by its location in the hierarchy as well as by its class definition. In the above example, MyProduct registers itself with two constructors, the methods manage_addMyProductForm and manage_addMyProduct.
A CMF type must register itself not only with Zope but also with CMF, so it can appear in the various CMF tools. Our product's initialize() method thus needs to include CMF-specific registration, which means that __init__.py needs to import modules from CMF. Moreover, every type in CMF must register itself with one of the CMF-specific initialization routines in Products.CMFCore.utils. For example, __init__.py from CMFDefault, which comes with CMF, first defines the different classes it will register:
contentClasses = ( Document.Document , File.File , Image.Image , Link.Link , Favorite.Favorite , NewsItem.NewsItem , SkinnedFolder.SkinnedFolder )
It then defines the constructor for each of the classes:
contentConstructors = \ ( Document.addDocument , File.addFile , Image.addImage , Link.addLink , Favorite.addFavorite , NewsItem.addNewsItem , SkinnedFolder.addSkinnedFolder )
And, of course, every type can have its own specific tool:
tools = ( DiscussionTool.DiscussionTool , MembershipTool.MembershipTool , RegistrationTool.RegistrationTool , PropertiesTool.PropertiesTool , URLTool.URLTool , MetadataTool.MetadataTool , SyndicationTool.SyndicationTool )
Finally, the initialize() method, abbreviated slightly here, within the package registers these classes using CMF with utils.ToolInit(), for tools, or ContentInit, for content. It then invokes initialize(context) on what it receives back, thus registering the new object with Zope:
def initialize( context ): utils.ToolInit('CMFDefault Tool', tools=tools, product_name='CMFDefault', icon='tool.gif', ).initialize( context ) utils.ContentInit( 'CMFDefault Content' , content_types=contentClasses , permission=AddPortalContent , extra_constructors=contentConstructors , fti=Portal.factory_type_information ).initialize( context ) context.registerClass(Portal.CMFSite, constructors=(Portal.manage_addCMFSiteForm, Portal.manage_addCMFSite, ))
The final statement in the above version of initialize(), as you can see, is similar to the final statement in the version of initialize() from the sample MyProduct(), demonstrating that CMF types are Zope products, only with some extra hooks included.
This article concludes our look at Zope as a platform for content management, which began with Plone and concluded with CMF and CMF types. Now that we've looked at CMF in a bit more detail, let's consider whether it is worth using for projects that require a CMS.
The good news is that CMF is a powerful and flexible system. In the hands of a skilled and knowledgeable developer, CMF makes it possible to produce a custom CMS with lower cost and greater flexibility than the proprietary systems now on the market. The fact that everything is built on top of Zope, which is designed for rapid development, makes it quick and easy to create new types, modify templates and develop functionality.
But CMF, like much open-source software, suffers from a terrible lack of up-to-date and useful documentation. I'm sure that one of the reasons for the success of the Plone CMS is the excellent documentation that comes with Plone.
So if you're going to use CMF, be ready and willing to read through a great deal of Python code, to experiment quite a bit and to ask other CMF developers for help. Given the central role that CMF already is playing in the Zope world already, I expect that the amount and quality of CMF documentation will continue to increase. But until it does, working with CMF will require patience, reading the source code and a lot of trial and error.
The current state of the CMF is such that I would be somewhat hesitant to use it for anything but the largest and most complex content management systems. That said, the flexibility and power of the CMF is designed to solve problems of precisely this magnitude. In short, as inappropriate as CMF might be for small jobs, it probably is quite appropriate for large ones. And as time goes on, I expect CMF to play an increasingly prominent role in the world of open-source content management, providing a framework for the rapid development of custom CMS software.
Zope's CMF is an impressive framework for building a custom CMS. I have no doubt that CMF makes it easy to create a CMS, at a significantly lower cost and with far less effort than would be the case with a full-fledged proprietary solution. That said, CMF still is not quite ready for prime time for anyone who is not intimately familiar with it or willing to spend a great deal of time learning it. I would argue that Plone has pushed CMF into the spotlight, and the fact that Zope 3 will be largely or completely merged with CMF means there is now greater incentive at Zope Corporation to make CMF more impressive and better-documented than was previously the case.
If you have a fair amount of programming experience with Python and Zope, you almost certainly can use CMF to create your own custom types as Zope products—and with those, create impressive, interesting sites for yourself and your clients. However, until the type-creation system becomes easier to understand, CMF will not get the attention it deserves from outside the Zope community. Creating Zope products is no longer the black art that it used to be, and I expect that creating CMF types will be treated similarly in the near future.
Next month, we will shift gears dramatically, looking at another open-source CMS known as Bricolage. Bricolage, which uses Mason, mod_perl and PostgreSQL, has gained a great deal of ground in the past year, and it is an increasingly prominent player in the open-source CMS community.