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
128 def paginate(context, window=DEFAULT_WINDOW, hashtag=''):
130 Renders the ``pagination/pagination.html`` template, resulting in a
131 Digg-like display of the available pages, given the current page. If there
132 are too many pages to be displayed before and after the current page, then
133 elipses will be used to indicate the undisplayed gap between page numbers.
135 Requires one argument, ``context``, which should be a dictionary-like data
136 structure and must contain the following keys:
139 A ``Paginator`` or ``QuerySetPaginator`` object.
142 This should be the result of calling the page method on the
143 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
146 This same ``context`` dictionary-like data structure may also include:
149 A dictionary of all of the **GET** parameters in the current request.
150 This is useful to maintain certain types of state, even when requesting
154 paginator = context['paginator']
155 page_obj = context['page_obj']
156 page_range = paginator.page_range
157 # Calculate the record range in the current page for display.
158 records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
159 records['last'] = records['first'] + paginator.per_page - 1
160 if records['last'] + paginator.orphans >= paginator.count:
161 records['last'] = paginator.count
162 # First and last are simply the first *n* pages and the last *n* pages,
163 # where *n* is the current window size.
164 first = set(page_range[:window])
165 last = set(page_range[-window:])
166 # Now we look around our current page, making sure that we don't wrap
168 current_start = page_obj.number-1-window
169 if current_start < 0:
171 current_end = page_obj.number-1+window
174 current = set(page_range[current_start:current_end])
176 # If there's no overlap between the first set of pages and the current
177 # set of pages, then there's a possible need for elusion.
178 if len(first.intersection(current)) == 0:
179 first_list = list(first)
181 second_list = list(current)
183 pages.extend(first_list)
184 diff = second_list[0] - first_list[-1]
185 # If there is a gap of two, between the last page of the first
186 # set and the first page of the current set, then we're missing a
189 pages.append(second_list[0] - 1)
190 # If the difference is just one, then there's nothing to be done,
191 # as the pages need no elusion and are correct.
194 # Otherwise, there's a bigger gap which needs to be signaled for
195 # elusion, by pushing a None value to the page list.
198 pages.extend(second_list)
200 unioned = list(first.union(current))
202 pages.extend(unioned)
203 # If there's no overlap between the current set of pages and the last
204 # set of pages, then there's a possible need for elusion.
205 if len(current.intersection(last)) == 0:
206 second_list = list(last)
208 diff = second_list[0] - pages[-1]
209 # If there is a gap of two, between the last page of the current
210 # set and the first page of the last set, then we're missing a
213 pages.append(second_list[0] - 1)
214 # If the difference is just one, then there's nothing to be done,
215 # as the pages need no elusion and are correct.
218 # Otherwise, there's a bigger gap which needs to be signaled for
219 # elusion, by pushing a None value to the page list.
222 pages.extend(second_list)
224 differenced = list(last.difference(current))
226 pages.extend(differenced)
228 'MEDIA_URL': settings.MEDIA_URL,
231 'page_obj': page_obj,
232 'paginator': paginator,
234 'is_paginated': paginator.count > paginator.per_page,
236 if 'request' in context:
237 getvars = context['request'].GET.copy()
238 if 'page' in getvars:
240 if len(getvars.keys()) > 0:
241 to_return['getvars'] = "&%s" % getvars.urlencode()
243 to_return['getvars'] = ''
245 except KeyError, AttributeError:
248 register.inclusion_tag('pagination/pagination.html', takes_context=True)(
250 register.tag('autopaginate', do_autopaginate)