[Checkins] SVN: z3c.table/trunk/ - Implemented batching UI

Roger Ineichen roger at projekt01.ch
Sat Mar 1 19:41:59 EST 2008


Log message for revision 84388:
  - Implemented batching UI
  - Cleanup imports
  - Added more unit tests
  - Added coverage folder to the ignore file list
  
  Now we have 100% coverage

Changed:
  _U  z3c.table/trunk/
  D   z3c.table/trunk/TODO.txt
  U   z3c.table/trunk/setup.py
  U   z3c.table/trunk/src/z3c/table/README.txt
  A   z3c.table/trunk/src/z3c/table/batch.py
  U   z3c.table/trunk/src/z3c/table/column.py
  U   z3c.table/trunk/src/z3c/table/column.txt
  A   z3c.table/trunk/src/z3c/table/configure.zcml
  U   z3c.table/trunk/src/z3c/table/interfaces.py
  U   z3c.table/trunk/src/z3c/table/table.py
  U   z3c.table/trunk/src/z3c/table/testing.py
  U   z3c.table/trunk/src/z3c/table/tests.py

-=-

Property changes on: z3c.table/trunk
___________________________________________________________________
Name: svn:ignore
   - .installed.cfg
bin
develop-eggs
parts

   + .installed.cfg
bin
develop-eggs
parts
coverage


Deleted: z3c.table/trunk/TODO.txt
===================================================================
--- z3c.table/trunk/TODO.txt	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/TODO.txt	2008-03-02 00:41:59 UTC (rev 84388)
@@ -1,7 +0,0 @@
-====
-TODO
-====
-
-- implement batch template
-
-- Implement LinkColumn

Modified: z3c.table/trunk/setup.py
===================================================================
--- z3c.table/trunk/setup.py	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/setup.py	2008-03-02 00:41:59 UTC (rev 84388)
@@ -51,19 +51,24 @@
     namespace_packages = ['z3c'],
     extras_require = dict(
         test = [
+            'z3c.testing',
+            'zope.app.testing',
+            'zope.publisher',
+            'zope.security',
             'zope.testbrowser',
-            'zope.app.securitypolicy',
-            'zope.app.testing',
-            'zope.app.twisted',
-            'z3c.testing',
             ],
         ),
     install_requires = [
         'setuptools',
+        'z3c.batching',
+        'zope.component',
+        'zope.contentprovider',
+        'zope.i18nmessageid',
         'zope.interface',
-        'zope.contentprovider',
-        'z3c.template',
-        'z3c.batching',
+        'zope.location',
+        'zope.schema',
+        'zope.security',
+        'zope.traversing',
         ],
     zip_safe = False,
 )
\ No newline at end of file

Modified: z3c.table/trunk/src/z3c/table/README.txt
===================================================================
--- z3c.table/trunk/src/z3c/table/README.txt	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/src/z3c/table/README.txt	2008-03-02 00:41:59 UTC (rev 84388)
@@ -10,25 +10,23 @@
 Some important concepts we use
 ------------------------------
 
-- separate impleentation in update render parts, This allows to manipulate 
-  data after update call before we render.
+- separate implementation in update render parts, This allows to manipulate 
+  data after update call and before we render them.
 
-- allo to use page templates if needed
+- allow to use page templates if needed. By default all is done in python.
 
-- allow to use the rendered batch outside the existing table HTML part. This
-  could be done if you use a view which takes care and offers table and batch 
-  varaiables as a you know from model - view - controller patterns.
-  If you use viewlets, it gets a little bit harder to separate the table and
-  batch into different components. But this is also possible. You can implement
-  a table as named adapter for context, request and implement content a 
-  provider or viewlets for batch and table. With this separation you can call 
-  the batch before the table because you can get the named table adapter and 
-  update the data. After update and render the batch you can later access the 
-  table content provider and get the named adapter again and only render the 
-  table. This supports not calling the table adapters update method more then 
-  once.
+- allow to use the rendered batch outside the existing table HTML part.
 
+No skins
+--------
 
+This package does not provide any kind of template or skin support. Most the 
+time if you need to render a table, you will use a own skin concept. This means
+you can render the table or batch within your own tamplates. This will ensure
+that we have as less dependencies as possible in this package and the package
+can get reused with any skin concept.
+
+
 Note
 ----
 
@@ -47,8 +45,13 @@
   >>> from zope.app.container import btree
   >>> class Container(btree.BTreeContainer):
   ...     """Sample container."""
+  ...     __name__ = u'container'
   >>> container = Container()
 
+and set a parent for the container:
+
+  >>> root['container'] = container
+
 and create a sample content object which we use as container item:
 
   >>> class Content(object):
@@ -74,7 +77,8 @@
   >>> request = TestRequest()
   >>> plainTable = table.Table(container, request)
 
-Now we can update and render the table:
+Now we can update and render the table. As you can see with an empty container
+we will not get anything whihc looks like a table. We just get an empty string:
 
   >>> plainTable.update()
   >>> plainTable.render()
@@ -658,7 +662,7 @@
   ...             column.addColumn(self, TitleColumn, u'title',
   ...                              cellRenderer=cellRenderer,
   ...                              headCellRenderer=headCellRenderer,
-  ...                              weight=1),
+  ...                              weight=1, colspan=0),
   ...             column.addColumn(self, SimpleColumn, name=u'simple',
   ...                              weight=2, header=u'The second column',
   ...                              cssClasses = {'th':'thCol', 'td':'tdCol'})
