4 from sets import Set as set
6 from django import template
7 from django.template import TOKEN_BLOCK
8 from django.http import Http404
9 from django.core.paginator import Paginator, InvalidPage
10 from django.conf import settings
12 register = template.Library()
14 DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
15 DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
16 DEFAULT_MARGIN = getattr(settings, 'PAGINATION_DEFAULT_MARGIN', DEFAULT_WINDOW)
17 DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
18 INVALID_PAGE_RAISES_404 = getattr(settings,
19 'PAGINATION_INVALID_PAGE_RAISES_404', False)
20 DISPLAY_PAGE_LINKS = getattr(settings, 'PAGINATION_DISPLAY_PAGE_LINKS', True)
21 PREVIOUS_LINK_DECORATOR = getattr(settings, 'PAGINATION_PREVIOUS_LINK_DECORATOR', "‹‹ ")
22 NEXT_LINK_DECORATOR = getattr(settings, 'PAGINATION_NEXT_LINK_DECORATOR', " ››")
23 DISPLAY_DISABLED_PREVIOUS_LINK = getattr(settings, 'PAGINATION_DISPLAY_DISABLED_PREVIOUS_LINK', False)
24 DISPLAY_DISABLED_NEXT_LINK = getattr(settings, 'PAGINATION_DISPLAY_DISABLED_NEXT_LINK', False)
27 def do_autopaginate(parser, token):
29 Splits the arguments to the autopaginate tag and formats them correctly.
33 autopaginate QUERYSET [PAGINATE_BY] [ORPHANS] [as NAME]
35 # Check whether there are any other autopaginations are later in this template
36 expr = lambda obj: (obj.token_type == TOKEN_BLOCK and \
37 len(obj.split_contents()) > 0 and obj.split_contents()[0] == "autopaginate")
38 multiple_paginations = len(filter(expr, parser.tokens)) > 0
40 i = iter(token.split_contents())
48 assert word == "autopaginate"
49 queryset_var = i.next()
54 paginate_by = int(paginate_by)
61 orphans = int(orphans)
66 context_var = i.next()
69 if queryset_var is None:
70 raise template.TemplateSyntaxError(
71 "Invalid syntax. Proper usage of this tag is: "
72 "{%% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]"
73 " [as CONTEXT_VAR_NAME] %%}"
75 return AutoPaginateNode(queryset_var, multiple_paginations, paginate_by, orphans, context_var)
78 class AutoPaginateNode(template.Node):
80 Emits the required objects to allow for Digg-style pagination.
82 First, it looks in the current context for the variable specified, and using
83 that object, it emits a simple ``Paginator`` and the current page object
84 into the context names ``paginator`` and ``page_obj``, respectively.
86 It will then replace the variable specified with only the objects for the
91 It is recommended to use *{% paginate %}* after using the autopaginate
92 tag. If you choose not to use *{% paginate %}*, make sure to display the
93 list of available pages, or else the application may seem to be buggy.
95 def __init__(self, queryset_var, multiple_paginations, paginate_by=None,
96 orphans=None, context_var=None):
97 if paginate_by is None:
98 paginate_by = DEFAULT_PAGINATION
100 orphans = DEFAULT_ORPHANS
101 self.queryset_var = template.Variable(queryset_var)
102 if isinstance(paginate_by, int):
103 self.paginate_by = paginate_by
105 self.paginate_by = template.Variable(paginate_by)
106 if isinstance(orphans, int):
107 self.orphans = orphans
109 self.orphans = template.Variable(orphans)
110 self.context_var = context_var
111 self.multiple_paginations = multiple_paginations
113 def render(self, context):
114 if self.multiple_paginations or context.has_key('paginator'):
115 page_suffix = '_%s' % self.queryset_var
119 key = self.queryset_var.var
120 value = self.queryset_var.resolve(context)
121 if isinstance(self.paginate_by, int):
122 paginate_by = self.paginate_by
124 paginate_by = self.paginate_by.resolve(context)
125 if isinstance(self.orphans, int):
126 orphans = self.orphans
128 orphans = self.orphans.resolve(context)
129 paginator = Paginator(value, paginate_by, orphans)
131 page_obj = paginator.page(context['request'].page(page_suffix))
133 if INVALID_PAGE_RAISES_404:
134 raise Http404('Invalid page requested. If DEBUG were set to ' +
135 'False, an HTTP 404 page would have been shown instead.')
137 context['invalid_page'] = True
139 if self.context_var is not None:
140 context[self.context_var] = page_obj.object_list
142 context[key] = page_obj.object_list
143 context['paginator'] = paginator
144 context['page_obj'] = page_obj
145 context['page_suffix'] = page_suffix
149 def paginate(context, window=DEFAULT_WINDOW, margin=DEFAULT_MARGIN):
151 Renders the ``pagination/pagination.html`` template, resulting in a
152 Digg-like display of the available pages, given the current page. If there
153 are too many pages to be displayed before and after the current page, then
154 elipses will be used to indicate the undisplayed gap between page numbers.
156 Requires one argument, ``context``, which should be a dictionary-like data
157 structure and must contain the following keys:
160 A ``Paginator`` or ``QuerySetPaginator`` object.
163 This should be the result of calling the page method on the
164 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
167 This same ``context`` dictionary-like data structure may also include:
170 A dictionary of all of the **GET** parameters in the current request.
171 This is useful to maintain certain types of state, even when requesting
174 ``pagination_template``
175 A custom template to include in place of the default ``pagination/default.html``
178 Argument ``window`` is number to pages before/after current page. If window
179 exceeds pagination border (1 and end), window is moved to left or right.
181 Argument ``margin``` is number of pages on start/end of pagination.
183 window=2, margin=1, current=6 1 ... 4 5 [6] 7 8 ... 11
184 window=2, margin=0, current=1 [1] 2 3 4 5 ...
185 window=2, margin=0, current=5 ... 3 4 [5] 6 7 ...
186 window=2, margin=0, current=11 ... 7 8 9 10 [11]
190 raise ValueError('Parameter "window" cannot be less than zero')
192 raise ValueError('Parameter "margin" cannot be less than zero')
194 paginator = context['paginator']
195 page_obj = context['page_obj']
196 page_suffix = context.get('page_suffix', '')
197 page_range = paginator.page_range
198 pagination_template = context.get('pagination_template', 'pagination/default.html')
199 # Calculate the record range in the current page for display.
200 records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
201 records['last'] = records['first'] + paginator.per_page - 1
202 if records['last'] + paginator.orphans >= paginator.count:
203 records['last'] = paginator.count
206 window_start = page_obj.number - window - 1
207 window_end = page_obj.number + window
209 # solve if window exceeded page range
211 window_end = window_end - window_start
213 if window_end > paginator.num_pages:
214 window_start = window_start - (window_end - paginator.num_pages)
215 window_end = paginator.num_pages
216 pages = page_range[window_start:window_end]
218 # figure margin and add elipses
221 tmp_pages = set(pages)
222 tmp_pages = tmp_pages.union(page_range[:margin])
223 tmp_pages = tmp_pages.union(page_range[-margin:])
224 tmp_pages = list(tmp_pages)
227 pages.append(tmp_pages[0])
228 for i in range(1, len(tmp_pages)):
229 # figure gap size => add elipses or fill in gap
230 gap = tmp_pages[i] - tmp_pages[i - 1]
234 pages.append(tmp_pages[i] - 1)
235 pages.append(tmp_pages[i])
238 pages.insert(0, None)
239 if pages[-1] != paginator.num_pages:
243 'MEDIA_URL': settings.MEDIA_URL,
244 'STATIC_URL': getattr(settings, "STATIC_URL", None),
245 'display_disabled_next_link': DISPLAY_DISABLED_NEXT_LINK,
246 'display_disabled_previous_link': DISPLAY_DISABLED_PREVIOUS_LINK,
247 'display_page_links': DISPLAY_PAGE_LINKS,
248 'is_paginated': paginator.count > paginator.per_page,
249 'next_link_decorator': NEXT_LINK_DECORATOR,
250 'page_obj': page_obj,
251 'page_suffix': page_suffix,
253 'pagination_template': pagination_template,
254 'paginator': paginator,
255 'previous_link_decorator': PREVIOUS_LINK_DECORATOR,
258 if 'request' in context:
259 getvars = context['request'].GET.copy()
260 if 'page%s' % page_suffix in getvars:
261 del getvars['page%s' % page_suffix]
262 if len(getvars.keys()) > 0:
263 to_return['getvars'] = "&%s" % getvars.urlencode()
265 to_return['getvars'] = ''
267 except (KeyError, AttributeError):
271 register.inclusion_tag(
272 'pagination/pagination.html', takes_context=True)(paginate)
274 register.tag('autopaginate', do_autopaginate)