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
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()
21 return AutoPaginateNode(split[1])
24 paginate_by = int(split[2])
26 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
27 return AutoPaginateNode(split[1], paginate_by=paginate_by)
30 paginate_by = int(split[2])
32 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
34 orphans = int(split[3])
36 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])
37 return AutoPaginateNode(split[1], paginate_by=paginate_by, orphans=orphans)
39 raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0])
41 class AutoPaginateNode(template.Node):
43 Emits the required objects to allow for Digg-style pagination.
45 First, it looks in the current context for the variable specified. This
46 should be either a QuerySet or a list.
48 1. If it is a QuerySet, this ``AutoPaginateNode`` will emit a
49 ``QuerySetPaginator`` and the current page object into the context names
50 ``paginator`` and ``page_obj``, respectively.
52 2. If it is a list, this ``AutoPaginateNode`` will emit a simple
53 ``Paginator`` and the current page object into the context names
54 ``paginator`` and ``page_obj``, respectively.
56 It will then replace the variable specified with only the objects for the
61 It is recommended to use *{% paginate %}* after using the autopaginate
62 tag. If you choose not to use *{% paginate %}*, make sure to display the
63 list of availabale pages, or else the application may seem to be buggy.
65 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION, orphans=DEFAULT_ORPHANS):
66 self.queryset_var = template.Variable(queryset_var)
67 self.paginate_by = paginate_by
68 self.orphans = orphans
70 def render(self, context):
71 key = self.queryset_var.var
72 value = self.queryset_var.resolve(context)
73 if issubclass(value.__class__, QuerySet):
75 paginator_class = QuerySetPaginator
79 model = value[0].__class__
82 paginator_class = Paginator
83 paginator = paginator_class(value, self.paginate_by, self.orphans)
85 page_obj = paginator.page(context['request'].page)
88 context['invalid_page'] = True
90 context[key] = page_obj.object_list
91 context['paginator'] = paginator
92 context['page_obj'] = page_obj
95 def paginate(context, window=DEFAULT_WINDOW):
97 Renders the ``pagination/pagination.html`` template, resulting in a
98 Digg-like display of the available pages, given the current page. If there
99 are too many pages to be displayed before and after the current page, then
100 elipses will be used to indicate the undisplayed gap between page numbers.
102 Requires one argument, ``context``, which should be a dictionary-like data
103 structure and must contain the following keys:
106 A ``Paginator`` or ``QuerySetPaginator`` object.
109 This should be the result of calling the page method on the
110 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
113 This same ``context`` dictionary-like data structure may also include:
116 A dictionary of all of the **GET** parameters in the current request.
117 This is useful to maintain certain types of state, even when requesting
121 paginator = context['paginator']
122 page_obj = context['page_obj']
123 page_range = paginator.page_range
124 # First and last are simply the first *n* pages and the last *n* pages,
125 # where *n* is the current window size.
126 first = set(page_range[:window])
127 last = set(page_range[-window:])
128 # Now we look around our current page, making sure that we don't wrap
130 current_start = page_obj.number-1-window
131 if current_start < 0:
133 current_end = page_obj.number-1+window
136 current = set(page_range[current_start:current_end])
138 # If there's no overlap between the first set of pages and the current
139 # set of pages, then there's a possible need for elusion.
140 if len(first.intersection(current)) == 0:
141 first_list = sorted(list(first))
142 second_list = sorted(list(current))
143 pages.extend(first_list)
144 diff = second_list[0] - first_list[-1]
145 # If there is a gap of two, between the last page of the first
146 # set and the first page of the current set, then we're missing a
149 pages.append(second_list[0] - 1)
150 # If the difference is just one, then there's nothing to be done,
151 # as the pages need no elusion and are correct.
154 # Otherwise, there's a bigger gap which needs to be signaled for
155 # elusion, by pushing a None value to the page list.
158 pages.extend(second_list)
160 pages.extend(sorted(list(first.union(current))))
161 # If there's no overlap between the current set of pages and the last
162 # set of pages, then there's a possible need for elusion.
163 if len(current.intersection(last)) == 0:
164 second_list = sorted(list(last))
165 diff = second_list[0] - pages[-1]
166 # If there is a gap of two, between the last page of the current
167 # set and the first page of the last set, then we're missing a
170 pages.append(second_list[0] - 1)
171 # If the difference is just one, then there's nothing to be done,
172 # as the pages need no elusion and are correct.
175 # Otherwise, there's a bigger gap which needs to be signaled for
176 # elusion, by pushing a None value to the page list.
179 pages.extend(second_list)
181 pages.extend(sorted(list(last.difference(current))))
184 'page_obj': page_obj,
185 'paginator': paginator,
186 'is_paginated': paginator.count > paginator.per_page,
188 if 'request' in context:
189 getvars = context['request'].GET.copy()
190 if 'page' in getvars:
192 if len(getvars.keys()) > 0:
193 to_return['getvars'] = "&%s" % getvars.urlencode()
195 to_return['getvars'] = ''
199 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
200 register.tag('autopaginate', do_autopaginate)