@@ -751,10 +755,19 @@
 Batching
 --------
 
-Or table implements a concept for batching by default. If you set the attribute
-``startBatchingAt`` to a size smaller then the rows generated based on the given
-items, our table starts to generate a batch. Let's define a new Table:
+Or table implements a concept for batching out of the box. If the amount of 
+row items is smaller then the given ``startBatchingAt`` size, the table starts 
+to batch at this size. Let's define a new Table:
 
+We need to configure our batch provider for the next step first. See the 
+section ``BatchProvider`` below for more infos about batch rendering:
+
+  >>> from zope.configuration.xmlconfig import XMLConfig
+  >>> import zope.app.component
+  >>> import z3c.table
+  >>> XMLConfig('meta.zcml', zope.component)()
+  >>> XMLConfig('configure.zcml', z3c.table)()
+
   >>> class BatchingTable(table.Table):
   ... 
   ...     def setUpColumns(self):
@@ -771,6 +784,12 @@
 
   >>> batchingTable = BatchingTable(container, request)
 
+We also need to give the table a location and a name like we normaly setup
+in traversing:
+
+  >>> batchingTable.__parent__ = container
+  >>> batchingTable.__name__ = u'batchingTable.html'
+
 And add some more items to our container:
 
   >>> container[u'sixt'] = Content('Sixt', 6)
@@ -891,7 +910,8 @@
   >>> batchingTable.startBatchingAt
   50
 
-We will set the size to ``10`` for now:
+We will set the batch start to ``5`` for now. This means the first 5 items
+do not get used:
 
   >>> batchingTable.startBatchingAt = 5
   >>> batchingTable.startBatchingAt
@@ -908,7 +928,7 @@
   5
 
 Now we can update and render the table again. But you will see that we only get
-a table size of 5 rows whihc is correct. But the order doesn't depend on the 
+a table size of 5 rows which is correct. But the order doesn't depend on the 
 numbers we see in cells:
 
   >>> batchingTable.update()
@@ -1118,6 +1138,12 @@
   ...                                     'table-sortOn': 'table-number-1'})
   >>> requestBatchingTable = BatchingTable(container, batchingRequest)
 
+We also need to give the table a location and a name like we normaly setup
+in traversing:
+
+  >>> requestBatchingTable.__parent__ = container
+  >>> requestBatchingTable.__name__ = u'requestBatchingTable.html'
+
 Note; our table needs to start batching at smaller amount of items then we 
 have by default otherwise we don't get a batch:
 
@@ -1154,3 +1180,466 @@
       </tr>
     </tbody>
   </table>
