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