[Zope3-checkins] SVN: Zope3/branches/roger-contentprovider/src/zope/viewlet/ Some initial work on the refactoring; we are getting there.

Stephan Richter srichter at cosmos.phy.tufts.edu
Sat Oct 8 12:40:03 EDT 2005


Log message for revision 38968:
  Some initial work on the refactoring; we are getting there.
  

Changed:
  U   Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
  U   Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt
  U   Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
  A   Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py
  D   Zope3/branches/roger-contentprovider/src/zope/viewlet/tests/
  A   Zope3/branches/roger-contentprovider/src/zope/viewlet/tests.py

-=-
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt	2005-10-08 16:27:57 UTC (rev 38967)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt	2005-10-08 16:40:03 UTC (rev 38968)
@@ -1,605 +1,305 @@
-========
-Viewlets
-========
+=============================
+Viewlets and Viewlet Managers
+=============================
 
-This package provides a framework to develop componentized Web GUI
-applications. Instead of describing the content of a page using a single
-template or static system of templates and METAL macros, page regions can be
-defined and are filled dynamically with content (viewlets) based on the setup
-of the application.
+Let's start with some motivation. Using content providers allows us to insert
+one piece of HTML content. In most Web development, however, you are often
+interested in defining some sort of region and then allow developers to
+register content for those regions.
 
+  >>> from zope.viewlet import interfaces
 
-Getting Started
----------------
 
-Let's say we have simple two-column page. In the smaller left column, there
-are boxes filled with various pieces of information, such as news, latest
-software releases, etc. This content, however, generally changes depending on
-the view and the object being viewed.
+The Viewlet Manager
+-------------------
 
+In this implementation of viewlets, those regions are just content providers
+called viewlet managers that manage other content providers known as
+viewlets. Every viewlet manager can handle viewlets of a certain type:
 
-Regions
-~~~~~~~
+  >>> class ILeftColumnViewlet(interfaces.IViewlet):
+  ...     """This is a viewlet located in the left column."""
 
-Instead of hard-coding the pieces of content in the left column in the page
-template or using macros, we simply define a region for it. Regions are
-interfaces that act as content placeholders. Here is a common setup:
+You can then create a viewlet manager for this viewlet type:
 
-  >>> import zope.interface
-  >>> class ILeftColumn(zope.interface.Interface):
-  ...     '''The left column of a Web site.'''
+  >>> from zope.viewlet import manager
+  >>> leftColumn = manager.ViewletManager(ILeftColumnViewlet)
 
-  >>> from zope.contentprovider.interfaces import IRegion
-  >>> zope.interface.directlyProvides(ILeftColumn, IRegion)
+So initially nothing gets rendered:
 
-  >>> import zope.component
-  >>> zope.component.provideUtility(ILeftColumn, IRegion,
-  ...                               'webpage.LeftColumn')
+  >>> leftColumn()
+  u''
 
-It is important that the region interface provides the ``IRegion``
-interface and that it is registered as a utility providing
-``IRegion``. If omitted, the framework will be unable to find the
-region later.
+But now we register some viewlets for the manager
 
-
-Viewlet
-~~~~~~~
-
-Viewlets are snippets of content that can be placed into a region, such as the
-one defined above. As the name suggests, viewlets are views, but they are
-qualified not only by the context object and the request, but also the view
-they appear in. Also, the viewlet must *provide* the region interface it is
-filling; we will demonstrate a more advanced example later, where the purpose
-of this requirement becomes clear.
-
-Like regular views, viewlets can either use page templates to provide content
-or provide a simple ``__call__`` method. For our first viewlet, let's develop
-a more commonly used page-template-driven viewlet:
-
-  >>> import os, tempfile
-  >>> temp_dir = tempfile.mkdtemp()
-
-  >>> viewletFileName = os.path.join(temp_dir, 'viewlet.pt')
-  >>> open(viewletFileName, 'w').write('''
-  ...         <div class="box">
-  ...           <tal:block replace="viewlet/title" />
-  ...         </div>
-  ... ''')
-
-  >>> class ViewletBase(object):
-  ...     def title(self):
-  ...         return 'Viewlet Title'
-
-As you can see, the viewlet Python object is known as ``viewlet`` inside the
-template, while the view object is still available as ``view``. Next we build
-and register the viewlet using a special helper function:
-
-  # Create the viewlet class
-  >>> from zope.viewlet import viewlet
-  >>> Viewlet = viewlet.SimpleViewletClass(
-  ...     viewletFileName, bases=(ViewletBase,), name='viewlet')
-
-  # Generate a viewlet checker
-  >>> from zope.security.checker import NamesChecker, defineChecker
-  >>> viewletChecker = NamesChecker(('__call__', 'weight', 'title',))
-  >>> defineChecker(Viewlet, viewletChecker)
-
-  # Register the viewlet with component architecture
+  >>> import zope.component
   >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
   >>> from zope.app.publisher.interfaces.browser import IBrowserView