+
+
+BatchProvider
+-------------
+
+The batch provider allows us to render the batch HTML independent of our
+table. This means by default the batch get not rendered in the render method.
+You can change this in your custom table implementation and return the batch
+and the table in the render method. 
+
+As we can see, our table rows provides IBatch if it come to batching:
+
+  >>> from z3c.batching.interfaces import IBatch
+  >>> IBatch.providedBy(requestBatchingTable.rows)
+  True
+
+Let's check some batch variables before we render our test. htis let us compare
+the rendered result. For more information about batching see the README.txt in 
+z3c.batching:
+
+  >>> requestBatchingTable.rows.start
+  11
+
+  >>> requestBatchingTable.rows.index
+  2
+
+  >>> requestBatchingTable.rows.batches
+  <z3c.batching.batch.Batches object at ...>
+
+  >>> len(requestBatchingTable.rows.batches)
+  4
+
+We use our previous batching table and render the batch with the built in 
+``renderBatch`` method:
+
+  >>> requestBatchingTable.update()
+  >>> print requestBatchingTable.renderBatch()
+  <a href="...html?table-batchStart=0&table-batchSize=5" class="first">1</a>
+  <a href="...html?table-batchStart=5&table-batchSize=5">2</a>
+  <a href="...html?table-batchStart=11&table-batchSize=5" class="current">3</a>
+  <a href="...html?table-batchStart=15&table-batchSize=5" class="last">4</a>
+
+Now let's add more items that we can test the skipped links in large batches:
+
+  >>> for i in range(1000):
+  ...     idx = i+20
+  ...     container[str(idx)] = Content(str(idx), idx)
+
+Now let's test the batching table again with the new amount of items and 
+the same ``startBatchingAt`` of 5 but starting the batch at item ``100``
+and sorted on the second numbered column:
+
+  >>> batchingRequest = TestRequest(form={'table-batchStart': '100',
+  ...                                     'table-batchSize': '5',
+  ...                                     'table-sortOn': 'table-number-1'})
+  >>> requestBatchingTable = BatchingTable(container, batchingRequest)
+  >>> requestBatchingTable.startBatchingAt = 5
+
+We also need to give the table a location and a name like we normaly setup
+in traversing:
+
+  >>> requestBatchingTable.__parent__ = container
+  >>> requestBatchingTable.__name__ = u'requestBatchingTable.html'
+
+  >>> requestBatchingTable.update()
+  >>> print requestBatchingTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>100 item</td>
+        <td>number: 100</td>
+      </tr>
+      <tr>
+        <td>101 item</td>
+        <td>number: 101</td>
+      </tr>
+      <tr>
+        <td>102 item</td>
+        <td>number: 102</td>
+      </tr>
+      <tr>
+        <td>103 item</td>
+        <td>number: 103</td>
+      </tr>
+      <tr>
+        <td>104 item</td>
+        <td>number: 104</td>
+      </tr>
+    </tbody>
+  </table>
+
+And test the batch. Note the three dots between the links get rendered by the
+batch provider and is not a part of the doctest:
+
+  >>> print requestBatchingTable.renderBatch()
+  <a href="...html?table-batchStart=0&table-batchSize=5" class="first">1</a>
+  ...
+  <a href="...html?table-batchStart=85&table-batchSize=5">18</a>
+  <a href="...html?table-batchStart=90&table-batchSize=5">19</a>
+  <a href="...html?table-batchStart=95&table-batchSize=5">20</a>
+  <a href="...html?table-batchStart=100&table-batchSize=5" class="current">21</a>
+  <a href="...html?table-batchStart=105&table-batchSize=5">22</a>
+  <a href="...html?table-batchStart=110&table-batchSize=5">23</a>
+  <a href="...html?table-batchStart=115&table-batchSize=5">24</a>
+  ...
+  <a href="...html?table-batchStart=1015&table-batchSize=5" class="last">204</a>
+
+You can change the spacer in the batch provider if you set the ``batchSpacer``
+value:
+
+  >>> from z3c.table.batch import BatchProvider
+  >>> class XBatchProvider(BatchProvider):
+  ...     """Just another batch provider."""
+  ...     batchSpacer = u'xxx'
+
+Now register the new batch provider for our batching table:
+
+  >>> import zope.publisher.interfaces.browser
+  >>> zope.component.provideAdapter(XBatchProvider,
+  ...     (zope.interface.Interface,
+  ...      zope.publisher.interfaces.browser.IBrowserRequest,
+  ...      BatchingTable), name='batch')
+
+If we update and render our table, the new batch provider should get used.
+As you can see the spacer get changed now:
+
+  >>> requestBatchingTable.update()
+  >>> print requestBatchingTable.renderBatch()
+  <a href="...html?table-batchStart=0&table-batchSize=5" class="first">1</a>
+  xxx
+  <a href="...html?table-batchStart=85&table-batchSize=5">18</a>
+  <a href="...html?table-batchStart=90&table-batchSize=5">19</a>
+  <a href="...html?table-batchStart=95&table-batchSize=5">20</a>
+  <a href="...html?table-batchStart=100&table-batchSize=5" class="current">21</a>
+  <a href="...html?table-batchStart=105&table-batchSize=5">22</a>
+  <a href="...html?table-batchStart=110&table-batchSize=5">23</a>
+  <a href="...html?table-batchStart=115&table-batchSize=5">24</a>
+  xxx
+  <a href="...html?table-batchStart=1015&table-batchSize=5" class="last">204</a>
+
+None previous and next batch size. Probably it doesn't make sense but let's 
+show what happens if we set the previous and next batch size to 0 (zero):
+
+  >>> from z3c.table.batch import BatchProvider
+  >>> class ZeroBatchProvider(BatchProvider):
+  ...     """Just another batch provider."""
+  ...     batchSpacer = u'xxx'
+  ...     previousBatchSize = 0
+  ...     nextBatchSize = 0
+
+Now register the new batch provider for our batching table:
+
+  >>> import zope.publisher.interfaces.browser
+  >>> zope.component.provideAdapter(ZeroBatchProvider,
+  ...     (zope.interface.Interface,
+  ...      zope.publisher.interfaces.browser.IBrowserRequest,
+  ...      BatchingTable), name='batch')
+
+Update the table and render the batch:
+
+  >>> requestBatchingTable.update()
+  >>> print requestBatchingTable.renderBatch()
+  <a href="...html?table-batchStart=0&table-batchSize=5" class="first">1</a>
+  xxx
+  <a href="...html?table-batchStart=100&table-batchSize=5" class="current">21</a>
+  xxx
+  <a href="...html?table-batchStart=1015&table-batchSize=5" class="last">204</a>
+
+
+SequenceTable
+-------------
+
+A sequence table can be used if we need to provide a table for a sequence 
+of items instead of a mapping. Define the same sequence of items we used before
+we added the other 1000 items: 
+
+  >>> dataSequence = sorted(container.values())[:20]
+
+Now let's define a new SequenceTable:
+
+  >>> class SequenceTable(table.SequenceTable):
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, TitleColumn, u'title',
+  ...                              cellRenderer=cellRenderer,
+  ...                              headCellRenderer=headCellRenderer,
+  ...                              weight=1),
+  ...             column.addColumn(self, NumberColumn, name=u'number',
+  ...                              weight=2, header=u'Number')
+  ...             ]
+
+Now we can create our table adapting our sequence:
+
+  >>> sequenceRequest = TestRequest(form={'table-batchStart': '0',
+  ...                                     'table-sortOn': 'table-number-1'})
+  >>> sequenceTable = SequenceTable(dataSequence, sequenceRequest)
+
+We also need to give the table a location and a name like we normaly setup
+in traversing:
+
+  >>> sequenceTable.__parent__ = container
+  >>> sequenceTable.__name__ = u'sequenceTable.html'
+
+And update and render the sequence table:
+
+  >>> sequenceTable.update()
+  >>> print sequenceTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Zero item</td>
+        <td>number: 0</td>
+      </tr>
+      <tr>
+        <td>First item</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Second item</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Third item</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Fourth item</td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td>Sixt item</td>
+        <td>number: 6</td>
+      </tr>
+      <tr>
+        <td>Seventh item</td>
+        <td>number: 7</td>
+      </tr>
+      <tr>
+        <td>Eighth item</td>
+        <td>number: 8</td>
+      </tr>
+      <tr>
+        <td>Ninth item</td>
+        <td>number: 9</td>
+      </tr>
+      <tr>
+        <td>Tenth item</td>
+        <td>number: 10</td>
+      </tr>
+      <tr>
+        <td>Eleventh item</td>
+        <td>number: 11</td>
+      </tr>
+      <tr>
+        <td>Twelfth item</td>
+        <td>number: 12</td>
+      </tr>
+      <tr>
+        <td>Thirteenth item</td>
+        <td>number: 13</td>
+      </tr>
+      <tr>
+        <td>Fourteenth item</td>
+        <td>number: 14</td>
+      </tr>
+      <tr>
+        <td>Fifteenth item</td>
+        <td>number: 15</td>
+      </tr>
+      <tr>
+        <td>Sixteenth item</td>
+        <td>number: 16</td>
+      </tr>
+      <tr>
+        <td>Seventeenth item</td>
+        <td>number: 17</td>
+      </tr>
+      <tr>
+        <td>Eighteenth item</td>
+        <td>number: 18</td>
+      </tr>
+      <tr>
+        <td>Nineteenth item</td>
+        <td>number: 19</td>
+      </tr>
+      <tr>
+        <td>Twentieth item</td>
+        <td>number: 20</td>
+      </tr>
+    </tbody>
+  </table>
+
+As you can see, the items get rendered based on a data sequence. Now we set
+the ``start batch at`` size to ``5``:
+
+  >>> sequenceTable.startBatchingAt = 5
+
+And the ``batchSize`` to ``5``. 
+
+  >>> sequenceTable.batchSize = 5
+
+Now we can update and render the table again. But you will see that we only get
+a table size of 5 rows:
+
+  >>> sequenceTable.update()
+  >>> print sequenceTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Zero item</td>
+        <td>number: 0</td>
+      </tr>
+      <tr>
+        <td>First item</td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td>Second item</td>
+        <td>number: 2</td>
+      </tr>
+      <tr>
+        <td>Third item</td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td>Fourth item</td>
+        <td>number: 4</td>
+      </tr>
+    </tbody>
+  </table>
+
+And we set the sort order to ``reverse`` even if we use batching: 
+
+  >>> sequenceTable.sortOrder = u'reverse'
+  >>> sequenceTable.update()
+  >>> print sequenceTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>My items</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>Twentieth item</td>
+        <td>number: 20</td>
+      </tr>
+      <tr>
+        <td>Nineteenth item</td>
+        <td>number: 19</td>
+      </tr>
+      <tr>
+        <td>Eighteenth item</td>
+        <td>number: 18</td>
+      </tr>
+      <tr>
+        <td>Seventeenth item</td>
+        <td>number: 17</td>
+      </tr>
+      <tr>
+        <td>Sixteenth item</td>
+        <td>number: 16</td>
+      </tr>
+    </tbody>
+  </table>
+
+
+Miscellaneous
+-------------
+
+Make coverage report happy and test different things.
+
+Test if the getWeight method returns 0 (zero) on AttributeError:
+
+  >>> from z3c.table.table import getWeight
+  >>> getWeight(None)
+  0
+
+Try to call a simple table and call renderBatch which should return an empty 
+string:
+
+  >>> simpleTable = table.Table(container, request)
+  >>> simpleTable.renderBatch()
+  u''
+
+Try to render an empty table adapting an empty mapping:
+
+  >>> simpleTable = table.Table({}, request)
+  >>> simpleTable.render()
+  u''
+
+Let's see if the addColumn raises a ValueError if ther is no Column class:
+
+  >>> column.addColumn(simpleTable, column.Column, u'dummy')
+  <Column u'dummy'>
+
+  >>> column.addColumn(simpleTable, None, u'dummy')
+  Traceback (most recent call last):
+  ...
+  ValueError: class_ None must implement IColumn.
+
+Test if we can set additional kws in addColumn
+
+
+  >>> simpleColumn = column.addColumn(simpleTable, column.Column, u'dummy',
+  ...     foo='foo value', bar=u'something else', counter=99)
+  >>> simpleColumn.foo
+  'foo value'
+
+  >>> simpleColumn.bar
+  u'something else'
+
+  >>> simpleColumn.counter
+  99
+  
+The NoneCell class provides some methods which never get. But this methods must
+be there because the interfaces defines them. Let's test the default values 
+and make coverage report happy. 
+
+Let's get an container item first:
+
+  >>> firstItem = container[u'first']
+  >>> noneCellColumn = column.addColumn(simpleTable, column.NoneCell, u'none')
+  >>> noneCellColumn.renderCell(firstItem)
+  u''
+
+  >>> noneCellColumn.getColspan(firstItem)
+  0
+
+  >>> noneCellColumn.renderHeadCell()
+  u''
+
+  >>> noneCellColumn.renderCell(firstItem)
+  u''
+
+The default ``Column`` implementation raises an NotImplementedError if we 
+do not override the renderCell method:
+
+  >>> defaultColumn = column.addColumn(simpleTable, column.Column, u'default')
+  >>> defaultColumn.renderCell(firstItem)
+  Traceback (most recent call last):
+  ...
+  NotImplementedError: Subclass must implement renderCell

