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