You are here: Home Articles Dexterity meet Grok
Navigation
OpenID Log in

 

Dexterity meet Grok

by Martin Aspeli last modified Aug 28, 2008 05:39 AM

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?

Document Actions
Plone Book
Professional Plone 4 Development

I am the author of a book called Professional Plone Development. You can read more about it here.

About this site

This Plone site is kindly hosted by: 

Six Feet Up