Skip to content.
Sections

Quantum Magic & Love Quarks

Personal tools
You are here: Home » Notes » Walking through Five to Zope 3
 

Walking through Five to Zope 3

Document Actions
This is a joint document between Jean Francois (Jeff) Roche and Russ Ferriday. It comes from some collaboration that started at the Plone Multimedia Sprint in September 2005, immediately after the Vienna Plone Conference.

Walking through Five to Zope 3

source: https://svn.plone.org/svn/collective/ATContentTypes/branches/photoimagemerge-branch/docs/treeOperations.stx

Introduction

Authors

Jeff was porting his famous ZPhotoSlides Product to create ATPhoto and ATPhotoAlbum for Plone. Russ gave a little help but Jeff did the bulk of the work, with the help of Gawel and others, to make a great job of the ATPhoto package. Now we are working together again to move the features into ATContentTypes (ATCT) using Five, ready for the eventual move to Zope 3. We can offer a useful perspective because Jeff had some experience of the Five interfaces, and Russ was a newbie to Five at the start of this. We've both been learning more as we have progress, and we hope this will become a useful resource.

Assumptions

  • You have some background in Zope 2 Products development. You know about object oriented programming. You have a basic knowledge of UML. You are interested in the Zope 3 Component Architecture. You know what unit tests are, and why we use them.

Our Goals

More and more people are speaking about the Zope 3 Component Architecture(CA). We are for the moment in a transition period where people have to mix the Zope 2 and the Zope 3 concepts. We understand that it's not that easy to change your way of thinking to the component architecture, as it comes with many (really beautiful, interesting and useful) concepts. There isn't much documentation and usage of the Zope 3 CA in Zope 2, so we will try here to make our contribution in the hope of helping your progress towards Zope 3.

If you consider that Zope 3 CA is a nice way to code/think when you have read this document then we will have reached our goal.

The pattern we are going to implement

We have been working on moving over to Five ATPhoto's Zip feature, which can archive a tree of objects to a Zip file. We had completed and tested a more generic Archiver. And even merged it into the ATContentTypes tree. But while documenting what we had done, we realized that we had missed an opportunity to decompose our design into more useful and reusable components. So we took a close look, and decided to define a general purpose iteration mechanism that can be used for traversing and operating on a wide variety of objects or object trees. This has delayed our original task a bit, but we think the learning value and reuse aspect were worth it. This tutorial is about what we have made.

Separation of concerns, testability, and easy maintenance are important factors in design. But we also want people to use our work, and that means being able to understand it, too! So we came up with three easy to define classes that implement our concept.

  • An Operator that knows how to operate on nodes in a tree
  • A Filter that knows how to filter items in a tree so that we only look at items that are interesting for our purpose
  • A TreeWalker, that knows how to visit nodes in a tree, given a starting point, an Operator, and optionally a Filter.

The behaviour of the TreeWalker might never need to be changed, but the whole idea of the Operator and Filter is that they will be overridden by new classes, to achieve new functionality. So we introduce the concept of Interfaces. Interfaces are part of python. But in plain python they do not have the same power that they do in Java, C++, Delphi and other languages. Five and Zope 3 change all that with the interfaces package, which adds rigor to the definition and use of interfaces, and also enables the adapter concept. We needed to define interfaces for Filter and Operator, as you will see later. Though we did not need it, we also made an interface for TreeWalker to provide rigor now and more flexibility later.

In the UML diagram below, you can see the following interfaces:

  • ITreeWalker
  • IOperator
  • IFilter

They define the interface contracts for the classes that implement them.

ITreeWalker is implemented in TreeWalker. And this is the same TreeWalker you will instantiate to do your recursive task.

IOperator is provided only as an interface. You will make an implementation of it to do your task. Just for example, we have shown the ArchivingOperator created for the Archiver that we made the TreeWalker for. You can provide any Operator you like to replace ArchivingOperator.

IFilterFolder is an Interface which you can re-implement as you wish, if you only want to process certain types while traversing the tree. We provide a default implementation called FolderFilter that might be all you ever need. FolderFilter, looks at all folders and items in those folders. We'll talk more about Filtering actions later.

