diff --git a/docs/settings.rst b/docs/settings.rst index a4299be6..8ecac7c9 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -479,8 +479,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 ========= diff --git a/pelican/paginator.py b/pelican/paginator.py index 067215c2..df8606ec 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -1,15 +1,37 @@ # -*- coding: utf-8 -*- from __future__ import unicode_literals, print_function +import six # From django.core.paginator +from collections import namedtuple +import functools +import logging +import os + from math import ceil +logger = logging.getLogger(__name__) + + +PaginationRule = namedtuple( + 'PaginationRule', + 'min_page URL SAVE_AS', +) + 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 +40,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 +68,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 +112,48 @@ 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.""" + + rule = None + + # find the last matching pagination rule + for p in self.settings['PAGINATION_PATTERNS']: + if p.min_page <= self.number: + rule = p + + if not rule: + return '' + + 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')) diff --git a/pelican/settings.py b/pelican/settings.py index 7a7f091a..87f9c86f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -74,6 +74,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_PATTERNS': [ + (0, '{name}{number}.html', '{name}{number}.html'), + ], 'YEAR_ARCHIVE_SAVE_AS': False, 'MONTH_ARCHIVE_SAVE_AS': False, 'DAY_ARCHIVE_SAVE_AS': False, @@ -237,6 +240,19 @@ 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.get( + 'PAGINATION_PATTERNS', + DEFAULT_CONFIG['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', 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..25f49aeb 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 = os.path.splitext(name)[0] + # 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)