4 from sets import Set as set
5 from django import template
6 from django.core.paginator import Paginator, InvalidPage
7 from django.conf import settings
9 register = template.Library()
11 DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
12 DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
13 DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
15 def do_autopaginate(parser, token):
17 Splits the arguments to the autopaginate tag and formats them correctly.
19 split = token.split_contents()
22 for i, bit in enumerate(split):
26 if as_index is not None:
28 context_var = split[as_index + 1]
30 raise template.TemplateSyntaxError("Context variable assignment " +\
31 "must take the form of {%% %r object.example_set.all ... as " +\
32 "context_var_name %%}" % split[0])
33 del split[as_index:as_index + 2]
35 return AutoPaginateNode(split[1])
37 return AutoPaginateNode(split[1], paginate_by=split[2],
38 context_var=context_var)
41 orphans = int(split[3])
43 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])
44 return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
45 context_var=context_var)
47 raise template.TemplateSyntaxError('%r tag takes one required ' + \
48 'argument and one optional argument' % split[0])
50 class AutoPaginateNode(template.Node):
52 Emits the required objects to allow for Digg-style pagination.
54 First, it looks in the current context for the variable specified, and using
55 that object, it emits a simple ``Paginator`` and the current page object
56 into the context names ``paginator`` and ``page_obj``, respectively.
58 It will then replace the variable specified with only the objects for the
63 It is recommended to use *{% paginate %}* after using the autopaginate
64 tag. If you choose not to use *{% paginate %}*, make sure to display the
65 list of available pages, or else the application may seem to be buggy.
67 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION,
68 orphans=DEFAULT_ORPHANS, context_var=None):
69 self.queryset_var = template.Variable(queryset_var)
70 if isinstance(paginate_by, int):
71 self.paginate_by = paginate_by
73 self.paginate_by = template.Variable(paginate_by)
74 self.orphans = orphans
75 self.context_var = context_var
77 def render(self, context):
78 key = self.queryset_var.var
79 value = self.queryset_var.resolve(context)
80 if isinstance(self.paginate_by, int):
81 paginate_by = self.paginate_by
83 paginate_by = self.paginate_by.resolve(context)
84 paginator = Paginator(value, paginate_by, self.orphans)
86 page_obj = paginator.page(context['request'].page)
89 context['invalid_page'] = True
91 if self.context_var is not None:
92 context[self.context_var] = page_obj.object_list
94 context[key] = page_obj.object_list
95 context['paginator'] = paginator
96 context['page_obj'] = page_obj
99 def paginate(context, window=DEFAULT_WINDOW):
101 Renders the ``pagination/pagination.html`` template, resulting in a
102 Digg-like display of the available pages, given the current page. If there
103 are too many pages to be displayed before and after the current page, then
104 elipses will be used to indicate the undisplayed gap between page numbers.
106 Requires one argument, ``context``, which should be a dictionary-like data
107 structure and must contain the following keys:
110 A ``Paginator`` or ``QuerySetPaginator`` object.
113 This should be the result of calling the page method on the
114 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
117 This same ``context`` dictionary-like data structure may also include:
120 A dictionary of all of the **GET** parameters in the current request.
121 This is useful to maintain certain types of state, even when requesting
125 paginator = context['paginator']
126 page_obj = context['page_obj']
127 page_range = paginator.page_range
128 # First and last are simply the first *n* pages and the last *n* pages,
129 # where *n* is the current window size.
130 first = set(page_range[:window])
131 last = set(page_range[-window:])
132 # Now we look around our current page, making sure that we don't wrap
134 current_start = page_obj.number-1-window
135 if current_start < 0:
137 current_end = page_obj.number-1+window
140 current = set(page_range[current_start:current_end])
142 # If there's no overlap between the first set of pages and the current
143 # set of pages, then there's a possible need for elusion.
144 if len(first.intersection(current)) == 0:
145 first_list = list(first)
147 second_list = list(current)
149 pages.extend(first_list)
150 diff = second_list[0] - first_list[-1]
151 # If there is a gap of two, between the last page of the first
152 # set and the first page of the current set, then we're missing a
155 pages.append(second_list[0] - 1)
156 # If the difference is just one, then there's nothing to be done,
157 # as the pages need no elusion and are correct.
160 # Otherwise, there's a bigger gap which needs to be signaled for
161 # elusion, by pushing a None value to the page list.
164 pages.extend(second_list)
166 unioned = list(first.union(current))
168 pages.extend(unioned)
169 # If there's no overlap between the current set of pages and the last
170 # set of pages, then there's a possible need for elusion.
171 if len(current.intersection(last)) == 0:
172 second_list = list(last)
174 diff = second_list[0] - pages[-1]
175 # If there is a gap of two, between the last page of the current
176 # set and the first page of the last set, then we're missing a
179 pages.append(second_list[0] - 1)
180 # If the difference is just one, then there's nothing to be done,
181 # as the pages need no elusion and are correct.
184 # Otherwise, there's a bigger gap which needs to be signaled for
185 # elusion, by pushing a None value to the page list.
188 pages.extend(second_list)
190 differenced = list(last.difference(current))
192 pages.extend(differenced)
195 'page_obj': page_obj,
196 'paginator': paginator,
197 'is_paginated': paginator.count > paginator.per_page,
199 if 'request' in context:
200 getvars = context['request'].GET.copy()
201 if 'page' in getvars:
203 if len(getvars.keys()) > 0:
204 to_return['getvars'] = "&%s" % getvars.urlencode()
206 to_return['getvars'] = ''
208 except KeyError, AttributeError:
210 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
211 register.tag('autopaginate', do_autopaginate)