###diagram see http://www.jfroche.be/treeWalker.png ###

The UML gives a very general overview of what we have done. The next section shows you how to use it, and later we get deeper to the implementation.

--------------------- End section ------------------------------------------------- Using TreeWalker

Getting started

To use the TreeWalker in your code, you don't have to do much! That's the whole point. Yes, there are imports to deal with, as usual, but apart from that, you need only to define an Operator, and you are in business!

First let's look at the simplest thing you can do, and work from there. We'll just walk a tree and perform no operation. For our own testing we defined a NullOperator that does exactly nothing, except satisfy the IOperator interface. We can use this class to get started.

Incidentally, these snippets I'm going to use are taken from the doctest treeWalker.txt file in the ATContentTypes Product (see docs folder). That's why you'll see the >>> at the left of each line (if you aren't familiar with doctest you shouldn't worry about them - just consider that they are python string that you could use in your favorite python shell). If you want to run this doctest you should use this photoimage branch with Archetypes 1.4 branch (https://svn.plone.org/svn/archetypes/Archetypes/branches/1.4) and CMFDynamicFTI trunk (https://svn.plone.org/svn/collective/CMFDynamicViewFTI/trunk) You are also welcome to look at treeWalker.txt for reference. You can expect it to be kept up-to-date with any interface changes that take place, whereas this document may not always reflect the most recent changes.

First let's import TreeWalker and NullOperator:

      >>> from Products.ATContentTypes.adapters.treeWalker import TreeWalker
      >>> from Products.ATContentTypes.adapters.operator import NullOperator

Then let's instantiate the NullOperator:

      >>> nulloperator = NullOperator()

Now we instantiate a TreeWalker using the nulloperator - we always need to tell the TreeWalker what we want it to do at each node it visits. In this case, nothing.:

      >>> walker = TreeWalker(nulloperator)

Let it walk the tree starting at some folder.:

      >>> walker.walk(folder)

That's it. The nodes are visited. The operate method on the NullOperator class is called, and we expect nothing to happen.

Doing something real

Imagine you want to make a text outline or tree (like the unix tree command) showing the path of each object in a folder hierarchy. And assume you don't want to deal with the iteration details.

We can do that quite quickly. All we need to do is define an operator conforming to the IOperator interface, and pass it to a new tree walker. Here's the code from our doctest:

        >>> from zope.interface import implements
        >>> from Products.ATContentTypes.interface.operator import IOperator
        >>> class TracingOperator (object):
        ...     implements(IOperator)
        ...     def __init__(self):
        ...        self.trace = []
        ...     def getTrace(self):
        ...        return self.trace
        ...     def operate(self, context, path='', **kwargs):
        ...        self.trace.append(path)

We called it TracingOperator, because it returns a trace of the traversal of the hierarchy. We needed to add two new imports: implements which gives us the new interface rigor mentioned in the introduction, and IOperator, which is the interface we need to implement.

The constructor initializes a list that will hold the places we visit. getTrace() returns that list, and operate() is the method, defined in the IOperator interface, that is called for every node in the tree. In this case, operate simply appends to the trace the path from the node on which the walk started.

Quoting directly from the doctest:

     Add some folders

        >>> folder = self.folder
        >>> folder.invokeFactory('Folder', 'f1')
        'f1'
        >>> folder.f1.invokeFactory('Folder', 'subf1_1')
        'subf1_1'
        >>> folder.f1.invokeFactory('Folder', 'subf1_2')
        'subf1_2'
        >>> folder.f1.subf1_2.invokeFactory('Folder', 'subsubf1_2_1')
        'subsubf1_2_1'

Add a selection of documents

>>> folder.f1.invokeFactory(Document, d1) d1 >>> folder.f1.d1.setText("A nice text") >>> folder.f1.subf1_1.invokeFactory(Document, d2) d2 >>> folder.f1.subf1_1.d2.setText("A nice text") >>> folder.f1.subf1_1.invokeFactory(Document, d3) d3 >>> folder.f1.subf1_1.d3.setText("A nice text") >>> folder.f1.subf1_2.subsubf1_2_1.invokeFactory(Document, d4) d4

