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, 'PAGINATION_INVALID_PAGE_RAISES_404',
19 def do_autopaginate(parser, token):
21 Splits the arguments to the autopaginate tag and formats them correctly.
23 split = token.split_contents()
26 for i, bit in enumerate(split):
30 if as_index is not None:
32 context_var = split[as_index + 1]
34 raise template.TemplateSyntaxError("Context variable assignment " +\
35 "must take the form of {%% %r object.example_set.all ... as " +\
36 "context_var_name %%}" % split[0])
37 del split[as_index:as_index + 2]
39 return AutoPaginateNode(split[1])
41 return AutoPaginateNode(split[1], paginate_by=split[2],
42 context_var=context_var)
45 orphans = int(split[3])
47 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])
48 return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
49 context_var=context_var)
51 raise template.TemplateSyntaxError('%r tag takes one required ' + \
52 'argument and one optional argument' % split[0])
54 class AutoPaginateNode(template.Node):
56 Emits the required objects to allow for Digg-style pagination.
58 First, it looks in the current context for the variable specified, and using
59 that object, it emits a simple ``Paginator`` and the current page object
60 into the context names ``paginator`` and ``page_obj``, respectively.
62 It will then replace the variable specified with only the objects for the
67 It is recommended to use *{% paginate %}* after using the autopaginate
68 tag. If you choose not to use *{% paginate %}*, make sure to display the
69 list of available pages, or else the application may seem to be buggy.
71 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION,
72 orphans=DEFAULT_ORPHANS, context_var=None):
73 self.queryset_var = template.Variable(queryset_var)
74 if isinstance(paginate_by, int):
75 self.paginate_by = paginate_by
77 self.paginate_by = template.Variable(paginate_by)
78 self.orphans = orphans
79 self.context_var = context_var
81 def render(self, context):
82 key = self.queryset_var.var
83 value = self.queryset_var.resolve(context)
84 if isinstance(self.paginate_by, int):
85 paginate_by = self.paginate_by
87 paginate_by = self.paginate_by.resolve(context)
88 paginator = Paginator(value, paginate_by, self.orphans)
90 page_obj = paginator.page(context['request'].page)
92 if INVALID_PAGE_RAISES_404:
93 raise Http404('Invalid page requested. If DEBUG were set to ' +
94 'False, an HTTP 404 page would have been shown instead.')
96 context['invalid_page'] = True
98 if self.context_var is not None:
99 context[self.context_var] = page_obj.object_list
101 context[key] = page_obj.object_list
102 context['paginator'] = paginator
103 context['page_obj'] = page_obj
106 def paginate(context, window=DEFAULT_WINDOW):
108 Renders the ``pagination/pagination.html`` template, resulting in a
109 Digg-like display of the available pages, given the current page. If there
110 are too many pages to be displayed before and after the current page, then
111 elipses will be used to indicate the undisplayed gap between page numbers.
113 Requires one argument, ``context``, which should be a dictionary-like data
114 structure and must contain the following keys:
117 A ``Paginator`` or ``QuerySetPaginator`` object.
120 This should be the result of calling the page method on the
121 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
124 This same ``context`` dictionary-like data structure may also include:
127 A dictionary of all of the **GET** parameters in the current request.
128 This is useful to maintain certain types of state, even when requesting
132 paginator = context['paginator']
133 page_obj = context['page_obj']
134 page_range = paginator.page_range
135 # First and last are simply the first *n* pages and the last *n* pages,
136 # where *n* is the current window size.
137 first = set(page_range[:window])
138 last = set(page_range[-window:])
139 # Now we look around our current page, making sure that we don't wrap
141 current_start = page_obj.number-1-window
142 if current_start < 0:
144 current_end = page_obj.number-1+window
147 current = set(page_range[current_start:current_end])
149 # If there's no overlap between the first set of pages and the current
150 # set of pages, then there's a possible need for elusion.
151 if len(first.intersection(current)) == 0:
152 first_list = list(first)
154 second_list = list(current)
156 pages.extend(first_list)
157 diff = second_list[0] - first_list[-1]
158 # If there is a gap of two, between the last page of the first
159 # set and the first page of the current set, then we're missing a
162 pages.append(second_list[0] - 1)
163 # If the difference is just one, then there's nothing to be done,
164 # as the pages need no elusion and are correct.
167 # Otherwise, there's a bigger gap which needs to be signaled for
168 # elusion, by pushing a None value to the page list.
171 pages.extend(second_list)
173 unioned = list(first.union(current))
175 pages.extend(unioned)
176 # If there's no overlap between the current set of pages and the last
177 # set of pages, then there's a possible need for elusion.
178 if len(current.intersection(last)) == 0:
179 second_list = list(last)
181 diff = second_list[0] - pages[-1]
182 # If there is a gap of two, between the last page of the current
183 # set and the first page of the last set, then we're missing a
186 pages.append(second_list[0] - 1)
187 # If the difference is just one, then there's nothing to be done,
188 # as the pages need no elusion and are correct.
191 # Otherwise, there's a bigger gap which needs to be signaled for
192 # elusion, by pushing a None value to the page list.
195 pages.extend(second_list)
197 differenced = list(last.difference(current))
199 pages.extend(differenced)
202 'page_obj': page_obj,
203 'paginator': paginator,
204 'is_paginated': paginator.count > paginator.per_page,
206 if 'request' in context:
207 getvars = context['request'].GET.copy()
208 if 'page' in getvars:
210 if len(getvars.keys()) > 0:
211 to_return['getvars'] = "&%s" % getvars.urlencode()
213 to_return['getvars'] = ''
215 except KeyError, AttributeError:
217 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
218 register.tag('autopaginate', do_autopaginate)