Skip to content

Latest commit

 

History

History
652 lines (575 loc) · 20.3 KB

batch.rst

File metadata and controls

652 lines (575 loc) · 20.3 KB

Batching

Our table implements batching out of the box. If the amount of row items is smaller than 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 z3c.table
>>> import zope.component
>>> XMLConfig('meta.zcml', zope.component)()
>>> XMLConfig('configure.zcml', z3c.table)()

Now we can create our table:

>>> from zope.publisher.browser import TestRequest
>>> from z3c.table.testing import Container, Content, SimpleTable
>>> container = Container()
>>> root['container-1'] = container
>>> request = TestRequest()
>>> batchingTable = SimpleTable(container, request)
>>> batchingTable.cssClassSortedOn = None

We also need to give the table a location and a name like we normally setup in traversing:

>>> batchingTable.__parent__ = container
>>> batchingTable.__name__ = u'batchingTable.html'

Now setup some items:

>>> container[u'zero'] = Content('Zero', 0)
>>> container[u'first'] = Content('First', 1)
>>> container[u'second'] = Content('Second', 2)
>>> container[u'third'] = Content('Third', 3)
>>> container[u'fourth'] = Content('Fourth', 4)
>>> container[u'sixth'] = Content('Sixth', 6)
>>> container[u'seventh'] = Content('Seventh', 7)
>>> container[u'eighth'] = Content('Eighth', 8)
>>> container[u'ninth'] = Content('Ninth', 9)
>>> container[u'tenth'] = Content('Tenth', 10)
>>> container[u'eleventh'] = Content('Eleventh', 11)
>>> container[u'twelfth '] = Content('Twelfth', 12)
>>> container[u'thirteenth'] = Content('Thirteenth', 13)
>>> container[u'fourteenth'] = Content('Fourteenth', 14)
>>> container[u'fifteenth '] = Content('Fifteenth', 15)
>>> container[u'sixteenth'] = Content('Sixteenth', 16)
>>> container[u'seventeenth'] = Content('Seventeenth', 17)
>>> container[u'eighteenth'] = Content('Eighteenth', 18)
>>> container[u'nineteenth'] = Content('Nineteenth', 19)
>>> container[u'twentieth'] = Content('Twentieth', 20)

Now let's show the full table without batching:

>>> batchingTable.update()
>>> print(batchingTable.render())
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Eighteenth item</td>
      <td>number: 18</td>
    </tr>
    <tr>
      <td>Eighth item</td>
      <td>number: 8</td>
    </tr>
    <tr>
      <td>Eleventh item</td>
      <td>number: 11</td>
    </tr>
    <tr>
      <td>Fifteenth item</td>
      <td>number: 15</td>
    </tr>
    <tr>
      <td>First item</td>
      <td>number: 1</td>
    </tr>
    <tr>
      <td>Fourteenth item</td>
      <td>number: 14</td>
    </tr>
    <tr>
      <td>Fourth item</td>
      <td>number: 4</td>
    </tr>
    <tr>
      <td>Nineteenth item</td>
      <td>number: 19</td>
    </tr>
    <tr>
      <td>Ninth item</td>
      <td>number: 9</td>
    </tr>
    <tr>
      <td>Second item</td>
      <td>number: 2</td>
    </tr>
    <tr>
      <td>Seventeenth item</td>
      <td>number: 17</td>
    </tr>
    <tr>
      <td>Seventh item</td>
      <td>number: 7</td>
    </tr>
    <tr>
      <td>Sixteenth item</td>
      <td>number: 16</td>
    </tr>
    <tr>
      <td>Sixth item</td>
      <td>number: 6</td>
    </tr>
    <tr>
      <td>Tenth item</td>
      <td>number: 10</td>
    </tr>
    <tr>
      <td>Third item</td>
      <td>number: 3</td>
    </tr>
    <tr>
      <td>Thirteenth item</td>
      <td>number: 13</td>
    </tr>
    <tr>
      <td>Twelfth item</td>
      <td>number: 12</td>
    </tr>
    <tr>
      <td>Twentieth item</td>
      <td>number: 20</td>
    </tr>
    <tr>
      <td>Zero item</td>
      <td>number: 0</td>
    </tr>
  </tbody>
</table>

As you can see, the table is not ordered and it uses all items. If we like to use the batch, we need to set the startBatchingAt size to a lower value than it is set by default. The default value which a batch is used is set to 50:

>>> batchingTable.startBatchingAt
50

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
5

There is also a batchSize value which we need to set to 5. By default the value gets initialized by the batchSize value:

>>> batchingTable.batchSize
50
>>> batchingTable.batchSize = 5
>>> batchingTable.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, which is correct. But the order doesn't depend on the numbers we see in cells:

