Merge branch 'pagination-zebuline'

This commit is contained in:
Alexis Metaireau 2011-02-17 19:04:30 +00:00
commit e9515130e0
13 changed files with 248 additions and 51 deletions

View file

@ -78,7 +78,7 @@ class Pelican(object):
return generators return generators
def get_writer(self): def get_writer(self):
return Writer(self.output_path) return Writer(self.output_path, settings=self.settings)

View file

@ -16,6 +16,7 @@ from pelican.readers import read_file
_TEMPLATES = ('index', 'tag', 'tags', 'article', 'category', 'categories', _TEMPLATES = ('index', 'tag', 'tags', 'article', 'category', 'categories',
'archives', 'page') 'archives', 'page')
_DIRECT_TEMPLATES = ('index', 'tags', 'categories', 'archives') _DIRECT_TEMPLATES = ('index', 'tags', 'categories', 'archives')
_PAGINATED_DIRECT_TEMPLATES = ('index', )
class Generator(object): class Generator(object):
@ -35,7 +36,8 @@ class Generator(object):
templates ready to use with Jinja2. templates ready to use with Jinja2.
""" """
path = os.path.expanduser(os.path.join(self.theme, 'templates')) 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 = {} templates = {}
for template in _TEMPLATES: for template in _TEMPLATES:
try: try:
@ -134,25 +136,34 @@ class ArticlesGenerator(Generator):
writer.write_file, writer.write_file,
relative_urls = self.settings.get('RELATIVE_URLS') 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): for article in chain(self.translations, self.articles):
write('%s' % article.save_as, write('%s' % article.save_as,
templates['article'], self.context, article=article, templates['article'], self.context, article=article,
category=article.category) category=article.category)
for template in _DIRECT_TEMPLATES: 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, write('%s.html' % template, templates[template], self.context,
blog=True) blog=True, paginated=paginated, page_name=template)
# and subfolders after that # and subfolders after that
for tag, articles in self.tags.items(): for tag, articles in self.tags.items():
for article in articles: dates = [article for article in self.dates if article in articles]
write('tag/%s.html' % tag, templates['tag'], self.context, write('tag/%s.html' % tag, templates['tag'], self.context,
tag=tag, articles=articles) tag=tag, articles=articles, dates=dates,
paginated={'articles': articles, 'dates': dates},
page_name='tag/%s'%tag)
for cat, articles in self.categories: 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, 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): def generate_context(self):
"""change the context""" """change the context"""
@ -281,7 +292,8 @@ class PdfGenerator(Generator):
pass pass
def generate_output(self, writer=None): 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...' print u' Generating PDF files...'
pdf_path = os.path.join(self.output_path, 'pdf') pdf_path = os.path.join(self.output_path, 'pdf')
try: try:

85
pelican/paginator.py Normal file
View file

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

View file

@ -30,6 +30,9 @@ _DEFAULT_CONFIG = {'PATH': None,
'DATE_FORMATS': {}, 'DATE_FORMATS': {},
'JINJA_EXTENSIONS': [], 'JINJA_EXTENSIONS': [],
'LOCALE': '', # default to user locale 'LOCALE': '', # default to user locale
'WITH_PAGINATION': True,
'DEFAULT_PAGINATION': 5,
'DEFAULT_ORPHANS': 0,
} }
def read_settings(filename): def read_settings(filename):

View file