-  >>> zope.component.provideAdapter(
-  ...     Viewlet,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     ILeftColumn,
-  ...     name='viewlet')
 
-As you can see from the security checker registration, a viewlet provides also
-a weight, which acts as a hint to determine the order in which the viewlets of
-a region should be displayed. The view the viewlet is used in can also be
-accessed via the ``view`` attribute of the viewlet class.
-
-
-Creating the View
-~~~~~~~~~~~~~~~~~
-
-Now that we have a region with a viewlet registered for it, let's use it by
-creating the front page of our Web Site:
-
-  >>> templateFileName = os.path.join(temp_dir, 'template.pt')
-  >>> open(templateFileName, 'w').write('''
-  ... <html>
-  ...   <body>
-  ...     <h1>My Web Page</h1>
-  ...     <div class="left-column">
-  ...       <div class="column-item"
-  ...            tal:repeat="viewlet providers:webpage.LeftColumn">
-  ...         <tal:block replace="structure viewlet" />
-  ...       </div>
-  ...     </div>
-  ...     <div class="main">
-  ...       Content here
-  ...     </div>
-  ...   </body>
-  ... </html>
-  ... ''')
-
-and registering it as a view (browser page) for all objects:
-
-  >>> from zope.app.pagetemplate.simpleviewclass import SimpleViewClass
-  >>> FrontPage = SimpleViewClass(templateFileName, name='main.html')
-
-  >>> zope.component.provideAdapter(
-  ...     FrontPage,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer),
-  ...     zope.interface.Interface,
-  ...     name='main.html')
-
-That is all of the setup. Let's now render the view.
-
-
-Using the View
-~~~~~~~~~~~~~~
-
-Let's create a content object that can be viewed:
-
-  >>> class Content(object):
-  ...     zope.interface.implements(zope.interface.Interface)
-
-  >>> content = Content()
-
-Finally we look up the view and render it:
-
-  >>> from zope.publisher.browser import TestRequest
-  >>> request = TestRequest()
-
-  >>> view = zope.component.getMultiAdapter((content, request),
-  ...                                       name='main.html')
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>My Web Page</h1>
-      <div class="left-column">
-        <div class="column-item">
-  <BLANKLINE>
-          <div class="box">
-            Viewlet Title
-          </div>
-  <BLANKLINE>
-        </div>
-      </div>
-      <div class="main">
-        Content here
-      </div>
-    </body>
-  </html>
-
-
-Class-driven Viewlets
-~~~~~~~~~~~~~~~~~~~~~
-
-Let's now have a look into the steps involved to create a viewlet class from
-scratch. We also want to ensure that this viewlet always displays second and
-not before the first one. Here is a most simple implementation:
-
-  >>> from zope.app.publisher.browser import BrowserView
-  >>> from zope.viewlet.interfaces import IViewlet
-  >>> class InfoViewlet(BrowserView):
-  ...     zope.interface.implements(IViewlet, ILeftColumn)
-  ...     weight = 1
+  >>> class WeatherBox(ILeftColumnViewlet):
   ...
   ...     def __init__(self, context, request, view):
-  ...         super(InfoViewlet, self).__init__(context, request)
-  ...         self.view = view
+  ...         pass
   ...
   ...     def __call__(self):
-  ...         return u'<h3>Some Information.</h3>'
+  ...         return u'<div class="box">It is sunny today!</div>'
 
-  >>> defineChecker(InfoViewlet, viewletChecker)
-
   >>> zope.component.provideAdapter(
-  ...     InfoViewlet,
+  ...     WeatherBox,
   ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     ILeftColumn,
-  ...     name='infoViewlet')
+  ...     ILeftColumnViewlet, name='weather')
 
