diff --git a/pelican/__init__.py b/pelican/__init__.py index 1ef7fd2f..419b40b0 100755 --- a/pelican/__init__.py +++ b/pelican/__init__.py @@ -78,7 +78,7 @@ class Pelican(object): return generators def get_writer(self): - return Writer(self.output_path) + return Writer(self.output_path, settings=self.settings) diff --git a/pelican/generators.py b/pelican/generators.py index c7239154..1725e6b4 100755 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -16,6 +16,7 @@ from pelican.readers import read_file _TEMPLATES = ('index', 'tag', 'tags', 'article', 'category', 'categories', 'archives', 'page') _DIRECT_TEMPLATES = ('index', 'tags', 'categories', 'archives') +_PAGINATED_DIRECT_TEMPLATES = ('index', ) class Generator(object): @@ -35,7 +36,8 @@ class Generator(object): templates ready to use with Jinja2. """ path = os.path.expanduser(os.path.join(self.theme, 'templates')) - env = Environment(loader=FileSystemLoader(path),extensions=self.settings.get('JINJA_EXTENSIONS', [])) + env = Environment(loader=FileSystemLoader(path), + extensions=self.settings.get('JINJA_EXTENSIONS', [])) templates = {} for template in _TEMPLATES: try: @@ -134,25 +136,34 @@ class ArticlesGenerator(Generator): writer.write_file, relative_urls = self.settings.get('RELATIVE_URLS') ) - # to minimize the number of relative path stuff modification in writer, articles pass first + # to minimize the number of relative path stuff modification + # in writer, articles pass first for article in chain(self.translations, self.articles): write('%s' % article.save_as, templates['article'], self.context, article=article, category=article.category) for template in _DIRECT_TEMPLATES: + paginated = {} + if template in _PAGINATED_DIRECT_TEMPLATES: + paginated = {'articles': self.articles, 'dates': self.dates} write('%s.html' % template, templates[template], self.context, - blog=True) + blog=True, paginated=paginated, page_name=template) # and subfolders after that for tag, articles in self.tags.items(): - for article in articles: - write('tag/%s.html' % tag, templates['tag'], self.context, - tag=tag, articles=articles) + dates = [article for article in self.dates if article in articles] + write('tag/%s.html' % tag, templates['tag'], self.context, + tag=tag, articles=articles, dates=dates, + paginated={'articles': articles, 'dates': dates}, + page_name='tag/%s'%tag) for cat, articles in self.categories: + dates = [article for article in self.dates if article in articles] write('category/%s.html' % cat, templates['category'], self.context, - category=cat, articles=articles) + category=cat, articles=articles, dates=dates, + paginated={'articles': articles, 'dates': dates}, + page_name='category/%s' % cat) def generate_context(self): """change the context""" @@ -281,7 +292,8 @@ class PdfGenerator(Generator): pass def generate_output(self, writer=None): - # we don't use the writer passed as argument here, since we write our own files + # we don't use the writer passed as argument here + # since we write our own files print u' Generating PDF files...' pdf_path = os.path.join(self.output_path, 'pdf') try: diff --git a/pelican/paginator.py b/pelican/paginator.py new file mode 100644 index 00000000..a2a452b9 --- /dev/null +++ b/pelican/paginator.py @@ -0,0 +1,85 @@ +# From django.core.paginator +from math import ceil + +class Paginator(object): + def __init__(self, object_list, per_page, orphans=0): + self.object_list = object_list + self.per_page = per_page + self.orphans = orphans + self._num_pages = self._count = None + + def page(self, number): + "Returns a Page object for the given 1-based page number." + bottom = (number - 1) * self.per_page + top = bottom + self.per_page + if top + self.orphans >= self.count: + top = self.count + return Page(self.object_list[bottom:top], number, self) + + def _get_count(self): + "Returns the total number of objects, across all pages." + if self._count is None: + self._count = len(self.object_list) + return self._count + count = property(_get_count) + + def _get_num_pages(self): + "Returns the total number of pages." + if self._num_pages is None: + hits = max(1, self.count - self.orphans) + self._num_pages = int(ceil(hits / float(self.per_page))) + return self._num_pages + 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. + """ + return range(1, self.num_pages + 1) + page_range = property(_get_page_range) + +class Page(object): + def __init__(self, object_list, number, paginator): + self.object_list = object_list + self.number = number + self.paginator = paginator + + def __repr__(self): + return '' % (self.number, self.paginator.num_pages) + + def has_next(self): + return self.number < self.paginator.num_pages + + def has_previous(self): + return self.number > 1 + + def has_other_pages(self): + return self.has_previous() or self.has_next() + + def next_page_number(self): + return self.number + 1 + + def previous_page_number(self): + return self.number - 1 + + def start_index(self): + """ + Returns the 1-based index of the first object on this page, + relative to total objects in the paginator. + """ + # Special case, return zero if no items. + if self.paginator.count == 0: + return 0 + return (self.paginator.per_page * (self.number - 1)) + 1 + + def end_index(self): + """ + Returns the 1-based index of the last object on this page, + relative to total objects found (hits). + """ + # Special case for the last page because there can be orphans. + if self.number == self.paginator.num_pages: + return self.paginator.count + return self.number * self.paginator.per_page + diff --git a/pelican/settings.py b/pelican/settings.py index d84178fe..dbd230d4 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -30,6 +30,9 @@ _DEFAULT_CONFIG = {'PATH': None, 'DATE_FORMATS': {}, 'JINJA_EXTENSIONS': [], 'LOCALE': '', # default to user locale + 'WITH_PAGINATION': True, + 'DEFAULT_PAGINATION': 5, + 'DEFAULT_ORPHANS': 0, } def read_settings(filename): diff --git a/pelican/themes/notmyidea/templates/index.html b/pelican/themes/notmyidea/templates/index.html index 5969e6c8..217bacf2 100644 --- a/pelican/themes/notmyidea/templates/index.html +++ b/pelican/themes/notmyidea/templates/index.html @@ -2,24 +2,32 @@ {% block content_title %}{% endblock %} {% block content %} {% if articles %} -{% for article in articles %} - {% if loop.index == 1 %} - + {% if loop.length > 1 %} +
+

