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])
24 return AutoPaginateNode(split[1], paginate_by=split[2])
27 orphans = int(split[3])
29 raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])
30 return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans)
32 raise template.TemplateSyntaxError('%r tag takes one required argument and one optional argument' % split[0])
34 class AutoPaginateNode(template.Node):
36 Emits the required objects to allow for Digg-style pagination.
38 First, it looks in the current context for the variable specified, and using
39 that object, it emits a simple ``Paginator`` and the current page object
40 into the context names ``paginator`` and ``page_obj``, respectively.
42 It will then replace the variable specified with only the objects for the
47 It is recommended to use *{% paginate %}* after using the autopaginate
48 tag. If you choose not to use *{% paginate %}*, make sure to display the
49 list of available pages, or else the application may seem to be buggy.
51 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION, orphans=DEFAULT_ORPHANS):
52 self.queryset_var = template.Variable(queryset_var)
53 if isinstance(paginate_by, int):
54 self.paginate_by = paginate_by
56 self.paginate_by = template.Variable(paginate_by)
57 self.orphans = orphans
59 def render(self, context):
60 key = self.queryset_var.var
61 value = self.queryset_var.resolve(context)
62 if isinstance(self.paginate_by, int):
63 paginate_by = self.paginate_by
65 paginate_by = self.paginate_by.resolve(context)
66 paginator = Paginator(value, paginate_by, self.orphans)
68 page_obj = paginator.page(context['request'].page)
71 context['invalid_page'] = True
73 context[key] = page_obj.object_list
74 context['paginator'] = paginator
75 context['page_obj'] = page_obj
78 def paginate(context, window=DEFAULT_WINDOW):
80 Renders the ``pagination/pagination.html`` template, resulting in a
81 Digg-like display of the available pages, given the current page. If there
82 are too many pages to be displayed before and after the current page, then
83 elipses will be used to indicate the undisplayed gap between page numbers.
85 Requires one argument, ``context``, which should be a dictionary-like data
86 structure and must contain the following keys:
89 A ``Paginator`` or ``QuerySetPaginator`` object.
92 This should be the result of calling the page method on the
93 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
96 This same ``context`` dictionary-like data structure may also include:
99 A dictionary of all of the **GET** parameters in the current request.
100 This is useful to maintain certain types of state, even when requesting
104 paginator = context['paginator']
105 page_obj = context['page_obj']
106 page_range = paginator.page_range
107 # First and last are simply the first *n* pages and the last *n* pages,
108 # where *n* is the current window size.
109 first = set(page_range[:window])
110 last = set(page_range[-window:])
111 # Now we look around our current page, making sure that we don't wrap
113 current_start = page_obj.number-1-window
114 if current_start < 0:
116 current_end = page_obj.number-1+window
119 current = set(page_range[current_start:current_end])
121 # If there's no overlap between the first set of pages and the current
122 # set of pages, then there's a possible need for elusion.
123 if len(first.intersection(current)) == 0:
124 first_list = list(first)
126 second_list = list(second)
128 pages.extend(first_list)
129 diff = second_list[0] - first_list[-1]
130 # If there is a gap of two, between the last page of the first
131 # set and the first page of the current set, then we're missing a
134 pages.append(second_list[0] - 1)
135 # If the difference is just one, then there's nothing to be done,
136 # as the pages need no elusion and are correct.
139 # Otherwise, there's a bigger gap which needs to be signaled for
140 # elusion, by pushing a None value to the page list.
143 pages.extend(second_list)
145 unioned = list(first.union(current))
147 pages.extend(unioned)
148 # If there's no overlap between the current set of pages and the last
149 # set of pages, then there's a possible need for elusion.
150 if len(current.intersection(last)) == 0:
151 second_list = list(last)
153 diff = second_list[0] - pages[-1]
154 # If there is a gap of two, between the last page of the current
155 # set and the first page of the last set, then we're missing a
158 pages.append(second_list[0] - 1)
159 # If the difference is just one, then there's nothing to be done,
160 # as the pages need no elusion and are correct.
163 # Otherwise, there's a bigger gap which needs to be signaled for
164 # elusion, by pushing a None value to the page list.
167 pages.extend(second_list)
169 differenced = list(last.difference(current))
171 pages.extend(differenced)
174 'page_obj': page_obj,
175 'paginator': paginator,
176 'is_paginated': paginator.count > paginator.per_page,
178 if 'request' in context:
179 getvars = context['request'].GET.copy()
180 if 'page' in getvars:
182 if len(getvars.keys()) > 0:
183 to_return['getvars'] = "&%s" % getvars.urlencode()
185 to_return['getvars'] = ''
187 except KeyError, AttributeError:
189 register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
190 register.tag('autopaginate', do_autopaginate)