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
10 from django.template.loader import select_template
11 from django.template import Context
13 register = template.Library()
15 DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
16 DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
17 DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
18 INVALID_PAGE_RAISES_404 = getattr(settings,
19 'PAGINATION_INVALID_PAGE_RAISES_404', False)
21 def do_autopaginate(parser, token):
23 Splits the arguments to the autopaginate tag and formats them correctly.
25 split = token.split_contents()
28 for i, bit in enumerate(split):
32 if as_index is not None:
34 context_var = split[as_index + 1]
36 raise template.TemplateSyntaxError("Context variable assignment " +
37 "must take the form of {%% %r object.example_set.all ... as " +
38 "context_var_name %%}" % split[0])
39 del split[as_index:as_index + 2]
41 return AutoPaginateNode(split[1])
43 return AutoPaginateNode(split[1], paginate_by=split[2],
44 context_var=context_var)
47 orphans = int(split[3])
49 raise template.TemplateSyntaxError(u'Got %s, but expected integer.'
51 return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
52 context_var=context_var)
54 raise template.TemplateSyntaxError('%r tag takes one required ' +
55 'argument and one optional argument' % split[0])
57 class AutoPaginateNode(template.Node):
59 Emits the required objects to allow for Digg-style pagination.
61 First, it looks in the current context for the variable specified, and using
62 that object, it emits a simple ``Paginator`` and the current page object
63 into the context names ``paginator`` and ``page_obj``, respectively.
65 It will then replace the variable specified with only the objects for the
70 It is recommended to use *{% paginate %}* after using the autopaginate
71 tag. If you choose not to use *{% paginate %}*, make sure to display the
72 list of available pages, or else the application may seem to be buggy.
74 def __init__(self, queryset_var, paginate_by=DEFAULT_PAGINATION,
75 orphans=DEFAULT_ORPHANS, context_var=None):
76 self.queryset_var = template.Variable(queryset_var)
77 if isinstance(paginate_by, int):
78 self.paginate_by = paginate_by
80 self.paginate_by = template.Variable(paginate_by)
81 self.orphans = orphans
82 self.context_var = context_var
84 def render(self, context):
85 key = self.queryset_var.var
86 value = self.queryset_var.resolve(context)
87 if isinstance(self.paginate_by, int):
88 paginate_by = self.paginate_by
90 paginate_by = self.paginate_by.resolve(context)
91 paginator = Paginator(value, paginate_by, self.orphans)
93 page_obj = paginator.page(context['request'].page)
95 if INVALID_PAGE_RAISES_404:
96 raise Http404('Invalid page requested. If DEBUG were set to ' +
97 'False, an HTTP 404 page would have been shown instead.')
99 context['invalid_page'] = True
101 if self.context_var is not None:
102 context[self.context_var] = page_obj.object_list
104 context[key] = page_obj.object_list
105 context['paginator'] = paginator
106 context['page_obj'] = page_obj
110 class PaginateNode(template.Node):
112 def __init__(self, template=None):
113 self.template = template
115 def render(self, context):
116 template_list = ['pagination/pagination.html']
117 to_return = paginate(context)
120 template_list.insert(0, self.template)
122 t = select_template(template_list)
125 context = Context(to_return)
126 return t.render(context)
129 def do_paginate(parser, token):
131 {% paginate [using] [template] %}
134 {% paginate using paginations/custom_pagination.html %}
136 argv = token.contents.split()
140 raise template.TemplateSyntaxError, "Tag %s takes at most 2 argument." % argv[0]
143 return PaginateNode()
144 if argc == 3 and argv[1] == 'using':
145 return PaginateNode(template=argv[2])
147 raise template.TemplateSyntaxError, "Tag %s is invalid. Please check the syntax" % argv[0]
150 def paginate(context, window=DEFAULT_WINDOW, hashtag=''):
152 Renders the ``pagination/pagination.html`` template, resulting in a
153 Digg-like display of the available pages, given the current page. If there
154 are too many pages to be displayed before and after the current page, then
155 elipses will be used to indicate the undisplayed gap between page numbers.
157 Requires one argument, ``context``, which should be a dictionary-like data
158 structure and must contain the following keys:
161 A ``Paginator`` or ``QuerySetPaginator`` object.
164 This should be the result of calling the page method on the
165 aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
168 This same ``context`` dictionary-like data structure may also include:
171 A dictionary of all of the **GET** parameters in the current request.
172 This is useful to maintain certain types of state, even when requesting
176 paginator = context['paginator']
177 page_obj = context['page_obj']
178 page_range = paginator.page_range
179 # Calculate the record range in the current page for display.
180 records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
181 records['last'] = records['first'] + paginator.per_page - 1
182 if records['last'] + paginator.orphans >= paginator.count:
183 records['last'] = paginator.count
184 # First and last are simply the first *n* pages and the last *n* pages,
185 # where *n* is the current window size.
186 first = set(page_range[:window])
187 last = set(page_range[-window:])
188 # Now we look around our current page, making sure that we don't wrap
190 current_start = page_obj.number-1-window
191 if current_start < 0:
193 current_end = page_obj.number-1+window
196 current = set(page_range[current_start:current_end])
198 # If there's no overlap between the first set of pages and the current
199 # set of pages, then there's a possible need for elusion.
200 if len(first.intersection(current)) == 0:
201 first_list = list(first)
203 second_list = list(current)
205 pages.extend(first_list)
206 diff = second_list[0] - first_list[-1]
207 # If there is a gap of two, between the last page of the first
208 # set and the first page of the current set, then we're missing a
211 pages.append(second_list[0] - 1)
212 # If the difference is just one, then there's nothing to be done,
213 # as the pages need no elusion and are correct.
216 # Otherwise, there's a bigger gap which needs to be signaled for
217 # elusion, by pushing a None value to the page list.
220 pages.extend(second_list)
222 unioned = list(first.union(current))
224 pages.extend(unioned)
225 # If there's no overlap between the current set of pages and the last
226 # set of pages, then there's a possible need for elusion.
227 if len(current.intersection(last)) == 0:
228 second_list = list(last)
230 diff = second_list[0] - pages[-1]
231 # If there is a gap of two, between the last page of the current
232 # set and the first page of the last set, then we're missing a
235 pages.append(second_list[0] - 1)
236 # If the difference is just one, then there's nothing to be done,
237 # as the pages need no elusion and are correct.
240 # Otherwise, there's a bigger gap which needs to be signaled for
241 # elusion, by pushing a None value to the page list.
244 pages.extend(second_list)
246 differenced = list(last.difference(current))
248 pages.extend(differenced)
250 'MEDIA_URL': settings.MEDIA_URL,
253 'page_obj': page_obj,
254 'paginator': paginator,
256 'is_paginated': paginator.count > paginator.per_page,
258 if 'request' in context:
259 to_return['request'] = context['request']
260 getvars = context['request'].GET.copy()
261 if 'page' in getvars:
263 if len(getvars.keys()) > 0:
264 to_return['getvars'] = "&%s" % getvars.urlencode()
266 to_return['getvars'] = ''
268 except (KeyError, AttributeError):
271 #register.inclusion_tag('pagination/pagination.html', takes_context=True)(
273 register.tag('paginate', do_paginate)
274 register.tag('autopaginate', do_autopaginate)