4 from sets import Set as set
6 from django import template
7 from django.http import Http404
8 from django.core.paginator import Paginator, InvalidPage
9 from django.conf import settings
11 register = template.Library()
13 DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
14 DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
15 DEFAULT_MARGIN = getattr(settings, 'PAGINATION_DEFAULT_MARGIN', DEFAULT_WINDOW)
16 DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
17 INVALID_PAGE_RAISES_404 = getattr(settings,
18 'PAGINATION_INVALID_PAGE_RAISES_404', False)
20 def do_autopaginate(parser, token):
22 Splits the arguments to the autopaginate tag and formats them correctly.
24 split = token.split_contents()
27 for i, bit in enumerate(split):
31 if as_index is not None:
33 context_var = split[as_index + 1]
35 raise template.TemplateSyntaxError("Context variable assignment " +
36 "must take the form of {%% %r object.example_set.all ... as " +
37 "context_var_name %%}" % split[0])
38 del split[as_index:as_index + 2]
40 return AutoPaginateNode(split[1])
42 return AutoPaginateNode(split[1], paginate_by=split[2],
43 context_var=context_var)
46 orphans = int(split[3])
48 raise template.TemplateSyntaxError(u'Got %s, but expected integer.'
50 return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
51 context_var=context_var)
53 raise template.TemplateSyntaxError('%r tag takes one required ' +
54 'argument and one optional argument' % split[0])
56 class AutoPaginateNode(template.Node):
58 Emits the required objects to allow for Digg-style pagination.
60 First, it looks in the current context for the variable specified, and using
61 that object, it emits a simple ``Paginator`` and the current page object
62 into the context names ``paginator`` and ``page_obj``, respectively.
64 It will then replace the variable specified with only the objects for the
69 It is recommended to use *{% paginate %}* after using the autopaginate
70 tag. If you choose not to use *{% paginate %}*, make sure to display the
71 list of available pages, or else the application may seem to be buggy.
73 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION,
74 orphans=DEFAULT_ORPHANS, context_var=None):
75 self.queryset_var = template.Variable(queryset_var)
76 if isinstance(paginate_by, int):
77 self.paginate_by = paginate_by
79 self.paginate_by = template.Variable(paginate_by)
80 self.orphans = orphans
81 self.context_var = context_var
83 def render(self, context):
84 key = self.queryset_var.var
85 value = self.queryset_var.resolve(context)
86 if isinstance(self.paginate_by, int):
87 paginate_by = self.paginate_by
89 paginate_by = self.paginate_by.resolve(context)
90 paginator = Paginator(value, paginate_by, self.orphans)
92 page_obj = paginator.page(context['request'].page)
94 if INVALID_PAGE_RAISES_404:
95 raise Http404('Invalid page requested. If DEBUG were set to ' +
96 'False, an HTTP 404 page would have been shown instead.')
98 context['invalid_page'] = True
100 if self.context_var is not None:
101 context[self.context_var] = page_obj.object_list
103 context[key] = page_obj.object_list
104 context['paginator'] = paginator
105 context['page_obj'] = page_obj
109 def paginate(context, window=DEFAULT_WINDOW, hashtag='', margin=DEFAULT_MARGIN):
111 Renders the ``pagination/pagination.html`` template, resulting in a
112 Digg-like display of the available pages, given the current page. If there
113 are too many pages to be displayed before and after the current page, then
114 elipses will be used to indicate the undisplayed gap between page numbers.
116 Requires one argument, ``context``, which should be a dictionary-like data
117 structure and must contain the following keys:
120 A ``Paginator`` or ``QuerySetPaginator`` object.
123 This should be the result of calling the page method on the
124 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
127 This same ``context`` dictionary-like data structure may also include:
130 A dictionary of all of the **GET** parameters in the current request.
131 This is useful to maintain certain types of state, even when requesting
134 Argument ``window`` is number to pages before/after current page. If window
135 exceed pagination border (1 and end), window is move to left or right.
136 Argument ``margin``` is number of pages on start/end of pagination.
138 window=2, margin=1, current=6 1 ... 4 5 [6] 7 8 ... 11
139 window=2, margin=0, current=1 [1] 2 3 4 5 ...
140 window=2, margin=0, current=5 ... 3 4 [5] 6 7 ...
141 window=2, margin=0, current=11 ... 7 8 9 10 [11]
145 raise Exception, 'Parameter "window" cannot be less than zero'
147 raise Exception, 'Parameter "margin" cannot be less than zero'
150 paginator = context['paginator']
151 page_obj = context['page_obj']
152 page_range = paginator.page_range
153 # Calculate the record range in the current page for display.
154 records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
155 records['last'] = records['first'] + paginator.per_page - 1
156 if records['last'] + paginator.orphans >= paginator.count:
157 records['last'] = paginator.count
160 window_start = page_obj.number - window - 1
161 window_end = page_obj.number + window
162 # solve if window exceeded page range
164 window_end = window_end - window_start
166 if window_end > paginator.num_pages:
167 window_start = window_start - (window_end - paginator.num_pages)
168 window_end = paginator.num_pages
169 pages = page_range[window_start:window_end]
171 # figure margin and add elipses
174 tmp_pages = set(pages)
175 tmp_pages = tmp_pages.union(page_range[:margin])
176 tmp_pages = tmp_pages.union(page_range[-margin:])
177 tmp_pages = list(tmp_pages)
180 pages.append(tmp_pages[0])
181 for i in range(1, len(tmp_pages)):
182 # figure gap size => add elipses or fill in gap
183 gap = tmp_pages[i] - tmp_pages[i - 1]
187 pages.append(tmp_pages[i] - 1)
188 pages.append(tmp_pages[i])
191 pages.insert(0, None)
192 if pages[-1] != paginator.num_pages:
196 'MEDIA_URL': settings.MEDIA_URL,
199 'page_obj': page_obj,
200 'paginator': paginator,
202 'is_paginated': paginator.count > paginator.per_page,
204 if 'request' in context:
205 getvars = context['request'].GET.copy()
206 if 'page' in getvars:
208 if len(getvars.keys()) > 0:
209 to_return['getvars'] = "&%s" % getvars.urlencode()
211 to_return['getvars'] = ''
213 except KeyError, AttributeError:
216 register.inclusion_tag('pagination/pagination.html', takes_context=True)(
218 register.tag('autopaginate', do_autopaginate)