-
-Note that you would commonly not state in the class itself that it
-implements a particular region, since it is usually done by the ZCML
-directive, which is introduced in `directives.zcml`.
-
-When we now render the view, the content of our info viewlet appears as well:
-
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>My Web Page</h1>
-      <div class="left-column">
-        <div class="column-item">
-  <BLANKLINE>
-          <div class="box">
-            Viewlet Title
-          </div>
-  <BLANKLINE>
-        </div>
-        <div class="column-item">
-          <h3>Some Information.</h3>
-        </div>
-      </div>
-      <div class="main">
-        Content here
-      </div>
-    </body>
-  </html>
-
-
-Changing the Weight
-~~~~~~~~~~~~~~~~~~~
-
-Let's ensure that the weight really affects the order of the viewlets. If we
-change the weights around,
-
-  >>> InfoViewlet.weight = 0
-  >>> Viewlet._weight = 1
-
-the order of the left column in the page template should change:
-
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>My Web Page</h1>
-      <div class="left-column">
-        <div class="column-item">
-          <h3>Some Information.</h3>
-        </div>
-        <div class="column-item">
-  <BLANKLINE>
-          <div class="box">
-            Viewlet Title
-          </div>
-  <BLANKLINE>
-        </div>
-      </div>
-      <div class="main">
-        Content here
-      </div>
-    </body>
-  </html>
-
-
-Looking Up a Viewlet by Name
-----------------------------
-
-In some cases you want to be able to look up a particular viewlet for a region,
-given a context and a view. For this use case, you can simply use a second
-TALES namespace called ``viewlet`` that selects the viewlet using the
-expression ``<path to region>/<viewlet name>``.
-
-Since everything else is already set up, we can simply register a new view:
-
-  >>> template2FileName = os.path.join(temp_dir, 'template2.pt')
-  >>> open(template2FileName, 'w').write('''
-  ... <html>
-  ...   <body>
-  ...     <h1>My Web Page - Take 2</h1>
-  ...     <div class="left-column">
-  ...       <div class="column-item">
-  ...         <tal:block
-  ...           replace="structure provider:webpage.LeftColumn/viewlet" />
-  ...       </div>
-  ...     </div>
-  ...   </body>
-  ... </html>
-  ... ''')
-
-  >>> SecondPage = SimpleViewClass(template2FileName, name='second.html')
-  >>> zope.component.provideAdapter(
-  ...     SecondPage,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer),
-  ...     ILeftColumn,
-  ...     name='second.html')
-
-  >>> view = zope.component.getMultiAdapter((content, request),
-  ...                                       name='second.html')
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>My Web Page - Take 2</h1>
-      <div class="left-column">
-        <div class="column-item">
-  <BLANKLINE>
-          <div class="box">
-            Viewlet Title
-          </div>
-  <BLANKLINE>
-        </div>
-      </div>
-    </body>
-  </html>
-
-Note that this namespace returns the rendered viewlet and not the viewlet
-view, like the ``viewlets`` TALES namespace.
-
-
-Region Schemas
---------------
-
-In some use cases you want to be able to provide variables to a viewlet that
-cannot be accessed via the view class or the context object. They are usually
-variables that are defined by the view template. Since we do not just want all
-of the view's template variables to be available (because it would be implicit
-and not all viewlets must be called from within page templates), we must
-specify the variables that the environment of the viewlet provides in the slot
-interface as fields.
-
-Let's say in your view you want to display a list of objects and you would
-like to allow various columns that are controlled by viewlets:
-
-  >>> class ObjectItems(object):
+  >>> class SportBox(ILeftColumnViewlet):
   ...
-  ...     def objectInfo(self):
-  ...         return [{'name': 'README.txt', 'size': '1.2kB'},
-  ...                 {'name': 'logo.png', 'size': '100 x 100'}]
-
-  >>> contentsFileName = os.path.join(temp_dir, 'items.pt')
-  >>> open(contentsFileName, 'w').write('''
-  ... <html>
-  ...   <body>
-  ...     <h1>Contents</h1>
-  ...     <table>
-  ...       <tr tal:repeat="item view/objectInfo">
-  ...         <td tal:repeat="column providers:webpage.ObjectInfoColumn"
-  ...             tal:content="structure column" />
-  ...       </tr>
-  ...     </table>
-  ...   </body>
-  ... </html>
-  ... ''')
-
-  >>> Contents = SimpleViewClass(contentsFileName, bases=(ObjectItems,),
-  ...                            name='contents.html')
-
-  >>> zope.component.provideAdapter(
-  ...     Contents,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer),
-  ...     zope.interface.Interface,
-  ...     name='contents.html')
-
-As you can see from the page template code, in order for the viewlets to be
-of any use, they need access to the ``item`` variable as defined in the page
-template. Thus, the region definition will state that the viewlet must have
-access to a variable called ``item`` that contains the value of ``item`` in
-the page template:
-
-  >>> import zope.schema
-  >>> class IObjectInfoColumn(zope.interface.Interface):
-  ...     '''Place holder for the columns of a contents view.'''
-  ...
-  ...     item = zope.schema.Dict(
-  ...         title=u'Object info dictionary',
-  ...         required=True)
-
-  >>> zope.interface.directlyProvides(
-  ...     IObjectInfoColumn, IRegion)
-
-  >>> zope.component.provideUtility(
-  ...     IObjectInfoColumn, IRegion, 'webpage.ObjectInfoColumn')
-
-
-Next we implement two very simple viewlets, one displaying the name
-
-  >>> class NameColumnViewlet(BrowserView):
-  ...     zope.interface.implements(IViewlet, IObjectInfoColumn)
-  ...     weight = 0
-  ...
   ...     def __init__(self, context, request, view):