Added: z3c.table/trunk/src/z3c/table/batch.py
===================================================================
--- z3c.table/trunk/src/z3c/table/batch.py	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/batch.py	2008-03-02 00:41:59 UTC (rev 84388)
@@ -0,0 +1,154 @@
+##############################################################################
+#
+# Copyright (c) 2008 Zope Foundation 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.
+#
+##############################################################################
+"""
+$Id:$
+"""
+__docformat__ = "reStructuredText"
+
+import zope.interface
+import zope.i18nmessageid
+from zope.traversing.browser import absoluteURL
+
+from z3c.table import interfaces
+
+_ = zope.i18nmessageid.MessageFactory('z3c')
+
+
+class BatchProvider(object):
+    """Batch provider.
+
+    A batch provider is responsible for rendering the batch HTML and not for
+    batching. The batch setup is directly done in the table. A batch provider
+    get only used if the table rows is a batch.
+
+    This batch provider offers a batch presentation for a given table. The
+    batch provides different configuration options which can be ovreriden in
+    custom implementations:
+    
+    The batch acts like this. If we have more batches then then
+    (previousBatchSize + nextBatchSize + 3) then the advanced batch is used.
+
+    If the total amount of items is smaller then the previousBatchSize, current
+    item and nextBatchSize. We will render all batch links.
+    
+    Note, the additional factor 3 is the placeholder for the first, current and
+    last item.
+
+    Such a batch look like:
+
+    Renders the link for the first batch, spacers, the amount of links for 
+    previous batches, the current batch link, spacers, the amount of links for 
+    previous batches and the link for the last batch.
+    
+    Sample for 1000 items with 100 batches with batchSize of 10 and a     
+    previousBatchSize of 3 and a nextBatchSize of 3:
+    
+    [1] ... [6][7][8][*9*][10][11][12] ... [100]
+
+    """
+
+    zope.interface.implements(interfaces.IBatchProvider)
+
+    batchItems = []
+
+    previousBatchSize = 3
+    nextBatchSize = 3
+    batchSpacer = u'...'
+
+    def __init__(self, context, request, table):
+        self.__parent__ = context
+        self.context = context
+        self.request = request
+        self.table = table
+        self.batch = table.rows
+        self.batches = table.rows.batches
+
+    def renderBatchLink(self, batch, cssClass=None):
+        query = '%s=%s&%s=%s' % (self.table.prefix +'-batchStart', batch.start,
+            self.table.prefix +'-batchSize', batch.size)
+        tableURL = absoluteURL(self.table, self.request)
+        idx = batch.index +1
+        css = ' class="%s"' % cssClass
+        cssClass = cssClass and css or u''
+        return '<a href="%s?%s"%s>%s</a>' % (tableURL, query, cssClass, idx)
+
+    def update(self):
+        self.batchItems = []
+        total = self.previousBatchSize + self.nextBatchSize + 3
+        if total < self.batch.total:
+
+            # setup some batches and indexes
+            currentBatchIdx = self.batch.index
+            prevIdx = currentBatchIdx - self.previousBatchSize
+            nextIdx = currentBatchIdx +1
+            firstBatch = self.batches[0]
+            lastBatch = self.batches[len(self.batches)-1]
+
+            # add first batch
+            self.batchItems.append(firstBatch)
+
+            # there must probably be space
+            if firstBatch.index +1 != prevIdx:
+                # we skip batches between first batch and first previous batch
+                self.batchItems.append(None)
+
+            # add previous batches
+            for i in range(self.previousBatchSize):
+                # append previous batch
+                self.batchItems.append(self.batches[prevIdx])
+                prevIdx += 1
+
+            # add current batch
+            self.batchItems.append(self.batch)
+
+            # add next batches
+            for i in range(self.nextBatchSize):
+                # append previous batch
+                self.batchItems.append(self.batches[nextIdx])
+                nextIdx += 1
+
+            # there must probably be space
+            if lastBatch.index -1 != nextIdx:
+                # we skip batches between last batch and last next batch
+                self.batchItems.append(None)
+
+            # add last batch
+            self.batchItems.append(lastBatch)
+
+        else:
+            self.batchItems = self.batch.batches
+
+    def render(self):
+        self.update()
+        res = []
+        append = res.append
+        idx = 0
+        lastIdx = len(self.batchItems)
+        for batch in self.batchItems:
+            idx += 1
+            # render spaces
+            if batch is None:
+                append(self.batchSpacer)
+            elif idx == 1:
+                # render first
+                append(self.renderBatchLink(batch, 'first'))
+            elif batch == self.batch:
+                # render current
+                append(self.renderBatchLink(batch, 'current'))
+            elif idx == lastIdx:
+                # render last
+                append(self.renderBatchLink(batch, 'last'))
+            else:
+                append(self.renderBatchLink(batch))
+        return u'\n'.join(res)


