Thursday, March 22, 2007

Modeling Content Types with Schemata

Object oriented analysis, design, and programming solves a wide array of problems. Relational databases make persistence of data a snap. How can you bring the best of these two worlds together? In this article, you'll look at using an object schema to define the attributes of content types. You'll explore Archetypes, a content schema system, and see how it fits in with content management systems. And, you'll create new content types to tackle simple but real-world challenge, managing your DVD library, and all with a pretty user interface.
Presenting Pages in Portals
In the last article, you got a whirlwind introduction to Plone, a content management system (CMS) that lets you create, edit, and publish content through the web to maintain a portal, intranet, public-facing company website, or other web presence. But, CMSs can do a lot more than lower the bar for non-technical people to take web publishing into their own hands. And that's where content types come into play.
A portal's job, regardless of the kind of application in which it's situated, is to publish content. Out of the box, CMSs like Plone and others include content types that reflect traditional web usage: web pages and images that appear on those pages. Plone adds a few additional content types as well, again reflecting the typical use of portals and similar web applications: news items, events, folders in which to make hierarchies of content, and a few others.
But, Plone goes one step further in letting web developers define content types of their own. By modeling application objects as content types, you can go on to define complete web applications in terms of content management. This has the immediate effect of simplifying web application development by hooking into all of the features that a CMS provides "for free": persistence into a database, authentication and authorization, form and page generation, data validation, and so forth.
How do you define content types? By using a schema language called Archetypes. Try it out!
Plone Goes to the Movies
Almost any domestic challenge, whether organizing recipes, scheduling sports meets, or publishing the family newsletter, can be made worse with computer technology. Yet occasionally, computers do help out. And, that's what you'll attempt to do with Plone: Develop a small web application to manage your DVD collection. Rather than futilely try to keep DVDs in order on a shelf by genre or title or age-appropriate rating, you'll put them in an arbitrary but numbered order, and let Plone index them to make locating the right film trivial. And, rather than simply create a free-text web page for each DVD, you'll define a new content type that captures all of the metadata (that is, data about data, in this case, data about a DVD) for each DVD. So, grab a Sharpie and number those DVDs—and then head back to the computer.
With Plone, you use the Archetypes schema language to define your own content types. Content types are defined in products. Products are add-ons to Plone (through its underlying Zope application server) that provide new features. To define a new product, you create a subdirectory in your Plone installation's Products directory. You also create a few standard subdirectories under this directory that holds the initialization code and user interface elements for the product. For the DVD Collection product, we'll make a directory called DVDCollection. Windows users can use Windows Explorer to make the directories, or you might enter the following commands at the Command Prompt:C:>cd "c:Program FilesPlone 2DataProducts"
C:Program FilesPlone 2DataProducts>mkdir DVDCollection
C:Program FilesPlone 2DataProducts>mkdir
DVDCollectionExtensions
C:Program FilesPlone 2DataProducts>mkdir DVDCollectionskins
C:Program FilesPlone 2DataProducts>mkdir
DVDCollectionskinsDVDCollection
Mac OS X and other Unix users would do something similar to this:% cd /Applications/Plone-2.5.2/Instance/Products
% mkdir DVDCollection
% mkdir DVDCollection/Extensions
% mkdir DVDCollection/skins
% mkdir DVDCollection/skins/DVDCollection
Now, fire up your favorite text editor. The first file you need to make in the DVDCollection directory is called __init__.py. Yes, the py extension means you'll be writing some Python code. Plone, Archetypes, and even the Zope application server on which Plone runs are written in Python, an object-oriented scripting language that focuses on readability and programmer productivity. By learning a little bit of Python, you'll be joining such ranks as NASA, Google, YouTube, and more, all of whom use Python prodigiously.
What do you put in this file? Mostly just boilerplate. Here it is:from config import SKINS_DIR, GLOBALS, PROJECT_NAME
from Products.Archetypes.public import process_types, listTypes
from Products.CMFCore.DirectoryView import registerDirectory
from Products.CMFCore.permissions import AddPortalContent
from Products.CMFCore.utils import ContentInit
registerDirectory(SKINS_DIR, GLOBALS)
def initialize(context):
import DVD
contentTypes, constructors, facTypeInfo =
process_types(listTypes(PROJECT_NAME), PROJECT_NAME)
ContentInit(PROJECT_NAME + ' Content',
content_types=contentTypes, permission=AddPortalContent,
extra_constructors=constructors,
fti=facTypeInfo).initialize(context)
Seasoned Python programmers know that having a file called __init__.py makes the directory in which it's located a Python package. However, because the directory DVDCollection is under your Plone installation's Products directory, it's also a product for the application server on which Plone is running. In other words, products are special kinds of Python packages.
The __init__.py file referenced a module called config, so next you need to create that in the DVDCollection directory by making another Python file, config.py. Its contents should be:PROJECT_NAME = 'DVDCollection'
GLOBALS = globals()
SKINS_DIR = 'skins'
This file just provides definitions for the name of the project, the currently available global symbols, and also the location of the "skins" directory. The skins directory is where user-interface elements of your product go, such as images, CSS files, page templates, and so forth.
Lastly, you need one more file of boilerplate. This file should be called Install.py and it needs to go under the Extensions subdirectory. Here's what you put inside that file:from Products.Archetypes.Extensions.utils import installTypes,
install_subskin
from Products.Archetypes.public import listTypes
from Products.DVDCollection.config import PROJECT_NAME, GLOBALS
from StringIO import StringIO
def install(context):
log = StringIO()
installTypes(context, log, listTypes(PROJECT_NAME), PROJECT_NAME)
print >>log, 'Installed %s content types' % PROJECT_NAME
install_subskin(context, log, GLOBALS)
print >>log, 'Installed skin layer'
return log.getvalue()
Where's your DVD content type in all this? Nowhere, yet! You'll create one more Python file in the DVDCollection directory, this time called DVD.py. Inside this file, you'll use the Archetypes schema language to define the attributes of a DVD in your collection, as well as create a Python class definition for a new class called DVD.
Introducing Archetypes
Calling Archetypes a schema language is a stretch. It's really just a collection of Python class definitions and functions that you use in a specific way to define the data fields of a content type. The field definitions include the name and data type of each field, validation to apply to incoming data, on-page widgets to use to display the field and to provide for users to edit their values, and so forth. You can mix in other schemata and build re-usable libraries of schemata to make defining new content types a piece of cake.
After specifying the schema for your content type, you define a new Python class that refers to that schema. Plone uses that class definition to create objects of your content type, persist them into its object database, display them on a web page, and so forth.
For your DVD collection, you need to define one content type: a DVD. You'll do all this in a file called DVD.py in the DVDCollection directory. Because you're giving each DVD its own number, start out this file with the field definition for a field called dvdNumber:from Products.Archetypes.public import Schema, IntegerField,
IntegerWidget
idSchema = Schema((
IntegerField('dvdNumber', required=True, index='FieldIndex:schema',
widget=IntegerWidget(label='DVD Number',
description='Numeric ID assigned to each DVD.')),
))
You now have a complete schema called idSchema with a single field called dvdNumber. Setting the required flag to True says that when users create a new DVD in your web application, they must provide a value for this field—leaving it blank will give an error message. The index attribute says that you want Plone to index this field for quick lookup and retrieval. Finally, you tell Archetypes what on-screen widget to use: in this case, an IntegerWidget. This widget automatically checks what users enter for values to make sure they're valid integers.
Now, create another schema that'll give you ways to classify DVDs. Add the following to the DVD.py file:from Products.Archetypes.public import StringField, SelectionWidget,
MultiSelectionWidget
classifierSchema = Schema((
IntegerField('runningTime', required=False,
widget=IntegerWidget(label='Running Time',
description='Length of the movie in minutes.')),
StringField('rating', required=False, index='FieldIndex',
vocabulary=['G', 'PG', 'PG-13', 'R', 'NC-17'],
widget=SelectionWidget(label='Rating',
description='MPAA rating assigned to the movie.')),
StringField('genre', required=True, index='FieldIndex',
vocabulary=['Action/Adventure', 'Comedy', 'Drama', 'Family',
'Romance', 'Spaghetti Western'],
multiValued=True,
widget=MultiSelectionWidget(label='Genre',
description='Artisitic style of the movie.',
format='checkbox')),
))