@ -2,24 +2,32 @@
{% block content_title %}{% endblock %} {% block content_title %}{% endblock %}
{% block content %} {% block content %}
{% if articles %} {% if articles %}
{% for article in articles %} {% for article in articles_page.object_list %}
{% if loop.index == 1 %}
<aside id="featured" class="body"><article> {# First item #}
<h1 class="entry-title"><a href="{{ SITEURL }}/{{ article.url {% if loop.first and not articles_page.has_previous() %}
}}">{{ article.title }}</a></h1> <aside id="featured" class="body">
{% include 'article_infos.html' %} <article>
{{ article.content }} <h1 class="entry-title"><a href="{{ SITEURL }}/{{ article.url }}">{{ article.title }}</a></h1>
{% include 'comments.html' %} {% include 'article_infos.html' %}{{ article.content }}{% include 'comments.html' %}
</article> </article>
</aside><!-- /#featured --> {% if loop.length == 1 %}
{% if loop.length > 1 %} {% include 'pagination.html' %}
<section id="content" class="body"> {% endif %}
<h1>Other articles</h1> </aside><!-- /#featured -->
<hr /> {% if loop.length > 1 %}
<ol id="posts-list" class="hfeed"> <section id="content" class="body">
{% endif %} <h1>Other articles</h1>
{% else %} <hr />
<li><article class="hentry"> <ol id="posts-list" class="hfeed">
{% endif %}
{# other items #}
{% else %}
{% if loop.first and articles_page.has_previous %}
<section id="content" class="body">
<ol id="posts-list" class="hfeed" start="{{ articles_paginator.per_page -1 }}">
{% endif %}
<li><article class="hentry">
<header> <header>
<h1><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title}}">{{ article.title }}</a></h1> <h1><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title}}">{{ article.title }}</a></h1>
</header> </header>
@ -30,11 +38,17 @@
<a class="readmore" href="{{ SITEURL }}/{{ article.url }}">read more</a> <a class="readmore" href="{{ SITEURL }}/{{ article.url }}">read more</a>
{% include 'comments.html' %} {% include 'comments.html' %}
</div><!-- /.entry-content --> </div><!-- /.entry-content -->
</article></li> </article></li>
{% 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 %} {% endfor %}
</ol><!-- /#posts-list --> {% if loop.length > 1 or articles_page.has_previous() %}
</section><!-- /#content --> </ol><!-- /#posts-list -->
</section><!-- /#content -->
{% endif %}
{% else %} {% else %}
<section id="content" class="body"> <section id="content" class="body">
<h2>Pages</h2> <h2>Pages</h2>

View file