Property changes on: z3c.table/trunk/src/z3c/table/batch.py
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: z3c.table/trunk/src/z3c/table/column.py
===================================================================
--- z3c.table/trunk/src/z3c/table/column.py	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/src/z3c/table/column.py	2008-03-02 00:41:59 UTC (rev 84388)
@@ -17,7 +17,6 @@
 __docformat__ = "reStructuredText"
 
 import zope.interface
-import zope.component
 import zope.location
 import zope.i18nmessageid
 from zope.dublincore.interfaces import IZopeDublinCore
@@ -205,7 +204,10 @@
             return safeGetAttr(obj, self.attrName, self.defaultValue)
         return self.defaultValue
 
+    def renderCell(self, item):
+        return self.getValue(item)
 
+
 class FormatterColumn(Column):
     """Formatter column."""
 

Modified: z3c.table/trunk/src/z3c/table/column.txt
===================================================================
--- z3c.table/trunk/src/z3c/table/column.txt	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/src/z3c/table/column.txt	2008-03-02 00:41:59 UTC (rev 84388)
@@ -199,7 +199,6 @@
 
 Let's define a table using the RadioColumn:
 
-  >>> from z3c.table import table
   >>> class CheckBoxTable(table.Table):
   ... 
   ...     def setUpColumns(self):