>>> batchingTable.update()
>>> print(batchingTable.render())
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Eighteenth item</td>
      <td>number: 18</td>
    </tr>
    <tr>
      <td>Eighth item</td>
      <td>number: 8</td>
    </tr>
    <tr>
      <td>Eleventh item</td>
      <td>number: 11</td>
    </tr>
    <tr>
      <td>Fifteenth item</td>
      <td>number: 15</td>
    </tr>
    <tr>
      <td>First item</td>
      <td>number: 1</td>
    </tr>
  </tbody>
</table>

I think we should order the table by the second column before we show the next batch values. We do this by simply set the defaultSortOn:

>>> batchingTable.sortOn = u'table-number-1'

Now we should see a nice ordered and batched table:

>>> batchingTable.update()
>>> print(batchingTable.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>

The batch concept allows us to choose from all batches and render the rows for this batched items. We can do this by set any batch as rows. as you can see we have 4 batched row data available:

>>> len(batchingTable.rows.batches)
4

We can set such a batch as row values, then this batch data are used for rendering. But take care, if we update the table, our rows get overridden and reset to the previous values. this means you can set any batch as rows data and only render them. This is possible since the update method sorted all items and all batch contain ready-to-use data. This concept could be important if you need to cache batches etc. :

>>> batchingTable.rows = batchingTable.rows.batches[1]
>>> print(batchingTable.render())
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Sixth 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>
  </tbody>
</table>

And like described above, if you call update our batch to rows setup get reset:

>>> batchingTable.update()
>>> print(batchingTable.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>

This means you can probably update all batches, cache them and use them after. But this is not useful for normal usage in a page without an enhanced concept which is not a part of this implementation. This also means, there must be another way to set the batch index. Yes there is, there are two other ways how we can set the batch position. We can set a batch position by setting the batchStart value in our table or we can use a request variable. Let's show the first one first:

>>> batchingTable.batchStart = 6
>>> batchingTable.update()
>>> print(batchingTable.render())
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <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>
  </tbody>
</table>

We can also set the batch position by using the batchStart value in a request. Note that we need the table prefix and column __name__ like we use in the sorting concept:

>>> batchingRequest = TestRequest(form={'table-batchStart': '11',
...                                     'table-batchSize': '5',
...                                     'table-sortOn': 'table-number-1'})
>>> requestBatchingTable = SimpleTable(container, batchingRequest)
>>> requestBatchingTable.cssClassSortedOn = None

We also need to give the table a location and a name like we normally set up in traversing:

>>> requestBatchingTable.__parent__ = container
>>> requestBatchingTable.__name__ = u'requestBatchingTable.html'

Note: our table needs to start batching at smaller amount of items than we have by default otherwise we don't get a batch:

>>> requestBatchingTable.startBatchingAt = 5
>>> requestBatchingTable.update()
>>> print(requestBatchingTable.render())
<table>
  <thead>
    <tr>
      <th>My items</th>
      <th>Number</th>
    </tr>
  </thead>
  <tbody>
    <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>
  </tbody>
</table>

BatchProvider

The batch provider allows us to render the batch HTML independently of our table. This means by default the batch gets 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 comes to batching:

>>> from z3c.batching.interfaces import IBatch
>>> IBatch.providedBy(requestBatchingTable.rows)
True

Let's check some batch variables before we render our test. This 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-batchSize=5&table-batchStart=0&..." class="first">1</a>
<a href="...html?table-batchSize=5&table-batchStart=5&...">2</a>
<a href="...html?table-batchSize=5&table-batchStart=11&..." class="current">3</a>
<a href="...html?table-batchSize=5&table-batchStart=15&..." class="last">4</a>

Now let's add more items so 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 = SimpleTable(container, batchingRequest)
>>> requestBatchingTable.startBatchingAt = 5
>>> requestBatchingTable.cssClassSortedOn = None

We also need to give the table a location and a name like we normally 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 are rendered by the batch provider and are not a part of the doctest:

>>> print(requestBatchingTable.renderBatch())
<a href="...html?table-batchSize=5&table-batchStart=0&table-sortOn=table-number-1" class="first">1</a>
...
<a href="...html?table-batchSize=5&table-batchStart=85&table-sortOn=table-number-1">18</a>
<a href="...html?table-batchSize=5&table-batchStart=90&table-sortOn=table-number-1">19</a>
<a href="...html?table-batchSize=5&table-batchStart=95&table-sortOn=table-number-1">20</a>
<a href="...html?table-batchSize=5&table-batchStart=100&table-sortOn=table-number-1" class="current">21</a>
<a href="...html?table-batchSize=5&table-batchStart=105&table-sortOn=table-number-1">22</a>
<a href="...html?table-batchSize=5&table-batchStart=110&table-sortOn=table-number-1">23</a>
<a href="...html?table-batchSize=5&table-batchStart=115&table-sortOn=table-number-1">24</a>
...
<a href="...html?table-batchSize=5&table-batchStart=1015&table-sortOn=table-number-1" 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
>>> from z3c.table.interfaces import IBatchProvider
>>> from zope.interface import implementer
>>> @implementer(IBatchProvider)
... 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
>>> from zope.component import getSiteManager
>>> sm = getSiteManager(container)
>>> sm.registerAdapter(XBatchProvider,
...     (zope.interface.Interface,
...      zope.publisher.interfaces.browser.IBrowserRequest,
...      SimpleTable), 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()
>>> requestBatchingTable.batchProvider
<...XBatchProvider object at ...>
>>> print(requestBatchingTable.renderBatch())
<a href="...html?table-batchSize=5&table-batchStart=0&table-sortOn=table-number-1" class="first">1</a>
xxx
<a href="...html?table-batchSize=5&table-batchStart=85&table-sortOn=table-number-1">18</a>
<a href="...html?table-batchSize=5&table-batchStart=90&table-sortOn=table-number-1">19</a>
<a href="...html?table-batchSize=5&table-batchStart=95&table-sortOn=table-number-1">20</a>
<a href="...html?table-batchSize=5&table-batchStart=100&table-sortOn=table-number-1" class="current">21</a>
<a href="...html?table-batchSize=5&table-batchStart=105&table-sortOn=table-number-1">22</a>
<a href="...html?table-batchSize=5&table-batchStart=110&table-sortOn=table-number-1">23</a>
<a href="...html?table-batchSize=5&table-batchStart=115&table-sortOn=table-number-1">24</a>
xxx
<a href="...html?table-batchSize=5&table-batchStart=1015&table-sortOn=table-number-1" class="last">204</a>

Now test the extremities, need to define a new batchingRequest: Beginning by the left end point:

>>> leftBatchingRequest = TestRequest(form={'table-batchStart': '10',
...                                        'table-batchSize': '5',
...                                       'table-sortOn': 'table-number-1'})
>>> leftRequestBatchingTable = SimpleTable(container, leftBatchingRequest)
>>> leftRequestBatchingTable.__parent__ = container
>>> leftRequestBatchingTable.__name__ = u'leftRequestBatchingTable.html'
>>> leftRequestBatchingTable.update()
>>> print(leftRequestBatchingTable.renderBatch())
<a href="http://...html?table-batchSize=5&table-batchStart=0&table-sortOn=table-number-1" class="first">1</a>
<a href="http://...html?table-batchSize=5&table-batchStart=5&table-sortOn=table-number-1">2</a>
<a href="http://...html?table-batchSize=5&table-batchStart=10&table-sortOn=table-number-1" class="current">3</a>
<a href="http://...html?table-batchSize=5&table-batchStart=15&table-sortOn=table-number-1">4</a>
<a href="http://...html?table-batchSize=5&table-batchStart=20&table-sortOn=table-number-1">5</a>
<a href="http://...html?table-batchSize=5&table-batchStart=25&table-sortOn=table-number-1">6</a>
xxx
<a href="http://...html?table-batchSize=5&table-batchStart=1015&table-sortOn=table-number-1" class="last">204</a>

Go on with the right extremity:

>>> rightBatchingRequest = TestRequest(form={'table-batchStart': '1005',
...                                     'table-batchSize': '5',
...                                     'table-sortOn': 'table-number-1'})
>>> rightRequestBatchingTable = SimpleTable(container, rightBatchingRequest)
>>> rightRequestBatchingTable.__parent__ = container
>>> rightRequestBatchingTable.__name__ = u'rightRequestBatchingTable.html'
>>> rightRequestBatchingTable.update()
>>> print(rightRequestBatchingTable.renderBatch())
<a href="http://...html?table-batchSize=5&table-batchStart=0&table-sortOn=table-number-1" class="first">1</a>
xxx
<a href="http://...html?table-batchSize=5&table-batchStart=990&table-sortOn=table-number-1">199</a>
<a href="http://...html?table-batchSize=5&table-batchStart=995&table-sortOn=table-number-1">200</a>
<a href="http://...html?table-batchSize=5&table-batchStart=1000&table-sortOn=table-number-1">201</a>
<a href="http://...html?table-batchSize=5&table-batchStart=1005&table-sortOn=table-number-1" class="current">202</a>
<a href="http://...html?table-batchSize=5&table-batchStart=1010&table-sortOn=table-number-1">203</a>
<a href="http://...html?table-batchSize=5&table-batchStart=1015&table-sortOn=table-number-1" 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
>>> sm.registerAdapter(ZeroBatchProvider,
...     (zope.interface.Interface,
...      zope.publisher.interfaces.browser.IBrowserRequest,
...      SimpleTable), name='batch')

Update the table and render the batch:

>>> requestBatchingTable.update()
>>> print(requestBatchingTable.renderBatch())
<a href="...html?table-batchSize=5&table-batchStart=0&table-sortOn=table-number-1" class="first">1</a>
xxx
<a href="...html?table-batchSize=5&table-batchStart=100&table-sortOn=table-number-1" class="current">21</a>
xxx
<a href="...html?table-batchSize=5&table-batchStart=1015&table-sortOn=table-number-1" class="last">204</a>