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)
20 def do_autopaginate(parser, token):
22 Splits the arguments to the autopaginate tag and formats them correctly.
26 autopaginate SOMETHING [PAGINATE_BY] [ORPHANS] [as NAME]
28 # Check whether there are any other autopaginations are later in this template
29 expr = lambda obj: (obj.token_type == TOKEN_BLOCK and \
30 len(obj.split_contents()) > 0 and obj.split_contents()[0] == "autopaginate")
31 multiple_paginations = len(filter(expr, parser.tokens)) > 0
33 i = iter(token.split_contents())
41 assert word == "autopaginate"
42 queryset_var = i.next()
47 paginate_by = int(paginate_by)
54 orphans = int(orphans)
59 context_var = i.next()
62 if queryset_var is None:
63 raise template.TemplateSyntaxError(
64 "Invalid syntax. Proper usage of this tag is: "
65 "{%% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]"
66 " [as CONTEXT_VAR_NAME] %%}"
68 return AutoPaginateNode(queryset_var, multiple_paginations, paginate_by, orphans, context_var)
71 class AutoPaginateNode(template.Node):
73 Emits the required objects to allow for Digg-style pagination.
75 First, it looks in the current context for the variable specified, and using
76 that object, it emits a simple ``Paginator`` and the current page object
77 into the context names ``paginator`` and ``page_obj``, respectively.
79 It will then replace the variable specified with only the objects for the
84 It is recommended to use *{% paginate %}* after using the autopaginate
85 tag. If you choose not to use *{% paginate %}*, make sure to display the
86 list of available pages, or else the application may seem to be buggy.
88 def __init__(self, queryset_var, multiple_paginations, paginate_by=None,
89 orphans=None, context_var=None):
90 if paginate_by is None:
91 paginate_by = DEFAULT_PAGINATION
93 orphans = DEFAULT_ORPHANS
94 self.queryset_var = template.Variable(queryset_var)
95 if isinstance(paginate_by, int):
96 self.paginate_by = paginate_by
98 self.paginate_by = template.Variable(paginate_by)
99 if isinstance(orphans, int):
100 self.orphans = orphans
102 self.orphans = template.Variable(orphans)
103 self.context_var = context_var
104 self.multiple_paginations = multiple_paginations
106 def render(self, context):
107 if self.multiple_paginations or context.has_key('paginator'):
108 page_suffix = '_%s' % self.queryset_var
112 key = self.queryset_var.var
113 value = self.queryset_var.resolve(context)
114 if isinstance(self.paginate_by, int):
115 paginate_by = self.paginate_by
117 paginate_by = self.paginate_by.resolve(context)
118 if isinstance(self.orphans, int):
119 orphans = self.orphans
121 orphans = self.orphans.resolve(context)
122 paginator = Paginator(value, paginate_by, orphans)
124 page_obj = paginator.page(context['request'].page(page_suffix))
126 if INVALID_PAGE_RAISES_404:
127 raise Http404('Invalid page requested. If DEBUG were set to ' +
128 'False, an HTTP 404 page would have been shown instead.')
130 context['invalid_page'] = True
132 if self.context_var is not None:
133 context[self.context_var] = page_obj.object_list
135 context[key] = page_obj.object_list
136 context['paginator'] = paginator
137 context['page_obj'] = page_obj
138 context['page_suffix'] = page_suffix
142 def paginate(context, window=DEFAULT_WINDOW):
144 Renders the ``pagination/pagination.html`` template, resulting in a
145 Digg-like display of the available pages, given the current page. If there
146 are too many pages to be displayed before and after the current page, then
147 elipses will be used to indicate the undisplayed gap between page numbers.
149 Requires one argument, ``context``, which should be a dictionary-like data
150 structure and must contain the following keys:
153 A ``Paginator`` or ``QuerySetPaginator`` object.
156 This should be the result of calling the page method on the
157 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
160 This same ``context`` dictionary-like data structure may also include:
163 A dictionary of all of the **GET** parameters in the current request.
164 This is useful to maintain certain types of state, even when requesting
168 paginator = context['paginator']
169 page_obj = context['page_obj']
170 page_suffix = context.get('page_suffix', '')
171 page_range = paginator.page_range
172 # First and last are simply the first *n* pages and the last *n* pages,
173 # where *n* is the current window size.
174 first = set(page_range[:window])
175 last = set(page_range[-window:])
176 # Now we look around our current page, making sure that we don't wrap
178 current_start = page_obj.number-1-window
179 if current_start < 0:
181 current_end = page_obj.number-1+window
184 current = set(page_range[current_start:current_end])
186 # If there's no overlap between the first set of pages and the current
187 # set of pages, then there's a possible need for elusion.
188 if len(first.intersection(current)) == 0:
189 first_list = list(first)
191 second_list = list(current)
193 pages.extend(first_list)
194 diff = second_list[0] - first_list[-1]
195 # If there is a gap of two, between the last page of the first
196 # set and the first page of the current set, then we're missing a
199 pages.append(second_list[0] - 1)
200 # If the difference is just one, then there's nothing to be done,
201 # as the pages need no elusion and are correct.
204 # Otherwise, there's a bigger gap which needs to be signaled for
205 # elusion, by pushing a None value to the page list.
208 pages.extend(second_list)
210 unioned = list(first.union(current))
212 pages.extend(unioned)
213 # If there's no overlap between the current set of pages and the last
214 # set of pages, then there's a possible need for elusion.
215 if len(current.intersection(last)) == 0:
216 second_list = list(last)
218 diff = second_list[0] - pages[-1]
219 # If there is a gap of two, between the last page of the current
220 # set and the first page of the last set, then we're missing a
223 pages.append(second_list[0] - 1)
224 # If the difference is just one, then there's nothing to be done,
225 # as the pages need no elusion and are correct.
228 # Otherwise, there's a bigger gap which needs to be signaled for
229 # elusion, by pushing a None value to the page list.
232 pages.extend(second_list)
234 differenced = list(last.difference(current))
236 pages.extend(differenced)
239 'page_obj': page_obj,
240 'paginator': paginator,
241 'is_paginated': paginator.count > paginator.per_page,
242 'page_suffix': page_suffix,
244 if 'request' in context:
245 getvars = context['request'].GET.copy()
246 if 'page%s' % page_suffix in getvars:
247 del getvars['page%s' % page_suffix]
248 if len(getvars.keys()) > 0:
249 to_return['getvars'] = "&%s" % getvars.urlencode()
251 to_return['getvars'] = ''
253 except KeyError, AttributeError:
257 register.inclusion_tag(
258 'pagination/pagination.html', takes_context=True)(paginate)
260 register.tag('autopaginate', do_autopaginate)