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
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.
We want an adapter which will adapt each object which implements
ATFolder object implements it and it can be adapted by our adapter:
We say here that the functionality that the adapter implements is described in
We say now that the adapter which implement the
So, summarizing, this directive means that we will register our
in the adapter registry for any object which implements
We want an adapter which will adapt each object which implement
Products.ATContentTypes.interface.IPhotoAlbum interface. So basically
we adapt a 'PhotoAlbum':
We say here that the functionality that the adapter implements is described
We say now that the adapter which implement the IFilterFolder is in 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
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
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
and by registering your new adapter as we did in the previous ZCML example. An upcoming section goes into more detail of this.