And just operate

>>> tracingoperator = TracingOperator() >>> tracingwalker = TreeWalker(tracingoperator) >>> tracingwalker.walk(folder.f1) >>> trace = tracingoperator.getTrace()

We instantiate the TracingOperator, and pass it to the instantiation of TreeWalker. Then we walk, starting from an example folder we create in the doctests.

Finally we see the list of items on the path that we visited:

        >>> print trace
          ['f1', 'f1/subf1_1', 'f1/subf1_1/d2', 'f1/subf1_1/d3', 'f1/subf1_2', 'f1/subf1_2/subsubf1_2_1', 'f1/subf1_2/subsubf1_2_1/d4', 'f1/d1']

This is a trace of the original folder structure. There are a couple of things to note:

  • the origin of the walk, "folder.f1" in this case, is seen in the result. Meaning that operate was called on it.
  • intermediate folders were also visited. For example, f1/subf1_2 is a folder that contains another folder.
  • folders are visited before their contents. This is a type of recursion called pre-order meaning that folders are visited before their contents. This seemed like the most common use-case.

More on Filtering

Filtering is an important part of tree walking. It improves performance and can simplify the code in your operator. We wanted power and flexibility without complexity. Initially, filtering was simple and homogenous - the filter provided by default, or the filter optionally passed on creation, was applied across all folders in the tree. We improved this by offering a third option that we might call Heterogenous filtering, or perhaps adaptive filtering - automagically applying different filters at different nodes of a tree.

This can be clarified with these simple statements:

  1. If a filterClass is passed as a parameter to TreeWalker(), that class is applied to filter the start node, and all sub nodes.
  2. Otherwise, if a folder implements IFilterFolder, it will be adapted during recursion through that interface, and the filter configured for that type will be applied.
  3. Otherwise, the default filter - FolderFilter - will be applied. FolderFilter returns all items in a folder.

This gives us the option, through configuration of special adapters to a type, to control the number and type of items returned on filtering. A simple example would be a PhotoAlbum, where if a custome adapter were defined, only photos and sub-albums would be returned. Any other documents in a PhotoAlbum hierarchy would be ignored during traversal of the tree. This is developed in more detail in the Implementation section, below.

Using filterClass=FolderFilter as parameter to TreeWalker(), we also have the option to easily force operation on all content, by forcing application of the broadest filter to all folderish nodes, without exception.

--------------- End of Section --------------------------------------

Implementation

"Generalization, generalization but no over generalization" is the main point. We want to make an implementation that can be used by others trying to do something close to what we want to do but not exactly the same. But be aware that too much generalization defeats the object. The trick is to find the balance. Let us know if you think we have done that with TreeWalker

Previously we used inheritance, mixin and multiple inheritance mechanism to generalize/specialize the behaviour of a class. This meant to organize the class into a hierarchy, generalize in a bottom-up approach and and specialize in a top-down approach.

In the Zope 3 CA, implementation will be different and will use different components:

  • The interfaces
  • The adapter
  • The ZCML configuration
  • The browser view

This will help to separate the different functionality into different components.

These components are available in Zope 2 thanks to the Five product which is shipped with the recent Zope versions (2.8, 2.9). This product will help the migration from Zope 2 to Zope 3, step by step.

Interfaces

Interfaces are really useful to describe what a class will do. Zope 2 and Zope 3 interfaces both document the "what" without speaking about the "how" but Zope 3 goes further than this and uses the interfaces as components for adapting a class and thus specialize its behaviour.

Zope 2

In the Zope 2 world, interfaces were just meant to document the class and give an overview of the methods and attributes. So you could have a quick look at the different methods of a class and their doc strings without bothering about the implementation.

Zope 2 interfaces inherit from these packages:

      >>> from Interface import Interface
      >>> from Interface import Attribute

So an interface is just a class with methods without code and attributes which inherit from Interface.

