Friday, January 11, 2008

When adapting content types, schemaextender is the way to go

Lightning Talks are among the best investments of time you can make on any Plone Conference: there's always a lot of smart people doing great things.

During the Plone Conference 2007, in Naples, Florian Schulze spoke about schemaxtender, a package that allows you to inject new fields into an Archetypes schema using an adapter. When I saw his talk, I knew I was going to use it for what I had in mind.

As I mentioned before in Mapping NITF into Plone's metadata, at La Jornada we were looking for a way to adapt Plone's standard News Item content type to:

  • add new fields to it (property, section, urgency and byline)
  • change fields' order among different schematas to make the edition of new content easier for the publishers

Using schemaextender to accomplish these tasks was so easy that I was really excited when I finished. I spent about 40 hours (in fact, a little bit more after the second release) to read Part 1 of Philip von Weitershausen's excellent book Web Component Development with Zope 3, to understand the way schemaextender works, to find out how to make it work on Plone 2.5, to start using the adapted content with Smart Folders, and even to write some tests for it.

All this work is available in a product called nitf4plone in case you want to try it (be aware this is an beta release). The product works on both, Plone 2.5 and Plone 3.0.

Let's analize the code... but first, a disclaimer: don't try this on Plone 2.5.

Why? schemaextender was written to work with Archetypes version 1.5 or later (that's, Plone 3.0 and later). There's a branch to make it work with Plone 2.5 patched by Erik Rose of the WebLion Project Team at PSU, but it will never be merged into the maintenance one. If you use this branch you are on your own in the event of a bug. Also, have in mind that there's no way back on doing this and after installing schemaextender in Plone 2.5, the adapter will be available for all sites in the instance.

So, yes, do as I say, not as I did.

Having warned you, let's dive a little bit into the code; schemaextender includes 3 types of adapters:

  • ISchemaExtender lets you can add new fields to a schema
  • IOrderableSchemaExtender lets you add new fields and reorder them
  • ISchemaModifier is a low-level hook that allows direct manipulation of the schema

You can find information and examples on all of them on the source code. As you might have expected, I decided to use IOrderableSchemaExtender.

To write the adapter, we need to declare the new fields first; let's take a look to the section field as an example:

class SectionField(ExtensionField, atapi.StringField):
     """Named section of a publication where a news object appear
     """

     def getDefault(self, instance):
          …
          return nitf.default_section

     def Vocabulary(self, instance):
          …
          return atapi.DisplayList([(x, x) for x in nitf.sections])

As you can see, we need to subclass from ExtensionField and StringField; please note that it's mandatory to keep this order. ExtensionField will provide standard accessors and mutators which are not generated on the class. StringField will provide standard attributes and the widget for the field. Also we override getDefault() and Vocabulary() methods to set the default value and vocabulary.

Let's take a look to the adapter's class now:

class NITFExtender(object):
    """Adapter to add NITF fields to News Items
    """
    implements(IOrderableSchemaExtender)
    adapts(IATNewsItem)

    fields = [
        …
        SectionField('section',
        languageIndependent = 1,
        enforceVocabulary = 1,
        required = 1,
        widget = atapi.SelectionWidget(
            label = 'Section',
            label_msgid = 'section',
            description = 'Named section where the news object appear',
            description_msgid = 'help_section',
            i18n_domain = 'nitf4plone')),
    …
    ]

def __init__(self, context):
    self.context = context

def getFields(self):
    return self.fields

def getOrder(self, original):
    # we only need to change the order of the fields in Plone 2.5
    if 'metadata' in original:
        # first we remove the fields from whichever schemata they are
        for schemata in original.keys():
            if 'relatedItems' in original[schemata]:
                original[schemata].remove('relatedItems')
            if 'subject' in original[schemata]:
                original[schemata].remove('subject')

        # now we insert them where we want them to appear
        idx = original['default'].index('property')
        original['default'].insert(idx, 'subject')
        original['metadata'].insert(0, 'relatedItems')
    return original

As I mentioned, our adapter implements IOrderableSchemaExtender. In Plone 3.0 adapters can be registered locally at installation time:

sm = portal.getSiteManager()
sm.registerAdapter(NITFExtender, (IATNewsItem, ), IOrderableSchemaExtender)

In Plone 2.5 we can't have local adapters and registrations aren't persistent, so we have to handle this in a different way:

Here you can see the way news items look after applying the adapter:

schemaextender in action: the news item now contains new attributes and the order of the fields is modified to make the work of the publishers easy.

The code of the adapter is pretty clean and easy to understand.

Having finished it, we followed Mikko Ohtamaa's procedure to adding new fields to Smart Folders search in order to display all news articles for a given section and it worked fine.

Right now we are working on the migration of the content of our site to use the new fields; we are also preparing some templates to display the new information and some CompositePack's viewlets to use them to create the front pages in a better way.

Please let me know if you find this development interesting or if you want to participate in some way.

I want to thank all the members of the Plone community who helped me answering my questions at the #plone channel on IRC and the Product Developers forum, specially Martin Aspeli and Florian Schulze (who helped me with the schemaxtender internals and were really patient with me), Mikel Larreategui and Erik Rose (who helped me with the installer), Wichert Akkerman and Andreas Jung (who are always available answering all sort of questions on the forums).

No comments:

Post a Comment