4 from sets import Set as set
6 from django import template
7 from django.http import Http404
8 from django.core.paginator import Paginator, InvalidPage
9 from django.conf import settings
11 register = template.Library()
13 DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
14 DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
15 DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
16 INVALID_PAGE_RAISES_404 = getattr(settings,
17 'PAGINATION_INVALID_PAGE_RAISES_404', False)
19 def do_autopaginate(parser, token):
21 Splits the arguments to the autopaginate tag and formats them correctly.
25 autopaginate SOMETHING [PAGINATE_BY] [ORPHANS] [as NAME]
27 i = iter(token.split_contents())
35 assert word == "autopaginate"
36 queryset_var = i.next()
41 paginate_by = int(paginate_by)
48 orphans = int(orphans)
53 context_var = i.next()
56 if queryset_var is None:
57 raise template.TemplateSyntaxError(
58 "Invalid syntax. Proper usage of this tag is: "
59 "{%% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]"
60 " [as CONTEXT_VAR_NAME] %%}"
62 return AutoPaginateNode(queryset_var, paginate_by, orphans, context_var)
64 class AutoPaginateNode(template.Node):
66 Emits the required objects to allow for Digg-style pagination.
68 First, it looks in the current context for the variable specified, and using
69 that object, it emits a simple ``Paginator`` and the current page object
70 into the context names ``paginator`` and ``page_obj``, respectively.
72 It will then replace the variable specified with only the objects for the
77 It is recommended to use *{% paginate %}* after using the autopaginate
78 tag. If you choose not to use *{% paginate %}*, make sure to display the
79 list of available pages, or else the application may seem to be buggy.
81 def __init__(self, queryset_var, paginate_by=None,
82 orphans=None, context_var=None):
83 if paginate_by is None:
84 paginate_by = DEFAULT_PAGINATION
86 orphans = DEFAULT_ORPHANS
87 self.queryset_var = template.Variable(queryset_var)
88 if isinstance(paginate_by, int):
89 self.paginate_by = paginate_by
91 self.paginate_by = template.Variable(paginate_by)
92 if isinstance(orphans, int):
93 self.orphans = orphans
95 self.orphans = template.Variable(orphans)
96 self.context_var = context_var
98 def render(self, context):
99 key = self.queryset_var.var
100 value = self.queryset_var.resolve(context)
101 if isinstance(self.paginate_by, int):
102 paginate_by = self.paginate_by
104 paginate_by = self.paginate_by.resolve(context)
105 if isinstance(self.orphans, int):
106 orphans = self.orphans
108 orphans = self.orphans.resolve(context)
109 paginator = Paginator(value, paginate_by, orphans)
111 page_obj = paginator.page(context['request'].page)
113 if INVALID_PAGE_RAISES_404:
114 raise Http404('Invalid page requested. If DEBUG were set to ' +
115 'False, an HTTP 404 page would have been shown instead.')
117 context['invalid_page'] = True
119 if self.context_var is not None:
120 context[self.context_var] = page_obj.object_list
122 context[key] = page_obj.object_list
123 context['paginator'] = paginator
124 context['page_obj'] = page_obj
127 def paginate(context, window=DEFAULT_WINDOW):
129 Renders the ``pagination/pagination.html`` template, resulting in a
130 Digg-like display of the available pages, given the current page. If there
131 are too many pages to be displayed before and after the current page, then
132 elipses will be used to indicate the undisplayed gap between page numbers.
134 Requires one argument, ``context``, which should be a dictionary-like data
135 structure and must contain the following keys:
138 A ``Paginator`` or ``QuerySetPaginator`` object.
141 This should be the result of calling the page method on the
142 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
145 This same ``context`` dictionary-like data structure may also include:
148 A dictionary of all of the **GET** parameters in the current request.
149 This is useful to maintain certain types of state, even when requesting
153 paginator = context['paginator']
154 page_obj = context['page_obj']
155 page_range = paginator.page_range
156 # Calculate the record range in the current page for display.
157 records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
158 records['last'] = records['first'] + paginator.per_page - 1
159 if records['last'] + paginator.orphans >= paginator.count:
160 records['last'] = paginator.count
161 # First and last are simply the first *n* pages and the last *n* pages,
162 # where *n* is the current window size.
163 first = set(page_range[:window])
164 last = set(page_range[-window:])
165 # Now we look around our current page, making sure that we don't wrap
167 current_start = page_obj.number-1-window
168 if current_start < 0:
170 current_end = page_obj.number-1+window
173 current = set(page_range[current_start:current_end])
175 # If there's no overlap between the first set of pages and the current
176 # set of pages, then there's a possible need for elusion.
177 if len(first.intersection(current)) == 0:
178 first_list = list(first)
180 second_list = list(current)
182 pages.extend(first_list)
183 diff = second_list[0] - first_list[-1]
184 # If there is a gap of two, between the last page of the first
185 # set and the first page of the current set, then we're missing a
188 pages.append(second_list[0] - 1)
189 # If the difference is just one, then there's nothing to be done,
190 # as the pages need no elusion and are correct.
193 # Otherwise, there's a bigger gap which needs to be signaled for
194 # elusion, by pushing a None value to the page list.
197 pages.extend(second_list)
199 unioned = list(first.union(current))
201 pages.extend(unioned)
202 # If there's no overlap between the current set of pages and the last
203 # set of pages, then there's a possible need for elusion.
204 if len(current.intersection(last)) == 0:
205 second_list = list(last)
207 diff = second_list[0] - pages[-1]
208 # If there is a gap of two, between the last page of the current
209 # set and the first page of the last set, then we're missing a
212 pages.append(second_list[0] - 1)
213 # If the difference is just one, then there's nothing to be done,
214 # as the pages need no elusion and are correct.
217 # Otherwise, there's a bigger gap which needs to be signaled for
218 # elusion, by pushing a None value to the page list.
221 pages.extend(second_list)
223 differenced = list(last.difference(current))
225 pages.extend(differenced)
227 'MEDIA_URL': settings.MEDIA_URL,
230 'page_obj': page_obj,
231 'paginator': paginator,
232 'is_paginated': paginator.count > paginator.per_page,
234 if 'request' in context:
235 getvars = context['request'].GET.copy()
236 if 'page' in getvars:
238 if len(getvars.keys()) > 0:
239 to_return['getvars'] = "&%s" % getvars.urlencode()
241 to_return['getvars'] = ''
243 except KeyError, AttributeError:
246 register.inclusion_tag('pagination/pagination.html', takes_context=True)(
248 register.tag('autopaginate', do_autopaginate)