From: Brian Rosner Date: Fri, 31 Jul 2009 05:22:43 +0000 (-0600) Subject: Merge branch '1.0.5.X' X-Git-Tag: 1.0.6~3 X-Git-Url: https://git.mdrn.pl/django-pagination.git/commitdiff_plain/66ce4cd0b0c8dab29de4dc992e81bcb2fb030e5a?hp=5dac354654552cbcac9a06b3ad235f930fb767d0 Merge branch '1.0.5.X' --- diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0d20b64 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.pyc diff --git a/docs/usage.txt b/docs/usage.txt index 9c97c51..b520169 100644 --- a/docs/usage.txt +++ b/docs/usage.txt @@ -70,6 +70,16 @@ That's it! You have now paginated ``object_list`` and given users of the site 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 ------------------ diff --git a/pagination/locale/de/LC_MESSAGES/django.mo b/pagination/locale/de/LC_MESSAGES/django.mo index c3a4b3f..1be7abc 100644 Binary files a/pagination/locale/de/LC_MESSAGES/django.mo and b/pagination/locale/de/LC_MESSAGES/django.mo differ diff --git a/pagination/locale/de/LC_MESSAGES/django.po b/pagination/locale/de/LC_MESSAGES/django.po index d2cf07c..ff9872a 100644 --- a/pagination/locale/de/LC_MESSAGES/django.po +++ b/pagination/locale/de/LC_MESSAGES/django.po @@ -8,7 +8,7 @@ msgid "" 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 \n" "Language-Team: LANGUAGE \n" @@ -19,9 +19,9 @@ msgstr "" #: 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" diff --git a/pagination/middleware.py b/pagination/middleware.py index 5e917c5..f8a2a6f 100644 --- a/pagination/middleware.py +++ b/pagination/middleware.py @@ -1,10 +1,17 @@ +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 diff --git a/pagination/paginator.py b/pagination/paginator.py new file mode 100644 index 0000000..f67aa23 --- /dev/null +++ b/pagination/paginator.py @@ -0,0 +1,171 @@ +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 '' % 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 diff --git a/pagination/templatetags/pagination_tags.py b/pagination/templatetags/pagination_tags.py index 7505b74..3733434 100644 --- a/pagination/templatetags/pagination_tags.py +++ b/pagination/templatetags/pagination_tags.py @@ -13,8 +13,8 @@ register = template.Library() 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): """ @@ -31,8 +31,8 @@ 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: @@ -44,11 +44,12 @@ def do_autopaginate(parser, token): 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): @@ -198,6 +199,7 @@ def paginate(context, window=DEFAULT_WINDOW): differenced.sort() pages.extend(differenced) to_return = { + 'MEDIA_URL': settings.MEDIA_URL, 'pages': pages, 'page_obj': page_obj, 'paginator': paginator, @@ -214,5 +216,7 @@ def paginate(context, window=DEFAULT_WINDOW): 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) diff --git a/pagination/tests.py b/pagination/tests.py index 647bbfd..a55ef49 100644 --- a/pagination/tests.py +++ b/pagination/tests.py @@ -30,32 +30,101 @@ >>> 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