Dexterity meet Grok
Two frameworks with silly names make great partners
As some of you know, I've been working with Laurence Rowe, Alec Mitchell, David Glick and others on a development tool called Dexterity. In brief, Dexterity is:
- All about content types.
- Using existing infrastructure from Zope 3, such as the basic content type infrastructure and z3c.form, to enable people to build content types that are easy to understand and debug.
- Small. The mantra for Dexterity is "less is more". Through re-use and careful design, we want to minimise the maintenance burden and have as little code as possible that could go wrong. This principle extends to types that you may build with Dexterity. From Grok, we learned the principle of Don't Repeat Yourself. There is virtually no unnecessary boilerplate when you work with Dexterity.
- Powerful. By briding the gap between Zope 3 style content and Zope 2/CMF style content better than ever before, Dexerity makes it easier to use dozens if not hundreds of Zope 3 packages.
In designing Dexterity, we've thought very carefully about "framework usability". There should be one, obvious way to do things. It should be possible to extend and re-use existing types easily, both in code and through-the-web. It should be possible to start building a type through-the-web and then augment it with filesystem code, or start with filesystem code and customise through-the-web.
Many of these principles resonate with those of Grok, the development framework that's trying to make Zope 3 more accessible and fun. In the spirit of unifying concepts and making it easier to transfer knowledge between the various corners of the Zope and Python worlds, the patterns that Dexterity espouses have been modelled after and based on their Grok equivalents.
For example, here is a fully usable, Plone-looking content type with a WYSIWYG editor text field (Kupu), tabbed fieldsets and so on:
from five import grok
from plone import dexterity
from zope import schema
from z3c.form import group, field
from plone.app.z3cform.wysiwyg import WysiwygFieldWidget
class IFSPage(dexterity.Schema):
title = schema.TextLine(title=u"Title")
description = schema.Text(title=u"Description",
description=u"Summary of the body")
body = schema.Text(title=u"Body text",
required=False,
default=u"Body text goes here")
details = schema.Text(title=u"Details",
required=False)
class FSPage(dexterity.Item):
grok.implements(IFSPage)
dexterity.portal_type('example.fspage')
def __init__(self, id=None, title=None, description=None, body=None, details=None):
self.id = id # required - or call super() with this argument
self.title = title
self.description = description
self.body = body
self.details = details
class View(grok.View):
grok.context(IFSPage)
grok.require('zope2.View')
fields = field.Fields(IFSPage, omitReadOnly=True).omit('details')
fields['body'].widgetFactory = WysiwygFieldWidget
class ExtraFieldsGroup(group.Group):
fields = field.Fields(IFSPage).select('details')
label = u"Extra fields"
class AddForm(dexterity.AddForm):
dexterity.portal_type('example.fspage')
fields = fields
groups = (ExtraFieldsGroup,)
class EditForm(dexterity.EditForm):
grok.context(IFSPage)
fields = fields
groups = (ExtraFieldsGroup,)
Here, we have opted to keep quite a lot of control, by defining an explicit class as well as defining our add- and edit forms in code. We could in fact have skipped these, and Dexterity would be able to supply defaults that would work in most cases.
To install this, you need a GenericSetup profile as always. This may look like this:
<?xml version="1.0"?>
<object name="example.pypage" meta_type="Dexterity FTI"
xmlns:i18n="http://xml.zope.org/namespaces/i18n"
i18n:domain="example.dexterity">
<!-- Basic information -->
<property name="title">Example filesystem code page</property>
<property name="description" i18n:translate="">This page does everything in custom Python</property>
<property name="content_icon">document_icon.gif</property>
<property name="global_allow">True</property>
<property name="allow_discussion">False</property>
<property name="filter_content_types">False</property>
<property name="allowed_content_types"/>
<property name="klass">example.dexterity.fspage.FSPage</property>
<property name="add_permission">Add portal content</property>
<property name="schema">example.dexterity.fspage.IFSPage</property>
<!-- View information -->
<property name="default_view">view</property>
<property name="default_view_fallback">False</property>
<property name="view_methods">
<element value="view"/>
</property>
<!-- Method aliases -->
<alias from="(Default)" to="(selected layout)"/>
<alias from="edit" to="@@edit"/>
<alias from="sharing" to="@@sharing"/>
<alias from="view" to="@@view"/>
<!-- Actions -->
<action title="View" action_id="view" category="object" condition_expr=""
url_expr="string:${object_url}" visible="True">
<permission value="View"/>
</action>
<action title="Edit" action_id="edit" category="object" condition_expr=""
url_expr="string:${object_url}/edit" visible="True">
<permission value="Modify portal content"/>
</action>
</object>
The only other thing you need is a configure.zcml that's mercifully short.
<configure
xmlns="http://namespaces.zope.org/zope"
xmlns:grok="http://namespaces.zope.org/grok"
xmlns:genericsetup="http://namespaces.zope.org/genericsetup"
i18n_domain="example.dexterity">
<!-- Include our dependencies -->
<include package="plone.dexterity" />
<!-- Grok the package to initialise schema interfaces and content classes -->
<grok:grok package="." />
<!-- Register an extension profile to make the product installable -->
<genericsetup:registerProfile
name="default"
title="Example Dexterity content"
description="A test case for Dexterity"
directory="profiles/default"
provides="Products.GenericSetup.interfaces.EXTENSION"
/>
</configure>
This is just one example of how to use Dexterity. There is also a facility to specify schemata through XML, for example if you are using external tools, or indeed the forthcoming Plone GUI types editor. Schemata defined in XML can seamlessly extend or be extended by schemata defined in Python, without code generation or other nastiness. There is also a facility to easily package up "behaviours" - effectively adapters that can provide field sets, much like the Archetypes schema extender - and apply them to types declaratively - even though-the-web.
For a few more examples of how you may work with Dexterity, see the example.dexterity package.
Sprint
There is a Dexterity sprint scheduled after the Plone Conference in Washington DC. Our aim is to get a first beta out. The largest remaining piece is to build the GUI for the through-the-web type creation story (the backend is mostly complete, and TTW types work if you don't mind typing XML into a text box).
We also want to use the framework to replicate some of the standard Plone features, like the DC metadata fieldsets, in part to exercise the various APIs and in part to make re-usable components for these.
If you are interested, why not join us?

Previous:
Support Tarek in coming to Plone conference
