Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support ordering pages and articles when iterating in templates. #1348

Merged
merged 7 commits into from
Sep 18, 2014
25 changes: 22 additions & 3 deletions docs/settings.rst
Original file line number Diff line number Diff line change
Expand Up @@ -260,11 +260,19 @@ posts for the month at ``posts/2011/Aug/index.html``.
arrive at an appropriate archive of posts, without having to specify
a page name.

====================================================== ========================================================
====================================================== ==============================================================
Setting name (followed by default value, if any) What does it do?
====================================================== ========================================================
====================================================== ==============================================================
``ARTICLE_URL = '{slug}.html'`` The URL to refer to an article.
``ARTICLE_SAVE_AS = '{slug}.html'`` The place where we will save an article.
``ARTICLE_ORDER_BY = 'slug'`` The metadata attribute used to sort articles. By default,
the ``articles_page.object_list`` template variable is
ordered by slug. If you modify this, make sure all
articles contain the attribute you specify. You can also
specify a "sorting" function of one argument that is used
to extract a comparison key from each article. For example,
sorting by title without using the built-in functionality
would use the function ``operator.attrgetter('title')``.
``ARTICLE_LANG_URL = '{slug}-{lang}.html'`` The URL to refer to an article which doesn't use the
default language.
``ARTICLE_LANG_SAVE_AS = '{slug}-{lang}.html'`` The place where we will save an article which
Expand All @@ -279,6 +287,17 @@ Setting name (followed by default value, if any) What does it do?
``PAGE_SAVE_AS = 'pages/{slug}.html'`` The location we will save the page. This value has to be
the same as PAGE_URL or you need to use a rewrite in
your server config.

``PAGE_ORDER_BY = 'basename'`` The metadata attribute used to sort pages. By default
the ``PAGES`` template variable is ordered by basename
(i.e., path not included). Note that the option ``'basename'``
is a special option supported in the source code. If
you modify this setting, make sure all pages contain
the attribute you specify. You can also specify a "sorting"
function of one argument that is used to extract a comparison
key from each page. For example, the basename function looks
similar to
``lambda x: os.path.basename(getattr(x, 'source_path', ''))``.
``PAGE_LANG_URL = 'pages/{slug}-{lang}.html'`` The URL we will use to link to a page which doesn't
use the default language.
``PAGE_LANG_SAVE_AS = 'pages/{slug}-{lang}.html'`` The location we will save the page which doesn't
Expand All @@ -296,7 +315,7 @@ Setting name (followed by default value, if any) What does it do?
non-alphanumerics when generating slugs. Specified
as a list of 2-tuples of ``(from, to)`` which are
applied in order.
====================================================== ========================================================
====================================================== ==============================================================

.. note::

Expand Down
6 changes: 4 additions & 2 deletions pelican/generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -501,7 +501,8 @@ def generate_context(self):
(repr(article.status),
repr(f)))

self.articles, self.translations = process_translations(all_articles)
self.articles, self.translations = process_translations(all_articles,
order_by=self.settings['ARTICLE_ORDER_BY'])
self.drafts, self.drafts_translations = \
process_translations(all_drafts)

Expand Down Expand Up @@ -618,7 +619,8 @@ def generate_context(self):
(repr(page.status),
repr(f)))

self.pages, self.translations = process_translations(all_pages)
self.pages, self.translations = process_translations(all_pages,
order_by=self.settings['PAGE_ORDER_BY'])
self.hidden_pages, self.hidden_translations = (
process_translations(hidden_pages))

Expand Down
2 changes: 2 additions & 0 deletions pelican/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
'OUTPUT_RETENTION': (),
'ARTICLE_URL': '{slug}.html',
'ARTICLE_SAVE_AS': '{slug}.html',
'ARTICLE_ORDER_BY': 'slug',
'ARTICLE_LANG_URL': '{slug}-{lang}.html',
'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html',
'DRAFT_URL': 'drafts/{slug}.html',
Expand All @@ -69,6 +70,7 @@
'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'),
'PAGE_URL': 'pages/{slug}.html',
'PAGE_SAVE_AS': os.path.join('pages', '{slug}.html'),
'PAGE_ORDER_BY': 'basename',
'PAGE_LANG_URL': 'pages/{slug}-{lang}.html',
'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'),
'STATIC_URL': '{path}',
Expand Down
6 changes: 6 additions & 0 deletions pelican/tests/TestPages/page_used_for_sorting_test.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
A Page (Test) for sorting
#########################

:slug: zzzz

