a way to navigate between the different pages--all without touching your views.
+A Note About Uploads
+--------------------
+
+It is important, when using django-pagination in conjunction with file uploads,
+to be aware of when ``request.page`` is accessed. As soon as ``request.page``
+is accessed, ``request.upload_handlers`` is frozen and cannot be altered in any
+way. It's a good idea to access the ``page`` attribute on the request object
+as late as possible in your views.
+
+
Optional Settings
------------------
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
-"POT-Creation-Date: 2008-10-24 00:41-0700\n"
+"POT-Creation-Date: 2009-03-16 16:26+0100\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
"Language-Team: LANGUAGE <LL@li.org>\n"
#: templates/pagination/pagination.html:5
#: templates/pagination/pagination.html:7
msgid "previous"
-msgstr "zurück"
+msgstr "Zurück"
#: templates/pagination/pagination.html:21
#: templates/pagination/pagination.html:23
msgid "next"
-msgstr "weiter"
+msgstr "Weiter"
+def get_page(self):
+ """
+ A function which will be monkeypatched onto the request to get the current
+ integer representing the current page.
+ """
+ try:
+ return int(self.REQUEST['page'])
+ except (KeyError, ValueError, TypeError):
+ return 1
+
class PaginationMiddleware(object):
"""
Inserts a variable representing the current page onto the request object if
it exists in either **GET** or **POST** portions of the request.
"""
def process_request(self, request):
- try:
- request.page = int(request.REQUEST['page'])
- except (KeyError, ValueError, TypeError):
- request.page = 1
\ No newline at end of file
+ request.__class__.page = property(get_page)
\ No newline at end of file
--- /dev/null
+from django.core.paginator import Paginator, Page, PageNotAnInteger, EmptyPage
+
+class InfinitePaginator(Paginator):
+ """
+ Paginator designed for cases when it's not important to know how many total
+ pages. This is useful for any object_list that has no count() method or can
+ be used to improve performance for MySQL by removing counts.
+
+ The orphans parameter has been removed for simplicity and there's a link
+ template string for creating the links to the next and previous pages.
+ """
+
+ def __init__(self, object_list, per_page, allow_empty_first_page=True,
+ link_template='/page/%d/'):
+ orphans = 0 # no orphans
+ super(InfinitePaginator, self).__init__(object_list, per_page, orphans,
+ allow_empty_first_page)
+ # no count or num pages
+ del self._num_pages, self._count
+ # bonus links
+ self.link_template = link_template
+
+ def validate_number(self, number):
+ """
+ Validates the given 1-based page number.
+ """
+ try:
+ number = int(number)
+ except ValueError:
+ raise PageNotAnInteger('That page number is not an integer')
+ if number < 1:
+ raise EmptyPage('That page number is less than 1')
+ return number
+
+ def page(self, number):
+ """
+ Returns a Page object for the given 1-based page number.
+ """
+ number = self.validate_number(number)
+ bottom = (number - 1) * self.per_page
+ top = bottom + self.per_page
+ page_items = self.object_list[bottom:top]
+ # check moved from validate_number
+ if not page_items:
+ if number == 1 and self.allow_empty_first_page:
+ pass
+ else:
+ raise EmptyPage('That page contains no results')
+ return InfinitePage(page_items, number, self)
+
+ def _get_count(self):
+ """
+ Returns the total number of objects, across all pages.
+ """
+ raise NotImplementedError
+ count = property(_get_count)
+
+ def _get_num_pages(self):
+ """
+ Returns the total number of pages.
+ """
+ raise NotImplementedError
+ num_pages = property(_get_num_pages)
+
+ def _get_page_range(self):
+ """
+ Returns a 1-based range of pages for iterating through within
+ a template for loop.
+ """
+ raise NotImplementedError
+ page_range = property(_get_page_range)
+
+
+class InfinitePage(Page):
+
+ def __repr__(self):
+ return '<Page %s>' % self.number
+
+ def has_next(self):
+ """
+ Checks for one more item than last on this page.
+ """
+ try:
+ next_item = self.paginator.object_list[
+ self.number * self.paginator.per_page]
+ except IndexError:
+ return False
+ return True
+
+ def end_index(self):
+ """
+ Returns the 1-based index of the last object on this page,
+ relative to total objects found (hits).
+ """
+ return ((self.number - 1) * self.paginator.per_page +
+ len(self.object_list))
+
+ #Bonus methods for creating links
+
+ def next_link(self):
+ if self.has_next():
+ return self.paginator.link_template % (self.number + 1)
+ return None
+
+ def previous_link(self):
+ if self.has_previous():
+ return self.paginator.link_template % (self.number - 1)
+ return None
+
+class FinitePaginator(InfinitePaginator):
+ """
+ Paginator for cases when the list of items is already finite.
+
+ A good example is a list generated from an API call. This is a subclass
+ of InfinitePaginator because we have no idea how many items exist in the
+ full collection.
+
+ To accurately determine if the next page exists, a FinitePaginator MUST be
+ created with an object_list_plus that may contain more items than the
+ per_page count. Typically, you'll have an object_list_plus with one extra
+ item (if there's a next page). You'll also need to supply the offset from
+ the full collection in order to get the page start_index.
+
+ This is a very silly class but useful if you love the Django pagination
+ conventions.
+ """
+
+ def __init__(self, object_list_plus, per_page, offset=None,
+ allow_empty_first_page=True, link_template='/page/%d/'):
+ super(FinitePaginator, self).__init__(object_list_plus, per_page,
+ allow_empty_first_page, link_template)
+ self.offset = offset
+
+ def validate_number(self, number):
+ super(FinitePaginator, self).validate_number(number)
+ # check for an empty list to see if the page exists
+ if not self.object_list:
+ if number == 1 and self.allow_empty_first_page:
+ pass
+ else:
+ raise EmptyPage('That page contains no results')
+ return number
+
+ def page(self, number):
+ """
+ Returns a Page object for the given 1-based page number.
+ """
+ number = self.validate_number(number)
+ # remove the extra item(s) when creating the page
+ page_items = self.object_list[:self.per_page]
+ return FinitePage(page_items, number, self)
+
+class FinitePage(InfinitePage):
+
+ def has_next(self):
+ """
+ Checks for one more item than last on this page.
+ """
+ try:
+ next_item = self.paginator.object_list[self.paginator.per_page]
+ except IndexError:
+ return False
+ return True
+
+ def start_index(self):
+ """
+ Returns the 1-based index of the first object on this page,
+ relative to total objects in the paginator.
+ """
+ ## TODO should this holler if you haven't defined the offset?
+ return self.paginator.offset
\ No newline at end of file
DEFAULT_PAGINATION = getattr(settings, 'PAGINATION_DEFAULT_PAGINATION', 20)
DEFAULT_WINDOW = getattr(settings, 'PAGINATION_DEFAULT_WINDOW', 4)
DEFAULT_ORPHANS = getattr(settings, 'PAGINATION_DEFAULT_ORPHANS', 0)
-INVALID_PAGE_RAISES_404 = getattr(settings, 'PAGINATION_INVALID_PAGE_RAISES_404',
- False)
+INVALID_PAGE_RAISES_404 = getattr(settings,
+ 'PAGINATION_INVALID_PAGE_RAISES_404', False)
def do_autopaginate(parser, token):
"""
try:
context_var = split[as_index + 1]
except IndexError:
- raise template.TemplateSyntaxError("Context variable assignment " +\
- "must take the form of {%% %r object.example_set.all ... as " +\
+ raise template.TemplateSyntaxError("Context variable assignment " +
+ "must take the form of {%% %r object.example_set.all ... as " +
"context_var_name %%}" % split[0])
del split[as_index:as_index + 2]
if len(split) == 2:
try:
orphans = int(split[3])
except ValueError:
- raise template.TemplateSyntaxError(u'Got %s, but expected integer.' % split[3])
+ raise template.TemplateSyntaxError(u'Got %s, but expected integer.'
+ % split[3])
return AutoPaginateNode(split[1], paginate_by=split[2], orphans=orphans,
context_var=context_var)
else:
- raise template.TemplateSyntaxError('%r tag takes one required ' + \
+ raise template.TemplateSyntaxError('%r tag takes one required ' +
'argument and one optional argument' % split[0])
class AutoPaginateNode(template.Node):
differenced.sort()
pages.extend(differenced)
to_return = {
+ 'MEDIA_URL': settings.MEDIA_URL,
'pages': pages,
'page_obj': page_obj,
'paginator': paginator,
return to_return
except KeyError, AttributeError:
return {}
-register.inclusion_tag('pagination/pagination.html', takes_context=True)(paginate)
+
+register.inclusion_tag('pagination/pagination.html', takes_context=True)(
+ paginate)
register.tag('autopaginate', do_autopaginate)
>>> t = Template("{% load pagination_tags %}{% autopaginate var 2 %}{% paginate %}")
-# WARNING: Please, please nobody read this portion of the code!
->>> class GetProxy(object):
-... def __iter__(self): yield self.__dict__.__iter__
-... def copy(self): return self
-... def urlencode(self): return u''
-... def keys(self): return []
->>> class RequestProxy(object):
+>>> from django.http import HttpRequest as DjangoHttpRequest
+>>> class HttpRequest(DjangoHttpRequest):
... page = 1
-... GET = GetProxy()
->>>
-# ENDWARNING
->>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
+>>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
u'\\n\\n<div class="pagination">...
>>>
>>> t = Template("{% load pagination_tags %}{% autopaginate var %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
+>>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
u'\\n\\n<div class="pagination">...
>>> t = Template("{% load pagination_tags %}{% autopaginate var 20 %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'request': RequestProxy()}))
+>>> t.render(Context({'var': range(21), 'request': HttpRequest()}))
u'\\n\\n<div class="pagination">...
>>> t = Template("{% load pagination_tags %}{% autopaginate var by %}{% paginate %}")
->>> t.render(Context({'var': range(21), 'by': 20, 'request': RequestProxy()}))
+>>> t.render(Context({'var': range(21), 'by': 20, 'request': HttpRequest()}))
u'\\n\\n<div class="pagination">...
>>> t = Template("{% load pagination_tags %}{% autopaginate var by as foo %}{{ foo }}")
->>> t.render(Context({'var': range(21), 'by': 20, 'request': RequestProxy()}))
+>>> t.render(Context({'var': range(21), 'by': 20, 'request': HttpRequest()}))
u'[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]'
>>>
+
+# Testing InfinitePaginator
+
+>>> from paginator import InfinitePaginator
+
+>>> InfinitePaginator
+<class 'pagination.paginator.InfinitePaginator'>
+>>> p = InfinitePaginator(range(20), 2, link_template='/bacon/page/%d')
+>>> p.validate_number(2)
+2
+>>> p.orphans
+0
+>>> p3 = p.page(3)
+>>> p3
+<Page 3>
+>>> p3.end_index()
+6
+>>> p3.has_next()
+True
+>>> p3.has_previous()
+True
+>>> p.page(10).has_next()
+False
+>>> p.page(1).has_previous()
+False
+>>> p3.next_link()
+'/bacon/page/4'
+>>> p3.previous_link()
+'/bacon/page/2'
+
+# Testing FinitePaginator
+
+>>> from paginator import FinitePaginator
+
+>>> FinitePaginator
+<class 'pagination.paginator.FinitePaginator'>
+>>> p = FinitePaginator(range(20), 2, offset=10, link_template='/bacon/page/%d')
+>>> p.validate_number(2)
+2
+>>> p.orphans
+0
+>>> p3 = p.page(3)
+>>> p3
+<Page 3>
+>>> p3.start_index()
+10
+>>> p3.end_index()
+6
+>>> p3.has_next()
+True
+>>> p3.has_previous()
+True
+>>> p3.next_link()
+'/bacon/page/4'
+>>> p3.previous_link()
+'/bacon/page/2'
+
+>>> p = FinitePaginator(range(20), 20, offset=10, link_template='/bacon/page/%d')
+>>> p2 = p.page(2)
+>>> p2
+<Page 2>
+>>> p2.has_next()
+False
+>>> p3.has_previous()
+True
+>>> p2.next_link()
+
+>>> p2.previous_link()
+'/bacon/page/1'
+
+>>> from pagination.middleware import PaginationMiddleware
+>>> from django.core.handlers.wsgi import WSGIRequest
+>>> from StringIO import StringIO
+>>> middleware = PaginationMiddleware()
+>>> request = WSGIRequest({'REQUEST_METHOD': 'POST', 'CONTENT_TYPE': 'multipart', 'wsgi.input': StringIO()})
+>>> middleware.process_request(request)
+>>> request.upload_handlers.append('asdf')
"""
\ No newline at end of file