Attention

This document was written for an unsupported version of Plone, Plone 2.1.x, and was last updated 1202 days ago.

For more information, see the version support policy.

To learn how to upgrade to the current version of Plone, read the upgrade manual.

Adapters

by Russ Ferriday last modified Feb 07, 2010 01:18 AM
This section covers the rationale for and use of Adapters with a real example, including details of configuration, and a demonstration of adapter lookups in action.

The main problem in Zope 2 classes is that they become really big over time (even though inheritance techniques help somewhat to reduce the number of functionalities in a class). The new Zope 3 way of thinking says: keep your base class really small and add functionality to your base class using adapters. So adding functionality means adding an adapter, an adapter adds a functionality for a base class. All the specific implementations of a functionality are to be hidden in the Zope 3 CA by the interfaces.

So let's keep in mind that there are 3 important parts here:

  • The Interfaces that define the functionality
  • The adapter which implements the functionality
  • The object that can be adapted

You can have different adapters which implement an interface. The adapter interface describe only the functionality and must be kept as general as possible. So let's take a look at the Interfaces that define a functionality.

Let's take the Filter functionality. In a folderish object you want to list only specific content (eg: in a PhotoAlbum you just want to list PhotoAlbums and Images). This will have a really simple interface:

    >>> from zope.interface import Interface
    >>> class IFilterFolder(Interface):
    ...     """
    ...         Filter the content of a folderish object
    ...     """
    ...     def listObjects():
    ...         """
    ...             return the list of filtered objects contained in the folder
    ...         """

Let's create a dummy filter adapter which implements this interface and that will just return all the objects in the folder:

    >>> from zope.interface import implements
    >>> class FolderFilter(object):
    ...     """
    ...         Dummy Filter which doesn't filter anything
    ...     """
    ...     implements(IFilterFolder)
    ...
    ...     def __init__(self, context):
    ...         """
    ...            Initialize our adapter
    ...         """
    ...         self.context = context
    ...
    ...     def listObjects(self):
    ...         """
    ...             return the list of objects contained in the folder
    ...         """
    ...         return self.context.objectValues()

As you see an adapter inherits from the object class, the most simple type you can imagine. Note that simple adapter takes the object to adapt (context) at initialization. The convention is to name it context.

Let's create another adapter for a PhotoAlbum. This adapter will filter the Image and contained PhotoAlbums:

    >>> class PhotoAlbumFilter(object):
    ...     """
    ...         Filter which returns only Image and PhotoAlbum in the folder
    ...     """
    ...     implements(IFilterFolder)
    ...
    ...     def __init__(self, context):
    ...         """
    ...            Initialize our adapter
    ...         """
    ...         self.context = context
    ...
    ...     def listObjects(self):
    ...         """
    ...             return the list of Image and PhotoAlbum in the folder
    ...         """
    ...         return [item for item in self.context.ObjectValues(['Image','Folder'])]

So we have two adapters for one Interface. We should now put some glue to make a FolderFilter adapt a Folder and a PhotoAlbumFilter to adapt a PhotoAlbum. Again this will be done thanks to ZCML. We will define an adapter implementing an Interface (and so a functionality) for a certain type of object implementing another interface (this will become clearer with the example,... we hope :)):

  <configure
      xmlns="http://namespaces.zope.org/zope"
      xmlns:five="http://namespaces.zope.org/five"
      >

    <adapter
      for="Products.ATContentTypes.interface.IATFolder"
      provides="Products.ATContentTypes.interface.IFilterFolder"
      factory="Products.ATContentTypes.adapters.folder.FolderFilter"
      />

    <adapter
      for="Products.ATContentTypes.interface.IPhotoAlbum"
      provides="Products.ATContentTypes.interface.IFilterFolder"
      factory="Products.ATContentTypes.adapters.image.PhotoAlbumFilter"
      />

  </configure>

So now it's easy to understand, we define two adapters which will adapt two different content types.

First IATFolder

We want an adapter which will adapt each object which implements the Products.ATContentTypes.interface.IATFolder interface. So an ATFolder object implements it and it can be adapted by our adapter:

      for="Products.ATContentTypes.interface.IATFolder"

