]> git.openstreetmap.org Git - osqa.git/blob - forum/utils/pagination.py
Adds a couple of options to manage the "accepting answers" workflow.
[osqa.git] / forum / utils / pagination.py
1 import math
2 from django.utils.datastructures import SortedDict
3 from django import template
4 from django.core.paginator import Paginator, EmptyPage
5 from django.utils.translation import ugettext as _
6 from django.http import Http404
7 from django.utils.safestring import mark_safe
8 from django.utils.html import strip_tags
9
10 import logging
11
12 class SortBase(object):
13     def __init__(self, label, description=''):
14         self.label = label
15         self.description = description
16
17 class SimpleSort(SortBase):
18     def __init__(self, label, order_by, description=''):
19         super(SimpleSort, self) .__init__(label, description)
20         self.order_by = order_by
21
22     def _get_order_by(self):
23         return isinstance(self.order_by, (list, tuple)) and self.order_by or [self.order_by]
24
25     def apply(self, objects):
26         return objects.order_by(*self._get_order_by())
27
28 class PaginatorContext(object):
29     visible_page_range = 5
30     outside_page_range = 1
31
32     base_path = None
33
34     def __init__(self, id, sort_methods=None, default_sort=None, force_sort = None,
35                  pagesizes=None, default_pagesize=None, prefix=''):
36         self.id = id
37         if sort_methods:
38             self.has_sort = True
39             self.sort_methods = SortedDict(data=sort_methods)
40
41             if not default_sort:
42                 default_sort = sort_methods[0][0]
43
44             self.default_sort = default_sort
45         else:
46             self.has_sort = False
47
48
49         if pagesizes:
50             self.has_pagesize = True
51             self.pagesizes = pagesizes
52
53             if not default_pagesize:
54                 self.default_pagesize = pagesizes[int(math.ceil(float(len(pagesizes)) / 2)) - 1]
55             else:
56                 self.default_pagesize = default_pagesize
57         else:
58             self.has_pagesize = False
59
60         self.force_sort = force_sort
61         self.prefix = prefix
62
63     def preferences(self, request):
64         if request.user.is_authenticated():
65             if request.user.prop.pagination:
66                 preferences = request.user.prop.pagination.get(self.id, {})
67             else:
68                 preferences = {}
69         else:
70             preferences = request.session.get('paginator_%s%s' % (self.prefix, self.id), {})
71
72         return preferences
73
74     def set_preferences(self, request, preferences):
75         if request.user.is_authenticated():
76             all_preferences = request.user.prop.pagination or {}
77             all_preferences[self.id] = preferences
78             request.user.prop.pagination = all_preferences
79         else:
80             request.session['paginator_%s%s' % (self.prefix, self.id)] = preferences
81
82     def pagesize(self, request, session_prefs=None):
83         if not session_prefs:
84             session_prefs = self.preferences(request)
85
86
87         if self.has_pagesize:
88             if request.GET.get(self.PAGESIZE, None):
89                 try:
90                     pagesize = int(request.GET[self.PAGESIZE])
91                 except ValueError:
92                     logging.error('Found invalid page size "%s", loading %s, refered by %s' % (
93                         request.GET.get(self.PAGESIZE, ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
94                     ))
95                     raise Http404()
96
97                 session_prefs[self.PAGESIZE] = pagesize
98             else:
99                 pagesize = session_prefs.get(self.PAGESIZE, self.default_pagesize)
100
101             if not pagesize in self.pagesizes:
102                 pagesize = self.default_pagesize
103         else:
104             pagesize = 30
105
106         return pagesize
107
108     def page(self, request):
109         try:
110             return int(request.GET.get(self.PAGE, 1))
111         except ValueError:
112             logging.error('Found invalid page number "%s", loading %s, refered by %s' % (
113                 request.GET.get(self.PAGE, ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
114             ))
115             raise Http404()
116
117     def sort(self, request, session_prefs=None):
118         if not session_prefs:
119             session_prefs = self.preferences(request)
120
121         sort = None
122         sticky = request.user.is_authenticated() and request.user.prop.preferences and request.user.prop.preferences.get('sticky_sorts', False)
123
124         if self.has_sort:
125             if request.GET.get(self.SORT, None):
126                 sort = request.GET[self.SORT]
127
128                 if sticky:
129                     session_prefs[self.SORT] = sort
130             else:
131                 sort = self.force_sort or (sticky and session_prefs.get(self.SORT, None)) or self.default_sort
132
133             if not sort in self.sort_methods:
134                 sort = self.default_sort
135
136         return sort
137
138     def sorted(self, objects, request, session_prefs=None):
139         sort = self.sort(request, session_prefs)
140
141         if sort:
142             objects = self.sort_methods[sort].apply(objects)
143
144         return sort, objects
145
146     @property
147     def PAGESIZE(self):
148         return self.prefix and "%s_%s" % (self.prefix, _('pagesize')) or _('pagesize')
149
150     @property
151     def PAGE(self):
152         return self.prefix and "%s_%s" % (self.prefix, _('page')) or _('page')
153
154     @property
155     def SORT(self):
156         return self.prefix and "%s_%s" % (self.prefix, _('sort')) or _('sort')
157
158 page_numbers_template = template.loader.get_template('paginator/page_numbers.html')
159 page_sizes_template = template.loader.get_template('paginator/page_sizes.html')
160 sort_tabs_template = template.loader.get_template('paginator/sort_tabs.html')
161
162 def paginated(request, paginators, tpl_context):
163     if len(paginators) == 2 and isinstance(paginators[0], basestring):
164         paginators = (paginators,)
165
166     for list_name, context in paginators:
167         tpl_context[list_name] = _paginated(request, tpl_context[list_name], context)
168
169     return tpl_context
170
171 def _paginated(request, objects, context):
172     session_prefs = context.preferences(request)
173
174     pagesize = context.pagesize(request, session_prefs)
175     page = context.page(request)
176     sort, objects = context.sorted(objects, request, session_prefs)
177
178     paginator = Paginator(objects, pagesize)
179
180     try:
181         page_obj = paginator.page(page)
182     except EmptyPage:
183         logging.error('Found invalid page number "%s", loading %s, refered by %s' % (
184             request.GET.get(context.PAGE, ''), request.path, request.META.get('HTTP_REFERER', 'UNKNOWN')
185         ))
186         raise Http404()
187
188     if context.base_path:
189         base_path = context.base_path
190     else:
191         base_path = request.path
192         get_params = ["%s=%s" % (k, v) for k, v in request.GET.items() if not k in (context.PAGE, context.PAGESIZE, context.SORT)]
193
194         if get_params:
195             base_path += "?" + "&".join(get_params)
196
197     url_joiner = "?" in base_path and "&" or "?"
198
199
200     def get_page():
201         object_list = page_obj.object_list
202
203         if hasattr(object_list, 'lazy'):
204             return object_list.lazy()
205         return object_list
206     paginator.page = get_page()
207
208     total_pages = paginator.num_pages
209
210     if total_pages > 1:
211         def page_nums():
212             total_pages = paginator.num_pages
213
214             has_previous = page > 1
215             has_next = page < total_pages
216
217             range_start = page - context.visible_page_range / 2
218             range_end = page + context.visible_page_range / 2
219
220             if range_start < 1:
221                 range_end = context.visible_page_range
222                 range_start = 1
223
224             if range_end > total_pages:
225                 range_start = total_pages - context.visible_page_range + 1
226                 range_end = total_pages
227                 if range_start < 1:
228                     range_start = 1
229
230             page_numbers = []
231
232             if sort:
233                 url_builder = lambda n: mark_safe("%s%s%s=%s&%s=%s" % (base_path, url_joiner, context.SORT, sort, context.PAGE, n))
234             else:
235                 url_builder = lambda n: mark_safe("%s%s%s=%s" % (base_path, url_joiner, context.PAGE, n))
236
237             if range_start > (context.outside_page_range + 1):
238                 page_numbers.append([(n, url_builder(n)) for n in range(1, context.outside_page_range + 1)])
239                 page_numbers.append(None)
240             elif range_start > 1:
241                 page_numbers.append([(n, url_builder(n)) for n in range(1, range_start)])
242
243             page_numbers.append([(n, url_builder(n)) for n in range(range_start, range_end + 1)])
244
245             if range_end < (total_pages - context.outside_page_range):
246                 page_numbers.append(None)
247                 page_numbers.append([(n, url_builder(n)) for n in range(total_pages - context.outside_page_range + 1, total_pages + 1)])
248             elif range_end < total_pages:
249                 page_numbers.append([(n, url_builder(n)) for n in range(range_end + 1, total_pages + 1)])
250
251             return page_numbers_template.render(template.Context({
252                 'has_previous': has_previous,
253                 'previous_url': has_previous and url_builder(page - 1) or None,
254                 'has_next': has_next,
255                 'next_url': has_next and url_builder(page + 1) or None,
256                 'current': page,
257                 'page_numbers': page_numbers
258             }))
259         paginator.page_numbers = page_nums
260     else:
261         paginator.page_numbers = ''
262
263     if pagesize:
264         def page_sizes():
265             if sort:
266                 url_builder = lambda s: mark_safe("%s%s%s=%s&%s=%s" % (base_path, url_joiner, context.SORT, sort, context.PAGESIZE, s))
267             else:
268                 url_builder = lambda s: mark_safe("%s%s%s=%s" % (base_path, url_joiner, context.PAGESIZE, s))
269
270             sizes = [(s, url_builder(s)) for s in context.pagesizes]
271
272             return page_sizes_template.render(template.Context({
273                 'current': pagesize,
274                 'sizes': sizes
275             }))
276
277         paginator.page_sizes = page_sizes
278     else:
279         paginator.page_sizes = ''
280
281     if sort:
282         def sort_tabs():
283             url_builder = lambda s: mark_safe("%s%s%s=%s" % (base_path, url_joiner, context.SORT, s))
284             sorts = [(n, s.label, url_builder(n), strip_tags(s.description)) for n, s in context.sort_methods.items()]
285
286             return sort_tabs_template.render(template.Context({
287                 'current': sort,
288                 'sorts': sorts,
289                 'sticky': session_prefs.get('sticky_sort', False)
290             }))
291         paginator.sort_tabs = sort_tabs()
292         paginator.sort_description = mark_safe(context.sort_methods[sort].description)
293     else:
294         paginator.sort_tabs = paginator.sort_description = ''
295
296     context.set_preferences(request, session_prefs)
297     objects.paginator = paginator
298     return objects