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