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_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
17 INVALID_PAGE_RAISES_404 = getattr(settings,
18 'PAGINATION_INVALID_PAGE_RAISES_404', False)
19 DISPLAY_PAGE_LINKS = getattr(settings, 'PAGINATION_DISPLAY_PAGE_LINKS', True)
20 PREVIOUS_LINK_DECORATOR = getattr(settings, 'PAGINATION_PREVIOUS_LINK_DECORATOR', "‹‹ ")
21 NEXT_LINK_DECORATOR = getattr(settings, 'PAGINATION_NEXT_LINK_DECORATOR', " ››")
22 DISPLAY_DISABLED_PREVIOUS_LINK = getattr(settings, 'PAGINATION_DISPLAY_DISABLED_PREVIOUS_LINK', False)
23 DISPLAY_DISABLED_NEXT_LINK = getattr(settings, 'PAGINATION_DISPLAY_DISABLED_NEXT_LINK', False)
25 def do_autopaginate(parser, token):
27 Splits the arguments to the autopaginate tag and formats them correctly.
31 autopaginate SOMETHING [PAGINATE_BY] [ORPHANS] [as NAME]
33 # Check whether there are any other autopaginations are later in this template
34 expr = lambda obj: (obj.token_type == TOKEN_BLOCK and \
35 len(obj.split_contents()) > 0 and obj.split_contents()[0] == "autopaginate")
36 multiple_paginations = len(filter(expr, parser.tokens)) > 0
38 i = iter(token.split_contents())
46 assert word == "autopaginate"
47 queryset_var = i.next()
52 paginate_by = int(paginate_by)
59 orphans = int(orphans)
64 context_var = i.next()
67 if queryset_var is None:
68 raise template.TemplateSyntaxError(
69 "Invalid syntax. Proper usage of this tag is: "
70 "{%% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]"
71 " [as CONTEXT_VAR_NAME] %%}"
73 return AutoPaginateNode(queryset_var, multiple_paginations, paginate_by, orphans, context_var)
76 class AutoPaginateNode(template.Node):
78 Emits the required objects to allow for Digg-style pagination.
80 First, it looks in the current context for the variable specified, and using
81 that object, it emits a simple ``Paginator`` and the current page object
82 into the context names ``paginator`` and ``page_obj``, respectively.
84 It will then replace the variable specified with only the objects for the
89 It is recommended to use *{% paginate %}* after using the autopaginate
90 tag. If you choose not to use *{% paginate %}*, make sure to display the
91 list of available pages, or else the application may seem to be buggy.
93 def __init__(self, queryset_var, multiple_paginations, paginate_by=None,
94 orphans=None, context_var=None):
95 if paginate_by is None:
96 paginate_by = DEFAULT_PAGINATION
98 orphans = DEFAULT_ORPHANS
99 self.queryset_var = template.Variable(queryset_var)
100 if isinstance(paginate_by, int):
101 self.paginate_by = paginate_by
103 self.paginate_by = template.Variable(paginate_by)
104 if isinstance(orphans, int):
105 self.orphans = orphans
107 self.orphans = template.Variable(orphans)
108 self.context_var = context_var
109 self.multiple_paginations = multiple_paginations
111 def render(self, context):
112 if self.multiple_paginations or context.has_key('paginator'):
113 page_suffix = '_%s' % self.queryset_var
117 key = self.queryset_var.var
118 value = self.queryset_var.resolve(context)
119 if isinstance(self.paginate_by, int):
120 paginate_by = self.paginate_by
122 paginate_by = self.paginate_by.resolve(context)
123 if isinstance(self.orphans, int):
124 orphans = self.orphans
126 orphans = self.orphans.resolve(context)
127 paginator = Paginator(value, paginate_by, orphans)
129 page_obj = paginator.page(context['request'].page(page_suffix))
131 if INVALID_PAGE_RAISES_404:
132 raise Http404('Invalid page requested. If DEBUG were set to ' +
133 'False, an HTTP 404 page would have been shown instead.')
135 context['invalid_page'] = True
137 if self.context_var is not None:
138 context[self.context_var] = page_obj.object_list
140 context[key] = page_obj.object_list
141 context['paginator'] = paginator
142 context['page_obj'] = page_obj
143 context['page_suffix'] = page_suffix
147 def paginate(context, window=DEFAULT_WINDOW):
149 Renders the ``pagination/pagination.html`` template, resulting in a
150 Digg-like display of the available pages, given the current page. If there
151 are too many pages to be displayed before and after the current page, then
152 elipses will be used to indicate the undisplayed gap between page numbers.
154 Requires one argument, ``context``, which should be a dictionary-like data
155 structure and must contain the following keys:
158 A ``Paginator`` or ``QuerySetPaginator`` object.
161 This should be the result of calling the page method on the
162 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
165 This same ``context`` dictionary-like data structure may also include:
168 A dictionary of all of the **GET** parameters in the current request.
169 This is useful to maintain certain types of state, even when requesting
172 ``pagination_template``
173 A custom template to include in place of the default ``pagination.html``
178 paginator = context['paginator']
179 page_obj = context['page_obj']
180 page_suffix = context.get('page_suffix', '')
181 page_range = paginator.page_range
182 pagination_template = context.get('pagination_template', 'pagination/default.html')
183 # Calculate the record range in the current page for display.
184 records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
185 records['last'] = records['first'] + paginator.per_page - 1
186 if records['last'] + paginator.orphans >= paginator.count:
187 records['last'] = paginator.count
188 # First and last are simply the first *n* pages and the last *n* pages,
189 # where *n* is the current window size.
190 first = set(page_range[:window])
191 last = set(page_range[-window:])
192 # Now we look around our current page, making sure that we don't wrap
194 current_start = page_obj.number-1-window
195 if current_start < 0:
197 current_end = page_obj.number-1+window
200 current = set(page_range[current_start:current_end])
202 # If there's no overlap between the first set of pages and the current
203 # set of pages, then there's a possible need for elusion.
204 if len(first.intersection(current)) == 0:
205 first_list = list(first)
207 second_list = list(current)
209 pages.extend(first_list)
210 diff = second_list[0] - first_list[-1]
211 # If there is a gap of two, between the last page of the first
212 # set and the first page of the current set, then we're missing a
215 pages.append(second_list[0] - 1)
216 # If the difference is just one, then there's nothing to be done,
217 # as the pages need no elusion and are correct.
220 # Otherwise, there's a bigger gap which needs to be signaled for
221 # elusion, by pushing a None value to the page list.
224 pages.extend(second_list)
226 unioned = list(first.union(current))
228 pages.extend(unioned)
229 # If there's no overlap between the current set of pages and the last
230 # set of pages, then there's a possible need for elusion.
231 if len(current.intersection(last)) == 0:
232 second_list = list(last)
234 diff = second_list[0] - pages[-1]
235 # If there is a gap of two, between the last page of the current
236 # set and the first page of the last set, then we're missing a
239 pages.append(second_list[0] - 1)
240 # If the difference is just one, then there's nothing to be done,
241 # as the pages need no elusion and are correct.
244 # Otherwise, there's a bigger gap which needs to be signaled for
245 # elusion, by pushing a None value to the page list.
248 pages.extend(second_list)
250 differenced = list(last.difference(current))
252 pages.extend(differenced)
255 'page_obj': page_obj,
256 'paginator': paginator,
257 'is_paginated': paginator.count > paginator.per_page,
258 'page_suffix': page_suffix,
259 'display_page_links': DISPLAY_PAGE_LINKS,
260 'display_disabled_previous_link': DISPLAY_DISABLED_PREVIOUS_LINK,
261 'display_disabled_next_link': DISPLAY_DISABLED_NEXT_LINK,
262 'previous_link_decorator': PREVIOUS_LINK_DECORATOR,
263 'next_link_decorator': NEXT_LINK_DECORATOR,
264 'pagination_template': pagination_template,
266 if 'request' in context:
267 getvars = context['request'].GET.copy()
268 if 'page%s' % page_suffix in getvars:
269 del getvars['page%s' % page_suffix]
270 if len(getvars.keys()) > 0:
271 to_return['getvars'] = "&%s" % getvars.urlencode()
273 to_return['getvars'] = ''
275 except KeyError, AttributeError:
279 register.inclusion_tag(
280 'pagination/pagination.html', takes_context=True)(paginate)
282 register.tag('autopaginate', do_autopaginate)