Personal tools
You are here: Home Aiuti ed Info The Definitive Guide to Plone 13. Developing with Archetypes
To change the maximal image width select one of the following:

13. Developing with Archetypes

Document Actions

Chapter 13

Archetypes is a framework for automating the development of products in Plone. Once a description has been written for a content type in Python, Archetypes handles almost everything else, including creating view and edit forms for the developer. This allows you to develop content types quickly and with a minimum amount of code. The less code written means a lower probability of bugs, less code to maintain as Plone changes, a quick development cycle, and generally lower costs.

Because the entire product is based upon this object description, it allows you to use tools to generate that product. For example, ArchGenXML, which I'll cover later, allows you to generate a product in a Unified Modeling Language (UML) tool. You can then take the output of the UML model and pass it through ArchGenXML to have a product instantly appear in Plone; you don't actually have to write any code. If you found that writing the product in Python in Chapter 12 was a little too much like hard work, then this chapter is something you'll appreciate.

This doesn't mean Archetypes is right for every product; sometimes I've found Archetypes to be a little too much. For example, in one case my content type had one field, but about 16 different permutations on the data on that field were presented, which meant little of the existing Archetypes framework was used. That was an extreme case, however. Most of the time you'll find Archetypes is exactly what you need.

Several people complain that Archetypes makes life a little too easy for developers, and of course it's hard to charge people much for work that can take ten minutes. Personally I've never had a problem with this, and Archetypes has gotten me out of many sticky situations when all of a sudden the specification changes from four content types to fourteen.

One anecdote I've heard concerns a Web site development company. When the company visits clients, it takes along a programmer. As the client describes its needs, the programmer furiously types away into Archetypes. Before the meeting is finished, they can demonstrate a quick prototype of the working product to the client.

Overall, most of the Plone development team has adopted Archetypes as the way to develop products, so it has a great deal of mind share and really has become the standard for Plone development. Some of Archetypes' other key features are as follows:

  • It automatically creates view and edit pages, so you don't actually have to write any page template code.
  • It maintains unique object IDs. Every object you create will have a unique ID that users can't change. This means you can always find an object, even if it has moved.
  • It creates references between objects. Each object can have any number of relations to any other objects. For example, you could have any number of link objects attached to a news item.
  • It has standard security setup. All the security work is done for you, so if you want the default setup, you don't have to change anything.
  • It has alternate storage options, such as storing your data in relational database instead of Zope's standard database.
  • It has data transformation capabilities, such as changing Microsoft Word in Hypertext Markup Language (HTML), for example.

Archetypes isn't just Plone specific but can also be used in other Zope frameworks such as the Content Management Framework (CMF); however, at the moment it's used mostly by Plone. Eventually, when Plone moves to Zope 3, it's planned that the Archetypes and Zope 3 schemas will converge. So using Archetypes is a good way to future-proof your product so it's compatible with future Plone versions.

In this chapter, then, I run through building new content types with Archetypes. This chapter really pulls together all the information you've learned over the last few chapters and runs quite quickly through some basic concepts. After showing how to install Archetypes, I take you through how to create a basic content type.

Introducing Archetypes

Archetypes comes in the installers and packages for Plone, so chances are you'll already have Archetypes installed in your distribution. If you're unsure if this is the case, you should be able to see Archetypes in the Products part of the ZMI control panel. In these examples, I've tested Plone with Archetypes version 1.2.5-rc4. If you've got Archetypes installed, skip to the 'Developing with Archetypes” section.

To install Archetypes, you'll need to go the Web site at http://sf.net/projects/archetypes, click Files, and get the latest Archetypes release. In my example, this is archetypes-1.2.5-rc4.tgz. You'll need to unzip it, like so:

$ tar -zxf archetypes-1.2.5-rc4.tgz
$ cd archetypes-1.2.5-rc4l

At this point you'll need to decide what to install. The minimum to install is the Archetypes directory and the generator and validation modules. To install these modules, move them into the Products directory of your instance home. Again, in my case, this is /var/zope, so my command is as follows:

$ mv Archetypes /var/zope/Products
$ mv generator /var/zope/Products
$ mv validation /var/zope/Products

ArchExample and ArchGenXML are both optional, and you don't need them for Plone to work; however, I cover both of these as examples in this chapter, so you'll probably want to install them.

To install ArchExample, move the ArchExample product into the Products directory of your instance home, like so:

$ cd ..
$ mv ArchExample /var/zope/Products

If you want to use ArchGenXML, you don't need to install it anywhere in particular, so place it in any directory where you won't forget it. I usually just place it into the Products directory of my instance home along with everything else. It doesn't do any harm there, and I won't forget it. For example:

$ mv ArchGenXML /var/zope/Products

As stated in the ArchGenXML documentation, ArchGenXML does require PyXML to be installed. Again, if you used an installer such as the Windows or Mac installer, this was already included for you. If not, then go to http://pyxml.sf.net and download the package. In my case, the latest package was 0.8.3, so after downloading, I ran the following:

$ tar -xvf PyXML-0.8.3.tar.gz
$ cd PyXML-0.8.3
$ python setup.py install

NOTE: Usually, installing in this manner requires root privileges on Unix to install into the Python directories.

Now that you have everything set up and installed, you'll see some of the examples.

Diving Into Archetypes

A whole bunch of great examples are available for Archetypes, so rather than constructing one for the book, I show ArchExample, which comes with the Archetypes installation. This adds a content type called article to give a rough example of the power of Archetypes.

Article.py contains the main product code. You'll see that the code looks quite different from previous examples. It contains schemas

StringField("blurb",
            searchable = 1,
            widget = TextAreaWidget(),
            ),

This piece of code denotes that you have an attribute on the content type called blurb, that it's a string, and that it's going to be shown as an HTML text area. I discuss all the options for fields and widgets in a moment. For now, take a look at the content type in Plone. In Figure 13-1, I've added the blurb content type.

img/3294f1301.png

Figure 13-1. The blurb part of the content type

With just those four lines of code, you've added a field to your content type. The standard form elements that you'd expect in Plone are present; if you edit something and come back later, the old value is shown, errors are handled gracefully, and so on. As a demonstration of how easy it is to modify, change the label in the form to appear as Article Blurb, and change the field to be required. To do this, make the following changes:

