1 # Copyright (c) 2008, Eric Florenzano
2 # Copyright (C) 2010, 2011 Linaro Limited
5 # Redistribution and use in source and binary forms, with or without
6 # modification, are permitted provided that the following conditions are
9 # * Redistributions of source code must retain the above copyright
10 # notice, this list of conditions and the following disclaimer.
11 # * Redistributions in binary form must reproduce the above
12 # copyright notice, this list of conditions and the following
13 # disclaimer in the documentation and/or other materials provided
14 # with the distribution.
15 # * Neither the name of the author nor the names of other
16 # contributors may be used to endorse or promote products derived
17 # from this software without specific prior written permission.
19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 from django import template
33 from django.conf import settings
34 from django.core.exceptions import ImproperlyConfigured
35 from django.core.paginator import Paginator, InvalidPage
36 from django.http import Http404
37 from django.template import TOKEN_BLOCK
39 # TODO, import this normally later on
40 from linaro_django_pagination.settings import *
43 def do_autopaginate(parser, token):
45 Splits the arguments to the autopaginate tag and formats them correctly.
49 autopaginate QUERYSET [PAGINATE_BY] [ORPHANS] [as NAME]
51 # Check whether there are any other autopaginations are later in this template
52 expr = lambda obj: (obj.token_type == TOKEN_BLOCK and \
53 len(obj.split_contents()) > 0 and obj.split_contents()[0] == "autopaginate")
54 multiple_paginations = len(filter(expr, parser.tokens)) > 0
56 i = iter(token.split_contents())
64 assert word == "autopaginate"
65 queryset_var = i.next()
70 paginate_by = int(paginate_by)
77 orphans = int(orphans)
82 context_var = i.next()
85 if queryset_var is None:
86 raise template.TemplateSyntaxError(
87 "Invalid syntax. Proper usage of this tag is: "
88 "{%% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]"
89 " [as CONTEXT_VAR_NAME] %%}"
91 return AutoPaginateNode(queryset_var, multiple_paginations, paginate_by, orphans, context_var)
94 class AutoPaginateNode(template.Node):
96 Emits the required objects to allow for Digg-style pagination.
98 First, it looks in the current context for the variable specified, and using
99 that object, it emits a simple ``Paginator`` and the current page object
100 into the context names ``paginator`` and ``page_obj``, respectively.
102 It will then replace the variable specified with only the objects for the
107 It is recommended to use *{% paginate %}* after using the autopaginate
108 tag. If you choose not to use *{% paginate %}*, make sure to display the
109 list of available pages, or else the application may seem to be buggy.
111 def __init__(self, queryset_var, multiple_paginations, paginate_by=None,
112 orphans=None, context_var=None):
113 if paginate_by is None:
114 paginate_by = DEFAULT_PAGINATION
116 orphans = DEFAULT_ORPHANS
117 self.queryset_var = template.Variable(queryset_var)
118 if isinstance(paginate_by, int):
119 self.paginate_by = paginate_by
121 self.paginate_by = template.Variable(paginate_by)
122 if isinstance(orphans, int):
123 self.orphans = orphans
125 self.orphans = template.Variable(orphans)
126 self.context_var = context_var
127 self.multiple_paginations = multiple_paginations
129 def render(self, context):
130 if self.multiple_paginations or "paginator" in context:
131 page_suffix = '_%s' % self.queryset_var
135 key = self.queryset_var.var
136 value = self.queryset_var.resolve(context)
137 if isinstance(self.paginate_by, int):
138 paginate_by = self.paginate_by
140 paginate_by = self.paginate_by.resolve(context)
141 if isinstance(self.orphans, int):
142 orphans = self.orphans
144 orphans = self.orphans.resolve(context)
145 paginator = Paginator(value, paginate_by, orphans)
147 page_obj = paginator.page(context['request'].page(page_suffix))
149 if INVALID_PAGE_RAISES_404:
150 raise Http404('Invalid page requested. If DEBUG were set to ' +
151 'False, an HTTP 404 page would have been shown instead.')
153 context['invalid_page'] = True
155 if self.context_var is not None:
156 context[self.context_var] = page_obj.object_list
158 context[key] = page_obj.object_list
159 context['paginator'] = paginator
160 context['page_obj'] = page_obj
161 context['page_suffix'] = page_suffix
165 def paginate(context, window=DEFAULT_WINDOW, margin=DEFAULT_MARGIN):
167 Renders the ``pagination/pagination.html`` template, resulting in a
168 Digg-like display of the available pages, given the current page. If there
169 are too many pages to be displayed before and after the current page, then
170 elipses will be used to indicate the undisplayed gap between page numbers.
172 Requires one argument, ``context``, which should be a dictionary-like data
173 structure and must contain the following keys:
176 A ``Paginator`` or ``QuerySetPaginator`` object.
179 This should be the result of calling the page method on the
180 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
183 This same ``context`` dictionary-like data structure may also include:
186 A dictionary of all of the **GET** parameters in the current request.
187 This is useful to maintain certain types of state, even when requesting
190 ``pagination_template``
191 A custom template to include in place of the default ``pagination/default.html``
194 Argument ``window`` is number to pages before/after current page. If window
195 exceeds pagination border (1 and end), window is moved to left or right.
197 Argument ``margin``` is number of pages on start/end of pagination.
199 window=2, margin=1, current=6 1 ... 4 5 [6] 7 8 ... 11
200 window=2, margin=0, current=1 [1] 2 3 4 5 ...
201 window=2, margin=0, current=5 ... 3 4 [5] 6 7 ...
202 window=2, margin=0, current=11 ... 7 8 9 10 [11]
206 raise ValueError('Parameter "window" cannot be less than zero')
208 raise ValueError('Parameter "margin" cannot be less than zero')
210 paginator = context['paginator']
211 page_obj = context['page_obj']
212 page_suffix = context.get('page_suffix', '')
213 page_range = paginator.page_range
214 pagination_template = context.get('pagination_template', 'pagination/default.html')
215 # Calculate the record range in the current page for display.
216 records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
217 records['last'] = records['first'] + paginator.per_page - 1
218 if records['last'] + paginator.orphans >= paginator.count:
219 records['last'] = paginator.count
222 window_start = page_obj.number - window - 1
223 window_end = page_obj.number + window
225 # solve if window exceeded page range
227 window_end = window_end - window_start
229 if window_end > paginator.num_pages:
230 window_start = window_start - (window_end - paginator.num_pages)
231 window_end = paginator.num_pages
232 pages = page_range[window_start:window_end]
234 # figure margin and add elipses
237 tmp_pages = set(pages)
238 tmp_pages = tmp_pages.union(page_range[:margin])
239 tmp_pages = tmp_pages.union(page_range[-margin:])
240 tmp_pages = list(tmp_pages)
243 pages.append(tmp_pages[0])
244 for i in range(1, len(tmp_pages)):
245 # figure gap size => add elipses or fill in gap
246 gap = tmp_pages[i] - tmp_pages[i - 1]
250 pages.append(tmp_pages[i] - 1)
251 pages.append(tmp_pages[i])
254 pages.insert(0, None)
255 if pages[-1] != paginator.num_pages:
259 'MEDIA_URL': settings.MEDIA_URL,
260 'STATIC_URL': getattr(settings, "STATIC_URL", None),
261 'display_disabled_next_link': DISPLAY_DISABLED_NEXT_LINK,
262 'display_disabled_previous_link': DISPLAY_DISABLED_PREVIOUS_LINK,
263 'display_page_links': DISPLAY_PAGE_LINKS,
264 'is_paginated': paginator.count > paginator.per_page,
265 'next_link_decorator': NEXT_LINK_DECORATOR,
266 'page_obj': page_obj,
267 'page_suffix': page_suffix,
269 'pagination_template': pagination_template,
270 'paginator': paginator,
271 'previous_link_decorator': PREVIOUS_LINK_DECORATOR,
274 if 'request' in context:
275 getvars = context['request'].GET.copy()
276 if 'page%s' % page_suffix in getvars:
277 del getvars['page%s' % page_suffix]
278 if len(getvars.keys()) > 0:
279 to_return['getvars'] = "&%s" % getvars.urlencode()
281 to_return['getvars'] = ''
283 except (KeyError, AttributeError):
287 register = template.Library()
288 register.inclusion_tag(
289 'pagination/pagination.html', takes_context=True)(paginate)
290 register.tag('autopaginate', do_autopaginate)