1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
|
================
Batch Navigation
================
Most of the test for this behavior is in the lazr.batchnavigation package.
This documents and tests the Launchpad-specific elements of its usage.
Note that our use of the batching code relies on the registration of
canonical.launchpad.webapp.batching.FiniteSequenceAdapter for
storm.zope.interfaces.IResultSet and
storm.zope.interfaces.ISQLObjectResultSet.
Batch navigation provides a way to navigate batch results in a web
page by providing URL links to the next, previous and numbered pages
of results.
It uses two arguments to control the batching:
- start: The first item we should show in current batch.
- batch: Controls the amount of items we are showing per batch. It will only
appear if it's different from the default value set when the batch
is created.
Imports:
>>> from canonical.launchpad.webapp.batching import BatchNavigator
>>> from canonical.launchpad.webapp.servers import LaunchpadTestRequest
>>> def build_request(query_string_args=None, method='GET'):
... if query_string_args is None:
... query_string_args = {}
... query_string = "&".join(
... "%s=%s" % (k,v) for k,v in query_string_args.items())
... request = LaunchpadTestRequest(
... SERVER_URL='http://www.example.com/foo', method=method,
... environ={'QUERY_STRING': query_string})
... request.processInputs()
... return request
A dummy request object:
Some sample data.
>>> reindeer = [
... 'Dasher', 'Dancer', 'Prancer', 'Vixen', 'Comet',
... 'Cupid', 'Donner', 'Blitzen', 'Rudolph',
... ]
Multiple pages
==============
The batch navigator tells us whether multiple pages will be used.
>>> from lp.services.identity.model.emailaddress import EmailAddress
>>> select_results = EmailAddress.select(orderBy='id')
>>> batch_nav = BatchNavigator(select_results, build_request(), size=50)
>>> batch_nav.has_multiple_pages
True
>>> one_page_nav = BatchNavigator(
... select_results, build_request(), size=200)
>>> one_page_nav.has_multiple_pages
False
Maximum batch size
==================
Since the batch size is exposed in the URL, it's possible for users to
tweak the batch parameter to retrieve more results. Since that may
potentially exhaust server resources, an upper limit is put on the batch
size. If the requested batch parameter is higher than this, an
InvalidBatchSizeError is raised.
>>> from canonical.config import config
>>> from textwrap import dedent
>>> config.push('max-batch-size', dedent("""\
... [launchpad]
... max_batch_size: 5
... """))
>>> request = build_request({"start": "0", "batch": "20"})
>>> BatchNavigator(reindeer, request=request )
Traceback (most recent call last):
...
InvalidBatchSizeError: Maximum for "batch" parameter is 5.
>>> ignored = config.pop('max-batch-size')
Batch views
===========
A view is often used with a BatchNavigator to determine when to
display the current batch.
If the current batch is empty, nothing is rendered for the
upper and lower navigation link views.
>>> from zope.component import getMultiAdapter
>>> request = build_request({"start": "0", "batch": "10"})
>>> navigator = BatchNavigator([], request=request)
>>> upper_view = getMultiAdapter(
... (navigator, request), name='+navigation-links-upper')
>>> upper_view.render()
u''
>>> lower_view = getMultiAdapter(
... (navigator, request), name='+navigation-links-lower')
>>> lower_view.render()
u''
When there is a current batch, but there are no previous or next
batches, both the upper and lower navigation links view will render.
>>> navigator = BatchNavigator(reindeer, request=request)
>>> upper_view = getMultiAdapter(
... (navigator, request), name='+navigation-links-upper')
>>> print upper_view.render()
<table...
...<strong>1</strong>...→...<strong>9</strong>...of 9 results...
...<span class="first inactive">...First...
...<span class="previous inactive">...Previous...
...<span class="next inactive">...Next...
...<span class="last inactive">...Last...
>>> lower_view = getMultiAdapter(
... (navigator, request), name='+navigation-links-lower')
>>> print lower_view.render()
<table...
...<strong>1</strong>...→...<strong>9</strong>...of 9 results...
...<span class="first inactive">...First...
...<span class="previous inactive">...Previous...
...<span class="next inactive">...Next...
...<span class="last inactive">...Last...
|