59 return DEF_DEFAULT_PAGINATION |
58 return DEF_DEFAULT_PAGINATION |
60 |
59 |
61 |
60 |
62 OFFSET_KEY = 'offset_%d' |
61 OFFSET_KEY = 'offset_%d' |
63 LIMIT_KEY = 'limit_%d' |
62 LIMIT_KEY = 'limit_%d' |
|
63 OFFSET_LINKID_KEY = 'offset_linkid_%d' |
|
64 REVERSE_DIRECTION_KEY = 'reverse_sort_direction_%d' |
64 |
65 |
65 |
66 |
66 def makeOffsetKey(limit_idx): |
67 def makeOffsetKey(limit_idx): |
67 return OFFSET_KEY % limit_idx |
68 return OFFSET_KEY % limit_idx |
68 |
69 |
69 |
70 |
70 def makeLimitKey(limit_idx): |
71 def makeLimitKey(limit_idx): |
71 return LIMIT_KEY % limit_idx |
72 return LIMIT_KEY % limit_idx |
|
73 |
|
74 |
|
75 def makeOffsetLinkidKey(limit_idx): |
|
76 return OFFSET_LINKID_KEY % limit_idx |
|
77 |
|
78 |
|
79 def makeReverseDirectionKey(limit_idx): |
|
80 return REVERSE_DIRECTION_KEY % limit_idx |
72 |
81 |
73 |
82 |
74 def getListParameters(request, list_index): |
83 def getListParameters(request, list_index): |
75 """Retrieves, converts and validates values for one list |
84 """Retrieves, converts and validates values for one list |
76 |
85 |
108 if user_logic.isDeveloper(): |
117 if user_logic.isDeveloper(): |
109 limit = min(DEF_MAX_DEV_PAGINATION, limit) |
118 limit = min(DEF_MAX_DEV_PAGINATION, limit) |
110 else: |
119 else: |
111 limit = min(DEF_MAX_PAGINATION, limit) |
120 limit = min(DEF_MAX_PAGINATION, limit) |
112 |
121 |
113 return dict(limit=limit, offset=offset) |
122 result = dict(limit=limit, offset=offset) |
114 |
123 offset_linkid = request.GET.get(makeOffsetLinkidKey(list_index), |
115 |
124 '') |
116 def generateLinkFromGetArgs(request, offset_and_limits): |
125 # TODO(dbentley): URL unescape |
117 """Constructs the get args for the url. |
126 result['offset_linkid'] = offset_linkid |
118 """ |
127 |
119 |
128 reverse_direction = makeReverseDirectionKey(list_index) in request.GET |
120 args = ["%s=%s" % (k, v) for k, v in offset_and_limits.iteritems()] |
129 result['reverse_direction'] = reverse_direction |
121 link_suffix = '?' + '&'.join(args) |
130 |
122 |
131 return result |
123 return request.path + link_suffix |
132 |
124 |
133 |
125 |
134 class LinkCreator(object): |
126 def generateLinkForRequest(request, base_params, updated_params): |
135 """A way to create links for a page. |
127 """Create a link to the same page as request but with different params |
136 """ |
128 |
137 def __init__(self, request, list_idx, limit): |
129 Params: |
138 self.path = request.path |
130 request: the request for the page |
139 self.base_params = dict( |
131 base_params: the base parameters |
140 i for i in request.GET.iteritems() if |
132 updated_params: the parameters to update |
141 i[0].startswith('offset_') or i[0].startswith('limit_')) |
133 """ |
142 self.idx = list_idx |
134 params = base_params.copy() |
143 self.base_params[makeLimitKey(self.idx)] = limit |
135 params.update(updated_params) |
144 |
136 return generateLinkFromGetArgs(request, params) |
145 def create(self, offset_linkid=None, export=False, reverse_direction=False): |
|
146 params = self.base_params.copy() |
|
147 if offset_linkid is not None: |
|
148 # TODO(dbentley): URL encode |
|
149 if offset_linkid == '': |
|
150 try: |
|
151 del params[makeOffsetLinkidKey(self.idx)] |
|
152 except KeyError: |
|
153 pass |
|
154 else: |
|
155 params[makeOffsetLinkidKey(self.idx)]=offset_linkid |
|
156 if reverse_direction: |
|
157 params[makeReverseDirectionKey(self.idx)]=True |
|
158 link_suffix = '&'.join('%s=%s' % (k, v) for k, v in params.iteritems()) |
|
159 return '%s?%s' % (self.path, link_suffix) |
137 |
160 |
138 |
161 |
139 def getListContent(request, params, filter=None, order=None, |
162 def getListContent(request, params, filter=None, order=None, |
140 idx=0, need_content=False): |
163 idx=0, need_content=False): |
141 """Returns a dict with fields used for rendering lists. |
164 """Returns a dict with fields used for rendering lists. |
172 """ |
195 """ |
173 # TODO(dbentley): this appears to be unnecessary indirection, |
196 # TODO(dbentley): this appears to be unnecessary indirection, |
174 # as we only use this logic for getForFields, which is never overridden |
197 # as we only use this logic for getForFields, which is never overridden |
175 logic = params['logic'] |
198 logic = params['logic'] |
176 |
199 |
177 limit_key, offset_key = makeLimitKey(idx), makeOffsetKey(idx) |
200 limit_key = makeLimitKey(idx) |
|
201 # offset_key = makeOffsetKey(idx) |
|
202 # offset_linkid_key = makeOffsetLinkidKey(idx) |
|
203 # reverse_direction_key = makeReverseDirectionKey(idx) |
178 |
204 |
179 list_params = getListParameters(request, idx) |
205 list_params = getListParameters(request, idx) |
180 limit, offset = list_params['limit'], list_params['offset'] |
206 limit, offset = list_params['limit'], list_params['offset'] |
|
207 offset_linkid = list_params['offset_linkid'] |
|
208 reverse_direction = list_params['reverse_direction'] |
181 pagination_form = makePaginationForm(request, list_params['limit'], |
209 pagination_form = makePaginationForm(request, list_params['limit'], |
182 limit_key) |
210 limit_key) |
|
211 |
|
212 if offset_linkid: |
|
213 if filter is None: |
|
214 filter = {} |
|
215 |
|
216 if reverse_direction: |
|
217 filter['link_id <'] = offset_linkid |
|
218 else: |
|
219 filter['link_id >'] = offset_linkid |
|
220 |
|
221 if order is None: |
|
222 order = [] |
|
223 if reverse_direction: |
|
224 order.append('-link_id') |
|
225 else: |
|
226 order.append('link_id') |
|
227 |
|
228 |
183 |
229 |
184 # Fetch one more to see if there should be a 'next' link |
230 # Fetch one more to see if there should be a 'next' link |
185 data = logic.getForFields(filter=filter, limit=limit+1, offset=offset, |
231 data = logic.getForFields(filter=filter, limit=limit+1, offset=offset, |
186 order=order) |
232 order=order) |
187 |
233 |
188 if need_content and not data: |
234 if need_content and not data: |
189 return None |
235 return None |
190 |
236 |
191 more = len(data) > limit |
237 more = len(data) > limit |
|
238 if reverse_direction: |
|
239 data.reverse() |
192 |
240 |
193 if more: |
241 if more: |
194 del data[limit:] |
242 if reverse_direction: |
195 |
243 data = data[1:] |
|
244 else: |
|
245 data = data[:limit] |
|
246 |
|
247 should_have_next_link = True |
|
248 if not reverse_direction and not more: |
|
249 should_have_next_link = False |
|
250 |
|
251 # Calculating should_have_previous_link is tricky. It's possible we could |
|
252 # be creating a previous link to a page that would have 0 entities. |
|
253 # That would be suboptimal; what's a better way? |
|
254 should_have_previous_link = False |
|
255 if offset_linkid: |
|
256 should_have_previous_link = True |
|
257 if reverse_direction and not more: |
|
258 should_have_previous_link = False |
|
259 |
|
260 if data: |
|
261 first_displayed_item = data[0] |
|
262 last_displayed_item = data[-1] |
|
263 else: |
|
264 class Dummy(object): |
|
265 pass |
|
266 first_displayed_item = last_displayed_item = Dummy() |
|
267 first_displayed_item.link_id = None |
196 newest = next = prev = export_link = '' |
268 newest = next = prev = export_link = '' |
197 |
269 |
198 base_params = dict(i for i in request.GET.iteritems() if |
270 link_creator = LinkCreator(request, idx, limit) |
199 i[0].startswith('offset_') or i[0].startswith('limit_')) |
|
200 |
271 |
201 if params.get('list_key_order'): |
272 if params.get('list_key_order'): |
202 export_link = generateLinkForRequest(request, base_params, {'export' : idx}) |
273 export_link = link_creator.create(export=True) |
203 |
274 |
204 if more: |
275 if should_have_next_link: |
205 # TODO(dbentley): here we need to implement a new field "last_key" |
276 next = link_creator.create(offset_linkid=last_displayed_item.link_id) |
206 next = generateLinkForRequest(request, base_params, {offset_key : offset+limit, |
277 |
207 limit_key : limit}) |
278 if should_have_previous_link: |
208 |
279 prev = link_creator.create(offset_linkid=first_displayed_item.link_id, |
209 if offset > 0: |
280 reverse_direction=True) |
210 # TODO(dbentley): here we need to implement previous in the good way. |
281 |
211 prev = generateLinkForRequest(request, base_params, |
282 newest = link_creator.create(offset_linkid='') |
212 { offset_key : max(0, offset-limit), |
283 |
213 limit_key : limit }) |
284 # TODO(dbentley): add a "last" link (which is now possible because we can |
214 |
285 # query with a reverse linkid sorting |
215 if offset > limit: |
|
216 # Having a link to the first doesn't make sense on the first page (we're on |
|
217 # it). It also doesn't make sense on the second page (because the first |
|
218 # page is the previous page). |
|
219 |
|
220 # NOTE(dbentley): I personally disagree that it's simpler to do that way, |
|
221 # because sometimes you want to go to the first page without having to |
|
222 # consider what page you're on now. |
|
223 newest = generateLinkForGetArgs(request, base_params, {offset_key : 0, |
|
224 limit_key : limit}) |
|
225 |
286 |
226 content = { |
287 content = { |
227 'idx': idx, |
288 'idx': idx, |
228 'data': data, |
289 'data': data, |
229 'export': export_link, |
290 'export': export_link, |
230 'first': offset+1, |
291 'first': first_displayed_item.link_id, |
231 'last': len(data) > 1 and offset+len(data) or None, |
292 'last': last_displayed_item.link_id, |
232 'logic': logic, |
293 'logic': logic, |
233 'limit': limit, |
294 'limit': limit, |
234 'newest': newest, |
295 'newest': newest, |
235 'next': next, |
296 'next': next, |
236 'pagination_form': pagination_form, |
297 'pagination_form': pagination_form, |