By convention, an interface always begin with letter I. Here is a simple interface:

        >>> class ITreeWalker (Interface):
        ...     """
        ...       Walk over a tree
        ...     """
        ...
        ...     my_attribute = Attribute("""It's a dummy useless attribute that we describe here just for example purpose""")
        ...
        ...     def walk(context, path=''):
        ...         """
        ...             walk the whole tree.
        ...         """

Notice that we don't speak about self in the walk method in an interface, as we just describe methods of the class. See also how we define the Attribute in an interface.

We say that a class implements an interface as soon as the class has the same methods and attributes as its interface. A class can have more methods than its interface but not less! There is no problem for a class to implement more than one interface.

So let's create the class which implements the previous interface in the Zope 2 style:

      >>> class SimpleTreeWalker:
      ...     """
      ...       A really simple class which will walk a hierarchy
      ...     """
      ...
      ...     __implements__ = (ITreeWalker)
      ...
      ...     my_attribute = None
      ...
      ...     def walk(self, context, path=''):
      ...         """
      ...             walk the whole tree
      ...         """
      ...         pass

See the __implements__ , that's the Zope 2 way to say that this class implements its interfaces.

Note that Zope/Python won't complain if you didn't correctly implement the interfaces (necessary methods implemented, correct signatures, ...)

So creating a test for this is really useful:

>>> ITreeWalker.isImplementedByInstancesOf(SimpleTreeWalker) 1 >>> from Interface.Verify import verifyClass >>> verifyClass(ITreeWalker, SimpleTreeWalker) 1

We can also test on the instance of the SimpleTreeWalker class:

>>> simpleTreeWalkerObject = SimpleTreeWalker() >>> ITreeWalker.isImplementedBy(simpleTreeWalkerObject) 1 >>> from Interface.Verify import verifyObject >>> verifyObject(ITreeWalker, simpleTreeWalkerObject) 1

So we are happy we have an interface and a class which implements the interface correctly.

Zope 3

Creating a Zope 3 interface isn't different from Zope 2, just the inherited classes change.

Zope 3 interfaces inherit from a different package:

>>> from zope.interface import Interface >>> from zope.interface import Attribute

So we still can use the exact same interface as before:

>>> class ITreeWalker (Interface): ... """ ... Walk over a tree ... """ ... ... my_attribute = Attribute("""It's a dummy useless attribute that we describe here just for example purpose""") ... ... def walk(context, path=''): ... """ ... walk the whole tree. ... """

In the Zope 3 style we have two ways to define that a class implements an interface:

  1. The first way will be very different. We don't do it from the class itself as we did in Z2 but we do it from the ZCML file.
  2. The second way can be done from the class itself in the Z2 style

From the ZCML

ZCML digression

We must speak about the Zope 3 ZCML. Zope 3 comes with a new way to configure/wire/glue main Zope 3 components, using XML. This is considered as a good thing for some and as a really painful new thing for others, anyway we are not here to complain, let's just explain.

Here is a simple example zcml configuration file:""

xmlns defines the different namespaces. (W3C definition: "An XML namespace is a collection of names, identified by a URI reference ..., which are used in XML documents as element types and attribute names"). We declare here the zope 3 namespace for basic zcml configuration and the five namespace for being able to plug our Zope 2 code with the Zope 3 CA.

This zcml resides in configure.zcml and is parsed upon zope start by Five (in zope 2). If you need to define many zcml directives inside your configure.zcml, a good practice is to split up the file using the directive.

Example:

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

               <include file="implements.zcml"/>

           </configure>

So now that we can play with ZCML let's get back to our interface implementation. Let's create the class which implements the interface:

          >>> class SimpleTreeWalker:
          ...     """
          ...       A really simple class which will walk a hierarchy
          ...     """
          ...
          ...     my_attribute = None
          ...
          ...     def walk(self, context, path=''):
          ...         """
          ...             walk the whole tree
          ...         """
          ...         pass

See that this class is like the Z2 class except that we don't speak about __implements__ anymore! The new way to say that this class implements the ITreeWalker interface is:

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

           <five:implements
               class="Products.ATContentTypes.adapters.treeWalker.TreeWalker"
               interface="Products.ATContentTypes.interface.treeWalker.ITreeWalker"
               />

        </configure>

Now TreeWalker implements the interface ITreeWalker. Again Zope/Python will not complain if you don't correctly implement your interface so a test is really welcome:

Testing from the SimpleTreeWalker class

  • Zope 2 Interface implementation test was: ITreeWalker.isImplementedByInstanceOf(SimpleTreeWalker)
  • Zope 3 Interface implementation test is: ITreeWalker.implementedBy(SimpleTreeWalker)

Testing from the SimpleTreeWalker object (simpleTreeWalkerObject):

  • Zope 2 Interface implementation test was: ITreeWalker.isImplementedBy()
  • Zope 3 Interface implementation test is: ITreeWalker.providedBy(simpleTreeWalkerObject)

usage:

        >> ITreeWalker.implementedBy(SimpleTreeWalker)
        True
        >> from zope.interface.verify import verifyClass
        >> verifyClass(ITreeWalker, SimpleTreeWalker)
        True

Let's do the test from an instance of the SimpleTreeWalker class:

>> simpleTreeWalkerObject = SimpleTreeWalker() >> ITreeWalker.providedBy(simpleTreeWalkerObject) True >> from zope.interface.verify import verifyObject >> verifyObject(ITreeWalker, simpleTreeWalkerObject) True

Again we are so happy, our class implements a Zope 3 interface!

From the class

We wanted to introduce the zcml in the previous section, here is a much simpler way to define that a class implements a zope 3 interface.:

        >>> from zope.interface import implements
        >>> class SimpleTreeWalker:
        ...     """
        ...       A really simple class which will walk a hierarchy
        ...     """
        ...
        ...     implements(ITreeWalker)
        ...
        ...     my_attribute = None
        ...
        ...     def walk(self, context, path=''):
        ...         """
        ...             walk the whole tree
        ...         """
        ...         pass

And that's all, you don't have to go back to your configure.zcml anymore. Your class implements the Zope 3 interface.

Zope 2 and Zope 3 Interfaces file location

In ATCT, Zope 2 interfaces are defined in the interfaces.py file. All Zope 3 interfaces are in the folder interface. There isn't yet a definitive common practice convention on where to put the interfaces (even in Zope 3).

Zope 3 will force you to write interfaces (which is a good practice) as interfaces won't be just for documentation purposes, they are also the starting point for getting the adapter. Let's see that closer now.

Adapters

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 inherit 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,... hope so :)).

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

First one:

for="Products.ATContentTypes.interface.IATFolder"

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

provides="Products.ATContentTypes.interface.IFilterFolder"

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

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

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

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

Second one:

for="Products.ATContentTypes.interface.IPhotoAlbum"

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

provides="Products.ATContentTypes.interface.IFilterFolder"

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

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

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 this for this general directive:

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.

Hope this is clearer now ?

And now let's see the magic of adapter lookup in action. As we said before, the specific i mplementation 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__

This adapter uses our f1 folder as context.

>>> adapter.context

Now if we have a PhotoAlbum f1:

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

We have now a folder which is in fact a PhotoAlbum among all

We just adapt the PhotoAlbum by calling the IFilterFolder interface

>>> adapter = IFilterFolder(folder1)

And we get the PhotoAlbumFilter

>>> adapter.__class__

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 you own content type just by implementing the IFilterFolder interface and by registering your new adapter as we did in the previous ZCML example.

Browser View

Zope 2 views are defined in the class and methods called by the view are also inside the class, this become really ugly and the class becomes quickly big over time. Moreover as people don't want to make their class bigger they begin to program inside their ZPT and they completely mix interface and code. Others prefer to write python scripts or external methods (which is definitely better than the python: directive in ZPT). But all this proves that there is a big gap between core and presentation. This has been solved by Zope 3 views.

