[Zope3-checkins] SVN: Zope3/branches/roger-contentprovider/src/zope/viewlet/ Yep, we refactored again! But I think we are nearing a very flexible

Stephan Richter srichter at cosmos.phy.tufts.edu
Sun Oct 9 08:01:02 EDT 2005


Log message for revision 39005:
  Yep, we refactored again! But I think we are nearing a very flexible 
  implementation now and I feel better about it every time.
  

Changed:
  U   Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
  U   Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
  U   Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py

-=-
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt	2005-10-09 11:17:02 UTC (rev 39004)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt	2005-10-09 12:01:02 UTC (rev 39005)
@@ -14,16 +14,16 @@
 -------------------
 
 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:
+called viewlet managers that manage a special type of content providers known
+as viewlets. Every viewlet manager handles the viewlets registered for it:
 
-  >>> class ILeftColumnViewlet(interfaces.IViewlet):
-  ...     """This is a viewlet located in the left column."""
+  >>> class ILeftColumn(interfaces.IViewletManager):
+  ...     """Viewlet manager located in the left column."""
 
-You can then create a viewlet manager for this viewlet type:
+You can then create a viewlet manager using this interface now:
 
   >>> from zope.viewlet import manager
-  >>> LeftColumn = manager.ViewletManager(ILeftColumnViewlet)
+  >>> LeftColumn = manager.ViewletManager(ILeftColumn)
 
 Now we have to instantiate it:
 
@@ -55,27 +55,29 @@
   >>> from zope.publisher.interfaces.browser import IDefaultBrowserLayer
 
   >>> class WeatherBox(object):
-  ...     zope.interface.implements(ILeftColumnViewlet)
+  ...     zope.interface.implements(interfaces.IViewlet)
   ...
-  ...     def __init__(self, context, request, view):
+  ...     def __init__(self, context, request, view, manager):
   ...         pass
   ...
   ...     def __call__(self):
   ...         return u'<div class="box">It is sunny today!</div>'
 
+  # Create a security checker for viewlets.
   >>> from zope.security.checker import NamesChecker, defineChecker
   >>> viewletChecker = NamesChecker(('__call__', 'weight'))
   >>> defineChecker(WeatherBox, viewletChecker)
 
   >>> zope.component.provideAdapter(
   ...     WeatherBox,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     ILeftColumnViewlet, name='weather')
+  ...     (zope.interface.Interface, IDefaultBrowserLayer,
+  ...     IBrowserView, ILeftColumn),
+  ...     interfaces.IViewlet, name='weather')
 
   >>> class SportBox(object):
-  ...     zope.interface.implements(ILeftColumnViewlet)
+  ...     zope.interface.implements(interfaces.IViewlet)
   ...
-  ...     def __init__(self, context, request, view):
+  ...     def __init__(self, context, request, view, manager):
   ...         pass
   ...
   ...     def __call__(self):
@@ -85,8 +87,9 @@
 
   >>> zope.component.provideAdapter(
   ...     SportBox,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     ILeftColumnViewlet, name='sport')
+  ...     (zope.interface.Interface, IDefaultBrowserLayer,
+  ...      IBrowserView, ILeftColumn),
+  ...     interfaces.IViewlet, name='sport')
 
 and thus the left column is filled:
 
