Merge pull request #979 from nyergler/pagination_conf

Support flexible pagination configuration
This commit is contained in:
Justin Mayer 2013-08-03 14:58:47 -07:00
commit bea03bb4bb
9 changed files with 140 additions and 33 deletions

View file

@ -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
=========

View file

@ -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 '<Page %s of %s>' % (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'))

View file

@ -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',

View file

@ -1,15 +1,11 @@
{% if DEFAULT_PAGINATION %}
<p class="paginator">
{% if articles_page.has_previous() %}
{% if articles_page.previous_page_number() == 1 %}
<a href="{{ SITEURL }}/{{ page_name }}.html">&laquo;</a>
{% else %}
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.previous_page_number() }}.html">&laquo;</a>
{% endif %}
<a href="{{ SITEURL }}/{{ articles_previous_page.url }}">&laquo;</a>
{% endif %}
Page {{ articles_page.number }} / {{ articles_paginator.num_pages }}
{% if articles_page.has_next() %}
<a href="{{ SITEURL }}/{{ page_name }}{{ articles_page.next_page_number() }}.html">&raquo;</a>
<a href="{{ SITEURL }}/{{ articles_next_page.url }}">&raquo;</a>
{% endif %}
</p>
{% endif %}

View file

@ -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)