StringField("blurb",
            required = 1,
            searchable = 1,
            widget = TextAreaWidget(label="Article Blurb"
            ),

Here you've added the required = 1 parameter, which makes this field required, and a label parameter. If you restart Plone and add a new article, the user interface has now changed to reflect the new schema. The field is now called Article Blurb

img/3294f1302.png

Figure 13-2. The Article Blurb part of the content type

This isn't just a cosmetic change; the change reflects the alteration of the underlying schema, and this is the real power of Archetypes. Just comparing this to writing products in Python, you can see that all the drudgery has been removed from writing a product. It's like this: If you can define a schema, you can throw together an archetype in absolutely no time. Once you've done that, you can edit it easily and have the changes take effect.

If you do make any of these changes to try the following examples, you'll have to restart Plone. Only then will all the new changes be loaded and properly registered. For more information on this, see the 'Developing with Archetypes” section later in this chapter.

Explaining Schemas, Fields, and Widgets

Merriam-Webster's dictionary definition of a schema is as follows:

schema:

In Archetypes terms, a schema

Each field has one widget defined. A widget

Figure 13-3 shows the relationship between schemas, fields, and widgets.

img/3294f1303scrap.png

Figure 13-3. The relationship between schemas, fields, and widgets

Schemas and the BaseSchema
To create a schema, pass the fields you'd like in the schema object as a tuple of fields. For example, the article schema has three fields: group, blurb, and body. The following code starts the schema:
Schema((
    StringField('group',
        vocabulary=ARTICLE_GROUPS,
        widget=SelectionWidget(),
        ),
        # other fields here
        )

It's possible to add schemas together to get a summation of more than one schema. This is exactly what ArchExample actually does: it adds the schema defined in the content type to a schema called BaseSchema.

The BaseSchema contains the two elements every Plone content type should have, a title and an ID. The title is required so that something can display in the user interface, and the ID, or short name, corresponds to the standard Plone conventions for a naming scheme. The two schemas are simply added together to create one bigger schema. In ArchExample, you do this by adding the schema for the content type to the already existing BaseSchema. For example:

schema = BaseSchema +  Schema((
    StringField('group',
                vocabulary=ARTICLE_GROUPS,
                widget=SelectionWidget(),
                ),
    ...

It's worth noting that the items are returned from queries of the schema in the order they're added to the schema. This means you can rearrange the order that elements appear in the user interface by moving the fields around in the schema. That's also why the BaseSchema is added at the beginning so that the ID and title fields will appear at the top of the view and edit page, rather than at the bottom.

Fields
So far you've seen the StringField field, which is a common field that represents a string on your content type. A number of fields are available in Archetypes, as described in Table 13-1. Over time, more fields are being added, and if you need to, it's possible to create your own.

Each field has a default widget that will be used, unless you specify one. In the blurb example earlier, I specified a TextAreaWidget. (Widgets are covered in the next section.) All these fields are imported from the Archetypes public module; for example:

from Products.Archetypes.public import BooleanField

All fields are instantiated the same way—by creating a field and passing in the one required parameter for the field: name. You can optionally pass in any number of keyword parameters as needed. For instance:

from Products.Archetypes.public import IntegerField
# a simple field for age
age = IntegerField('age')

Table 13-1. Fields Available in Archetypes

Name Type Default Widget Description
BooleanField Boolean values ComputedWidget Simple storage of true or false for a field.
DateTimeField Date and time objects CalendarWidget For storing dates and times.
FileField Files FileWidget Storage for large chunks of data such as plain-text files, Microsoft Word documents, and so on.
FixedPointField Fixed-point numbers DecimalWidget For storing numerical data with fixed points.
FloatField Floats DecimalWidget For storing numerical data with floating points.
ImageField Image ImageWidget Stores an image and allows dynamic resizing of the image.
IntegerField Integer StringWidget For storing numerical data as integers.
LinesField Lists LinesWidget A list of data such as keywords.
PhotoField Image PhotoWidget Same as an image field but has more default image sizes.
ReferenceField Reference ReferenceWidget Contains a reference between this object and another.
StringField String StringWidget A string field optimized for smaller strings—say, fewer than 100 words.
TextField String TextWidget A string field optimized for larger strings—say, larger than 100 words. The string can also be transformed into multiple formats.

Each of the fields has attributes you can assign to the field. You've already seen at least two: the name and the widget attributes. The name attribute is the only required parameter to a field and should be unique, lowercase, and without spaces or periods. The name attribute is going to be used internally only, so sticking to this naming rule is important. All the other values are optional. Table 13-2 describes the attributes.

Table 13-2. Field Attributes

Name Description Possible Values
accessor The name of the method to get the value of the field, so you could change how this field is retrieved Any method name (for example, specialGetMethod) See the 'Overriding Default Methods” section later in this chapter.
default The default value for the field. Should be appropriate to the field.
default_method A string for obtaining a value for the field; one is created for you by default if you don't define one. Any string (for example, getSpecialDescription). See the 'Overriding Default Methods” section later in this chapter.
edit_accessor The name of a method to get the raw value of a field. Any method name (for example, rawGetMethod). See the 'Overriding Default Methods” section later in this chapter.
enforceVocabulary If enabled, you won't accept anything outside the vocabulary. True or False.
index 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 name of any index, such as KeywordIndex or KeywordIndex:schema.
name A unique name for this field. Any string, lowercase conforming to standard Python variable rules (for example, description, user_name, or coffee_bag_6).
mode The read and write mode of field, as a string; the default is to be read and write. For read only: r, for write only: w, for read and write: rw.
multiValued If this field can have multiple values, this is useful for things such as drop-down lists. True or False.
mutator The name of the method to alter the value of the field, so you could change how this field is set. Any method name (for example, specialSetMethod). See 'Overriding Default Methods” later in this chapter.
primary If True on a field, then this will be the field that responds to 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 do this for the main body attribute. True or False.
required Specifies that some value for this field required. True or False.
schemata Place the field into the grouping of other fields called schematas  
default    
metadata    
user_information    
searchable A boolean that specifies if this field will be added to the searchable text and can be used in the searches. True or False.
validators The validations that will be performed on the field as a tuple of strings; it starts at first and validates against each validation. Any validator; see the 'Validations of Input” section later.
vocabulary A list of values that a user can choose from, for example, the values to show in a drop-down list. List of strings (for example, ["Green", "Red", "Blue"]).
storage Where to store the value; 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). For more information, see the 'Storing Your Content in a SQL Database” section later in this database.
widget The widget that will be used to display this field. Any widget object.

Now that I've covered the default fields and attributes, it's time to move onto the actual widgets that are available.

Widgets
A widget contains the information about how the object will be represented visually. The view of an attribute displayed is often closely related to the type of the attribute; however, you have options for the display—a string could be selected in many ways. Given the set of widgets, the widget object can be almost anything. You can import any of the widgets from the Archetypes public module. For example:
from Products.Archetypes.public import BooleanWidget

All widgets are instantiated the same way—by creating a widget and passing in keyword parameters as needed. For example:

from Products.Archetypes.public import IntegerField
from Products.Archetypes.public import IntegerWidget
# a simple field for age
age = IntegerField('age',
       widget=IntegerWidget(label="Your age")
       )

Widgets can also have extra attributes, depending upon the type of widget. In most cases, these extra attributes correspond directly to HTML attributes; for example, on a StringWidget, you can set a size attribute. This will produce the appropriate HTML widget with the HTML size attribute set. So to have input that's 20 characters wide, you'd create the following widget:

bankAccountNumber = StringField('bank',
   widget=StringWidget(
           label="Bank account number",
           size=20)
         )

Table 13-3 describes all the widgets available to you in Archetypes.

Table 13-3. The Available Widgets

Name Description Other Attributes
BooleanWidget Shows two checkboxes for the possible values.  
CalendarWidget Returns a set of input boxes with a link to a helper pop-up box so that a user can select a date.  
ComputedWidget Returns the computed value as HTML.  
DecimalWidget A simple HTML input box for a string. size
EpozWidget An HTML Epoz widget that shows the Epoz rich-text editor for the content. You can provide format, rows, mode, and cols (for columns)
FileWidget Displays an HTML file element for users to upload files.  
IdWidget A simple HTML input box that's used for rendering autogenerated IDs.  
ImageWidget Shows and allows the editing of 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.
IntegerWidget A simple HTML input box for a string. size
KeywordWidget This displays a list of keywords from the catalog in a complicated widget, such as the one in the Properties tab on a normal object.  
LabelWidgets Used to display labels on forms; no values or form elements.  
LinesWidget Displays a text area that users can enter values. rows and columns
MultiSelectionWidget A selection widget; by default it's an HTML select widget. format, which can be one of select or checkbox
PasswordWidget An HTML password element.  
RichWidget Allows the input of a file in multiple formats that are then transformed. See 'Transforming Data' later in this chapter for more information. You can provide rows, cols *(columns), and *format.
ReferenceWidget Shows an HTML select element of a list of possible references.  
SelectionWidget Shows a selection widget. If it's flex (the default), then if the number of choices is more than four, a select element is used; otherwise a radio button is used. format, which can be one of flex, select, or radio.
StringWidget A simple HTML input box for a string size and maxlength.
TextAreaWidget A text area widget that allows the uploading of the content in multiple formats You can provide allowed_content_types, which is a list of strings; each string represents a meta_type of the type of content uploaded.

For each of the widgets listed in Table 13-3, Table 13-4 describes all the attributes that are common to all widgets. You've already seen the label attribute, which sets the description on your widget. In conjunction with the extra attributes for each widget, you have a complete set of widget attributes.

Table 13-4. Possible Values for the Widgets
Name Description Possible Values
label The label that will appear in the user interface with this field. Any string, for example, Start Date for a field start_date.
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 it's ("view", "edit").
populate If this is enabled, the view and edit fields will be populated. Usually this 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 If the attribute should be visible in the user interface. This is a dictionary mapping the view mode to a string, describing the visibility. Choices are visible, hidden (shown in an HTML hidden form value), invisible (not shown at all). For example, {'view': 'visible', 'edit': 'hidden' } means that the view will show, but the edit page will hide the value.

Some Example Field and Widget Combinations

This section contains some useful combinations that seem to be commonly used and that you may find as useful examples. For this example, make a drop-down list of your favorite fruits. You'll define the vocabulary attribute as a list of strings. Each of the values in the list is a string of the fruit type; hence, the field type is a StringField. Because you're defining the widget as a SelectionWidget, it'll show up as a drop-down list, like so:
StringField('fruit'
    vocabulary = ["Apple", "Orange", "Mano", "Banana"],
    widget = SelectionWidget(label = "Favourite Fruit")
    )

The ImageField is a useful one for creating and maintaining images in a Plone site. To have a nice simple field that users can upload images into, use the following:

ImageField('portrait',
    widget = ImageWidget(label = "My picture"),
    )

The following is quite a complicated content type. Most content types will have one main field that can take data. If you think of the basic document type, you'll notice a body field that you enter and edit. This one body field is the main text of the content type. So for this standard field, you'll have only a few attributes that should be added.

First, you'll want this field to be searchable, so you should set the searchable attribute. Second, you'll want this field to respond to FTP and WebDAV requests, so you must set the primary attribute (you can find more information on this in the sidebar 'The Primary Field: Marshaling and Responding to FTP and WebDAV”). You'll want multiple content types to be uploaded, so for this reason you set some allowable_content_types. Then, of course, you need to know how to show the field, so for that you set the default_output_type. This then gives you the following field:

TextField('body'
          searchable = 1,
          primary = 1,
          default_output_types = 'text/html',
          allowable_content_types = ('text/plain',
                                     'text/structured',
                                     'text/restructured',
                                     'text/html'),
          widget = RichWidget(label = 'Body'),
          )

The Primary Field: Marhsaling and Responding to FTP and WebDAV

Plone is an object-oriented system where an object has many attributes and can't simply be represented as a plain file. Unfortunately, most the existing protocols such as FTP and WebDAV treat content in exactly this manner. So there needs to be some way to translate between the two, and the primary field does this. By setting the primary field on an object, this field will become the one that's sent and received by these protocols—rather than the entire object.

This is an imperfect solution to a tricky problem, of course. If you've used FTP or External Editor on a file, you'll know that it tries to solve this by placing a number of lines at the top of the page containing key/value pairs for metadata on the object. This is another attempt to solve the same problem.

To set up primary field marshaling, you need to add the following to your schema: marshall=some_marshaller(). Currently, there are only two marshalers: a PrimaryFieldMarshaller, which takes the whole content and places into your object, and an RFC822Marshaller. This second marshaler handles the content for field name/value pairs as used in e-mail. For this chapter's purposes, I'll use the PrimaryFieldMarshaller to handle content with External Editor.

Validations of Input

Although the forms handle the content and some basic errors, such as omitted content, quite well, you'll probably want some more sophisticated error handling. You can make a series of validations to test that the content in your content type is correct. For example, if you have an integer field, you'll probably want to test that the data they've added is what it's meant to be.

To do this, you can add a validation parameter to the field. To test that your IntegerField really is an integer, for example, you'd do this:

from Products.Archetypes.public import IntegerField
from Products.Archetypes.public import IntegerWidget
# a simple field for age
age = IntegerField('age',
      validators=("isInt"),
      widget=IntegerWidget(label="Your age")
      )

Where did isInt come from? Well, isInt is the name of a validator registered in the validation framework. Only a few are in there, but they're quite useful; Table 13-5 describes them. For the exact details of some of these, I recommend reading the code and looking at the regular expressions—you can find these in the validation/validators/__init__.py module in your Products directory:

Table 13-5. Available Validations
Name Description
isDecimal This validates that the string is a decimal, including positive and negative, exponentials, and so on.
isInt This validates that it's an integer.
isPrintable This validates that this is a letter or a number.
isSSN This validates that it's nine numbers (the length of a U.S. Social Security number).
isUSPhoneNumber This validates that it's ten numbers and is optional.
isInternationalPhoneNumber This validates that it's at least one number and is optional.
isZipCode This validates that it's five or nine numbers.
isURL This validates that the input starts with http://, ftp://, or https://.
isEmail This validates that this conforms to the standard e-mail syntax.

You can also register your own validations. A validator is actually a simple class that implements the ivalidator interface. Two are already done: a RegexValidator that verifies a regular expression and a RangeValidator that verifies a range of values. To register a new validator that checks that a user is between, say, 0 and 150 years old (seems reasonable), you'd add the following to your content type in the Python module, before creating the field:

from validation.validators.validator import RangeValidator
from validation import validation
 
# the RangeValidator takes a name for the validator
# and a start and end value
validAge = RangeValidator("validAge", 0, 150)
validation.register(validAge)

Then you can change your validator to the following:

validators=("isInt","validAge"),

First, the code will check that you have a valid integer; second, it'll check that the integer is within the range of sensible ages. If you wanted to add a totally new validator that did something other than a regular expression or range validation, you'd need to add a new validation system. For this example, make a validation that a date is between two values. In the following example, this is called DateRangeValidator and will return a boolean value if the given date fits between the two given dates. This could be useful to assert that a vacation is within the school holidays.

So, now define a new validator called DateRangeValidator in the validators module. This will allow you to register date ranges to assert that the given date falls in the middle. To do this, you'll use the Zope DateTime object (which is covered in more detail in Appendix A). A validator is simple—it's a class that has a name and will respond to the __call__ method by checking the date. The following is the DateRangeValidator class, which was added to the validators module:

from DateTime import DateTime
 
class DateRangeValidator:
    __implements__ = (ivalidator,)
 
    def __init__(self, name):
        self.name = name
 
    def __call__(self, value, *args, **kwargs):
        min, max = args[:2]
        if not isinstance(value, DateTime):
            value = DateTime(value)
 
    return min < value and value < max

After restarting Zope, you can now register a new validation, like so:

from validation.validators.validator import DateRangeValidator
from validation import validation
from DateTime import DateTime
 
christmas = DateRangeValidator("ChristmasHolidays",
    DateTime('12/18/2004'),
    DateTime('01/09/2005'),)
validation.register(christmas)

Then you can create a validator in your schema in the following manner:

validators=("christmas",)

Overriding Views and Actions in the Base Class

Archetypes creates default views and actions for you based on a standard set of requirements that will suit many needs. The actions are view, edit, and properties, of course; references is another action you'll look at it a moment. You won't find any view or edit page templates for the object; those are generated automatically for you by Archetypes. However, you can override them.

I expect that in most cases you'll want to override the view method and provide one of your own; the default is basic and isn't aimed at being a perfect page (of course, being a Plone page, it's better than your average content management system). However, you may have specific needs that relate to the content; perhaps it's a matter of presenting your content in a certain way.

Archetypes actually creates a class for each content type. Much in the same way that you created a class for the source code type in the previous chapter, you can create a base class for your archetype. This base class is called BaseContent and is available in the public module of Archetypes for importing. This BaseContent class defines all the things that Archetypes needs to know. Making this class provides you with an opportunity to override almost anything you want in the class.

As I now show, there are two parts to this. First, you make an action that's to be used by the factory type information; in Archetypes you do this by assigning the actions attribute of the class. For example:

from Products.Archetypes.public import BaseContent
 
class Article(BaseContent):
    # other stuff
    actions = ({ 'id': 'view',
                 'name': 'View',
                 'action': 'string:${object_url}/article_view',
                 'permissions': (CMFCorePermissions.View,)
                },)

Second, you need to create a page template for the actual view of the object called article_view. This string defines a page template that the content type will locate in the skin for this product. In this case, you'll find the matching page template on the file system in the skins/archexample directory of ArchExample. You can also find a copy of this page template in Appendix B. This can be as simple or as complicated a page template as you'd like.

You have to restart Plone for the changes to take effect. In this case, you're changing an action that's installed into the portal_types tool whenever the product is installed. For this reason, if you do change this action, you'll need to reinstall the product. To do this in the Plone interface, click plone setup

All the elements in the factory type information can be overridden by creating an attribute of that name on the object. To override the content_icon setting, you'd make an attribute called content_item. For example:

class SomeProduct(BaseContent):
          """ Some product """
          content_icon = "some_icon.gif"

Overriding the Default Methods

In Table 13-2, I mentioned the option of overriding some of the default methods that occur on content types, such as accessor and mutator. This is an advanced option that allows you to manipulate the way fields are edited.

Just as with the source code project in the last chapter, you access these attributes or fields using accessors and mutator methods. Some default methods are available for you. If your field name is blurb, then these methods will be getBlurb and setBlurb. The term get

However, you may want to do something different in the accessor or mutator. Suppose you wanted to filter the value of a field, such as always correcting the spelling of your company name or changing the value of some other fields when a certain field changes. Then you could do so by overriding the default methods. In the following example, you'll make a new method called getSpecialBlurb that takes the blurb someone has entered and manipulates it before returning to the client. In this case, you're replacing the text Perl

getSpecialBlurb Article

class Article(BaseContent):
 
    def getSpecialBlurb(self):
        """ The view for an article """
        blurb = self.getField('blurb').get(self)
        blurb = blurb.replace('Perl', 'Python')
        return blurb

You'll also need to change your field so that it uses this method:

StringField('blurb',
    searchable=1,
    widget=TextAreaWidget(),
    accessor="getSpecialBlurb",
),

In this example, anytime the blurb field is accessed in a view or edit page, for example, the value of getSpecialBlurb is returned. Archetypes knows to access that method because the name of the method is defined as a string value passed to the accessor parameter. There's one bit of trickery involved—to access the raw value of the attribute, you need to get the field and then call the get method. This is the rather confusing blurb = self.getField('blurb').get(self) line. The pattern of getting the field and then calling a method on it is actually quite common in Archetypes.

The practical upshot of this is that now, no matter when or how a person types in the word Perl

Putting Together the Rest of the Content Type

I've now covered all the main elements of the content type. Listing 13-1 shows all the code together. You'll note that the rest of the code is more compact than the Python product because Archetypes does so much work for you.

Listing 13-1. Article.py

from Products.ArchExample.config import ARTICLE_GROUPS
from Products.Archetypes.public import BaseSchema, Schema
from Products.Archetypes.public import StringField, TextField
from Products.Archetypes.public import SelectionWidget, TextAreaWidget
from Products.Archetypes.public import RichWidget
from Products.Archetypes.public import BaseContent, registerType
from Products.Archetypes.Marshall import PrimaryFieldMarshaller
from Products.CMFCore import CMFCorePermissions
from config import PROJECTNAME
 
schema = BaseSchema +  Schema((
    StringField('group',
                vocabulary=ARTICLE_GROUPS,
                widget=SelectionWidget(),
                ),
    StringField('blurb',
                searchable=1,
                widget=TextAreaWidget(),
                ),
    TextField('body',
              searchable=1,
              required=1,
              primary=1,
              default_output_type='text/html',
              allowable_content_types=('text/plain',
                                       'text/structured',
                                       'text/restructured',
                                       'text/html',
                                       'application/msword'),
              widget=RichWidget(label='Body'),
              ),
           ),
     marshall=PrimaryFieldMarshaller(),
     )
 
class Article(BaseContent):
    """This is a sample article; it has an overridden view for show,
    but this is purely optional
    """
 
    schema = schema
 
    actions = ({
        'id': 'view',
        'name': 'View',
        'action': 'string:${object_url}/article_view',
        'permissions': (CMFCorePermissions.View,)
        },)
 
registerType(Article, PROJECTNAME)

Apart from a bunch of imports at the top and the schema, I've covered all the contents of this code, with one exception: registerType. This function registers your object with the product. Each product can have multiple content types, so this function takes the object and the name of the project. In this case, the name of the project is imported from the configuration file, as you saw earlier. The configuration file, config.py, contains similar variables for the product configuration, as shown in Listing 13-2.

Listing 13-2. The Configuration File for ArchExample
from Products.CMFCore.CMFCorePermissions import AddPortalContent
from Products.Archetypes.public import DisplayList
 
ADD_CONTENT_PERMISSION = AddPortalContent
PROJECTNAME = "ArchExample"
SKINS_DIR = 'skins'
 
GLOBALS = globals()
 
ARTICLE_GROUPS = DisplayList((
    ('headline', 'Headline'),
    ('bulletin', 'Special Bulletin'),
    ('column', 'Weekly Column'),
    ('editorial', 'Editorial'),
    ('release', 'Press Release'),
    ))

The variable ARTICLE_GROUPS is a tuple of a tuple of strings for use in the group widget. You could just use a simple tuple of strings, but in this case the example is using the DisplayList class, which is a chance to show a different value to the user from what will be put in the form. In this case, the HTML for ArticleGroups is displayed in an HTML select element, like so:

<option value="headline">Headline</option>
<option value="bulletin">Special Bulletin</option>
...

One other unusual insight is the use of globals function—this is a Python built-in function that contains all the global symbols. This calculates the path to the skins directory on the file system so that you can make a file system directory view of the skin for the skins tool. The product initialization function, __init__.py, is also much simpler. With one new inclusion, the process and list type functions look like this:

from Products.Archetypes.public import process_types, listTypes
content_types, constructors, ftis = process_types(
    listTypes(PROJECTNAME),
    PROJECTNAME)

The listTypes function is an Archetypes utility that will return all the types you've previously registered. This is then passed into the process_types function, which in turn returns you all the content types, constructors, and factory type information objects. These are all the same items you registered in the Python product; it's just that this utility function makes it all a little easier.

Finally, you have the installation function in the Extensions install.py. This script is now embarrassingly small because all the work you've done previously is handled by two utility functions, installTypes and install_subskin. The term subskin

Listing 13-3. Installation Script
from Products.Archetypes.public import listTypes
from Products.Archetypes.Extensions.utils import installTypes,
install_subskin
from Products.ArchExample.config import  PROJECTNAME, GLOBALS
 
from StringIO import StringIO
 
def install(self):
    out = StringIO()
    installTypes(self, out, listTypes(PROJECTNAME), PROJECTNAME)
    install_subskin(self, out, GLOBALS)
    out.write("Successfully installed %s." % PROJECTNAME)
    return out.getvalue()

The full version of ArchExample is available from the Plone Book Web site at http://plone-book.agmweb.ca, and you should be able to drop this and run it on your site. As you've seen, this content type is quick and easy to develop and easy to alter, without having to write lots of code.

Archetypes based Development

This section provides some more in-depth coverage on some of Archetypes' more advanced features. These features will provide some useful tools for the development of your content types. This will include making references, creating new widgets, and transforming content.

For this chapter, it's important to recognize how the changes you'll make can be carried through to Plone. As you've seen in the past, you have different stages in your product setup. When you change your product, it helps to know the steps you need to take.

If you change something in a skin, then just running in debug mode will ensure that the changes are propagated. If you change something that's passed onto the portal_actions tool such as actions or icon, then you need to restart Plone and reinstall the product through Add/Remove Products.

If you change a schema, then you just need to restart Plone, and all new instances of your class in Plone will be updated. However, what about all the old instances of the product? Fortunately, Archetypes has provided a dynamic update tool that goes through all the old instances of your product and updates them to the new schema. In the ZMI, click archetype_tool

Using Unique IDs

The concept of unique IDs is simple but something that was missing in Zope. Originally Zope developers assumed using the path to an object would do; unfortunately, this has shown to be inadequate. As a simple example, consider what happens when someone moves the location of a document—your unique reference to that document has been lost. A unique ID is a truly useful tool to have. Archetypes creates a unique ID on every object it creates and saves it for you on the object. It also saves it in a separate catalog for you called uid_catalog.

You can see the uid_catalog in the ZMI, and it's like the portal_catalog object except it's missing a little bit of extra Application Programming Interface (API) wrapping and all the indexes that the former contains. It's pretty simple to get an object now by going through this catalog. All you have to do is search the catalog for your object. For example, the following Script (Python) object can pull any registered object, provided you know its UID:

##paramaters=objectId
results = context.uid_catalog(UID=objectId)
return results[0].getObject()

But where this really is useful is in references between existing objects. Say you want to reference one or more separate images with your article. These images could be images uploaded by other users at another date, perhaps part of an image database. If those images are Archetypes objects, then you could add a field to your article schema that reads as follows:

ReferenceField("images"
    allowed_types=("Archetype Images",),
    multivalued=1,
    ),

The user will now get a drop-down box of all the Archetype Image objects and be able to choose them. Behind the scenes it's making a reference of that object's UID through the catalog. You'll find an excellent example on references in a product called ACME in the Archetypes CVS repository at http://sf.net/projects/archetypes.

Altering Widgets

A commonly asked question is, why does this widget do this? Another is, why does this widget look like this? Often the answer is something like, Because it does—now go and write your own. Since widgets are what the client sees, these requirements are often client driven.

All the widgets are represented as page template macros on the file system. So it's pretty simple to alter widgets or make your own. If you click portal_skins

portal_skins Actually, the term macro

The view macro displays on the view page and is a read-only, user-friendly view of the item. The edit macro is the one shown on the edit page, and it's the macro where a user edits the data. The search macro is called when you're assembling search pages for the code; it sometimes looks like the edit macro, although it may not. A string field may be edited as a string field and viewed as a string, but it's searched using a drop-down box to select from all the options available.

So, in the example product, assume you have a string field that's a person's e-mail address, and you want to show this is a clickable e-mail link. For this you'll make a new macro. In this case, you don't need to actually override the edit and search macros—it's just a string after all. So, make a new a page template called email_widget.pt, as follows, which you'll place in the skin of your product:

<html xmlns:tal="http://xml.zope.org/namespaces/tal"
      xmlns:metal="http://xml.zope.org/namespaces/metal"
      i18n:domain="plone">
 
  <body>
    <div metal:define-macro="edit">
      <div metal:use-macro="here/widgets/string/macros/edit" />
    </div>
 
    <div metal:define-macro="search">
      <div metal:use-macro="here/widgets/string/macros/search" />
    </div>

For the view, you need to make the string show as a mailto link, which is just the following simple adjustment:

    <div class="field" metal:define-macro="view">
        <a href="#" tal:attributes="href string:mailto:${accessor}"
          tal:content="accessor">email</a>
 
    </div>
</body>
</html>

Now that you've defined a page template that contains your code, you can simply reference the name of the template as the macro in your widget. In the following code, you define the e-mail field and a StringWidget as normal. You then change your macro to use your nice new email_template macro, like so:

StringField('email',
    validators = ('isEmail',),
    widget = StringWidget(
        label='Email',
        macro='email_template'
    )
)

At this point you're just changing the macro of an existing widget. Making an entirely new widget is just a matter of defining a new widget and then registering it. All widgets share the same base class. The following is a new module called EmailWidget.py that's placed inside the ArchExample directory. It creates a new widget and then registers it into the registry. Note that the macro property of the widget (highlighted) is set to the value of the template:

from Products.Archetypes.Widget import TypesWidget
from Products.Archetypes.Registry import registerWidget
 
class EmailWidget(TypesWidget):
    _properties = TypesWidget._properties.copy()
    _properties.update({
       'macro' : "email_template",
       'size' : '30',
       'maxlength' : '255',
       })
 
registerWidget(EmailWidget,
    title='String',
    description='Renders a clickable email field',
    used_for=('Products.Archetypes.Field.StringField',)
)

To include this in your article, you can now directly import the EmailWidget and use without having to explicitly define the macro, like so:

from EmailWidget import EmailWidget
 
StringField('email',
    validators = ('isEmail',),
    widget = EmailWidget(
        label='Email',
    )
)

Developing Folderish Objects

You've already worked with folderish objects a lot in Plone, but you may not have known it. A folderish object is one that exhibits similar characteristics to a folder or directory, namely, that it can contain separate individual objects. There really is nothing special about a folderish object—just that it inherits from a certain base class to provide its properties, and new content can be added to it.

Folderish objects are useful for you to make for several reasons. They provide a simple way to create collections of disparate objects in one location. They also allow users to manage the contents using the standard Plone user interface, without you having to do anything. Generally, it's best to keep the folder quite simple, and the logic should be on your objects and in the objects' workflow. However, some exceptions to that always exist, and a classic example is the family of collectors, CMFCollector and PloneCollector both of which are quite complicated collector objects that provide logic for a bunch of collector issues.

The easiest way again to make a folderish object is to use Archetypes. You've seen that there's a BaseContent type that handles all the work needed to create a nonfolderish object. Well, there's also a BaseFolder content type that contains everything for creating a folder. Futher, you'll find a special schema for folders, since folderish object also usually have a description. To make a folderish type, just ensure that you change your base classes and your schema. For example, possibly the simplest folder is as follows:

from Products.Archetypes.public import BaseFolder, BaseFolderSchema
 
schema = BaseFolderSchema
 
class SimpleFolder(BaseFolder):
    """A simple folderish archetype"""
    schema = schema
 
registerType(SimpleFolder)

If you're going to store a huge amount of content in a folder, then a folder tends to become inefficient at about 100 objects. That's a rather arbitrary figure that should be taken with a pinch of salt; normal folderish objects are designed to be fast for small numbers of object. If you wanted, you could use a binary tree folder, which stores the objects internally in a more efficient binary tree, rather than a Python dictionary. To use this, just import BaseBTreeFolder and BaseBTreeFolderSchema and use these in your object instead. As far as developing a product is concerned, they work just the same, but unless you put a lot of data into them, you wouldn't likely notice any difference. I've stored more than 100,000 objects in BTreeFolder, and it's always responded well.

Handling Microsoft Office Content

Handling Microsoft Office content such as Word and Excel documents is a bane that all content management systems face at some point. However, this is really the case for every type of content—Microsoft Office, OpenOffice.org, Portable Document Format (PDF), images, and so on. Using these content forms on Web sites usually causes a few problems; this is well known, of course. Editing is clumsy because clicking a document causes you to download it or, even worse, open it in your browser. When you've finished, you have to upload back up to the Web site, which can normally mean clicking around a bit to get back to it. When the content is online, the contents aren't searchable because they aren't in a plain-text format the catalog can understand. Further, you can't view the content online because again it isn't in a Web-friendly format.

You have a couple of solutions for solving the first problem of editing content. If again you assume most of your users are using Windows, then using WebDAV can be tricky for you because Microsoft's Web folder implementation is of questionable quality. Instead, External Editor fulfills this function quite well—if Plone can tell External Editor that this is a Word file, it'll open in Word.

As for the rest, well, Archetypes has a built-in transformation package, called Portal Transforms, that handles the transformation of content types. This can take a file in a certain format and transform it into HTML, which will then be cataloged, and the HTML will display to the user in the user interface. It does this by using an external transformation process to transform the data and read the results. For example, if you're using Windows (on the server), then Portal Transforms will take the uploaded Word document and start a Component Object Model (COM) object that transforms it.

This all happens behind the scenes for you; all you have to worry about is ensuring that any requirements for your transforms are installed and operational. You can do lots of different types of transforms, and there's more than one way to transform the data.

The OpenOffice.org suite provides excellent transformation of Microsoft content, so if you're running on a non-Microsoft platform, this is an excellent way to transform that content. I've also used wvWare (http://wvware.sourceforge.net/) quite successfully as another way of transforming content. All these options are available to you; however, they aren't trivial to set up. Once they're set up, of course, people usually report getting higher-quality transformations than when using a Microsoft server.

I recommend thinking first about exactly what you want to transform and then taking a look at the Portal Transforms source code to see if you can find a transformation that will do the job for you.

Setting Up a Content Type

You'll now create a simple content type for handling Microsoft Word documents, probably the most common type of content you'll have to handle. For this example, I did the transformation on Windows. However, if you can set up an alternative system, most of the following worked on Linux. In this case, the easiest thing to do is to use Windows to install your Plone instance. This creates an install with all the Win32 API modules installed and working. I had Microsoft Office installed on the server. Then you need to make a content type to handle the example, called WordExample. Essentially one field in the schema handles the content so that the schema looks like the following:
schema = BaseSchema +  Schema((
    TextField('body',
              searchable=1,
              required=1,
              primary=1,
              default_output_type='text/html',
              allowable_content_types=('application/msword',),
              widget=RichWidget(label='Body'),
              ),
    ),
    marshall=PrimaryFieldMarshaller(),
    )

The only difference is that you've added a Multipurpose Internet Mail Extensions (MIME) type for the Word document, application/msword. For each of content types to be transformed, add the MIME type into allowable_content_types. For example, if you wanted to handle Word and PDF documents, you'd have the following line:

allowable_content_types=('application/msword','application/pdf'),

This content type is about as simple as you can get at this point. But you could plug more stuff into this into the content type—such as a description or more properties. If you're doing a lot of work, then it'd conceivably be possible even to pull the metadata information from the Word document and place it as metadata inside Plone.

Setting Up a Transformation on Windows

The transformation should be set up for you automatically inside the portal_transforms tool. There isn't really a lot to look at in the tool; it merely provides a list of the transforms that are available. A transformation has a list of incoming MIME types that it'll transform (such as application/msword) and the output that will be produced (such as text/html). Each transformation is a module on the file system; in this case, you can find it in PortalTransforms/transforms/word_to_html.

For that transformation to work, though, you must run a utility to generate the COM bindings for Python. To do this, select Start - Plone - Pythonwin and then select Tools - COM Makepy Utility. This lists all the COM interfaces available; if you have Office installed, you'll see an entry for Word somewhere in the list. On my particular Windows server, this is called Microsoft Word 9.0 Object Library (8.1). Select that option, and click OK. Pythonwin will now generate the appropriate information. Once you've done this, you'll get a message back regarding 'Generating to.…”

You can now close Pythonwin and restart Plone.

Testing the Content Type

To test your content type, you'll need to restart Plone, ensure that the product is registered as usual, and install it using the Add/Remove Products screen. To add a new Word document, go to the Plone interface and in the drop-down box select Word Document; it's pretty obvious since you'll also see a little Word logo.

Next, select your file from the file system, and click Save to upload your file. Once this has been done, the file will be uploaded, and you'll be taken to the view page. This may take a little bit longer than you expect—it has to upload the document to the server and then perform the transformation. But you'll be taken to the view page for your content type, and sure enough it'll be shown in HTML, as shown in Figure 13-4.

img/3294f1304.png

Figure 13-4. The uploaded file

Now that it's uploaded, you'll want to edit it; for this, your best bet is to use External Editor. If you have External Editor installed on your local client, click the pencil icon in the top-right corner, and the file will open in Word, as shown in Figure 13-5. You can now alter and edit your Word document. Each time you save the file, it will be loaded back into Plone and transformed.

img/3294f1305.png

Figure 13-5. The document loaded into Word

TIP

http://sf.net/projects/archetypes http://www.logilab.org/projects/portaltransforms/documentation

Going Further: Writing Content Types in a UML Diagram

So you have a complicated content type and the idea of writing it manually is boring? Well, never fear, because ArchGenXML is here! This is a neat product that allows you to write your content in a UML graphical modeling tool and develop a product straight from that.

Phillip Auspberg developed ArchGenXML, and Jens Klein has provided some excellent documention. With kind permission, I'll reproduce some of those diagrams here.

The UML modeling tool must support a standard Extensible Markup Language (XML) generation system called XML Metadata Interchange (XMI). This tool has been tested with the following programs:

There are slightly different export properties for each of these, but if you take a look in the ArchGenXML samples, you'll see example outputs from the UML tools. Figure 13-6 shows a class project that shows a few objects: room, person project, resource, and task. There are relationships between all these objects, and there's even Python code specified on some of the objects.

img/3294f1306.png

Figure 13-6. A complex ArchGenXML example

The quickest way to test this is to go into the ArchGenXML folder on your file system that you kept around from the original install and then change into the examples folder. In that folder you'll see the mkdemo script. To run it, just type ./mkdemo

python ../ArchGenXML.py outline.zargo outline_argo

You then need to copy the product folder created (for example, outline_argo) into your Products directory and restart your Zope instance. If you take a peek inside the outline_argo directory, you'll see that everything has been created for you—the entire directory and all the relevant product initialization. Click plone setup, select Add/Remove Products, and install the outline_argo product. You can now add an outline object that has all the schema defined in the UML.

That's about as rapid development as you can get, and there are many, many options inside ArchGenXML for defining abstract classes, code fragments, and so on. Furthermore, generating code this way easily allows you to make changes and have great documentation. The only real drawback is a lack of round-tripping

http://plone.org/documentation/archetypes/ArchetypesDeveloperGuide/index_html#archgenxml-generating-archetypes-using-uml

Storing Your Content in a SQL Database

For almost all of this book, the content you've seen has been stored in the Zope object database. I've shown you how to store content on the file system, but a commonly asked question is, How do you store it in a relational database? Placing it inside a relational database is often a good thing to do if the following are true:

  • Your structure is rigid, and the schema doesn't change often (although Archetypes mitigates this aspect).
  • You have other applications that can or do access the relational database.
  • There are tools or other requirements that are already fulfilled by a relational database.
  • You have a large amount of repetitive data that's updated often.

In more a traditional CGI environment, you'd probably write some SQL statements for pulling the content out of a relational database. You can do this if you really want to by using ZSQL methods. These are useful for many situations and are covered in great detail in the Zope Book and several other books, but they can't be used to directly store content. A ZSQL method stores SQL statements and makes queries into your relational database based upon those queries. This is great if you want to make simple queries into relational database, but it doesn't persist Python classes or content types, and it really isn't the Plone way of thinking. You'll find an excellent chapter in the Zope Book on this at http://zope.org/Documentation/Books/ZopeBook/2_6Edition/RelationalDatabases.stx.

SQLStorage uses the fact that Archetypes provides an extra layer on top of normal objects to allow for special persistence mechanisms. Zope provides many different relational database adapters, and SQLStorage plugs into those. Currently there are two adapters, Postgres and MySQL. However, if the database supports standard SQL, then there's no reason one of these existing SQLStorage formats can't be used. If you find that your database adapter needs to do something different, then it wouldn't be difficult to alter the SQL statements inside SQLStorage so it worked with another database.

Making a Content Type Persist in a Relational Database

To persist the data, you must set up a relational database and make a Zope connection to it. In this case, I'll use Postgres, mainly because I already have a Postgres database for other work available but also because Postgres is good database for all levels of users. To connect Zope to Postgres, psycopg and ZPsycopg are probably to hardest to pronounce but most commonly used and best supported. You'll also find Debian packages and even Windows packages available from http://initd.org/software/psycopg.

Install your database adapter, and make a database connection from your Zope site to your relational database. Again, this is covered in great detail in the Zope Book at http://zope.org/Documentation/Books/ZopeBook/2_6Edition/RelationalDatabases.stx. But the short answer is go to the ZMI, add the database connection from the drop-down menu, and then enter the configuration to point to your relational database. If you're connecting as a certain user to your relational database, you need to make sure that the user has the privilege ability to create, insert, update, and delete tables. The create and delete privileges will be needed only during the debugging phase as you add and remove schemas and objects. Once your development has settled down or you move to production, this won't be necessary.

Next, in the ZMI click archetypes_tool and select Connections. This allows you to map the content type to the database adapter type. The easiest way is to map the default to your database connection. If you wanted, you could of course map specific types to different databases. Now you’ll change the schema to use the new storage. For this you need to import the appropriate storage class from SQLStorage (in this case, PostgreSQLStorage).

Then you need to set the storage parameter on the fields you want to store in the relational database. At this point, you'll note that you can put as many or as few fields inside the relational database as you want—you could have all the fields in the database or just some. Either way there will be content stored inside your Plone database, but at the least it'll be an object that has an ID. Anyway, in the example schema, you have two fields: an integer (age) and a string (email), as shown in Listing 13-4.

Listing 13-4. A Content Type Using SQLStorage
from Products.Archetypes.public import BaseSchema, Schema
from Products.Archetypes.public import BaseContent, registerType
from Products.Archetypes.public import IntegerField, StringField
from Products.Archetypes.public import IntegerWidget, StringWidget
from Products.Archetypes.SQLStorage import PostgreSQLStorage
from config import PROJECTNAME
 
schema = BaseSchema + Schema((
 
  IntegerField('age',
      validators=("isInt",),
      storage = PostgreSQLStorage(),
      widget=IntegerWidget(label="Your age"),
 
      ),
 
  StringField('email',
      validators = ("isEmail",),
      index = "TextIndex",
      storage = PostgresSQLStorage(),
      widget = StringWidget(label='Email',)
      ),
 
    ))
 
class PersonSQL(BaseContent):
    """Our person object"""
    schema = schema
 
registerType(PersonSQL, PROJECTNAME)

Finally, restart Plone and register your content type in your Plone site. You're now ready to test it. If you create a PersonSQL object, everything should proceed as normal and as expected for the content type. But of course the real test is if you look at your database.

You'll see that in the database you have a new table called personsql, and in that table you'll find four columns: uid, parentuid, age, and email. Using psql you can see this:

db=# \d
                  List of relations
 Schema |           Name           |   Type   | Owner
--------+--------------------------+----------+-------
 public | personsql                | table    | www-data
...
db=# \d personsql
  Table "public.personsql"
  Column   | Type | Modifiers
-----------+------+-----------
 uid       | text | not null
 parentuid | text |
 age       | int  |
 email     | text |
Indexes: personsql_pkey primary key btree (uid)

The column for age has been created as an int, and the column for email has been created as text. These are mappings created inside SQLStorage; you could change these mappings to more appropriate ones if you so desired. The uid column is the unique ID for your object inside Plone. The parentuid is the uid of the parent object. These are all the unique IDs for Archetypes that I've already mentioned. For example:

db=# SELECT * FROM personsql;
          uid              | parentuid | age | email
---------------------------+-----------+-----+-------------------
 PersonSQL.2003-07-23.4935 |           | 30  | andy@clearwind.ca

That's it—your data is being persisted inside your relational database. No SQL needs to be written, and you can have all the advantages a relational database brings! Joel Burton has written an excellent how-to article on SQLStorage at http://plone.sourceforge.net/archetypes/sqlstorage-howto.html. With kind permission, some parts of this section are based on Joel's document.


Andy McKay: The Definitive Guide to Plone. Apress 2004
This online version was generated using the 'PloneBook' product from docs.neuroinf.de/products.
It was last updated by
lallo on 2005-06-11 02:43 from the cvs source using
cvs -z3 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/plone-docs co PloneBook.

Powered by Plone CMS, the Open Source Content Management System

This site conforms to the following standards: