[Zope3-checkins] SVN: Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt Finished the complex example. It seems to work all out. A lot of thanks

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Oct 12 04:18:16 EDT 2005


Log message for revision 39086:
  Finished the complex example. It seems to work all out. A lot of thanks 
  goes to Bernd and Roger for helping me understand the seperation of 
  concerns. Also, most of this work was done sprinting with Bernd.
  
  

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

-=-
Modified: Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt
===================================================================
--- Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt	2005-10-12 08:07:25 UTC (rev 39085)
+++ Zope3/branches/roger-contentprovider/src/zope/viewlet/README.txt	2005-10-12 08:18:15 UTC (rev 39086)
@@ -213,6 +213,9 @@
 A Complex Example
 -----------------
 
+The Data
+~~~~~~~~
+
 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:
@@ -259,6 +262,10 @@
   >>> container['mypage.html'] = File('<html><body>Hello World!</body></html>')
   >>> container['data.xml'] = File('<message>Hello World!</message>')
 
+
+The View
+~~~~~~~~
+
 The contents view of the container should iterate through the container and
 represent the files in a table:
 
@@ -276,6 +283,9 @@
   >>> Contents = SimpleViewClass(contentsTemplate, name='contents.html')
 
 
+The Viewlet Manager
+~~~~~~~~~~~~~~~~~~~
+
 Now we have to write our own viewlet manager. In this case we cannot use the
 default implementation, since the viewlets will be looked up for each
 different item:
@@ -283,6 +293,7 @@
   >>> shownColumns = []
 
   >>> class ContentsViewletManager(object):
+  ...     zope.interface.implements(interfaces.IViewletManager)
   ...     index = None
   ...
   ...     def __init__(self, context, request, view):
@@ -328,7 +339,7 @@
   >>> zope.component.provideAdapter(
   ...     ContentsViewletManager,
   ...     (Container, IDefaultBrowserLayer, zope.interface.Interface),
-  ...     interfaces.IViewletManager,name='contents')
+  ...     interfaces.IViewletManager, name='contents')
 
 Since we have not defined any viewlets yet, the table is totally empty:
 
@@ -350,6 +361,10 @@
     </body>
   </html>
 
+
+The Viewlets and the Final Result
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
 Now let's create a first viewlet for the manager...
 
   >>> class NameViewlet(object):
@@ -365,7 +380,7 @@
   >>> zope.component.provideAdapter(
   ...     NameViewlet,
   ...     (IFile, IDefaultBrowserLayer,
-  ...      zope.interface.Interface, ContentsViewletManager),
+  ...      zope.interface.Interface, interfaces.IViewletManager),
   ...     interfaces.IViewlet, name='name')
 
 Note how you register the viewlet on ``IFile`` and not on the container. Now
@@ -398,186 +413,311 @@
     <body>
       <h1>Cotnents</h1>
       <div>
-  <table>
-    <tr>
-      <td>
-        mypage.html
-      </td>
-    </tr>
-    <tr>
-      <td>
-        data.xml
-      </td>
-    </tr>
-    <tr>
-      <td>
-        test.txt
-      </td>
-    </tr>
-  </table>
-  </div>
+        <table>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+          </tr>
+          <tr>
+            <td>
+              data.xml
+            </td>
+          </tr>
+          <tr>
+            <td>
+              test.txt
+            </td>
+          </tr>
+        </table>
+      </div>
     </body>
   </html>
 
+Let's now write a second viewlet that will display the size of the object for
+us:
 
+  >>> class SizeViewlet(object):
+  ...
+  ...     def __init__(self, context, request, view, manager):
+  ...         self.context = context
+  ...
+  ...     def __call__(self):
+  ...         return size.interfaces.ISized(self.context).sizeForDisplay()
 
-#
-#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.
-#
-#
-#
-#
-#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>
-#
-#
+  >>> zope.component.provideAdapter(
+  ...     SizeViewlet,
+  ...     (IFile, IDefaultBrowserLayer,
+  ...      zope.interface.Interface, interfaces.IViewletManager),
+  ...     interfaces.IViewlet, name='size')
 
+After we added it to the list of shown columns,
+
+  >>> shownColumns = ['name', 'size']
+
+we can see an entry for it:
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+            <td>
+              38 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              data.xml
+            </td>
+            <td>
+              31 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              test.txt
+            </td>
+            <td>
+              12 bytes
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+If we switch the two columns around,
+
+  >>> shownColumns = ['size', 'name']
+
+the result will be
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              38 bytes
+            </td>
+            <td>
+              mypage.html
+            </td>
+          </tr>
+          <tr>
+            <td>
+              31 bytes
+            </td>
+            <td>
+              data.xml
+            </td>
+          </tr>
+          <tr>
+            <td>
+              12 bytes
+            </td>
+            <td>
+              test.txt
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+
+Supporting Sorting
+~~~~~~~~~~~~~~~~~~
+
+Oftentimes you also want to batch and sort the entries in a table. Since those
+two features are not part of the view logic, they should be treated with
+independent components. In this example, we are going to only implement
+sorting using a simple utility:
+
+  >>> class ISorter(zope.interface.Interface):
+  ...
+  ...     def sort(values):
+  ...         """Sort the values."""
+
+  >>> class SortByName(object):
+  ...     zope.interface.implements(ISorter)
+  ...
+  ...     def sort(self, values):
+  ...         return sorted(values, lambda x, y: cmp(x.__name__, y.__name__))
+
+  >>> zope.component.provideUtility(SortByName(), name='name')
+
+  >>> class SortBySize(object):
+  ...     zope.interface.implements(ISorter)
+  ...
+  ...     def sort(self, values):
+  ...         return sorted(
+  ...             values,
+  ...             lambda x, y: cmp(size.interfaces.ISized(x).sizeForSorting(),
+  ...                              size.interfaces.ISized(y).sizeForSorting()))
+
+  >>> zope.component.provideUtility(SortBySize(), name='size')
+
+Note that we decided to give the sorter utilities the same name as the
+corresponding viewlet. This convention will make our implementation of the
+viewlet manager much simpler:
+
+  >>> sortByColumn = ''
+
+  >>> class SortedContentsViewletManager(object):
+  ...     zope.interface.implements(interfaces.IViewletManager)
+  ...     index = None
+  ...
+  ...     def __init__(self, context, request, view):
+  ...         self.context = context
+  ...         self.request = request
+  ...         self.view = view
+  ...
+  ...     def rows(self):
+  ...         values = self.context.values()
+  ...
+  ...         if sortByColumn:
+  ...            sorter = zope.component.queryUtility(ISorter, sortByColumn)
+  ...            if sorter:
+  ...                values = sorter.sort(values)
+  ...
+  ...         rows = []
+  ...         for value in values:
+  ...             rows.append(
+  ...                 [zope.component.getMultiAdapter(
+  ...                     (value, self.request, self.view, self),
+  ...                     interfaces.IViewlet, name=colname)
+  ...                  for colname in shownColumns])
+  ...         return rows
+  ...
+  ...     def __call__(self, *args, **kw):
+  ...         return self.index(*args, **kw)
+
+As you can see, the concern of sorting is cleanly separated from generating
+the view code. In MVC terms that means that the controller (sort) is logically
+separated from the view (viewlets). Let's now do the registration dance for
+the new viewlet manager. We simply override the existing registration:
+
+  >>> SortedContentsViewletManager = type(
+  ...     'SortedContentsViewletManager', (SortedContentsViewletManager,),
+  ...     {'index': ViewPageTemplateFile(tableTemplate)})
+
+  >>> zope.component.provideAdapter(
+  ...     SortedContentsViewletManager,
+  ...     (Container, IDefaultBrowserLayer, zope.interface.Interface),
+  ...     interfaces.IViewletManager, name='contents')
+
+Finally we sort the contents by name:
+
+  >>> shownColumns = ['name', 'size']
+  >>> sortByColumn = 'name'
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              data.xml
+            </td>
+            <td>
+              31 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+            <td>
+              38 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              test.txt
+            </td>
+            <td>
+              12 bytes
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+Now let's sort by size:
+
+  >>> sortByColumn = 'size'
+
+  >>> print contents().strip()
+  <html>
+    <body>
+      <h1>Cotnents</h1>
+      <div>
+        <table>
+          <tr>
+            <td>
+              test.txt
+            </td>
+            <td>
+              12 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              data.xml
+            </td>
+            <td>
+              31 bytes
+            </td>
+          </tr>
+          <tr>
+            <td>
+              mypage.html
+            </td>
+            <td>
+              38 bytes
+            </td>
+          </tr>
+        </table>
+      </div>
+    </body>
+  </html>
+
+That's it! As you can see, in a few steps we have built a pretty flexible
+contents view with selectable columns and sorting. However, there is a lot of
+room for extending this example:
+
+- Table Header: The table header cell for each column should be a different
+  type of viewlet, but registered under the same name. The column header
+  viewlet also adapts the container not the item. The header column should
+  also be able to control the sorting.
+
+- Batching: A simple implementation of batching should work very similar to
+  the sorting feature. Of course, efficient implementations should somehow
+  combine batching and sorting more effectively.
+
+- Sorting in ascending and descending order: Currently, you can only sort from
+  the smallest to the highest value; however, this limitation is almost
+  superficial and can easily be removed by making the sorters a bit more
+  flexible.
+
+- Further Columns: For a real application, you would want to implement other
+  columns, of course. You would also probably want some sort of fallback for
+  the case that a viewlet is not found for a particular container item and
+  column.
+
+
 Cleanup
 -------
 



More information about the Zope3-Checkins mailing list