diff --git a/docs/settings.rst b/docs/settings.rst index 4701e92d..55740296 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -265,6 +265,11 @@ 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. ``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 @@ -279,6 +284,14 @@ 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 = 'filename'`` The metadata attribute used to sort pages. By default + the PAGES template variable is ordered by filename + (path not included). Note that the option 'filename' + is a special option supported in the source code. If + you modify this settings, make sure all pages contain + the attribute you specify. You can also specify a + sorting function. ``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 diff --git a/pelican/generators.py b/pelican/generators.py index 7c6ba66b..8f934766 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -501,7 +501,8 @@ class ArticlesGenerator(CachingGenerator): (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) @@ -618,7 +619,8 @@ class PagesGenerator(CachingGenerator): (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)) diff --git a/pelican/settings.py b/pelican/settings.py index f759ff9e..f49d3bd4 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -61,6 +61,7 @@ DEFAULT_CONFIG = { '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', @@ -69,6 +70,7 @@ DEFAULT_CONFIG = { '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': 'filename', 'PAGE_LANG_URL': 'pages/{slug}-{lang}.html', 'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'), 'STATIC_URL': '{path}', diff --git a/pelican/tests/TestPages/page_used_for_sorting_test.rst b/pelican/tests/TestPages/page_used_for_sorting_test.rst new file mode 100644 index 00000000..40cdc7ea --- /dev/null +++ b/pelican/tests/TestPages/page_used_for_sorting_test.rst @@ -0,0 +1,6 @@ +A Page (Test) for sorting +######################### + +:slug: zzzz + +When using title, should be first. When using slug, should be last. diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 7b79e8f3..659383ac 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -7,6 +7,7 @@ try: from unittest.mock import MagicMock except ImportError: from mock import MagicMock +from operator import itemgetter from shutil import rmtree from tempfile import mkdtemp @@ -55,14 +56,12 @@ class TestArticlesGenerator(unittest.TestCase): 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() @@ -387,7 +386,8 @@ class TestPageGenerator(unittest.TestCase): ['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'], @@ -465,6 +465,42 @@ class TestPageGenerator(unittest.TestCase): 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): diff --git a/pelican/utils.py b/pelican/utils.py index 2af34ecf..ecdf5e0d 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -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 @@ -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')) @@ -455,6 +463,22 @@ 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 hasattr(order_by, '__call__'): + try: + index.sort(key=order_by) + except: + if hasattr(order_by, 'func_name'): + logger.error("Error sorting with function %s" % order_by.func_name) + else: + logger.error("Error sorting with function %r" % order_by) + elif order_by == 'filename': + index.sort(key=lambda x:os.path.basename( + x.source_path or '')) + elif order_by != 'slug': + index.sort(key=attrgetter(order_by)) + return index, translations