@@ -330,13 +329,51 @@
     </tbody>
   </table>
 
+Let's test the ``cssClassSelected`` without any other css class
 
+  >>> checkBoxRequest = TestRequest(form={'table-checkBoxColumn-0-selectedItems':
+  ...                                     ['first', 'third']})
+  >>> checkBoxTable = CheckBoxTable(container, checkBoxRequest)
+  >>> checkBoxTable.cssClassSelected = u'selected'
+  >>> checkBoxTable.update()
+  >>> print checkBoxTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th>X</th>
+        <th>Number</th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr class="selected">
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="first" checked="checked" /></td>
+        <td>number: 1</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="fourth"  /></td>
+        <td>number: 4</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="second"  /></td>
+        <td>number: 2</td>
+      </tr>
+      <tr class="selected">
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="third" checked="checked" /></td>
+        <td>number: 3</td>
+      </tr>
+      <tr>
+        <td><input type="checkbox" name="table-checkBoxColumn-0-selectedItems" value="zero"  /></td>
+        <td>number: 0</td>
+      </tr>
+    </tbody>
+  </table>
+
+
 CreatedColumn
 -------------
 
 Let's define a table using the CreatedColumn:
 
-  >>> from z3c.table import table
   >>> class CreatedColumnTable(table.Table):
   ... 
   ...     def setUpColumns(self):
@@ -383,7 +420,6 @@
 
 Let's define a table using the CreatedColumn:
 
-  >>> from z3c.table import table
   >>> class ModifiedColumnTable(table.Table):
   ... 
   ...     def setUpColumns(self):
@@ -423,3 +459,153 @@
       </tr>
     </tbody>
   </table>
+
+
+GetAttrColumn
+-------------
+
+The ``GetAttrColumn`` column is a mixin whihc is used in ``CreatedColumn`` and 
+in ``ModifiedColumn``. Not all code get used if everything is fine. So let's 
+test the column itself and force some usecase:
+
+
+  >>> class GetTitleColumn(column.GetAttrColumn):
+  ... 
+  ...     attrName = 'title'
+  ...     defaultValue = u'missing'
+
+  >>> class GetAttrColumnTable(table.Table):
+  ... 
+  ...     attrName = 'title'
+  ...     defaultValue = u'missing'
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, GetTitleColumn, u'title'),
+  ...             ]
+
+Render and update the table:
+
+  >>> request = TestRequest()
+  >>> getAttrColumnTable = GetAttrColumnTable(container, request)
+  >>> getAttrColumnTable.update()
+  >>> print getAttrColumnTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>First</td>
+      </tr>
+      <tr>
+        <td>Fourth</td>
+      </tr>
+      <tr>
+        <td>Second</td>
+      </tr>
+      <tr>
+        <td>Third</td>
+      </tr>
+      <tr>
+        <td>Zero</td>
+      </tr>
+    </tbody>
+  </table>
+
+If we use a none existing Attribute, we do not raise an AttributeError, we will
+get the default value defined from the ``GetAttrColumnTable``
+
+  >>> class UndefinedAttributeColumn(column.GetAttrColumn):
+  ... 
+  ...     attrName = 'undefined'
+  ...     defaultValue = u'missing'
+
+  >>> class GetAttrColumnTable(table.Table):
+  ... 
+  ...     attrName = 'title'
+  ...     defaultValue = u'missing'
+  ... 
+  ...     def setUpColumns(self):
+  ...         return [
+  ...             column.addColumn(self, UndefinedAttributeColumn, u'missing'),
+  ...             ]
+
+Render and update the table:
+
+  >>> request = TestRequest()
+  >>> getAttrColumnTable = GetAttrColumnTable(container, request)
+  >>> getAttrColumnTable.update()
+  >>> print getAttrColumnTable.render()
+  <table>
+    <thead>
+      <tr>
+        <th></th>
+      </tr>
+    </thead>
+    <tbody>
+      <tr>
+        <td>missing</td>
+      </tr>
+      <tr>
+        <td>missing</td>
+      </tr>
+      <tr>
+        <td>missing</td>
+      </tr>
+      <tr>
+        <td>missing</td>
+      </tr>
+      <tr>
+        <td>missing</td>
+      </tr>
+    </tbody>
+  </table>
+
+A missing ``attrName`` in ``GetAttrColumn`` whuold also end in return the
+``defaultValue``:
+
+  >>> class BadAttributeColumn(column.GetAttrColumn):
+  ... 
+  ...     defaultValue = u'missing'
+
+  >>> firstItem = container[u'first']
+  >>> simpleTable = table.Table(container, request)
+  >>> badColumn = column.addColumn(simpleTable, BadAttributeColumn, u'bad')
+  >>> badColumn.renderCell(firstItem)
+  u'missing'
+
+If we try to access a protected attribute the object raises an ``Unauthorized``.
+In thsi case we also return the defaultValue. Let's setup an object which
+raises such an error if we access the title:
+
+  >>> from zope.security.interfaces import Unauthorized
+  >>> class ProtectedItem(object):
+  ... 
+  ...     @property
+  ...     def forbidden(self):
+  ...         raise Unauthorized, 'forbidden'
+
+Setup and test the item:
+
+  >>> protectedItem = ProtectedItem()
+  >>> protectedItem.forbidden
+  Traceback (most recent call last):
+  ...
+  Unauthorized: forbidden
+
+Now define a column:
+
+  >>> class ForbiddenAttributeColumn(column.GetAttrColumn):
+  ... 
+  ...     attrName = 'forbidden'
+  ...     defaultValue = u'missing'
+
+And test the attribute access:
+
+  >>> simpleTable = table.Table(container, request)
+  >>> badColumn = column.addColumn(simpleTable, ForbiddenAttributeColumn, u'x')
+  >>> badColumn.renderCell(protectedItem)
+  u'missing'

