Merge lp:~adeuring/lazr.batchnavigator/lazr.batchnavigator-urlencode-batch-params into lp:lazr.batchnavigator

Proposed by Abel Deuring
Status: Merged
Merged at revision: 43
Proposed branch: lp:~adeuring/lazr.batchnavigator/lazr.batchnavigator-urlencode-batch-params
Merge into: lp:lazr.batchnavigator
Diff against target: 140 lines (+44/-21)
2 files modified
src/lazr/batchnavigator/_batchnavigator.py (+26/-21)
src/lazr/batchnavigator/tests/test_batchnavigator.py (+18/-0)
To merge this branch: bzr merge lp:~adeuring/lazr.batchnavigator/lazr.batchnavigator-urlencode-batch-params
Reviewer Review Type Date Requested Status
Benji York (community) code Approve
Review via email: mp+69118@code.launchpad.net

Description of the change

The class lazr.batchnavigator.BatchNavigator delegates the creation of the batch parameters to an IRangeFactory.

Batch URLs are generated by BatchNavigator.generateBatchURL(), which builds the URL from batch.range_memo, among other parameters. These parameters were not URL-escaped. This is OK, if the parameters themselves are URL safe, like string representations of integers.

But the main idea of IRangeFactory is that we can use other factories than the only currently existing implementation, ListRangeFactory, which integers for the memo value. Specifically, I am working on a class StormRangeFactory, which will create memo parameters from SQL table columns used to sort the query result. (This allows us to replace the OFFSET clause with something like WHERE Table.index_col > last_batch_row.index_col, as suggested by Jeroen some time ago.)

This means that we will have soon more or less arbitrary values in the memo field, and we could end up with a memo value like 'foo & bar'. In other words: URL query parameter 'meo' must be properly URL-escaped.

The implementation and the test are straightforward, I think.

Ah, and I removed trailing whitespaces o a few lines.

To post a comment you must log in.
Revision history for this message
Benji York (benji) wrote :

This looks good. Thanks.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'src/lazr/batchnavigator/_batchnavigator.py'
2--- src/lazr/batchnavigator/_batchnavigator.py 2011-07-13 05:49:45 +0000
3+++ src/lazr/batchnavigator/_batchnavigator.py 2011-07-25 15:56:22 +0000
4@@ -32,7 +32,7 @@
5 )
6
7 __all__ = [
8- 'BatchNavigator',
9+ 'BatchNavigator',
10 'ListRangeFactory',
11 ]
12
13@@ -159,7 +159,7 @@
14 self.default_size. The base class implementation uses the size passed
15 to the constructor, but other implementations may choose to clamp it or
16 force a particular default size.
17-
18+
19 :param size: Size passed to the constructor.
20 :param batch_params_source: User parameters dict.
21 :return: The size to be used for this batch.
22@@ -214,9 +214,9 @@
23 self._singular_heading = singular
24 self._plural_heading = plural
25
26- def getCleanQueryString(self, params=None):
27- """Removes batch nav params if present and returns a query
28- string.
29+ def getCleanQueryParams(self, params=None):
30+ """Removes batch nav params if present and returns a sequence
31+ of key-values pairs.
32
33 If ``params`` is None, uses the current query_string_params.
34 """
35@@ -232,40 +232,45 @@
36
37 # We need the doseq=True because some url params are for multi-value
38 # fields.
39- return urllib.urlencode(
40- [(key, value) for (key, value) in sorted(params)
41- if key not in self.transient_parameters],
42- doseq=True)
43+ return [
44+ (key, value) for (key, value) in sorted(params)
45+ if key not in self.transient_parameters]
46+
47+ def getCleanQueryString(self, params=None):
48+ """Removes batch nav params if present and returns a query
49+ string.
50+
51+ If ``params`` is None, uses the current query_string_params.
52+ """
53+ # We need the doseq=True because some url params are for multi-value
54+ # fields.
55+ return urllib.urlencode(self.getCleanQueryParams(params), doseq=True)
56
57 def generateBatchURL(self, batch, backwards=False):
58 url = ""
59 if batch is None:
60 return url
61
62- qs = self.getCleanQueryString()
63- if qs:
64- elements = [qs]
65- else:
66- elements = []
67+ params = self.getCleanQueryParams()
68
69 size = batch.size
70 if size != self.default_size:
71 # The current batch size should only be part of the URL if it's
72 # different from the default batch size.
73- elements.append("%s=%s" % (self.batch_variable_name, size))
74+ params.append((self.batch_variable_name, size))
75
76 if backwards:
77- elements.append("%s=backwards" % (self.direction_variable_name,))
78+ params.append((self.direction_variable_name, "backwards"))
79
80 if batch.range_memo:
81- elements.append("%s=%s" % (self.memo_variable_name, batch.range_memo))
82+ params.append((self.memo_variable_name, batch.range_memo))
83
84 start = batch.startNumber() - 1
85 if start:
86- elements.append("%s=%s" % (self.start_variable_name, start))
87+ params.append((self.start_variable_name, start))
88
89 base_url = str(self.request.URL)
90- return "%s?%s" % (base_url, '&'.join(elements))
91+ return "%s?%s" % (base_url, urllib.urlencode(params))
92
93 def firstBatchURL(self):
94 batch = self.batch.firstBatch()
95@@ -292,7 +297,7 @@
96
97 def _update_variable_names(self):
98 """Update self.x_variable_name with self.variable_name_prefix.
99-
100+
101 This gives the concrete instance the same prefix for all variables.
102 """
103 prefix = self.variable_name_prefix or ''
104@@ -317,7 +322,7 @@
105
106 def getEndpointMemos(self, batch):
107 """See `IRangeFactory`
108-
109+
110 Most implementations will want to use batch.sliced_list to retrieve
111 database keys.
112 """
113
114=== modified file 'src/lazr/batchnavigator/tests/test_batchnavigator.py'
115--- src/lazr/batchnavigator/tests/test_batchnavigator.py 2011-07-13 05:49:45 +0000
116+++ src/lazr/batchnavigator/tests/test_batchnavigator.py 2011-07-25 15:56:22 +0000
117@@ -149,6 +149,24 @@
118 Equals(SERVER_URL + '?prefix_batch=3&prefix_direction=backwards'
119 '&prefix_memo=6&prefix_start=3'))
120
121+ def test_range_factory_producing_url_unsafe_memos(self):
122+ # Memo values containing characters with special meaning in URLs
123+ # are properly escaped by generateBatchURL().
124+ class WeirdRangeFactory(ListRangeFactory):
125+ def getEndpointMemos(self, batch):
126+ return ('start&/1', 'end&/2')
127+
128+ collection = range(1, 11)
129+ request = TestRequest(
130+ SERVER_URL=SERVER_URL, method='GET',
131+ environ={'QUERY_STRING': "start=6&batch=3&memo=6"})
132+ range_factory = WeirdRangeFactory(collection)
133+ batchnav = BatchNavigator(
134+ collection, request, range_factory=range_factory)
135+ self.assertThat(
136+ batchnav.nextBatchURL(),
137+ Equals(SERVER_URL + '?batch=3&memo=end%26%2F2&start=9'))
138+
139
140 class TestListRangeFactory(testtools.TestCase):
141

Subscribers

People subscribed via source and target branches