3 # Copyright 2008 the Melange authors.
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
17 """Helpers used to render lists.
21 '"Chen Lunpeng" <forever.clp@gmail.com>',
22 '"Pawel Solyga" <pawel.solyga@gmail.com>',
26 from soc
.logic
import dicts
27 from soc
.logic
.models
.user
import logic
as user_logic
29 import soc
.views
.helper
.forms
32 DEF_DEFAULT_PAGINATION
= 50
33 DEF_MAX_PAGINATION
= 100
34 DEF_MAX_DEV_PAGINATION
= 1000
36 DEF_PAGINATION_CHOICES
= [
37 ('10', '10 items per page'),
38 ('25', '25 items per page'),
39 ('50', '50 items per page'),
40 ('100', '100 items per page'),
43 DEF_DEVELOPER_CHOICES
= [
44 ('500', '500 items per page'),
45 ('1000', '1000 items per page'),
49 def getPreferredListPagination(user
=None):
50 """Returns User's preferred list pagination limit.
53 user: User entity containing the list pagination preference;
54 default is None, to use the current logged-in User
56 # TODO: eventually this limit should be a User profile preference
57 # (stored in the site-wide User Model) preference
58 return DEF_DEFAULT_PAGINATION
61 OFFSET_KEY
= 'offset_%d'
62 LIMIT_KEY
= 'limit_%d'
65 def makeOffsetKey(limit_idx
):
66 return OFFSET_KEY
% limit_idx
69 def makeLimitKey(limit_idx
):
70 return LIMIT_KEY
% limit_idx
73 def getListParameters(request
, list_index
):
74 """Retrieves, converts and validates values for one list
77 list_index, int: which list to get the values for.
78 (there may be multiple lists on one page, which are multiplexed
82 a dictionary of str -> str. field name -> field value.
85 offset
= request
.GET
.get(makeOffsetKey(list_index
))
86 limit
= request
.GET
.get(makeLimitKey(list_index
))
102 limit
= getPreferredListPagination()
104 offset
= max(0, offset
)
105 limit
= max(1, limit
)
107 if user_logic
.isDeveloper():
108 limit
= min(DEF_MAX_DEV_PAGINATION
, limit
)
110 limit
= min(DEF_MAX_PAGINATION
, limit
)
112 return dict(limit
=limit
, offset
=offset
)
115 def generateLinkFromGetArgs(request
, offset_and_limits
):
116 """Constructs the get args for the url.
119 args
= ["%s=%s" % (k
, v
) for k
, v
in offset_and_limits
.iteritems()]
120 link_suffix
= '?' + '&'.join(args
)
122 return request
.path
+ link_suffix
125 def generateLinkForRequest(request
, base_params
, updated_params
):
126 """Create a link to the same page as request but with different params
129 request: the request for the page
130 base_params: the base parameters
131 updated_params: the parameters to update
133 params
= base_params
.copy()
134 params
.update(updated_params
)
135 return generateLinkFromGetArgs(request
, params
)
138 def getListContent(request
, params
, filter=None, order
=None,
139 idx
=0, need_content
=False):
140 """Returns a dict with fields used for rendering lists.
142 TODO(dbentley): we need better terminology. List, in this context, can have
144 Meaning 1: the underlying list, which may be very large.
145 Meaning 2: the returned list, which is at most 'limit' items.
148 request: the Django HTTP request object
149 params: a dict with params for the View this list belongs to
150 filter: a filter for this list
151 order: the order which should be used for the list (in getForFields format)
152 idx: the index of this list
153 need_content: iff True will return None if there is no data
156 A dictionary with the following values set:
159 'data': list data to be displayed
160 'main': url to list main template
161 'pagination': url to list pagination template
162 'row': url to list row template
163 'heading': url to list heading template
164 'limit': max amount of items per page,
165 'newest': url to first page of the list
166 'prev': url to previous page
167 'next': url to next page
168 'first': offset of the first item in the list
169 'last': offset of the last item in the list
172 # TODO(dbentley): this appears to be unnecessary indirection,
173 # as we only use this logic for getForFields, which is never overridden
174 logic
= params
['logic']
176 limit_key
, offset_key
= makeLimitKey(idx
), makeOffsetKey(idx
)
178 list_params
= getListParameters(request
, idx
)
179 limit
, offset
= list_params
['limit'], list_params
['offset']
180 pagination_form
= makePaginationForm(request
, list_params
['limit'],
183 # Fetch one more to see if there should be a 'next' link
184 data
= logic
.getForFields(filter=filter, limit
=limit
+1, offset
=offset
,
187 if need_content
and not data
:
190 more
= len(data
) > limit
195 newest
= next
= prev
= export_link
= ''
197 base_params
= dict(i
for i
in request
.GET
.iteritems() if
198 i
[0].startswith('offset_') or i
[0].startswith('limit_'))
200 if params
.get('list_key_order'):
201 export_link
= generateLinkForRequest(request
, base_params
, {'export': idx
})
204 # TODO(dbentley): here we need to implement a new field "last_key"
205 next
= generateLinkForRequest(request
, base_params
,
206 {offset_key
: offset
+ limit
,
210 # TODO(dbentley): here we need to implement previous in the good way.
211 prev
= generateLinkForRequest(request
, base_params
,
212 {offset_key
: max(0, 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).
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
= generateLinkForRequest(request
, base_params
, {offset_key
: 0,
229 'export': export_link
,
231 'last': len(data
) > 1 and offset
+len(data
) or None,
236 'pagination_form': pagination_form
,
240 updates
= dicts
.rename(params
, params
['list_params'])
241 content
.update(updates
)
246 def makePaginationForm(
247 request
, limit
, arg_name
, choices
=DEF_PAGINATION_CHOICES
,
248 field_name_fmt
=soc
.views
.helper
.forms
.DEF_SELECT_QUERY_ARG_FIELD_NAME_FMT
):
249 """Returns a customized pagination limit selection form.
252 request: the standard Django HTTP request object
253 limit: the initial value of the selection control
254 arg_name: see soc.views.helper.forms.makeSelectQueryArgForm(); default is 'limit'
255 choices: see soc.views.helper.forms.makeSelectQueryArgForm(); default is
256 DEF_PAGINATION_CHOICES
257 field_name_fmt: see soc.views.helper.forms.makeSelectQueryArgForm()
259 choices
= makeNewPaginationChoices(limit
=limit
, choices
=choices
)
261 return soc
.views
.helper
.forms
.makeSelectQueryArgForm(
262 request
, arg_name
, limit
, choices
)
265 def makeNewPaginationChoices(limit
=DEF_DEFAULT_PAGINATION
,
266 choices
=DEF_PAGINATION_CHOICES
):
267 """Updates the pagination limit selection form.
270 limit: the initial value of the selection control;
271 default is DEF_DEFAULT_PAGINATION
272 choices: see soc.views.helper.forms.makeSelectQueryArgForm();
273 default is DEF_PAGINATION_CHOICES
276 a new pagination choices list if limit is not in
277 DEF_PAGINATION_CHOICES, or DEF_PAGINATION_CHOICES otherwise
281 new_choice
= (str(limit
), '%s items per page' % limit
)
283 new_choices
.append(new_choice
)
284 new_choices
.extend(choices
)
286 if user_logic
.isDeveloper():
287 new_choices
.extend(DEF_DEVELOPER_CHOICES
)
289 new_choices
= set(new_choices
)
291 return sorted(new_choices
, key
=lambda (x
, y
): int(x
))