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()
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, and using
47 that object, it emits a simple ``Paginator`` and the current page object
48 into the context names ``paginator`` and ``page_obj``, respectively.
50 It will then replace the variable specified with only the objects for the
55 It is recommended to use *{% paginate %}* after using the autopaginate
56 tag. If you choose not to use *{% paginate %}*, make sure to display the
57 list of available pages, or else the application may seem to be buggy.
59 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION, orphans=DEFAULT_ORPHANS):
60 self.queryset_var = template.Variable(queryset_var)
61 self.paginate_by = paginate_by
62 self.orphans = orphans
64 def render(self, context):
65 key = self.queryset_var.var
66 value = self.queryset_var.resolve(context)
67 paginator = Paginator(value, self.paginate_by, self.orphans)
69 page_obj = paginator.page(context['request'].page)
72 context['invalid_page'] = True
74 context[key] = page_obj.object_list
75 context['paginator'] = paginator
76 context['page_obj'] = page_obj
79 def paginate(context, window=DEFAULT_WINDOW):
81 Renders the ``pagination/pagination.html`` template, resulting in a
82 Digg-like display of the available pages, given the current page. If there
83 are too many pages to be displayed before and after the current page, then
84 elipses will be used to indicate the undisplayed gap between page numbers.
86 Requires one argument, ``context``, which should be a dictionary-like data
87 structure and must contain the following keys:
90 A ``Paginator`` or ``QuerySetPaginator`` object.
93 This should be the result of calling the page method on the
94 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
97 This same ``context`` dictionary-like data structure may also include:
100 A dictionary of all of the **GET** parameters in the current request.
101 This is useful to maintain certain types of state, even when requesting
105 paginator = context['paginator']
106 page_obj = context['page_obj']
107 page_range = paginator.page_range
108 # First and last are simply the first *n* pages and the last *n* pages,
109 # where *n* is the current window size.
110 first = set(page_range[:window])
111 last = set(page_range[-window:])
112 # Now we look around our current page, making sure that we don't wrap
114 current_start = page_obj.number-1-window
115 if current_start < 0:
117 current_end = page_obj.number-1+window
120 current = set(page_range[current_start:current_end])
122 # If there's no overlap between the first set of pages and the current
123 # set of pages, then there's a possible need for elusion.
124 if len(first.intersection(current)) == 0:
125 first_list = list(first)
127 second_list = list(second)
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 unioned = list(first.union(current))
148 pages.extend(unioned)
149 # If there's no overlap between the current set of pages and the last
150 # set of pages, then there's a possible need for elusion.
151 if len(current.intersection(last)) == 0:
152 second_list = list(last)
154 diff = second_list[0] - pages[-1]
155 # If there is a gap of two, between the last page of the current
156 # set and the first page of the last set, then we're missing a
159 pages.append(second_list[0] - 1)
160 # If the difference is just one, then there's nothing to be done,
161 # as the pages need no elusion and are correct.
164 # Otherwise, there's a bigger gap which needs to be signaled for
165 # elusion, by pushing a None value to the page list.
168 pages.extend(second_list)
170 differenced = list(last.difference(current))
172 pages.extend(differenced)
175 'page_obj': page_obj,
176 'paginator': paginator,
177 'is_paginated': paginator.count > paginator.per_page,
179 if 'request' in context:
180 getvars = context['request'].GET.copy()
181 if 'page' in getvars:
183 if len(getvars.keys()) > 0:
184 to_return['getvars'] = "&%s" % getvars.urlencode()
186 to_return['getvars'] = ''
190 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
191 register.tag('autopaginate', do_autopaginate)