-  ...         super(NameColumnViewlet, self).__init__(context, request)
-  ...         self.view = view
+  ...         pass
   ...
   ...     def __call__(self):
-  ...         return '<b>' + self.item['name'] + '</b>'
+  ...         return u'<div class="box">Patriots (23) : Steelers (7)</div>'
 
-  >>> defineChecker(NameColumnViewlet, viewletChecker)
-
   >>> zope.component.provideAdapter(
-  ...     NameColumnViewlet,
+  ...     SportBox,
   ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     IObjectInfoColumn,
-  ...     name='name')
+  ...     ILeftColumnViewlet, name='sport')
 
-... and the other displaying the size of the of objects in the list:
+and thus the left column is filled:
 
-  >>> class SizeColumnViewlet(BrowserView):
-  ...     zope.interface.implements(IViewlet, IObjectInfoColumn)
-  ...     weight = 1
-  ...
-  ...     def __init__(self, context, request, view):
-  ...         super(SizeColumnViewlet, self).__init__(context, request)
-  ...         self.view = view
-  ...
-  ...     def __call__(self):
-  ...         return self.item['size']
+  >>> leftColumn()
 
-  >>> defineChecker(SizeColumnViewlet, viewletChecker)
+But this is of course pretty lame, since there is no way of specifying how the
+viewlets are put together. But we have a solution. The second argument of the
+``ViewletManager()`` function is a template in which we can specify how the
+viewlets are put together:
 
-  >>> zope.component.provideAdapter(
-  ...     SizeColumnViewlet,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     IObjectInfoColumn,
-  ...     name='size')
+  >>> import os, tempfile
+  >>> temp_dir = tempfile.mkdtemp()
+  >>> leftColTemplate = os.path.join(temp_dir, 'leftCol.pt')
+  >>> open(leftColTemplate, 'w').write('''
+  ... <div class="left-column">
+  ...   <tal:block repeat="viewlet viewlets"
+  ...              replace="structure viewlet" />
+  ... </div>
+  ... ''')
 
+  >>> leftColumn = manager.ViewletManager(ILeftColumnViewlet, leftColTemplate)
 
-Now let's have a look at the resulting view:
+As you can see, the viewlet manager provides a global ``viewlets`` variable
+that is an iterable of all the avialable viewlets in the correct order:
 
-  >>> view = zope.component.getMultiAdapter(
-  ...     (content, request), name='contents.html')
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>Contents</h1>
-      <table>
-        <tr>
-          <td><b>README.txt</b></td>
-          <td>1.2kB</td>
-        </tr>
-        <tr>
-          <td><b>logo.png</b></td>
-          <td>100 x 100</td>
-        </tr>
-      </table>
-    </body>
-  </html>
+  >>> leftColumn()
 
+You can also lookup the viewlets directly for management purposes:
 
-Content Provider Managers
--------------------------
+  >>> leftColumn['weather']
+  <WeatherBox ...>
+  >>> leftColumn.get('weather')
+  <WeatherBox ...>
 
-Until now we have always asserted that the viewlets returned by the TALES
-namespaces ``viewlets`` and ``viewlet`` always find the viewlets in the
-component architecture and then return them ordered by weight. This, however,
-is just the default policy. We could also register an alternative policy that
-has different rules on looking up, filtering and sorting the viewlets. The
-objects that implement those policies are known as content provider managers.
+If the viewlet is not found, then the expected behavior is provided:
 
-These are usually implemented as adapters from the context, request
-and view to the ``IViewletManager`` interface. They must implement two
-methods. The first one is ``getViewlets(region)``, which returns a list of
-viewlets for the specified region. The region argument is the region
-interface. The second method is ``getViewlet(name, region)``, which allows you
-to look up a specific viewlet by name and region.
+  >>> leftColumn['stock']
 