We say here that the functionality that the adapter implements is described in this Products.ATContentTypes.interface.IFilterFolder interface:

      provides="Products.ATContentTypes.interface.IFilterFolder"

We say now that the adapter which implement the IFilterFolder is in 'Products.ATContentTypes.adapters.folder.FolderFilter':

      factory="Products.ATContentTypes.adapters.folder.FolderFilter"

So, summarizing, this directive means that we will register our FolderFilter adapter in the adapter registry for any object which implements IATFolder.

Second IPhotoAlbum

We want an adapter which will adapt each object which implement the Products.ATContentTypes.interface.IPhotoAlbum interface. So basically we adapt a 'PhotoAlbum':

    for="Products.ATContentTypes.interface.IPhotoAlbum"

We say here that the functionality that the adapter implements is described in this Products.ATContentTypes.interface.IFilterFolder interface:

    provides="Products.ATContentTypes.interface.IFilterFolder"

We say now that the adapter which implement the IFilterFolder is in Products.ATContentTypes.adapters.image.PhotoAlbumFilter:

      factory="Products.ATContentTypes.adapters.image.PhotoAlbumFilter"

So summarized this directive means that this will register our PhotoAlbumFilter adapter in the adapter registry for any object which implements IPhotoAlbum.

Remember the general pattern for the adapter directive:

  <adapter
     for="A"
     provides="B"
     implements="C"
     />

  *for* any object implementing the A interface
    we *provide* a new functionality defined in the B interface
      the implementation of the functionality is defined by its *factory* in the C class.

We hope this is clearer now ?

And now let's see the magic of adapter lookup in action. As we said before, the specific implementation of a functionality is to be hidden in the Zope 3 CA by the interfaces. So if I want the filtered list of object of any folderish content without having to bother if it's a PhotoAlbum, a simple Folder, an Audio Folder... I just adapt the object to the interface - we just call the interface:

If we have a folder 'folder1':

    >>> self.folder.invokeFactory('Folder', 'folder1')
    'folder1'
    >>> folder1 = self.folder.folder1

We just adapt the folder to the IFilterFolder interface:

    >>> from Products.ATContentTypes.interface import IFilterFolder
    >>> adapter = IFilterFolder(folder1)

And we get the FolderFilter:

    >>> adapter.__class__
    <class 'Products.ATContentTypes.adapters.folder.FolderFilter'>

This adapter uses our folder1 folder as context:

    >>> adapter.context
    <ATFolder at ...>

Now if we have a PhotoAlbum folder1:

    >>> from Products.ATContentTypes.interface import IPhotoAlbum
    >>> from zope.interface import directlyProvides
    >>> directlyProvides(folder1, IPhotoAlbum)

We now have a folder which is in fact a PhotoAlbum.

We adapt the PhotoAlbum by calling the IFilterFolder interface:

    >>> adapter = IFilterFolder(folder1)

And we get the PhotoAlbumFilter

Here you may ask: "Why do we get a PhotoAlbumFilter and not a FolderFilter, since folder1 implements both IATFolder and IPhotoAlbum?". The answer is simple, the adapter lookup finds the first interface that matches an adapter.

So be aware that there is priority in Interface declaration and when we did directlyProvides(folder1, IPhotoAlbum), we gave the first priority to the IPhotoAlbum interface above all others. So the adapter lookup first takes IPhotoAlbum, looks if there is an adapter that matches this interface (looking for the "for" attribute in the zcml directive), if there is, just use it, if there is no adapter for this interface take the next provided Interface (which should be IATFolder) and do the same lookup for the adapter matching the interface:

    >>> adapter.__class__
    <class 'Products.ATContentTypes.adapters.image.PhotoAlbumFilter'>

Don't worry about the implementation of the adapter for your object, the adapter lookup will do it for you.

Now you can define other Filter adapters for your own content type just by implementing the IFilterFolder interface and by registering your new adapter as we did in the previous ZCML example. An upcoming section goes into more detail of this.


Contribute

Something wrong or out of date? Anybody can edit or create a new article in the knowledge base. Simply create an account on this site, log in, and click the Edit button to contribute.