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 = 20
14 def do_autopaginate(parser, token):
16 Splits the arguments to the autopaginate tag and formats them correctly.
18 split = token.split_contents()
20 return AutoPaginateNode(split[1])
23 paginate_by = int(split[2])
25 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[2])
26 return AutoPaginateNode(split[1], paginate_by=paginate_by)
28 raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0])
30 class AutoPaginateNode(template.Node):
32 Emits the required objects to allow for Digg-style pagination.
34 First, it looks in the current context for the variable specified. This
35 should be either a QuerySet or a list.
37 1. If it is a QuerySet, this ``AutoPaginateNode`` will emit a
38 ``QuerySetPaginator`` and the current page object into the context names
39 ``paginator`` and ``page_obj``, respectively.
41 2. If it is a list, this ``AutoPaginateNode`` will emit a simple
42 ``Paginator`` and the current page object into the context names
43 ``paginator`` and ``page_obj``, respectively.
45 It will then replace the variable specified with only the objects for the
50 It is recommended to use *{% paginate %}* after using the autopaginate
51 tag. If you choose not to use *{% paginate %}*, make sure to display the
52 list of availabale pages, or else the application may seem to be buggy.
54 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION):
55 self.queryset_var = template.Variable(queryset_var)
56 self.paginate_by = paginate_by
58 def render(self, context):
59 key = self.queryset_var.var
60 value = self.queryset_var.resolve(context)
61 if issubclass(value.__class__, QuerySet):
63 paginator_class = QuerySetPaginator
67 model = value[0].__class__
70 paginator_class = Paginator
71 paginator = paginator_class(value, self.paginate_by)
73 page_obj = paginator.page(context['request'].page)
76 context[key] = page_obj.object_list
77 context['paginator'] = paginator
78 context['page_obj'] = page_obj
81 def paginate(context, window=DEFAULT_WINDOW):
83 Renders the ``pagination/pagination.html`` template, resulting in a
84 Digg-like display of the available pages, given the current page. If there
85 are too many pages to be displayed before and after the current page, then
86 elipses will be used to indicate the undisplayed gap between page numbers.
88 Requires one argument, ``context``, which should be a dictionary-like data
89 structure and must contain the following keys:
92 A ``Paginator`` or ``QuerySetPaginator`` object.
95 This should be the result of calling the page method on the
96 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
99 This same ``context`` dictionary-like data structure may also include:
102 A dictionary of all of the **GET** parameters in the current request.
103 This is useful to maintain certain types of state, even when requesting
107 paginator = context['paginator']
108 page_obj = context['page_obj']
109 page_range = paginator.page_range
110 # First and last are simply the first *n* pages and the last *n* pages,
111 # where *n* is the current window size.
112 first = set(page_range[:window])
113 last = set(page_range[-window:])
114 # Now we look around our current page, making sure that we don't wrap
116 current_start = page_obj.number-1-window
117 if current_start < 0:
119 current_end = page_obj.number-1+window
122 current = set(page_range[current_start:current_end])
124 # If there's no overlap between the first set of pages and the current
125 # set of pages, then there's a possible need for elusion.
126 if len(first.intersection(current)) == 0:
127 first_list = sorted(list(first))
128 second_list = sorted(list(current))
129 pages.extend(first_list)
130 diff = second_list[0] - first_list[-1]
131 # If there is a gap of two, between the last page of the first
132 # set and the first page of the current set, then we're missing a
135 pages.append(second_list[0] - 1)
136 # If the difference is just one, then there's nothing to be done,
137 # as the pages need no elusion and are correct.
140 # Otherwise, there's a bigger gap which needs to be signaled for
141 # elusion, by pushing a None value to the page list.
144 pages.extend(second_list)
146 pages.extend(sorted(list(first.union(current))))
147 # If there's no overlap between the current set of pages and the last
148 # set of pages, then there's a possible need for elusion.
149 if len(current.intersection(last)) == 0:
150 second_list = sorted(list(last))
151 diff = second_list[0] - pages[-1]
152 # If there is a gap of two, between the last page of the current
153 # set and the first page of the last 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 pages.extend(sorted(list(last.difference(current))))
170 'page_obj': page_obj,
171 'paginator': paginator,
172 'is_paginated': paginator.count > paginator.per_page,
174 if 'request' in context:
175 getvars = context['request'].GET.copy()
176 if 'page' in getvars:
178 to_return['getvars'] = "&%s" % getvars.urlencode()
182 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
183 register.tag('autopaginate', do_autopaginate)