From c5eecd23eb3d7498210701c278cbb9365cfb45ff Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 29 Dec 2012 09:05:40 -0800 Subject: [PATCH 01/10] PAGINATION_URL/PAGINATION_SAVE_AS implementation allows the use of custom urls for pagination similar to *_URLS --- pelican/paginator.py | 44 ++++++++++++++++--- pelican/settings.py | 2 + .../custom/author/alexis-metaireau2.html | 4 +- .../custom/author/alexis-metaireau3.html | 4 +- pelican/tests/output/custom/index2.html | 4 +- pelican/tests/output/custom/index3.html | 4 +- .../themes/simple/templates/pagination.html | 8 +--- pelican/writers.py | 29 ++++++------ 8 files changed, 66 insertions(+), 33 deletions(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index 067215c2..5fc6357e 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -2,14 +2,26 @@ from __future__ import unicode_literals, print_function # From django.core.paginator +import functools +import logging + from math import ceil +logger = logging.getLogger(__name__) class Paginator(object): - def __init__(self, object_list, per_page, orphans=0): + def __init__(self, name, object_list, settings): + self.name = name self.object_list = object_list - self.per_page = per_page - self.orphans = orphans + self.settings = settings + + if settings.get('DEFAULT_PAGINATION'): + self.per_page = settings.get('DEFAULT_PAGINATION') + self.orphans = settings.get('DEFAULT_ORPHANS') + else: + self.per_page = len(object_list) + self.orphans = 0 + self._num_pages = self._count = None def page(self, number): @@ -18,7 +30,8 @@ class Paginator(object): top = bottom + self.per_page if top + self.orphans >= self.count: top = self.count - return Page(self.object_list[bottom:top], number, self) + return Page(self.name, self.object_list[bottom:top], number, self, + self.settings) def _get_count(self): "Returns the total number of objects, across all pages." @@ -45,10 +58,12 @@ class Paginator(object): class Page(object): - def __init__(self, object_list, number, paginator): + def __init__(self, name, object_list, number, paginator, settings): + self.name = name self.object_list = object_list self.number = number self.paginator = paginator + self.settings = settings def __repr__(self): return '' % (self.number, self.paginator.num_pages) @@ -87,3 +102,22 @@ class Page(object): if self.number == self.paginator.num_pages: return self.paginator.count return self.number * self.paginator.per_page + + def _from_settings(self, key): + """Returns URL information as defined in settings. Similar to + URLWrapper._from_settings, but specialized to deal with pagination + logic.""" + setting = "%s_%s" % ('PAGINATION', key) + value = self.settings[setting] + if not isinstance(value, basestring): + logger.warning(u'%s is set to %s' % (setting, value)) + return value + else: + context = self.__dict__ + if self.number == 1: + # no page numbers on the first page + context['number'] = '' + return unicode(value).format(**context) + + url = property(functools.partial(_from_settings, key='URL')) + save_as = property(functools.partial(_from_settings, key='SAVE_AS')) diff --git a/pelican/settings.py b/pelican/settings.py index 01203504..b65a6df7 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -73,6 +73,8 @@ DEFAULT_CONFIG = { 'TAG_SAVE_AS': os.path.join('tag', '{slug}.html'), 'AUTHOR_URL': 'author/{slug}.html', 'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'), + 'PAGINATION_URL': '{name}{number}.html', + 'PAGINATION_SAVE_AS': '{name}{number}.html', 'YEAR_ARCHIVE_SAVE_AS': False, 'MONTH_ARCHIVE_SAVE_AS': False, 'DAY_ARCHIVE_SAVE_AS': False, diff --git a/pelican/tests/output/custom/author/alexis-metaireau2.html b/pelican/tests/output/custom/author/alexis-metaireau2.html index 9f4a31e8..5d92303c 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau2.html +++ b/pelican/tests/output/custom/author/alexis-metaireau2.html @@ -142,8 +142,8 @@ as well as inline markup.

- « - Page 2 / 3 + « + Page 2 / 3 »

diff --git a/pelican/tests/output/custom/author/alexis-metaireau3.html b/pelican/tests/output/custom/author/alexis-metaireau3.html index 4eda0b62..64468725 100644 --- a/pelican/tests/output/custom/author/alexis-metaireau3.html +++ b/pelican/tests/output/custom/author/alexis-metaireau3.html @@ -59,8 +59,8 @@

- « - Page 3 / 3 + « + Page 3 / 3

diff --git a/pelican/tests/output/custom/index2.html b/pelican/tests/output/custom/index2.html index b8e2ac1a..7de87a34 100644 --- a/pelican/tests/output/custom/index2.html +++ b/pelican/tests/output/custom/index2.html @@ -140,8 +140,8 @@ YEAH !

- « - Page 2 / 3 + « + Page 2 / 3 »

diff --git a/pelican/tests/output/custom/index3.html b/pelican/tests/output/custom/index3.html index cf285ea2..18435df8 100644 --- a/pelican/tests/output/custom/index3.html +++ b/pelican/tests/output/custom/index3.html @@ -59,8 +59,8 @@

- « - Page 3 / 3 + « + Page 3 / 3

diff --git a/pelican/themes/simple/templates/pagination.html b/pelican/themes/simple/templates/pagination.html index 83c587ac..4219a5c3 100644 --- a/pelican/themes/simple/templates/pagination.html +++ b/pelican/themes/simple/templates/pagination.html @@ -1,15 +1,11 @@ {% if DEFAULT_PAGINATION %}

{% if articles_page.has_previous() %} - {% if articles_page.previous_page_number() == 1 %} - « - {% else %} - « - {% endif %} + « {% endif %} Page {{ articles_page.number }} / {{ articles_paginator.num_pages }} {% if articles_page.has_next() %} - » + » {% endif %}

{% endif %} diff --git a/pelican/writers.py b/pelican/writers.py index fe37b25d..1113a5ba 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -150,36 +150,37 @@ class Writer(object): # check paginated paginated = paginated or {} if paginated: + name_root, ext = os.path.splitext(name) + # pagination needed, init paginators paginators = {} for key in paginated.keys(): object_list = paginated[key] - if self.settings['DEFAULT_PAGINATION']: - paginators[key] = Paginator(object_list, - self.settings['DEFAULT_PAGINATION'], - self.settings['DEFAULT_ORPHANS']) - else: - paginators[key] = Paginator(object_list, len(object_list)) + paginators[key] = Paginator( + name_root, + object_list, + self.settings, + ) # generated pages, and write - name_root, ext = os.path.splitext(name) for page_num in range(list(paginators.values())[0].num_pages): paginated_localcontext = localcontext.copy() for key in paginators.keys(): paginator = paginators[key] + previous_page = paginator.page(page_num) \ + if page_num > 0 else None page = paginator.page(page_num + 1) + next_page = paginator.page(page_num + 2) \ + if page_num + 1 < paginator.num_pages else None paginated_localcontext.update( {'%s_paginator' % key: paginator, - '%s_page' % key: page}) - if page_num > 0: - paginated_name = '%s%s%s' % ( - name_root, page_num + 1, ext) - else: - paginated_name = name + '%s_page' % key: page, + '%s_previous_page' % key: previous_page, + '%s_next_page' % key: next_page}) _write_file(template, paginated_localcontext, self.output_path, - paginated_name) + page.save_as) else: # no pagination _write_file(template, localcontext, self.output_path, name) From e07b39dfcb4748066ade21ed75f41295ecd8ac7e Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sat, 29 Dec 2012 17:44:35 -0800 Subject: [PATCH 02/10] more robust PAGINATION_(URL|SAVE_AS) support - add base_name and number_seperator to context to give more flexibility when naming things --- pelican/paginator.py | 9 ++++++++- pelican/writers.py | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index 5fc6357e..6be64b39 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals, print_function # From django.core.paginator import functools import logging +import os from math import ceil @@ -114,10 +115,16 @@ class Page(object): return value else: context = self.__dict__ + context['base_name'] = os.path.dirname(self.name) + context['number_sep'] = '/' if self.number == 1: # no page numbers on the first page context['number'] = '' - return unicode(value).format(**context) + context['number_sep'] = '' + ret = unicode(value).format(**context) + if ret[0] == '/': + ret = ret[1:] + return ret url = property(functools.partial(_from_settings, key='URL')) save_as = property(functools.partial(_from_settings, key='SAVE_AS')) diff --git a/pelican/writers.py b/pelican/writers.py index 1113a5ba..25f49aeb 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -150,7 +150,7 @@ class Writer(object): # check paginated paginated = paginated or {} if paginated: - name_root, ext = os.path.splitext(name) + name_root = os.path.splitext(name)[0] # pagination needed, init paginators paginators = {} From 0caa101ec73ef392cb2a38d3de0e732b7d544b55 Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sun, 31 Mar 2013 07:41:14 -0700 Subject: [PATCH 03/10] use six.string_types for python 3 compat --- pelican/paginator.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index 6be64b39..a03ef2af 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function +import six # From django.core.paginator import functools @@ -110,7 +111,7 @@ class Page(object): logic.""" setting = "%s_%s" % ('PAGINATION', key) value = self.settings[setting] - if not isinstance(value, basestring): + if not isinstance(value, six.string_types): logger.warning(u'%s is set to %s' % (setting, value)) return value else: From 71e83635ea062c7b9cfdaca941f57e70628ba5ae Mon Sep 17 00:00:00 2001 From: Ross McFarland Date: Sun, 12 May 2013 17:59:44 -0700 Subject: [PATCH 04/10] remove u prefix from string literal, using unicode_literals --- pelican/paginator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index a03ef2af..e29a32fe 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -112,7 +112,7 @@ class Page(object): setting = "%s_%s" % ('PAGINATION', key) value = self.settings[setting] if not isinstance(value, six.string_types): - logger.warning(u'%s is set to %s' % (setting, value)) + logger.warning('%s is set to %s' % (setting, value)) return value else: context = self.__dict__ From 95890a2a611100a833ee727a4a39d5334b017ddf Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Mon, 29 Jul 2013 08:10:28 -0400 Subject: [PATCH 05/10] Allow definition of pagination rules by page index. --- pelican/paginator.py | 20 ++++++++++++++++++-- pelican/settings.py | 15 +++++++++++++-- 2 files changed, 31 insertions(+), 4 deletions(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index e29a32fe..b5db2d7d 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals, print_function import six # From django.core.paginator +from collections import namedtuple import functools import logging import os @@ -11,6 +12,13 @@ from math import ceil logger = logging.getLogger(__name__) + +PaginationRule = namedtuple( + 'PaginationRule', + 'min_page URL SAVE_AS', +) + + class Paginator(object): def __init__(self, name, object_list, settings): self.name = name @@ -109,8 +117,16 @@ class Page(object): """Returns URL information as defined in settings. Similar to URLWrapper._from_settings, but specialized to deal with pagination logic.""" - setting = "%s_%s" % ('PAGINATION', key) - value = self.settings[setting] + + rule = None + + # find the last matching pagination rule + for p in self.settings['PAGINATION_PATTERNS']: + if p.min_page <= self.number: + rule = p + + value = getattr(rule, key) + if not isinstance(value, six.string_types): logger.warning('%s is set to %s' % (setting, value)) return value diff --git a/pelican/settings.py b/pelican/settings.py index b65a6df7..ff5f4ce4 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -73,8 +73,9 @@ DEFAULT_CONFIG = { 'TAG_SAVE_AS': os.path.join('tag', '{slug}.html'), 'AUTHOR_URL': 'author/{slug}.html', 'AUTHOR_SAVE_AS': os.path.join('author', '{slug}.html'), - 'PAGINATION_URL': '{name}{number}.html', - 'PAGINATION_SAVE_AS': '{name}{number}.html', + 'PAGINATION_PATTERNS': [ + (0, '{name}{number}.html', '{name}{number}.html'), + ], 'YEAR_ARCHIVE_SAVE_AS': False, 'MONTH_ARCHIVE_SAVE_AS': False, 'DAY_ARCHIVE_SAVE_AS': False, @@ -238,6 +239,16 @@ def configure_settings(settings): 'http://docs.getpelican.com/en/latest/settings.html#timezone ' 'for more information') + # fix up pagination rules + from pelican.paginator import PaginationRule + pagination_rules = [ + PaginationRule(*r) for r in settings['PAGINATION_PATTERNS'] + ] + settings['PAGINATION_PATTERNS'] = sorted( + pagination_rules, + key=lambda r: r[0], + ) + # Save people from accidentally setting a string rather than a list path_keys = ( 'ARTICLE_EXCLUDES', From 9abc08ea9f6c8c58edae77a7c4cb9c8364b2729d Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Wed, 31 Jul 2013 18:57:21 -0700 Subject: [PATCH 06/10] Don't assume that PAGINATION_PATTERNS will always be present. --- pelican/settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/settings.py b/pelican/settings.py index ff5f4ce4..59f1354f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -242,7 +242,7 @@ def configure_settings(settings): # fix up pagination rules from pelican.paginator import PaginationRule pagination_rules = [ - PaginationRule(*r) for r in settings['PAGINATION_PATTERNS'] + PaginationRule(*r) for r in settings.get('PAGINATION_PATTERNS', []) ] settings['PAGINATION_PATTERNS'] = sorted( pagination_rules, From 74c7c72fb3dbe481bd891f447eb1e4056bcbb9a4 Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Wed, 31 Jul 2013 21:31:08 -0700 Subject: [PATCH 07/10] Use six.u instead of unicode. --- pelican/paginator.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index b5db2d7d..39ea0e3a 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -138,7 +138,7 @@ class Page(object): # no page numbers on the first page context['number'] = '' context['number_sep'] = '' - ret = unicode(value).format(**context) + ret = six.u(value).format(**context) if ret[0] == '/': ret = ret[1:] return ret From 5ffbf907decd2bea80b9b41803b2ac240267da62 Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Sat, 3 Aug 2013 13:49:43 -0700 Subject: [PATCH 08/10] Create new formatting context dict instead of using self.__dict__ Using self.__dict__ is fine, but when its mutated it changes the object's state. Creating a new dict avoids needing to think about that, and doesn't introduce Python 3 issues (ie, where self.number is accidentally set to ''). --- pelican/paginator.py | 44 ++++++++++++++++++++++++++++---------------- 1 file changed, 28 insertions(+), 16 deletions(-) diff --git a/pelican/paginator.py b/pelican/paginator.py index 39ea0e3a..df8606ec 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -125,23 +125,35 @@ class Page(object): if p.min_page <= self.number: rule = p - value = getattr(rule, key) + if not rule: + return '' - if not isinstance(value, six.string_types): - logger.warning('%s is set to %s' % (setting, value)) - return value - else: - context = self.__dict__ - context['base_name'] = os.path.dirname(self.name) - context['number_sep'] = '/' - if self.number == 1: - # no page numbers on the first page - context['number'] = '' - context['number_sep'] = '' - ret = six.u(value).format(**context) - if ret[0] == '/': - ret = ret[1:] - return ret + prop_value = getattr(rule, key) + + if not isinstance(prop_value, six.string_types): + logger.warning('%s is set to %s' % (key, prop_value)) + return prop_value + + # URL or SAVE_AS is a string, format it with a controlled context + context = { + 'name': self.name, + 'object_list': self.object_list, + 'number': self.number, + 'paginator': self.paginator, + 'settings': self.settings, + 'base_name': os.path.dirname(self.name), + 'number_sep': '/', + } + + if self.number == 1: + # no page numbers on the first page + context['number'] = '' + context['number_sep'] = '' + + ret = prop_value.format(**context) + if ret[0] == '/': + ret = ret[1:] + return ret url = property(functools.partial(_from_settings, key='URL')) save_as = property(functools.partial(_from_settings, key='SAVE_AS')) From 50ff7ce89f7f68c9fe8e4de75f4a06334695794d Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Sat, 3 Aug 2013 14:04:58 -0700 Subject: [PATCH 09/10] Fall back to the default PAGINATION PATTERNS when none specified. --- pelican/settings.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pelican/settings.py b/pelican/settings.py index 59f1354f..b8e634ca 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -242,7 +242,10 @@ def configure_settings(settings): # fix up pagination rules from pelican.paginator import PaginationRule pagination_rules = [ - PaginationRule(*r) for r in settings.get('PAGINATION_PATTERNS', []) + PaginationRule(*r) for r in settings.get( + 'PAGINATION_PATTERNS', + DEFAULT_CONFIG['PAGINATION_PATTERNS'], + ) ] settings['PAGINATION_PATTERNS'] = sorted( pagination_rules, From d61ef0c66ce6f6db4568f34662f7fd00f8dae6d2 Mon Sep 17 00:00:00 2001 From: Nathan Yergler Date: Sat, 3 Aug 2013 14:06:16 -0700 Subject: [PATCH 10/10] Adding basic documentation for PAGINATION_PATTERNS. --- docs/settings.rst | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/docs/settings.rst b/docs/settings.rst index eb0d028f..548e9cec 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -477,8 +477,32 @@ Setting name (default value) What does it do? `DEFAULT_PAGINATION` (``False``) The maximum number of articles to include on a page, not including orphans. False to disable pagination. +`PAGINATION_PATTERNS` A set of patterns that are used to determine advanced + pagination output. ================================================ ===================================================== +Using Pagination Patterns +------------------------- + +The ``PAGINATION_PATTERNS`` setting can be used to configure where +subsequent pages are created. The setting is a sequence of three +element tuples, where each tuple consists of:: + + (minimum page, URL setting, SAVE_AS setting,) + +For example, if you wanted the first page to just be ``/``, and the +second (and subsequent) pages to be ``/page/2/``, you would set +``PAGINATION_PATTERNS`` as follows:: + + PAGINATION_PATTERNS = ( + (1, '{base_name}/', '{base_name}/index.html'), + (2, '{base_name}/page/{number}/', '{base_name}/page/{number}/index.html'), + ) + +This would cause the first page to be written to +``{base_name}/index.html``, and subsequent ones would be written into +``page/{number}`` directories. + Tag cloud =========