+  >>> leftColumn.get('stock') is None
+  True
 
-An Alternative Content Provider Manager
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 
-Let's now imagine that we would like to allow the user to choose the columns
-for the contents view. Here it would not be enough to implement a condition as
-part of the viewlet class, since the TD tag appearance is not controlled by
-the viewlet itself. In those cases it is best to implement a custom content
-provider manager that only returns the viewlets that are specified in an
-option:
+Viewlet Weight Support
+----------------------
 
-  >>> showColumns = ['name', 'size']
 
-So our custom content provider manager could look something like this:
+A Complex Example
+-----------------
 
-  >>> from zope.contentprovider import manager
-  >>> from zope.contentprovider.interfaces import IContentProviderManager
-  >>> class ContentsContentProviderManager(manager.DefaultContentProviderManager):
-  ...
-  ...     def values(self):
-  ...         viewlets = zope.component.getAdapters(
-  ...             (self.context, self.request, self.view), self.region)
-  ...         viewlets = [(name, viewlet) for name, viewlet in viewlets
-  ...                     if name in showColumns]
-  ...         viewlets.sort(lambda x, y: cmp(showColumns.index(x[0]),
-  ...                                        showColumns.index(y[0])))
-  ...         return [viewlet for name, viewlet in viewlets]
+#
+#Viewlet
+#~~~~~~~
+#
+#Viewlets are snippets of content that can be placed into a region, such as the
+#one defined above. As the name suggests, viewlets are views, but they are
+#qualified not only by the context object and the request, but also the view
+#they appear in. Also, the viewlet must *provide* the region interface it is
+#filling; we will demonstrate a more advanced example later, where the purpose
+#of this requirement becomes clear.
+#
+#Like regular views, viewlets can either use page templates to provide content
+#or provide a simple ``__call__`` method. For our first viewlet, let's develop
+#a more commonly used page-template-driven viewlet:
+#
+#  >>> import os, tempfile
+#  >>> temp_dir = tempfile.mkdtemp()
+#
+#  >>> viewletFileName = os.path.join(temp_dir, 'viewlet.pt')
+#  >>> open(viewletFileName, 'w').write('''
+#  ...         <div class="box">
+#  ...           <tal:block replace="viewlet/title" />
+#  ...         </div>
+#  ... ''')
+#
+#  >>> class ViewletBase(object):
+#  ...     def title(self):
+#  ...         return 'Viewlet Title'
+#
+#As you can see, the viewlet Python object is known as ``viewlet`` inside the
+#template, while the view object is still available as ``view``. Next we build
+#and register the viewlet using a special helper function:
+#
+#  # Create the viewlet class
+#  >>> from zope.viewlet import viewlet
+#  >>> Viewlet = viewlet.SimpleViewletClass(
+#  ...     viewletFileName, bases=(ViewletBase,), name='viewlet')
+#
+#  # Generate a viewlet checker
+#  >>> from zope.security.checker import NamesChecker, defineChecker
+#  >>> viewletChecker = NamesChecker(('__call__', 'weight', 'title',))
+#  >>> defineChecker(Viewlet, viewletChecker)
+#
+#  # Register the viewlet with component architecture
+#  >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
+#  >>> from zope.app.publisher.interfaces.browser import IBrowserView
+#  >>> zope.component.provideAdapter(
+#  ...     Viewlet,
+#  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
+#  ...     ILeftColumn,
+#  ...     name='viewlet')
+#
+#As you can see from the security checker registration, a viewlet provides also
+#a weight, which acts as a hint to determine the order in which the viewlets of
+#a region should be displayed. The view the viewlet is used in can also be
+#accessed via the ``view`` attribute of the viewlet class.
+#
+#
+#Changing the Weight
+#~~~~~~~~~~~~~~~~~~~
+#
+#Let's ensure that the weight really affects the order of the viewlets. If we
+#change the weights around,
+#
+#  >>> InfoViewlet.weight = 0
+#  >>> Viewlet._weight = 1
+#
+#the order of the left column in the page template should change:
+#
+#  >>> print view().strip()
+#  <html>
+#    <body>
+#      <h1>My Web Page</h1>
+#      <div class="left-column">
+#        <div class="column-item">
+#          <h3>Some Information.</h3>
+#        </div>
+#        <div class="column-item">
+#  <BLANKLINE>
+#          <div class="box">
+#            Viewlet Title
+#          </div>
+#  <BLANKLINE>
+#        </div>
+#      </div>
+#      <div class="main">
+#        Content here
+#      </div>
+#    </body>
+#  </html>
+#
+#
+#
+#An Alternative Content Provider Manager
+#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+#
+#Let's now imagine that we would like to allow the user to choose the columns
+#for the contents view. Here it would not be enough to implement a condition as
+#part of the viewlet class, since the TD tag appearance is not controlled by
+#the viewlet itself. In those cases it is best to implement a custom content
+#provider manager that only returns the viewlets that are specified in an
+#option:
+#
+#  >>> showColumns = ['name', 'size']
+#
+#So our custom content provider manager could look something like this:
+#
+#  >>> from zope.contentprovider import manager
+#  >>> from zope.contentprovider.interfaces import IContentProviderManager
+#  >>> class ContentsContentProviderManager(manager.DefaultContentProviderManager):
+#  ...
+#  ...     def values(self):
+#  ...         viewlets = zope.component.getAdapters(
+#  ...             (self.context, self.request, self.view), self.region)
+#  ...         viewlets = [(name, viewlet) for name, viewlet in viewlets
+#  ...                     if name in showColumns]
+#  ...         viewlets.sort(lambda x, y: cmp(showColumns.index(x[0]),
+#  ...                                        showColumns.index(y[0])))
+#  ...         return [viewlet for name, viewlet in viewlets]
+#
+#We just have to register it as an adapter:
+#
+#  >>> zope.component.provideAdapter(
+#  ...     ContentsContentProviderManager,
+#  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView,
+#  ...      IObjectInfoColumn),
+#  ...     IContentProviderManager)
+#
+#  >>> view = zope.component.getMultiAdapter(
+#  ...     (content, request), name='contents.html')
+#  >>> print view().strip()
+#  <html>
+#    <body>
+#      <h1>Contents</h1>
+#      <table>
+#        <tr>
+#          <td><b>README.txt</b></td>
+#          <td>1.2kB</td>
+#        </tr>
+#        <tr>
+#          <td><b>logo.png</b></td>
+#          <td>100 x 100</td>
+#        </tr>
+#      </table>
+#    </body>
+#  </html>
+#
+#But if I turn the order around,
+#
+#  >>> showColumns = ['size', 'name']
+#
+#it will provide the columns in a different order as well:
+#
+#  >>> print view().strip()
+#  <html>
+#    <body>
+#      <h1>Contents</h1>
+#      <table>
+#        <tr>
+#          <td>1.2kB</td>
+#          <td><b>README.txt</b></td>
+#        </tr>
+#        <tr>
+#          <td>100 x 100</td>
+#          <td><b>logo.png</b></td>
+#        </tr>
+#      </table>
+#    </body>
+#  </html>
+#
+#On the other hand, it is as easy to remove a column:
+#
+#  >>> showColumns = ['name']
+#  >>> print view().strip()
+#  <html>
+#    <body>
+#      <h1>Contents</h1>
+#      <table>
+#        <tr>
+#          <td><b>README.txt</b></td>
+#        </tr>
+#        <tr>
+#          <td><b>logo.png</b></td>
+#        </tr>
+#      </table>
+#    </body>
+#  </html>
+#
+#
 
