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, QuerySetPaginator, 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()
22 return AutoPaginateNode(split[1])
25 paginate_by = int(split[2])
27 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
28 return AutoPaginateNode(split[1], paginate_by=paginate_by)
31 paginate_by = int(split[2])
33 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
35 orphans = int(split[3])
37 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])
38 return AutoPaginateNode(split[1], paginate_by=paginate_by, orphans=orphans)
40 raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0])
42 class AutoPaginateNode(template.Node):
44 Emits the required objects to allow for Digg-style pagination.
46 First, it looks in the current context for the variable specified. This
47 should be either a QuerySet or a list.
49 1. If it is a QuerySet, this ``AutoPaginateNode`` will emit a
50 ``QuerySetPaginator`` and the current page object into the context names
51 ``paginator`` and ``page_obj``, respectively.
53 2. If it is a list, this ``AutoPaginateNode`` will emit a simple
54 ``Paginator`` and the current page object into the context names
55 ``paginator`` and ``page_obj``, respectively.
57 It will then replace the variable specified with only the objects for the
62 It is recommended to use *{% paginate %}* after using the autopaginate
63 tag. If you choose not to use *{% paginate %}*, make sure to display the
64 list of availabale pages, or else the application may seem to be buggy.
66 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION, orphans=DEFAULT_ORPHANS):
67 self.queryset_var = template.Variable(queryset_var)
68 self.paginate_by = paginate_by
69 self.orphans = orphans
71 def render(self, context):
72 key = self.queryset_var.var
73 value = self.queryset_var.resolve(context)
74 if issubclass(value.__class__, QuerySet):
76 paginator_class = QuerySetPaginator
80 model = value[0].__class__
83 paginator_class = Paginator
84 paginator = paginator_class(value, self.paginate_by, self.orphans)
86 page_obj = paginator.page(context['request'].page)
89 context['invalid_page'] = True
91 context[key] = page_obj.object_list
92 context['paginator'] = paginator
93 context['page_obj'] = page_obj
96 def paginate(context, window=DEFAULT_WINDOW):
98 Renders the ``pagination/pagination.html`` template, resulting in a
99 Digg-like display of the available pages, given the current page. If there
100 are too many pages to be displayed before and after the current page, then
101 elipses will be used to indicate the undisplayed gap between page numbers.
103 Requires one argument, ``context``, which should be a dictionary-like data
104 structure and must contain the following keys:
107 A ``Paginator`` or ``QuerySetPaginator`` object.
110 This should be the result of calling the page method on the
111 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
114 This same ``context`` dictionary-like data structure may also include:
117 A dictionary of all of the **GET** parameters in the current request.
118 This is useful to maintain certain types of state, even when requesting
122 paginator = context['paginator']
123 page_obj = context['page_obj']
124 page_range = paginator.page_range
125 # First and last are simply the first *n* pages and the last *n* pages,
126 # where *n* is the current window size.
127 first = set(page_range[:window])
128 last = set(page_range[-window:])
129 # Now we look around our current page, making sure that we don't wrap
131 current_start = page_obj.number-1-window
132 if current_start < 0:
134 current_end = page_obj.number-1+window
137 current = set(page_range[current_start:current_end])
139 # If there's no overlap between the first set of pages and the current
140 # set of pages, then there's a possible need for elusion.
141 if len(first.intersection(current)) == 0:
142 first_list = sorted(list(first))
143 second_list = sorted(list(current))
144 pages.extend(first_list)
145 diff = second_list[0] - first_list[-1]
146 # If there is a gap of two, between the last page of the first
147 # set and the first page of the current set, then we're missing a
150 pages.append(second_list[0] - 1)
151 # If the difference is just one, then there's nothing to be done,
152 # as the pages need no elusion and are correct.
155 # Otherwise, there's a bigger gap which needs to be signaled for
156 # elusion, by pushing a None value to the page list.
159 pages.extend(second_list)
161 pages.extend(sorted(list(first.union(current))))
162 # If there's no overlap between the current set of pages and the last
163 # set of pages, then there's a possible need for elusion.
164 if len(current.intersection(last)) == 0:
165 second_list = sorted(list(last))
166 diff = second_list[0] - pages[-1]
167 # If there is a gap of two, between the last page of the current
168 # set and the first page of the last set, then we're missing a
171 pages.append(second_list[0] - 1)
172 # If the difference is just one, then there's nothing to be done,
173 # as the pages need no elusion and are correct.
176 # Otherwise, there's a bigger gap which needs to be signaled for
177 # elusion, by pushing a None value to the page list.
180 pages.extend(second_list)
182 pages.extend(sorted(list(last.difference(current))))
185 'page_obj': page_obj,
186 'paginator': paginator,
187 'is_paginated': paginator.count > paginator.per_page,
189 if 'request' in context:
190 getvars = context['request'].GET.copy()
191 if 'page' in getvars:
193 if len(getvars.keys()) > 0:
194 to_return['getvars'] = "&%s" % getvars.urlencode()
196 to_return['getvars'] = ''
200 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
201 register.tag('autopaginate', do_autopaginate)