This second schema, classifierSchema, has three fields in it:
The first field, runningTime, is an optional integer field that tells how long the movie on the DVD lasts. It's optional because the required flag is set to False. (You also could just leave out the required flag because False is the default.)
The second field, rating, tells the rating assigned by the Motion Picture Association of America. (You can substitute whatever other rating scheme you prefer.) This is a string-based field that's also optional. It's also indexed for quick lookup by setting the index attribute. The vocabulary attribute lists the valid values users may enter for this field, in this case, the MPAA rating codes. You also listed the widget for this field as a SelectionWidget. This kind of widget displays either radio buttons or a drop-down menu (depending on how many values are in the vocabulary), enabling the user to select one and only one value.
The last field, genre, is a required field. Like rating, it's indexed and it uses a controlled vocabulary for values. Unlike rating, it also includes the multiValued flag, set to True, meaning that movies may belong to more than one genre. In addition, the widget for this field is the MultiSelectionWidget that displays either checkboxes or a multi-select list. You added the format attribute to force the widget to use checkboxes (mostly because I don't like multi-select lists).
And now you're done defining the fields of a DVD! Wait, what about the title of the movie? Well, why define your own title field when you can reuse an existing schema that already provides it? Add these lines to DVD.py:from Products.Archetypes.public import BaseSchema
dvdSchema = BaseSchema.copy() + idSchema + classifierSchema
The schema called BaseSchema comes with Archetypes; it provides several fields, such as title, description, subject keywords, last modification date, and so forth—all of which could be useful for your DVD content type. You then just "add 'em up": the dvdSchema is a copy of the BaseSchema plus the idSchema plus the classifierSchema.
You're now the proud owner of a complete schema. But, although Archetypes letsyou define schemata, Plone, its underlying application server, and its object database deal with objects, not schemata. And, before Plone can make objects, you need to provide a class definition for those objects. Luckily, Archetypes helps out again by providing base classes from which you can inherit all the necessary behavior. Add these lines to DVD.py:from Products.Archetypes.public import BaseContent
class DVD(BaseContent):
schema = dvdSchema
archetype_name = portal_type = meta_type = 'DVD'
typeDescription = "A DVD object captures a DVD's ID number,
title, genre, and other attributes."
content_icon = 'dvd.gif'
This block of code defines a class called DVD, whose schema is the dvdSchema. It also tells Archetypes and other parts of Plone and Zope the name to use ("DVD", in thise case), plus some descriptive "help text" explaining what the DVD class does. Lastly, it tells what icon to use on-screen to represent DVD objects, in this case dvd.gif. Where do you get the DVD icon dvd.gif? Help yourself to this one: . Just save it as a file to the DVDCollection subdirectory under the skins subdirectory (all user-interface elements go under skins).
The coding's complete! You should now have the following files and directories:

No comments: