Browser View

by Russ Ferriday last modified Feb 07, 2010 01:18 AM
Here we look at the benefits, use, and configuration of, browser views, and touch on Marker Interfaces.

Browser View

Description

Here we look at the benefits, use, and configuration of, browser views, and touch on Marker Interfaces.

Zope 2 views are defined in the class, and methods called by the view are also inside the same class, this becomes really ugly and the class grows quickly. 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 doesn't 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))

Notice 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:

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

    <browser:page
       for=".interface.IATFolder"
       name="zip"
       class=".browser.archive.ArchiveView"
       attribute="getZipFile"
       permission="zope2.View"
       />

    <traversable class=".content.folder.ATFolder"/>

  </configure>

So we define a browser view coming from Products.ATContentTypes.browser.archive.ArchiveView for any objects implementing the IATFolder interface for the user who 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

sprinting

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:

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

        <implements
            class=".content.document.ATDocument"
            interface=".interface.archive.IArchivable"
        />

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:

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

        <browser:page
            for=".interface.IArchivable"
            name="zip"
            class=".browser.archive.ArchiveView"
            attribute="getZipFile"
            permission="zope2.View"
        />

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.