-We just have to register it as an adapter:
-
-  >>> zope.component.provideAdapter(
-  ...     ContentsContentProviderManager,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView,
-  ...      IObjectInfoColumn),
-  ...     IContentProviderManager)
-
-  >>> view = zope.component.getMultiAdapter(
-  ...     (content, request), name='contents.html')
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>Contents</h1>
-      <table>
-        <tr>
-          <td><b>README.txt</b></td>
-          <td>1.2kB</td>
-        </tr>
-        <tr>
-          <td><b>logo.png</b></td>
-          <td>100 x 100</td>
-        </tr>
-      </table>
-    </body>
-  </html>
-
-But if I turn the order around,
-
-  >>> showColumns = ['size', 'name']
-
-it will provide the columns in a different order as well:
-
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>Contents</h1>
-      <table>
-        <tr>
-          <td>1.2kB</td>
-          <td><b>README.txt</b></td>
-        </tr>
-        <tr>
-          <td>100 x 100</td>
-          <td><b>logo.png</b></td>
-        </tr>
-      </table>
-    </body>
-  </html>
-
-On the other hand, it is as easy to remove a column:
-
-  >>> showColumns = ['name']
-  >>> print view().strip()
-  <html>
-    <body>
-      <h1>Contents</h1>
-      <table>
-        <tr>
-          <td><b>README.txt</b></td>
-        </tr>
-        <tr>
-          <td><b>logo.png</b></td>
-        </tr>
-      </table>
-    </body>
-  </html>
-
-
-UML Diagram
------------
-
-                      _________
-                     |         |
-                     | Context |
-                     |_________|
-                          ^
-                          |
-                          |*
-                      ____|____
-                     |         |
-                     |   View  |
-                     |_________|
-                          |
-                          |
-                          |* a view is composed of regions
-                      ____v____
-                     |         |
-                     |  Region |
-                     |_________|
-                          |
-                          |
-                          |* a region contains a list of viewlets
-                      ____v____
-                     |         |
-                     | Viewlet |
-                     |_________|
-
-Natively, Zope 3 allows us to associate one or more views to a given
-object. Those views are either registered for the provided interfaces of the
-object or the object itself. In a view, usually a template, one can define
-zero or more view regions. Upon rendering time, those view regions are populated
-with the viewlets that have been assigned to the region.
-
-
 Cleanup
 -------
 

Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt	2005-10-08 16:27:57 UTC (rev 38967)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/css_viewlet.pt	2005-10-08 16:40:03 UTC (rev 38968)
@@ -1,4 +1,4 @@
 <link type="text/css" rel="stylesheet" href="somestyle.css" media="all"
       tal:attributes="rel viewlet/getRel;