Added: z3c.table/trunk/src/z3c/table/configure.zcml
===================================================================
--- z3c.table/trunk/src/z3c/table/configure.zcml	                        (rev 0)
+++ z3c.table/trunk/src/z3c/table/configure.zcml	2008-03-02 00:41:59 UTC (rev 84388)
@@ -0,0 +1,14 @@
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    i18n_domain="z3c">
+
+  <!-- batch provider -->
+  <adapter
+      name="batch"
+      factory="z3c.table.batch.BatchProvider"
+      for="zope.interface.Interface
+           zope.publisher.interfaces.browser.IBrowserRequest
+           z3c.table.interfaces.ITable"
+      />
+
+</configure>


Property changes on: z3c.table/trunk/src/z3c/table/configure.zcml
___________________________________________________________________
Name: svn:eol-style
   + native

Modified: z3c.table/trunk/src/z3c/table/interfaces.py
===================================================================
--- z3c.table/trunk/src/z3c/table/interfaces.py	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/src/z3c/table/interfaces.py	2008-03-02 00:41:59 UTC (rev 84388)
@@ -226,3 +226,13 @@
 
 class INoneCell(IColumn):
     """None cell used for colspan."""
+
+
+class IBatchProvider(IContentProvider):
+    """Batch content provider"""
+
+    def renderBatchLink(batch, cssClass=None):
+        """Renders batch links."""
+
+    def render():
+        """Plain render method without keyword arguments."""

Modified: z3c.table/trunk/src/z3c/table/table.py
===================================================================
--- z3c.table/trunk/src/z3c/table/table.py	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/src/z3c/table/table.py	2008-03-02 00:41:59 UTC (rev 84388)
@@ -22,6 +22,7 @@
 import zope.component
 import zope.location
 
+from z3c.batching.interfaces import IBatch
 from z3c.batching.batch import Batch
 from z3c.table import interfaces
 from z3c.table import column
@@ -55,6 +56,7 @@
     zope.interface.implements(interfaces.ITable)
 
     # private variables will be set in update call
+    batchProvider = None
     columnCounter = 0
     columnIndexById = {}
     columnByName = {}
@@ -78,6 +80,7 @@
     reverseSortOrderNames = [u'descending', u'reverse', u'down']
 
     # batch attributes
+    batchProviderName = 'batch'
     batchStart = 0
     batchSize = 50
     startBatchingAt = 50
@@ -85,6 +88,7 @@
     def __init__(self, context, request):
         self.context = context
         self.request = request
+        self.__parent__ = context
 
     def getCSSClass(self, element, cssClass=None):
         klass = self.cssClasses.get(element)
@@ -94,9 +98,6 @@
             klass = cssClass
         return klass and ' class=%s' % quoteattr(klass) or ''
 
-    def renderBatch(self):
-        return u'implement render batch'
-
     @property
     def values(self):
         return self.context.values()
@@ -190,6 +191,13 @@
             self.rows = Batch(self.rows, start=self.batchStart,
                 size=self.batchSize)
 
+    def updateBatch(self):
+        if IBatch.providedBy(self.rows):
+            self.batchProvider = zope.component.getMultiAdapter((self.context,
+                self.request, self), interfaces.IBatchProvider,
+                name=self.batchProviderName)
+            self.batchProvider.update()
+
     def isSelectedRow(self, row):
         item, col, colspan = row[0]
         if item in self.selectedItems:
