Merge branch 'turkish' of git://github.com/amiroff/django-pagination
[django-pagination.git] / linaro_django_pagination / templatetags / pagination_tags.py
1 try:
2     set
3 except NameError:
4     from sets import Set as set
5
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
11 register = template.Library()
12
13 DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
14 DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
15 DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
16 INVALID_PAGE_RAISES_404 = getattr(settings,
17     'PAGINATION_INVALID_PAGE_RAISES_404', False)
18
19 def do_autopaginate(parser, token):
20     """
21     Splits the arguments to the autopaginate tag and formats them correctly.
22
23     Syntax is:
24
25         autopaginate SOMETHING [PAGINATE_BY] [ORPHANS] [as NAME]
26     """
27     i = iter(token.split_contents())
28     paginate_by = None
29     queryset_var = None
30     context_var = None
31     orphans = None
32     word = None
33     try:
34         word = i.next()
35         assert word == "autopaginate"
36         queryset_var = i.next()
37         word = i.next()
38         if word != "as":
39             paginate_by = word
40             try:
41                 paginate_by = int(paginate_by)
42             except ValueError:
43                 pass
44             word = i.next()
45         if word != "as":
46             orphans = word
47             try:
48                 orphans = int(orphans)
49             except ValueError:
50                 pass
51             word = i.next()
52         assert word == "as"
53         context_var = i.next()
54     except StopIteration:
55         pass
56     if queryset_var is None:
57         raise template.TemplateSyntaxError(
58             "Invalid syntax. Proper usage of this tag is: "
59             "{%% autopaginate QUERYSET [PAGINATE_BY] [ORPHANS]"
60             " [as CONTEXT_VAR_NAME] %%}"
61         )
62     return AutoPaginateNode(queryset_var, paginate_by, orphans, context_var)
63
64 class AutoPaginateNode(template.Node):
65     """
66     Emits the required objects to allow for Digg-style pagination.
67     
68     First, it looks in the current context for the variable specified, and using
69     that object, it emits a simple ``Paginator`` and the current page object 
70     into the context names ``paginator`` and ``page_obj``, respectively.
71     
72     It will then replace the variable specified with only the objects for the
73     current page.
74     
75     .. note::
76         
77         It is recommended to use *{% paginate %}* after using the autopaginate
78         tag.  If you choose not to use *{% paginate %}*, make sure to display the
79         list of available pages, or else the application may seem to be buggy.
80     """
81     def __init__(self, queryset_var, paginate_by=None,
82         orphans=None, context_var=None):
83         if paginate_by is None:
84             paginate_by = DEFAULT_PAGINATION
85         if orphans is None:
86             orphans = DEFAULT_ORPHANS
87         self.queryset_var = template.Variable(queryset_var)
88         if isinstance(paginate_by, int):
89             self.paginate_by = paginate_by
90         else:
91             self.paginate_by = template.Variable(paginate_by)
92         if isinstance(orphans, int):
93             self.orphans = orphans
94         else:
95             self.orphans = template.Variable(orphans)
96         self.context_var = context_var
97
98     def render(self, context):
99         key = self.queryset_var.var
100         value = self.queryset_var.resolve(context)
101         if isinstance(self.paginate_by, int):
102             paginate_by = self.paginate_by
103         else:
104             paginate_by = self.paginate_by.resolve(context)
105         if isinstance(self.orphans, int):
106             orphans = self.orphans
107         else:
108             orphans = self.orphans.resolve(context)
109         paginator = Paginator(value, paginate_by, orphans)
110         try:
111             page_obj = paginator.page(context['request'].page)
112         except InvalidPage:
113             if INVALID_PAGE_RAISES_404:
114                 raise Http404('Invalid page requested.  If DEBUG were set to ' +
115                     'False, an HTTP 404 page would have been shown instead.')
116             context[key] = []
117             context['invalid_page'] = True
118             return u''
119         if self.context_var is not None:
120             context[self.context_var] = page_obj.object_list
121         else:
122             context[key] = page_obj.object_list
123         context['paginator'] = paginator
124         context['page_obj'] = page_obj
125         return u''
126
127 def paginate(context, window=DEFAULT_WINDOW):
128     """
129     Renders the ``pagination/pagination.html`` template, resulting in a
130     Digg-like display of the available pages, given the current page.  If there
131     are too many pages to be displayed before and after the current page, then
132     elipses will be used to indicate the undisplayed gap between page numbers.
133     
134     Requires one argument, ``context``, which should be a dictionary-like data
135     structure and must contain the following keys:
136     
137     ``paginator``
138         A ``Paginator`` or ``QuerySetPaginator`` object.
139     
140     ``page_obj``
141         This should be the result of calling the page method on the 
142         aforementioned ``Paginator`` or ``QuerySetPaginator`` object, given
143         the current page.
144     
145     This same ``context`` dictionary-like data structure may also include:
146     
147     ``getvars``
148         A dictionary of all of the **GET** parameters in the current request.
149         This is useful to maintain certain types of state, even when requesting
150         a different page.
151         """
152     try:
153         paginator = context['paginator']
154         page_obj = context['page_obj']
155         page_range = paginator.page_range
156         # Calculate the record range in the current page for display.
157         records = {'first': 1 + (page_obj.number - 1) * paginator.per_page}
158         records['last'] = records['first'] + paginator.per_page - 1
159         if records['last'] + paginator.orphans >= paginator.count:
160             records['last'] = paginator.count
161         # First and last are simply the first *n* pages and the last *n* pages,
162         # where *n* is the current window size.
163         first = set(page_range[:window])
164         last = set(page_range[-window:])
165         # Now we look around our current page, making sure that we don't wrap
166         # around.
167         current_start = page_obj.number-1-window
168         if current_start < 0:
169             current_start = 0
170         current_end = page_obj.number-1+window
171         if current_end < 0:
172             current_end = 0
173         current = set(page_range[current_start:current_end])
174         pages = []
175         # If there's no overlap between the first set of pages and the current
176         # set of pages, then there's a possible need for elusion.
177         if len(first.intersection(current)) == 0:
178             first_list = list(first)
179             first_list.sort()
180             second_list = list(current)
181             second_list.sort()
182             pages.extend(first_list)
183             diff = second_list[0] - first_list[-1]
184             # If there is a gap of two, between the last page of the first
185             # set and the first page of the current set, then we're missing a
186             # page.
187             if diff == 2:
188                 pages.append(second_list[0] - 1)
189             # If the difference is just one, then there's nothing to be done,
190             # as the pages need no elusion and are correct.
191             elif diff == 1:
192                 pass
193             # Otherwise, there's a bigger gap which needs to be signaled for
194             # elusion, by pushing a None value to the page list.
195             else:
196                 pages.append(None)
197             pages.extend(second_list)
198         else:
199             unioned = list(first.union(current))
200             unioned.sort()
201             pages.extend(unioned)
202         # If there's no overlap between the current set of pages and the last
203         # set of pages, then there's a possible need for elusion.
204         if len(current.intersection(last)) == 0:
205             second_list = list(last)
206             second_list.sort()
207             diff = second_list[0] - pages[-1]
208             # If there is a gap of two, between the last page of the current
209             # set and the first page of the last set, then we're missing a 
210             # page.
211             if diff == 2:
212                 pages.append(second_list[0] - 1)
213             # If the difference is just one, then there's nothing to be done,
214             # as the pages need no elusion and are correct.
215             elif diff == 1:
216                 pass
217             # Otherwise, there's a bigger gap which needs to be signaled for
218             # elusion, by pushing a None value to the page list.
219             else:
220                 pages.append(None)
221             pages.extend(second_list)
222         else:
223             differenced = list(last.difference(current))
224             differenced.sort()
225             pages.extend(differenced)
226         to_return = {
227             'MEDIA_URL': settings.MEDIA_URL,
228             'pages': pages,
229             'records': records,
230             'page_obj': page_obj,
231             'paginator': paginator,
232             'is_paginated': paginator.count > paginator.per_page,
233         }
234         if 'request' in context:
235             getvars = context['request'].GET.copy()
236             if 'page' in getvars:
237                 del getvars['page']
238             if len(getvars.keys()) > 0:
239                 to_return['getvars'] = "&%s" % getvars.urlencode()
240             else:
241                 to_return['getvars'] = ''
242         return to_return
243     except KeyError, AttributeError:
244         return {}
245
246 register.inclusion_tag('pagination/pagination.html', takes_context=True)(
247     paginate)
248 register.tag('autopaginate', do_autopaginate)