-			                href viewlet/getURL;
+                      href viewlet/getURL;
                       media viewlet/getMedia" />

Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py	2005-10-08 16:27:57 UTC (rev 38967)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py	2005-10-08 16:40:03 UTC (rev 38968)
@@ -17,61 +17,43 @@
 """
 __docformat__ = 'restructuredtext'
 
-import zope.component
 import zope.interface
 import zope.schema
-from zope.tales import interfaces
-
 from zope.app.i18n import ZopeMessageIDFactory as _
-from zope.app.publisher.interfaces.browser import IBrowserView
 
 from zope.contentprovider.interfaces import IContentProvider
 
 
+class IViewlet(IContentProvider):
+    """A content provider that is managed by another content provider, known
+    as viewlet manager.
+    """
 
-class IViewlet(IBrowserView, IContentProvider):
-    """A piece of content of a page.
 
-    Viewlets are objects that can fill the region specified in a page, most
-    often page templates. They are selected by the context, request and
-    view. All viewlets of a particular region must also provide the region
-    interface.
+class IViewletManager(IContentProvider, IReadMapping):
+    """An object that provides access to the content providers.
+
+    The viewlet manager's resposibilities are:
+
+      (1) Aggregation of all viewlets of a given type.
+
+      (2) Apply a set of filters to determine the availability of the
+          viewlets.
+
+      (3) Sort the viewlets based on some implemented policy.
     """
 
-    view = zope.interface.Attribute(
-        'The view the viewlet is used in.')
+    providerType = zope.interface.Attribute(
+        '''The specific type of provider that are displayed by this manager.''')
 
