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