When using title, should be first. When using slug, should be last.
48 changes: 45 additions & 3 deletions pelican/tests/test_generators.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from unittest.mock import MagicMock
except ImportError:
from mock import MagicMock
from operator import itemgetter
from shutil import rmtree
from tempfile import mkdtemp

Expand Down Expand Up @@ -55,15 +56,19 @@ def setUpClass(cls):
context=settings.copy(), settings=settings,
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
cls.generator.generate_context()
cls.articles = [[page.title, page.status, page.category.name,
page.template] for page in cls.generator.articles]
cls.articles = cls.distill_articles(cls.generator.articles)

def setUp(self):
self.temp_cache = mkdtemp(prefix='pelican_cache.')

def tearDown(self):
rmtree(self.temp_cache)

@staticmethod
def distill_articles(articles):
return [[article.title, article.status, article.category.name,
article.template] for article in articles]

def test_generate_feeds(self):
settings = get_settings()
settings['CACHE_PATH'] = self.temp_cache
Expand Down Expand Up @@ -388,7 +393,8 @@ def test_generate_context(self):
['This is a test page', 'published', 'page'],
['This is a markdown test page', 'published', 'page'],
['This is a test page with a preset template', 'published',
'custom']
'custom'],
['A Page (Test) for sorting', 'published', 'page'],
]
hidden_pages_expected = [
['This is a test hidden page', 'hidden', 'page'],
Expand Down Expand Up @@ -466,6 +472,42 @@ def test_ignore_cache(self):
generator.generate_context()
generator.readers.read_file.assert_called_count == orig_call_count

def test_generate_sorted(self):
settings = get_settings(filenames={})
settings['PAGE_DIR'] = 'TestPages' # relative to CUR_DIR
settings['DEFAULT_DATE'] = (1970, 1, 1)

# default sort (filename)
pages_expected_sorted_by_filename = [
['This is a test page', 'published', 'page'],
['This is a markdown test page', 'published', 'page'],
['A Page (Test) for sorting', 'published', 'page'],
['This is a test page with a preset template', 'published',
'custom'],
]
generator = PagesGenerator(
context=settings.copy(), settings=settings,
path=CUR_DIR, theme=settings['THEME'], output_path=None)
generator.generate_context()
pages = self.distill_pages(generator.pages)
self.assertEqual(pages_expected_sorted_by_filename, pages)

# sort by title
pages_expected_sorted_by_title = [
['A Page (Test) for sorting', 'published', 'page'],
['This is a markdown test page', 'published', 'page'],
['This is a test page', 'published', 'page'],
['This is a test page with a preset template', 'published',
'custom'],
]
settings['PAGE_ORDER_BY'] = 'title'
generator = PagesGenerator(
context=settings.copy(), settings=settings,
path=CUR_DIR, theme=settings['THEME'], output_path=None)
generator.generate_context()
pages = self.distill_pages(generator.pages)
self.assertEqual(pages_expected_sorted_by_title, pages)


class TestTemplatePagesGenerator(unittest.TestCase):

Expand Down
27 changes: 26 additions & 1 deletion pelican/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -400,7 +400,7 @@ def truncate_html_words(s, num, end_text='...'):
return out


def process_translations(content_list):
def process_translations(content_list, order_by=None):
""" Finds translation and returns them.

Returns a tuple with two lists (index, translations). Index list includes
Expand All @@ -410,6 +410,14 @@ def process_translations(content_list):
the same slug have that metadata.

For each content_list item, sets the 'translations' attribute.

order_by can be a string of an attribute or sorting function. If order_by
is defined, content will be ordered by that attribute or sorting function.
By default, content is ordered by slug.

Different content types can have default order_by attributes defined
in settings, e.g. PAGES_ORDER_BY='sort-order', in which case `sort-order`
should be a defined metadata attribute in each page.
"""
content_list.sort(key=attrgetter('slug'))
grouped_by_slugs = groupby(content_list, attrgetter('slug'))
Expand Down Expand Up @@ -455,6 +463,23 @@ def process_translations(content_list):
translations.extend([x for x in items if x not in default_lang_items])
for a in items:
a.translations = [x for x in items if x != a]

if order_by:
if callable(order_by):
try:
index.sort(key=order_by)
except Exception:
logger.error('Error sorting with function {}'.format(order_by))
elif order_by == 'basename':
index.sort(key=lambda x: os.path.basename(x.source_path or ''))
elif order_by != 'slug':
try:
index.sort(key=attrgetter(order_by))
except AttributeError:
error_msg = ('There is no "{}" attribute in the item metadata.'
'Defaulting to slug order.')
logger.warning(error_msg.format(order_by))

return index, translations


Expand Down