+
+class IWeightSupport(zope.interface.Interface):
+    """Components implementing this interface are sortable by weight."""
+
     weight = zope.schema.Int(
         title=_(u'weight'),
         description=_(u"""
-            Key for sorting viewlets if the viewlet collector is supporting
-            this sort mechanism."""),
+            Key for sorting viewlets if the viewlet manager is supporting this
+            sort mechanism."""),
         required=False,
         default=0)
-
-
-#class IViewletManager(zope.interface.Interface):
-#    """An object that provides access to the viewlets.
-#
-#    The viewlets are of a particular context, request and view configuration
-#    are accessible via a particular manager instance. Viewlets are looked up
-#    by the region they appear in and the name of the viewlet.
-#    """
-#
-#    context = zope.interface.Attribute(
-#        'The context of the view the viewlet appears in.')
-#
-#    view = zope.interface.Attribute(
-#        'The view the viewlet is used in.')
-#
-#    request = zope.interface.Attribute(
-#        'The request of the view the viewlet is used in.')
-#
-#    def values(region):
-#        """Get all available viewlets of the given region.
-#
-#        This method is responsible for sorting the viewlets as well.
-#        """
-#
-#    def __getitem__(self, name, region):
-#        """Get a particular viewlet of a region selected by name."""

Copied: Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py (from rev 38927, Zope3/branches/roger-contentprovider/src/zope/contentprovider/manager.py)
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/contentprovider/manager.py	2005-10-08 09:42:33 UTC (rev 38927)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py	2005-10-08 16:40:03 UTC (rev 38968)
@@ -0,0 +1,101 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Content Provider Manager implementation
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import zope.component
+import zope.interface
+import zope.security
+
+from zope.contentprovider import interfaces
+
+
+class ViewletManagerBase(object):
+    """The Viewlet Manager Base
+
+    A generic manager class which can be instantiated 
+    """
+    zope.interface.implements(interfaces.IViewletManager)
+
+    providerType = None
+
+    def __init__(self, context, request, view):
+        self.context = context
+        self.request = request
+        self.view = view
+
+
+    def __getitem__(self, name):
+        """See zope.interface.common.mapping.IReadMapping"""
+        # Find the content provider
+        provider = zope.component.queryMultiAdapter(
+            (self.context, self.request, self.view), self.region, name=name)
+
+        # If the content provider was not found, then raise a lookup error
+        if provider is None:
+            raise zope.component.interfaces.ComponentLookupError(
+                'No provider with name `%s` found.' %name)
+
+        # If the content provider cannot be accessed, then raise an
+        # unauthorized error
+        if not zope.security.canAccess(provider, '__call__'):
+            raise zope.security.interfaces.Unauthorized(
+                'You are not authorized to access the provider '
+                'called `%s`.' %name)
+
+        # Return the rendered content provider.
+        return provider
+
+
+    def get(self, name, default=None):
+        try:
+            return self[name]
+        except (zope.component.interfaces.ComponentLookupError,
+                zope.security.interfaces.Unauthorized):
+            return default
+
+
+    def __call__(self, *args, **kw)
+        """See zope.contentprovider.interfaces.IContentProvider"""
+
+        # Find all content providers for the region
+        viewlets = zope.component.getAdapters(
+            (self.context, self.request, self.view), self.viewType)
+
+        # Sort out all content providers that cannot be accessed by the
+        # principal
+        viewlets = [viewlet for name, viewlet in viewlets
+                    if zope.security.canAccess(viewlet, '__call__')]
+
+        # Sort the content providers by weight.
+        if interfaces.IWeightSupport in self.viewletType.flattened():
+            viewlets.sort(lambda x, y: cmp(x.weight, y.weight))
+        else:
+            viewlets.sort()
+
+        # Now render the view
+        if self.template:
+            return self.template(viewlets=viewlets)
+        else:
+            return u'\n'.join(viewlets)
+
+
+def ViewletManager(type, template=None):
+
+    return type('<ViewletManager for %s>' %type.getName(),
+                (ViewletManagerBase,),
+                {'providerType': type, 'template': None})

Copied: Zope3/branches/roger-contentprovider/src/zope/viewlet/tests.py (from rev 38943, Zope3/branches/roger-contentprovider/src/zope/viewlet/tests/test_doc.py)
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/tests/test_doc.py	2005-10-08 12:55:12 UTC (rev 38943)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/tests.py	2005-10-08 16:40:03 UTC (rev 38968)
@@ -0,0 +1,63 @@
+##############################################################################
+#
+# Copyright (c) 2004 Zope Corporation and Contributors.
+# All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+#
+##############################################################################
+"""Viewlet tests
+
+$Id$
+"""
+__docformat__ = 'restructuredtext'
+
+import unittest
+import zope.interface
+import zope.security
+from zope.testing import doctest
+from zope.testing.doctestunit import DocTestSuite, DocFileSuite
+from zope.app.testing import setup
+
+from zope.contentprovider.interfaces import IRegion
+from zope.viewlet import interfaces
+
+
+class TestParticipation(object):
+    principal = 'foobar'
+    interaction = None
+
+
+def setUp(test):
+    setup.placefulSetUp()
+
+    from zope.app.pagetemplate import metaconfigure
+    from zope.contentprovider import tales
+    metaconfigure.registerType('provider', tales.TALESProviderExpression)
+
+    zope.security.management.getInteraction().add(TestParticipation())
+
+
+def tearDown(test):
+    setup.placefulTearDown()
+
+
+def test_suite():
+    return unittest.TestSuite((
+        DocFileSuite('README.txt',
+                     setUp=setUp, tearDown=tearDown,
+                     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+                     ),
+        DocFileSuite('directives.txt',
+                     setUp=setUp, tearDown=tearDown,
+                     optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS,
+                     ),
+        ))
+
+if __name__ == '__main__':
+    unittest.main(defaultTest='test_suite')



More information about the Zope3-Checkins mailing list