And what would you think if we say now that a view on an object is just an adapter? Uhm nearly in fact. The goal is that we want to hide away all the view related stuff somewhere else. And that's what we will do, we move all those things into a Browser view class and into the zcml configuration. So that the base class doesn't have to care anymore about how consumers see its content and dont have to provide the consumer methods for the content.

A browser view class is a multi-adapter, we mean by this that it adapts an object and the request. Let's define a simple Browser View for our Archiver. We want to be able to call http://myhostname/plone/Afolder/zip and get a zip containing all the objects in the folder. Also we want to be able to call http://myhostname/plone/Adocument/zip and get the zipped content of the document.

As we can't really directly use Zope 3 views, we still have to use Five views

>>> from Products.Five import BrowserView >>> from Products.ATContentTypes.interface.archive import IArchiver

Let's define our view, we just inherit from BrowserView:

>>> class ArchiveView(BrowserView): ... """ ... View on an object to get its content zipped ... """ ... def getZipFile(self,**kwargs): ... """ ... """ ... adapted = IArchiver(self.context) ... self.request.RESPONSE.setHeader(Content-Type,application/zip) ... self.request.RESPONSE.addHeader("Content-Disposition","filename=%s.zip" % self.context.getId()) ... self.request.RESPONSE.write(adapted.getRawArchive(**kwargs))

See that we have a multiadapter here, we can use self.context as the adapted object and self.request as the adapted request (understand by this that init is something like __init__(self, context, request))

Have a look at adapted = IArchiver(self.context) ; Here again we use the adapter lookup magic to get an archiver of the View's adapted object. So this allows us to archive a Document or a folder calling the same methods (defined in the IArchiver Interface) without having to bother about how this is done! It's just simple magic!

Ok, now we have our BrowserView class, we should put some glue around it and stick it to some objects... Guess what, again we will use ZCML directives for that.

So let's say we want to be able to Zip Folders only, this is done by:

So we define a browser view coming from Products.ATContentTypes.browser.archive.ArchiveView for any objects implementing the IATFolder interface for the user which has the permission View on the object. This view can be called as a zip method to the object. Don't forget the traversable directive on the class implementing the interface in the for option of the browser directive. We are obliged to do this as Zope 2 publisher isn't aware of Zope 3 views (that will allow us to call http://myhostname/plone/Afolder/zip).

Now let's say we want to be able to Zip Documents and folders and this in the same zcml directive... We have a problem here because the for="A" (as for the adapter) option in the browser directive just accepts one Interface... We will see a useful concept (that i should have spoken about in the interface section ...): Marker Interfaces.

Marker Interfaces

As we don't want to define a browser for each object that implements a certain interface, we will create a general marker interface: IArchivable and mark the Document and Folder as IArchivable. This will create a kind of hierarchy. Marker means: Document and Folder are Archivable.

So first thing is to define the Marker Interface. A marker Interface is nothing more than a dummy interface:

>>> from zope.interface import Interface >>> class IArchivable(Interface): ... """ ... marker interface for possible archivable object ... """

We have our marker interface, now let's put again some glue around it with our friendly ZCML directives:

This way ATDocument and ATFolder are known now as IArchivable. And now we can use a single browser view directive pointing to our brand new marker interface:

Now both Document and Folder can "be viewed" as a zip archive. And as we didn't forget the traversable directive we can call: http://myhostname/plone/Adocument/zip and http://myhostname/plone/Afolder/zip. All this thanks to our Browser View, our Marker Interface, our adapters, our interfaces and our base classes.

References:

  Web Component Development with Zope 3
  by Philipp von Weitershausen
  http://worldcookery.com/

  Zope 3 Developer's Handbook
  by Stephan Richter
  http://www.amazon.com/gp/product/0672326175/103-9651091-6407816

  Literate Testing: Automated Testing with doctest 
  Jim Fulton, jim@zope.com 
  Tim Peters, tim@zope.com 
  PyCon 2004
  http://www.python.org/pycon/dc2004/papers/4/

Created by pf
Last modified 2006-02-16 02:09 PM
 

Powered by Plone