Other articles

+
+
    + {% endif %} + {# other items #} + {% else %} + {% if loop.first and articles_page.has_previous %} +
    +
      + {% endif %} +
    1. - {% endif %} + + {% endif %} + {% if loop.last and (articles_page.has_previous() + or not articles_page.has_previous() and loop.length > 1) %} + {% include 'pagination.html' %} + {% endif %} {% endfor %} -
    -
    + {% if loop.length > 1 or articles_page.has_previous() %} +
+
+{% endif %} {% else %}

Pages

diff --git a/pelican/themes/notmyidea/templates/pagination.html b/pelican/themes/notmyidea/templates/pagination.html new file mode 100644 index 00000000..9cce0237 --- /dev/null +++ b/pelican/themes/notmyidea/templates/pagination.html @@ -0,0 +1,13 @@ +

+ {% 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 %} +

diff --git a/pelican/themes/simple/templates/base.html b/pelican/themes/simple/templates/base.html index 26103864..eaf4484f 100644 --- a/pelican/themes/simple/templates/base.html +++ b/pelican/themes/simple/templates/base.html @@ -11,7 +11,7 @@ {% if categories %} {% endif %} {% block content %} diff --git a/pelican/themes/simple/templates/index.html b/pelican/themes/simple/templates/index.html index c09dbf5d..0e4ef141 100644 --- a/pelican/themes/simple/templates/index.html +++ b/pelican/themes/simple/templates/index.html @@ -6,7 +6,7 @@ {% endblock %}
    -{% for article in articles %} +{% for article in articles_page.object_list %}
  1. {% endfor %}
+

+ {% 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 %} +

{% endblock content %} diff --git a/pelican/writers.py b/pelican/writers.py index c40c0ea3..ea32de14 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -7,13 +7,15 @@ import locale from feedgenerator import Atom1Feed, Rss201rev2Feed from pelican.utils import get_relative_path +from pelican.paginator import Paginator class Writer(object): - def __init__(self, output_path): + def __init__(self, output_path, settings=None): self.output_path = output_path self.reminder = dict() + self.settings = settings or {} def _create_new_feed(self, feed_type, context): feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed @@ -74,15 +76,30 @@ class Writer(object): locale.setlocale(locale.LC_ALL, old_locale) def write_file(self, name, template, context, relative_urls=True, - **kwargs): + paginated=None, **kwargs): """Render the template and write the file. :param name: name of the file to output :param template: template to use to generate the content :param context: dict to pass to the templates. :param relative_urls: use relative urls or absolutes ones + :param paginated: dict of article list to paginate - must have the + same length (same list in different orders) :param **kwargs: additional variables to pass to the templates """ + + def _write_file(template, localcontext, output_path, name): + """Render the template write the file.""" + output = template.render(localcontext) + filename = os.sep.join((output_path, name)) + try: + os.makedirs(os.path.dirname(filename)) + except Exception: + pass + with open(filename, 'w', encoding='utf-8') as f: + f.write(output) + print u' [ok] writing %s' % filename + localcontext = context.copy() if relative_urls: localcontext['SITEURL'] = get_relative_path(name) @@ -90,22 +107,43 @@ class Writer(object): localcontext.update(kwargs) self.update_context_contents(name, localcontext) - output = template.render(localcontext) - filename = os.sep.join((self.output_path, name)) - try: - os.makedirs(os.path.dirname(filename)) - except Exception: - pass - with open(filename, 'w', encoding='utf-8') as f: - f.write(output) - print u' [ok] writing %s' % filename + # check paginated + paginated = paginated or {} + if self.settings.get('WITH_PAGINATION') and paginated: + # pagination needed, init paginators + paginators = {} + for key in paginated.iterkeys(): + object_list = paginated[key] + paginators[key] = Paginator(object_list, + self.settings.get('DEFAULT_PAGINATION'), + self.settings.get('DEFAULT_ORPHANS')) + # generated pages, and write + for page_num in range(paginators.values()[0].num_pages): + paginated_localcontext = localcontext.copy() + paginated_name = name + for key in paginators.iterkeys(): + paginator = paginators[key] + page = paginator.page(page_num+1) + paginated_localcontext.update({'%s_paginator' % key: paginator, + '%s_page' % key: page}) + if page_num > 0: + ext = '.' + paginated_name.rsplit('.')[-1] + paginated_name = paginated_name.replace(ext, + '%s%s' % (page_num + 1, ext)) + + _write_file(template, paginated_localcontext, self.output_path, + paginated_name) + else: + # no pagination + _write_file(template, localcontext, self.output_path, name) def update_context_contents(self, name, context): - """Recursively run the context to find elements (articles, pages, etc) whose content getter needs to - be modified in order to deal with relative paths. + """Recursively run the context to find elements (articles, pages, etc) + whose content getter needs to + be modified in order to deal with relative paths. - :param name: name of the file to output. - :param context: dict that will be passed to the templates. + :param name: name of the file to output. + :param context: dict that will be passed to the templates. """ if context is None: return None @@ -139,13 +177,13 @@ class Writer(object): return context def inject_update_method(self, name, item): - """Replace the content attribute getter of an element by a function that will deals with its - relatives paths. + """Replace the content attribute getter of an element by a function + that will deals with its relatives paths. """ def _update_object_content(name, input): - """Change all the relatives paths of the input content to relatives paths - suitable fot the ouput content + """Change all the relatives paths of the input content to relatives + paths suitable fot the ouput content :param name: path of the output. :param input: input resource that will be passed to the templates. diff --git a/samples/content/cat1/article1.rst b/samples/content/cat1/article1.rst new file mode 100644 index 00000000..4789543b --- /dev/null +++ b/samples/content/cat1/article1.rst @@ -0,0 +1,6 @@ +Article 1 +######### + +:date: 2011-02-17 + +Article 1 diff --git a/samples/content/cat1/article2.rst b/samples/content/cat1/article2.rst new file mode 100644 index 00000000..a4f87866 --- /dev/null +++ b/samples/content/cat1/article2.rst @@ -0,0 +1,6 @@ +Article 2 +######### + +:date: 2011-02-17 + +Article 2 diff --git a/samples/content/cat1/article3.rst b/samples/content/cat1/article3.rst new file mode 100644 index 00000000..53471177 --- /dev/null +++ b/samples/content/cat1/article3.rst @@ -0,0 +1,6 @@ +Article 3 +######### + +:date: 2011-02-17 + +Article 3 diff --git a/samples/pelican.conf.py b/samples/pelican.conf.py index 10d5497a..07c49d01 100755 --- a/samples/pelican.conf.py +++ b/samples/pelican.conf.py @@ -8,6 +8,7 @@ DISQUS_SITENAME = "blog-notmyidea" PDF_GENERATOR = False REVERSE_CATEGORY_ORDER = True LOCALE = 'fr_FR.utf8' +DEFAULT_PAGINATION = 2 FEED_RSS = 'feeds/all.rss.xml' CATEGORY_FEED_RSS = 'feeds/%s.rss.xml'