4 from sets import Set as set
5 from django import template
6 from django.db.models.query import QuerySet
7 from django.core.paginator import Paginator, InvalidPage
8 from django.conf import settings
10 register = template.Library()
12 DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
13 DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
14 DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
16 def do_autopaginate(parser, token):
18 Splits the arguments to the autopaginate tag and formats them correctly.
20 split = token.split_contents()
23 for i, bit in enumerate(split):
27 if as_index is not None:
29 context_var = split[as_index + 1]
31 raise template.TemplateSyntaxError("Context variable assignment " +\
32 "must take the form of {%% %r object.example_set.all ... as " +\
33 "context_var_name %%}" % split[0])
34 del split[as_index:as_index + 2]
36 return AutoPaginateNode(split[1])
38 return AutoPaginateNode(split[1], paginate_by=split[2],
39 context_var=context_var)
42 orphans = int(split[3])
44 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])
45 return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
46 context_var=context_var)
48 raise template.TemplateSyntaxError('%r tag takes one required ' + \
49 'argument and one optional argument' % split[0])
51 class AutoPaginateNode(template.Node):
53 Emits the required objects to allow for Digg-style pagination.
55 First, it looks in the current context for the variable specified, and using
56 that object, it emits a simple ``Paginator`` and the current page object
57 into the context names ``paginator`` and ``page_obj``, respectively.
59 It will then replace the variable specified with only the objects for the
64 It is recommended to use *{% paginate %}* after using the autopaginate
65 tag. If you choose not to use *{% paginate %}*, make sure to display the
66 list of available pages, or else the application may seem to be buggy.
68 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION,
69 orphans=DEFAULT_ORPHANS, context_var=None):
70 self.queryset_var = template.Variable(queryset_var)
71 if isinstance(paginate_by, int):
72 self.paginate_by = paginate_by
74 self.paginate_by = template.Variable(paginate_by)
75 self.orphans = orphans
76 self.context_var = context_var
78 def render(self, context):
79 key = self.queryset_var.var
80 value = self.queryset_var.resolve(context)
81 if isinstance(self.paginate_by, int):
82 paginate_by = self.paginate_by
84 paginate_by = self.paginate_by.resolve(context)
85 paginator = Paginator(value, paginate_by, self.orphans)
87 page_obj = paginator.page(context['request'].page)
90 context['invalid_page'] = True
92 if self.context_var is not None:
93 context[self.context_var] = page_obj.object_list
95 context[key] = page_obj.object_list
96 context['paginator'] = paginator
97 context['page_obj'] = page_obj
100 def paginate(context, window=DEFAULT_WINDOW):
102 Renders the ``pagination/pagination.html`` template, resulting in a
103 Digg-like display of the available pages, given the current page. If there
104 are too many pages to be displayed before and after the current page, then
105 elipses will be used to indicate the undisplayed gap between page numbers.
107 Requires one argument, ``context``, which should be a dictionary-like data
108 structure and must contain the following keys:
111 A ``Paginator`` or ``QuerySetPaginator`` object.
114 This should be the result of calling the page method on the
115 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
118 This same ``context`` dictionary-like data structure may also include:
121 A dictionary of all of the **GET** parameters in the current request.
122 This is useful to maintain certain types of state, even when requesting
126 paginator = context['paginator']
127 page_obj = context['page_obj']
128 page_range = paginator.page_range
129 # First and last are simply the first *n* pages and the last *n* pages,
130 # where *n* is the current window size.
131 first = set(page_range[:window])
132 last = set(page_range[-window:])
133 # Now we look around our current page, making sure that we don't wrap
135 current_start = page_obj.number-1-window
136 if current_start < 0:
138 current_end = page_obj.number-1+window
141 current = set(page_range[current_start:current_end])
143 # If there's no overlap between the first set of pages and the current
144 # set of pages, then there's a possible need for elusion.
145 if len(first.intersection(current)) == 0:
146 first_list = list(first)
148 second_list = list(second)
150 pages.extend(first_list)
151 diff = second_list[0] - first_list[-1]
152 # If there is a gap of two, between the last page of the first
153 # set and the first page of the current set, then we're missing a
156 pages.append(second_list[0] - 1)
157 # If the difference is just one, then there's nothing to be done,
158 # as the pages need no elusion and are correct.
161 # Otherwise, there's a bigger gap which needs to be signaled for
162 # elusion, by pushing a None value to the page list.
165 pages.extend(second_list)
167 unioned = list(first.union(current))
169 pages.extend(unioned)
170 # If there's no overlap between the current set of pages and the last
171 # set of pages, then there's a possible need for elusion.
172 if len(current.intersection(last)) == 0:
173 second_list = list(last)
175 diff = second_list[0] - pages[-1]
176 # If there is a gap of two, between the last page of the current
177 # set and the first page of the last set, then we're missing a
180 pages.append(second_list[0] - 1)
181 # If the difference is just one, then there's nothing to be done,
182 # as the pages need no elusion and are correct.
185 # Otherwise, there's a bigger gap which needs to be signaled for
186 # elusion, by pushing a None value to the page list.
189 pages.extend(second_list)
191 differenced = list(last.difference(current))
193 pages.extend(differenced)
196 'page_obj': page_obj,
197 'paginator': paginator,
198 'is_paginated': paginator.count > paginator.per_page,
200 if 'request' in context:
201 getvars = context['request'].GET.copy()
202 if 'page' in getvars:
204 if len(getvars.keys()) > 0:
205 to_return['getvars'] = "&%s" % getvars.urlencode()
207 to_return['getvars'] = ''
209 except KeyError, AttributeError:
211 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
212 register.tag('autopaginate', do_autopaginate)