@ -0,0 +1,13 @@
<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 %}
{% 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>
{% endif %}
</p>

View file

@ -11,7 +11,7 @@
</header><!-- /#banner --> </header><!-- /#banner -->
{% if categories %}<ul> {% if categories %}<ul>
{% for category, articles in categories %} {% for category, articles in categories %}
<li><a href="category/{{category}}.html">{{ category }}</a></li> <li><a href="{{ SITEURL }}/category/{{category}}.html">{{ category }}</a></li>
{% endfor %} {% endfor %}
</ul> {% endif %} </ul> {% endif %}
{% block content %} {% block content %}

View file

@ -6,7 +6,7 @@
{% endblock %} {% endblock %}
<ol id="post-list"> <ol id="post-list">
{% for article in articles %} {% for article in articles_page.object_list %}
<li><article class="hentry"> <li><article class="hentry">
<header> <h2 class="entry-title"><a href="{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title}}">{{ article.title }}</a></h2> </header> <header> <h2 class="entry-title"><a href="{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title}}">{{ article.title }}</a></h2> </header>
<footer class="post-info"> <footer class="post-info">
@ -17,5 +17,18 @@
</article></li> </article></li>
{% endfor %} {% endfor %}
</ol><!-- /#posts-list --> </ol><!-- /#posts-list -->
<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 %}
{% 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>
{% endif %}
</p>
</section><!-- /#content --> </section><!-- /#content -->
{% endblock content %} {% endblock content %}

View file

@ -7,13 +7,15 @@ import locale
from feedgenerator import Atom1Feed, Rss201rev2Feed from feedgenerator import Atom1Feed, Rss201rev2Feed
from pelican.utils import get_relative_path from pelican.utils import get_relative_path
from pelican.paginator import Paginator
class Writer(object): class Writer(object):
def __init__(self, output_path): def __init__(self, output_path, settings=None):
self.output_path = output_path self.output_path = output_path
self.reminder = dict() self.reminder = dict()
self.settings = settings or {}
def _create_new_feed(self, feed_type, context): def _create_new_feed(self, feed_type, context):
feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed feed_class = Rss201rev2Feed if feed_type == 'rss' else Atom1Feed
@ -74,15 +76,30 @@ class Writer(object):
locale.setlocale(locale.LC_ALL, old_locale) locale.setlocale(locale.LC_ALL, old_locale)
def write_file(self, name, template, context, relative_urls=True, def write_file(self, name, template, context, relative_urls=True,
**kwargs): paginated=None, **kwargs):
"""Render the template and write the file. """Render the template and write the file.
:param name: name of the file to output :param name: name of the file to output
:param template: template to use to generate the content :param template: template to use to generate the content
:param context: dict to pass to the templates. :param context: dict to pass to the templates.
:param relative_urls: use relative urls or absolutes ones :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 :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() localcontext = context.copy()
if relative_urls: if relative_urls:
localcontext['SITEURL'] = get_relative_path(name) localcontext['SITEURL'] = get_relative_path(name)
@ -90,22 +107,43 @@ class Writer(object):
localcontext.update(kwargs) localcontext.update(kwargs)
self.update_context_contents(name, localcontext) self.update_context_contents(name, localcontext)
output = template.render(localcontext) # check paginated
filename = os.sep.join((self.output_path, name)) paginated = paginated or {}
try: if self.settings.get('WITH_PAGINATION') and paginated:
os.makedirs(os.path.dirname(filename)) # pagination needed, init paginators
except Exception: paginators = {}
pass for key in paginated.iterkeys():
with open(filename, 'w', encoding='utf-8') as f: object_list = paginated[key]
f.write(output) paginators[key] = Paginator(object_list,
print u' [ok] writing %s' % filename 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): def update_context_contents(self, name, context):
"""Recursively run the context to find elements (articles, pages, etc) whose content getter needs to """Recursively run the context to find elements (articles, pages, etc)
be modified in order to deal with relative paths. whose content getter needs to
be modified in order to deal with relative paths.
:param name: name of the file to output. :param name: name of the file to output.
:param context: dict that will be passed to the templates. :param context: dict that will be passed to the templates.
""" """
if context is None: if context is None:
return None return None
@ -139,13 +177,13 @@ class Writer(object):
return context return context
def inject_update_method(self, name, item): def inject_update_method(self, name, item):
"""Replace the content attribute getter of an element by a function that will deals with its """Replace the content attribute getter of an element by a function
relatives paths. that will deals with its relatives paths.
""" """
def _update_object_content(name, input): def _update_object_content(name, input):
"""Change all the relatives paths of the input content to relatives paths """Change all the relatives paths of the input content to relatives
suitable fot the ouput content paths suitable fot the ouput content
:param name: path of the output. :param name: path of the output.
:param input: input resource that will be passed to the templates. :param input: input resource that will be passed to the templates.

View file

@ -0,0 +1,6 @@
Article 1
#########
:date: 2011-02-17
Article 1

View file

@ -0,0 +1,6 @@
Article 2
#########
:date: 2011-02-17
Article 2

View file

@ -0,0 +1,6 @@
Article 3
#########
:date: 2011-02-17
Article 3

View file

@ -8,6 +8,7 @@ DISQUS_SITENAME = "blog-notmyidea"
PDF_GENERATOR = False PDF_GENERATOR = False
REVERSE_CATEGORY_ORDER = True REVERSE_CATEGORY_ORDER = True
LOCALE = 'fr_FR.utf8' LOCALE = 'fr_FR.utf8'
DEFAULT_PAGINATION = 2
FEED_RSS = 'feeds/all.rss.xml' FEED_RSS = 'feeds/all.rss.xml'
CATEGORY_FEED_RSS = 'feeds/%s.rss.xml' CATEGORY_FEED_RSS = 'feeds/%s.rss.xml'