[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