@@ -109,12 +112,15 @@
   ... </div>
   ... ''')
 
-  >>> LeftColumn = manager.ViewletManager(ILeftColumnViewlet, leftColTemplate)
+  >>> LeftColumn = manager.ViewletManager(ILeftColumn, template=leftColTemplate)
   >>> leftColumn = LeftColumn(content, request, 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:
+XXX: Fix this silly thing; viewlets should be directly available.
 
+As you can see, the viewlet manager provides a global ``options/viewlets``
+variable that is an iterable of all the avialable viewlets in the correct
+order:
+
   >>> print leftColumn().strip()
   <div class="left-column">
     <div class="box">Patriots (23) : Steelers (7)</div>
@@ -138,42 +144,39 @@
   >>> leftColumn.get('stock') is None
   True
 
+Customizing the default Viewlet Manager
+---------------------------------------
 
-Viewlet Weight Support
-----------------------
+One important feature of any viewlet manager is to be able to filter and sort
+the viewlets it is displaying. The default viewlet manager that we have been
+using in the tests above, supports filtering by access availability and
+sorting via the viewlet's ``__cmp__()`` method (default). You can easily
+override this default policy by providing a base viewlet manager class.
 
-One important feature of any viewlet manager is to be able to sort the
-viewlets it is displaying. The default viewlet manager that we have been using
-in the tests above, supports sorting via the viewlet's ``__cmp__()`` method
-(default) or sorting by weight. To make this work, the provider type interface
-must inherit the ``IWeightSupport`` interface:
+In our case we will manage the viewlets using a global list:
 
-  >>> class IWeightedLeftColumnViewlet(interfaces.IViewlet,
-  ...                                  interfaces.IWeightSupport):
-  ...     """This is a viewlet located in the left column."""
+  >>> shown = ['weather', 'sport']
 
-This means we also need to change the provider type interface in the viewlet
-manager:
+The viewlet manager base class now uses this list:
 
-  >>> leftColumn.providerType = IWeightedLeftColumnViewlet
+  >>> class ListViewletManager(object):
+  ...
+  ...     def filter(self, viewlets):
+  ...         viewlets = super(ListViewletManager, self).filter(viewlets)
+  ...         return [(name, viewlet)
+  ...                 for name, viewlet in viewlets
+  ...                 if name in shown]
+  ...
+  ...     def sort(self, viewlets):
+  ...         viewlets = dict(viewlets)
+  ...         return [(name, viewlets[name]) for name in shown]
 
-Now we assign the weight to the viewlets and ensure that the interface is
-implemented and the viewlets are registered for this interface:
+Let's now create a new viewlet manager:
 
-  >>> WeatherBox.weight = 0
-  >>> zope.interface.classImplements(WeatherBox, IWeightedLeftColumnViewlet)
-  >>> zope.component.provideAdapter(
-  ...     WeatherBox,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     IWeightedLeftColumnViewlet, name='weather')
+  >>> LeftColumn = manager.ViewletManager(
+  ...     ILeftColumn, bases=(ListViewletManager,), template=leftColTemplate)
+  >>> leftColumn = LeftColumn(content, request, view)
 
-  >>> SportBox.weight = 1
-  >>> zope.interface.classImplements(SportBox, IWeightedLeftColumnViewlet)
-  >>> zope.component.provideAdapter(
-  ...     SportBox,
-  ...     (zope.interface.Interface, IDefaultBrowserLayer, IBrowserView),
-  ...     IWeightedLeftColumnViewlet, name='sport')
-
 So we get the weather box first and the sport box second:
 
   >>> print leftColumn().strip()
@@ -182,10 +185,9 @@
     <div class="box">Patriots (23) : Steelers (7)</div>
   </div>
 
-Now let's change the weight around ...
+Now let's change the order...
 
-  >>> WeatherBox.weight = 1
-  >>> SportBox.weight = 0
+  >>> shown.reverse()
 
 and the order should switch as well:
 
@@ -195,10 +197,77 @@
     <div class="box">It is sunny today!</div>
   </div>
 
+Of course, we also can remove a shown viewlet:
 
+  >>> weather = shown.pop()
+  >>> print leftColumn().strip()
+  <div class="left-column">
+    <div class="box">Patriots (23) : Steelers (7)</div>
+  </div>
+
+
+Viewlet Base Classes
+--------------------
+
+
 A Complex Example
 -----------------
 
+So far we have only demonstrated simple (maybe overly trivial) use cases of
+the viewlet system. In the following example, we are going to develop a
+generic contents view for files. The step is to create a file component:
+
+  >>> class IFile(zope.interface.Interface):
+  ...     data = zope.interface.Attribute('Data of file.')
+
+  >>> class File(object):
+  ...     zope.interface.implements(IFile)
+  ...     def __init__(self, data=''):
+  ...         self.data = data
+
+Since we want to also provide the size of a file, here a simple implementation
+of the ``ISized`` interface:
+
+  >>> from zope.app import size
+  >>> class FileSized(object):
+  ...     zope.interface.implements(size.interfaces.ISized)
+  ...     zope.component.adapts(IFile)
+  ...
+  ...     def __init__(self, file):
+  ...         self.file = file
+  ...
+  ...     def sizeForSorting(self):
+  ...         return 'byte', len(self.file.data)
+  ...
+  ...     def sizeForDisplay(self):
+  ...         return '%i bytes' %len(self.file.data)
+
+  >>> zope.component.provideAdapter(FileSized)
+
+We also need a container to which we can add files:
+
+  >>> class Container(dict):
+  ...     pass
+
+Here is some sample data:
+
+  >>> container = Container()
+  >>> container['test.txt'] = File('Hello World!')
+  >>> container['mypage.html'] = File('<html><body>Hello World!</body></html>')
+  >>> container['data.xml'] = File('<message>Hello World!</message>')
+
+The contents view of the container should iterate through the container and
+represent the files in a table 
+
+
+
+
+  >>> sortedBy = 'name', +1
+  >>> shownColumns = ['icon', 'name', 'size']
+
+
+
+
 #
 #Viewlet
 #~~~~~~~

Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py	2005-10-09 11:17:02 UTC (rev 39004)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/interfaces.py	2005-10-09 12:01:02 UTC (rev 39005)
@@ -27,34 +27,34 @@
 class IViewlet(IContentProvider):
     """A content provider that is managed by another content provider, known
     as viewlet manager.
+
+    Note that you *cannot* call viewlets directly as a provider, i.e. through
+    the TALES ``provider`` expression, since it always has to know its manager.
     """
 
+    manager = zope.interface.Attribute(
+        """The Viewlet Manager
 
+        The viewlet manager for which the viewlet is registered. The viewlet
+        manager will contain any additional data that was provided by the
+        view, for example the TAL namespace attributes.
+        """)
+
+
 class IViewletManager(IContentProvider,
                       zope.interface.common.mapping.IReadMapping):
-    """An object that provides access to the content providers.
+    """A component that provides access to the content providers.
 
     The viewlet manager's resposibilities are:
 
-      (1) Aggregation of all viewlets of a given type.
+      (1) Aggregation of all viewlets registered for the manager.
 
       (2) Apply a set of filters to determine the availability of the
           viewlets.
 
       (3) Sort the viewlets based on some implemented policy.
-    """
 
-    providerType = zope.interface.Attribute(
-        '''The specific type of provider that are displayed by this manager.''')
+      (4) Provide an environment in which the viewlets are rendered.
 
-
-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 manager is supporting this
-            sort mechanism."""),
-        required=False,
-        default=0)
+      (5) Render itself containing the HTML content of the viewlets.
+    """

Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py	2005-10-09 11:17:02 UTC (rev 39004)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/manager.py	2005-10-09 12:01:02 UTC (rev 39005)
@@ -32,8 +32,6 @@
     """
     zope.interface.implements(interfaces.IViewletManager)
 
-    providerType = None
-
     def __init__(self, context, request, view):
         self.context = context
         self.request = request
@@ -42,52 +40,64 @@
 
     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.providerType,
+        # Find the viewlet
+        viewlet = zope.component.queryMultiAdapter(
+            (self.context, self.request, self.view, self), interfaces.IViewlet,
             name=name)
 
-        # If the content provider was not found, then raise a lookup error
-        if provider is None:
+        # If the viewlet was not found, then raise a lookup error
+        if viewlet is None:
             raise zope.component.interfaces.ComponentLookupError(
                 'No provider with name `%s` found.' %name)
 
-        # If the content provider cannot be accessed, then raise an
+        # If the viewlet cannot be accessed, then raise an
         # unauthorized error
-        if not zope.security.canAccess(provider, '__call__'):
+        if not zope.security.canAccess(viewlet, '__call__'):
             raise zope.security.interfaces.Unauthorized(
                 'You are not authorized to access the provider '
                 'called `%s`.' %name)
 
-        # Return the rendered content provider.
-        return provider
+        # Return the rendered viewlet.
+        return viewlet
 
 
     def get(self, name, default=None):
+        """See zope.interface.common.mapping.IReadMapping"""
         try:
             return self[name]
         except (zope.component.interfaces.ComponentLookupError,
                 zope.security.interfaces.Unauthorized):
             return default
 
+    def filter(self, viewlets):
+        """Sort out all content providers
 
+        ``viewlets`` is a list of tuples of the form (name, viewlet).
+        """
+        # Only return viewlets accessible to the principal
+        return [(name, viewlet) for name, viewlet in viewlets
+                if zope.security.canAccess(viewlet, '__call__')]
+
+    def sort(self, viewlets):
+        """Sort the viewlets.
+
+        ``viewlets`` is a list of tuples of the form (name, viewlet).
+        """
+        # By default, use the standard Python way of doing sorting.
+        return sorted(viewlets, lambda x, y: cmp(x[1], y[1]))
+
     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.providerType)
+            (self.context, self.request, self.view, self), interfaces.IViewlet)
 
-        # 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__')]
+        viewlets = self.filter(viewlets)
+        viewlets = self.sort(viewlets)
 
-        # Sort the content providers by weight.
-        if self.providerType.extends(interfaces.IWeightSupport):
-            viewlets.sort(lambda x, y: cmp(x.weight, y.weight))
-        else:
-            viewlets.sort()
+        # Just use the viewlets from now on
+        viewlets = [viewlet for name, viewlet in viewlets]
 
         # Now render the view
         if self.template:
@@ -96,11 +106,14 @@
             return u'\n'.join([viewlet() for viewlet in viewlets])
 
 
-def ViewletManager(providerType, template=None):
+def ViewletManager(interface, template=None, bases=()):
 
     if template is not None:
         template = ViewPageTemplateFile(template)
 
-    return type('<ViewletManager for %s>' %providerType.getName(),
-                (ViewletManagerBase,),
-                {'providerType': providerType, 'template': template})
+    ViewletManager = type(
+        '<ViewletManager providing %s>' % interface.getName(),
+        bases+(ViewletManagerBase,),
+        {'template': template})
+    zope.interface.classImplements(ViewletManager, interface)
+    return ViewletManager



More information about the Zope3-Checkins mailing list