@@ -198,6 +206,11 @@
 
 # render
 
+    def renderBatch(self):
+        if self.batchProvider is None:
+            return u''
+        return self.batchProvider.render()
+
     def renderTable(self):
         if self.columns:
             cssClass = self.getCSSClass('table')
@@ -209,30 +222,22 @@
     def renderHead(self):
         cssClass = self.getCSSClass('thead')
         rStr = self.renderHeadRow()
-        if rStr:
-            return '\n  <thead%s>%s\n  </thead>' % (cssClass, rStr)
-        return u''
+        return '\n  <thead%s>%s\n  </thead>' % (cssClass, rStr)
 
     def renderHeadRow(self):
         cssClass = self.getCSSClass('tr')
         cells = [self.renderHeadCell(col) for col in self.columns]
-        if cells:
-            return u'\n    <tr%s>%s\n    </tr>' % (cssClass, u''.join(cells))
-        return u''
+        return u'\n    <tr%s>%s\n    </tr>' % (cssClass, u''.join(cells))
 
     def renderHeadCell(self, column):
         cssClass = column.cssClasses.get('th')
         cssClass = self.getCSSClass('th', cssClass)
-        if interfaces.INoneCell.providedBy(column):
-            return u''
         return u'\n      <th%s>%s</th>' % (cssClass, column.renderHeadCell())
 
     def renderBody(self):
         cssClass = self.getCSSClass('tbody')
         rStr = self.renderRows()
-        if rStr:
-            return '\n  <tbody%s>%s\n  </tbody>' % (cssClass, rStr)
-        return u''
+        return '\n  <tbody%s>%s\n  </tbody>' % (cssClass, rStr)
 
     def renderRows(self):
         counter = 0
@@ -251,19 +256,16 @@
         elif isSelected and self.cssClassSelected:
             cssClass = self.cssClassSelected
         cssClass = self.getCSSClass('tr', cssClass)
-
         cells = [self.renderCell(item, col, colspan)
                  for item, col, colspan in row]
-        if cells:
-            return u'\n    <tr%s>%s\n    </tr>' % (cssClass, u''.join(cells))
-        return u''
+        return u'\n    <tr%s>%s\n    </tr>' % (cssClass, u''.join(cells))
 
     def renderCell(self, item, column, colspan=0):
+        if interfaces.INoneCell.providedBy(column):
+            return u''
         cssClass = column.cssClasses.get('td')
         cssClass = self.getCSSClass('td', cssClass)
         colspanStr = colspan and ' colspan="%s"' % colspan or ''
-        if interfaces.INoneCell.providedBy(column):
-            return u''
         return u'\n      <td%s%s>%s</td>' % (cssClass, colspanStr,
             column.renderCell(item))
 
@@ -298,6 +300,8 @@
         # batch sorted rows
         self.batchRows()
 
+        self.updateBatch()
+
     def render(self):
         
         # allow to use a template for rendering the table, this will allow

Modified: z3c.table/trunk/src/z3c/table/testing.py
===================================================================
--- z3c.table/trunk/src/z3c/table/testing.py	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/src/z3c/table/testing.py	2008-03-02 00:41:59 UTC (rev 84388)
@@ -46,7 +46,7 @@
 
 
 def setUp(test):
-    test.globs = {'root': setup.placefulSetUp(True)}
+    test.globs['root'] = setup.placefulSetUp(True)
     zope.component.provideAdapter(DublinCoreAdapterStub)
 
 

Modified: z3c.table/trunk/src/z3c/table/tests.py
===================================================================
--- z3c.table/trunk/src/z3c/table/tests.py	2008-03-01 19:57:39 UTC (rev 84387)
+++ z3c.table/trunk/src/z3c/table/tests.py	2008-03-02 00:41:59 UTC (rev 84388)
@@ -17,20 +17,16 @@
 __docformat__ = "reStructuredText"
 
 import unittest
-import datetime
-import zope.interface
-import zope.component
-from zope.dublincore.interfaces import IZopeDublinCore
 from zope.testing import doctest
 from zope.publisher.browser import TestRequest
-from zope.security import checker
-from zope.app.testing import setup
 
 import z3c.testing
+from z3c.batching.batch import Batch
 from z3c.table import testing
 from z3c.table import interfaces
 from z3c.table import table
 from z3c.table import column
+from z3c.table import batch
 
 
 # table
@@ -112,6 +108,21 @@
         return ({}, TestRequest(), t)
 
 
+# batch
+class TestBatchProvider(z3c.testing.InterfaceBaseTest):
+
+    def getTestInterface(self):
+        return interfaces.IBatchProvider
+
+    def getTestClass(self):
+        return batch.BatchProvider
+
+    def getTestPos(self):
+        t = table.Table(None, TestRequest())
+        t.rows = Batch([])
+        return ({}, TestRequest(), t)
+
+
 def test_suite():
     return unittest.TestSuite((
         doctest.DocFileSuite('README.txt',
@@ -128,6 +139,7 @@
         unittest.makeSuite(TestNameColumn),
         unittest.makeSuite(TestRadioColumn),
         unittest.makeSuite(TestCheckBoxColumn),
+        unittest.makeSuite(TestBatchProvider),
         ))
 
 



More information about the Checkins mailing list