A short example
A few people, myself included, have been working on a system we've called "Dexterity". This is essentially a way to build content types for Plone. It leverages the "light-weight" (also known as "Zope 3 style" or "non-Archetypes") metaphor that has been perpetuated through plone.app.content and seen in packages like borg.project and oi.plum, and aims to make it easy to build new content types, even for non-programmers.
Dexterity isn't really about building a new framework. Rather, it's about taking what we have in Zope 3 and CMF and making it accessible and productive. It also explicitly supports through-the-web content type creation, with a well-defined path to move to filesystem code, as well as support for patterns where some work may be done TTW or with visual tools, and other work may be carried out by programmers on the fielsystem.
Dexterity now has the basic scaffolding in place, though there is much work to be done, especially around the UI. It is nowhere near being a viable replacement for Archetypes at this stage, though in the future I hope Archetypes and Dexterity can converge.
Let's take a quick look at how you may use Dexterity.
Starting through the web
The starting point for many people will probably be a through-the-web GUI, accessible via the Plone control panel. This is one of the bits that isn't done yet, though David Glick has started experimenting with the form components to support this. The UI will allow a site admin to create a new type and build a schema from standard fields, as well as to choose a set of "behaviours" - such as DC metadata, locking or versioning - to enable for the type.
Once the type has been built, it will be saved in a Factory Type Information object (of type "Dexterity FTI") in portal_types. This part works today. The FTI is has a number of properties, such as:
- Name, title and description
- Add permission
- Content class (Dexterity has a default class for Container types and one for Item types, though you can use another one)
- Views and actions
- A schema
The schema can be defined in a few different ways, but is ultimately just an interface with zope.schema fields. For types defined through-the-web, the schema will be serialised to XML. The XML syntax looks like this:
<model> <schema> <field name="title" type="zope.schema.TextLine"> <title>Title</title> <required>True</required> </field> <field name="body" type="zope.schema.Text"> <title>Body text</title> <required>False</required> </field> </schema> </model>
You can see an example of a type like this here. Since this is a GenericSetup export of the FTI, the XML of the schema is escaped, but in the browser, you can type it as normal.
Moving the model to a separate file
With a type like this installed, you can add content and fill in the two fields - title and body. Let's say, however, that you got tired of manipulating the type through-the-web. Perhaps you had a nice visual GUI tool that could produce XML in the format about (like ArchGenXML - again, something that doesn't exist just yet). Or perhaps you really like typing XML into a text editor.
In this case, you could move the file to a package on the filesystem, and referenced it in the FTI in the "Model file" field, leaving the "Model source" field blank. If, for example the model file was called "page.xml" and lived in a package "example.package", you could set this field to "example.package:page.xml".
Creating a filesystem interface
Dexterity can create add forms, edit forms and standard views for types defined with an XML schema only. It will also be able to support through-the-web view customisation, using the portal_view_customizations tool. However, as your requirements become more complex, you will almost certainly need to write some Python code that uses your type.
In many cases, you will not need to write a custom class. Instead, you can register adapters and views for your type by using a concrete/filesystem interface. Let's say we had our schema in a file called page.xml. We could then add code like this to create a filesystem interface:
from plone.dexterity import api class IPage(api.Schema): api.model('page.xml')
To use this for your type, you would leave the "Model source" and "Model file" field blank in the FTI, and specify a dotted name to the IPage interface in the "Schema" field instead.
The syntax above makes use of Grok-like directives. The IPage interface will be populated on startup with the fields defined in page.xml, and annotated with other metadata (such as security or widget hints) that may be encoded in the XML file. For this to take place, your package must have the following line in configure.zcml:
<grok:grok package="." />
Using a custom class
So far, our types have all used the standard Dexterity Item and Container classes. Dexterity uses a particular content factory that will make sure that instances actually provide the schema interface in question, and that the fields promised by the interface are all represented on the instance. This makes introspection and attribute manipulation straightforward - for example, you could access the "body" field of a context providing IPage as context.body.
For some uses cases, you may require a custom class. You may need this less often that you'd think - it is often preferable to use a filesystem interface and register adapters and views for this interface. However, if you want to use custom properties for some schema fields, or you want to override Zope 2/CMF functions like Title() or Description(), then a custom class may be in order.
To use a custom class, all you have to do is to specify the dotted name to the class in the "Class" attribute of the FTI. Typically, you'll want your class to derive from one of the standard Dexterity classes as well. For example, here is a type that uses a Python-only interface (no XML this time) and a concrete class. Again, this class is "grokked" on startup to take care of registering security and ensuring that the schema interface is properly provided by objects of the class.
from zope.interface import implements, Interface from zope import schema from plone.dexterity import api class IPyPage(api.Schema): title = schema.TextLine(title=u"Title") summary = schema.Text(title=u"Summary", description=u"Summary of the body", readonly=True) body = schema.Text(title=u"Body text", required=False, default=u"Body text goes here") class PyPage(api.Item): implements(IPyPage) @property def summary(self): if self.body: return "%s..." % self.body[:30] else: return "" def Description(self): return self.summary
The FTI is defined here.
Where we are
Dexterity is obviously far from finished, but already does a lot. The main thing we need right now is feedback and suggestions.
Then, there are a few functional areas that could use some volunteers, including:
- There is a syntax for specifying field-level security in the XML schema serialisation, but it is not yet applied to objects properly.
- The standard add and edit forms, which use z3c.form, need to be neatned up a bit, and they need to support composite schemata with fieldsets for standard Dublin Core metadata.
- There is a syntax for giving widget hints in the XML schema serialisation, but this is not used by the forms yet.
- The support for declaratively supported "behaviours", particularly at the form/UI level, is still incomplete.
- The schema format does not support i18n yet.
- If a schema is changed and there are "live" objects in the ZODB, these may need to migrated. We would like to support common types of migrations directly.
- The whole Plone UI story for creating and managing types is only just begun.
- Tool support ala ArchGenXML is still just a dream.
If you are interested in helping out, please do let us know!