From 0c69f4ad847c5f4b1dc06bd03d070b819a641d1f Mon Sep 17 00:00:00 2001 From: David Marble Date: Thu, 14 Nov 2013 12:29:01 -0800 Subject: [PATCH 1/5] Support ordering pages and articles when iterating in templates. Order can be set to a metadata attribute or a sorting function. Default to order by slug for articles and order by filename for pages. --- docs/settings.rst | 12 ++++++++++++ pelican/generators.py | 6 ++++-- pelican/settings.py | 2 ++ pelican/utils.py | 26 +++++++++++++++++++++++++- 4 files changed, 43 insertions(+), 3 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 82752436..3f11437c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -236,6 +236,11 @@ Setting name (default value) 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 @@ -244,6 +249,13 @@ Setting name (default value) 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 c55cdc37..2cdd4f89 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -409,7 +409,8 @@ class ArticlesGenerator(Generator): (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']) for article in self.articles: # only main articles are listed in categories and tags @@ -517,7 +518,8 @@ class PagesGenerator(Generator): (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 99828935..fbef91fc 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -57,10 +57,12 @@ 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', '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/utils.py b/pelican/utils.py index 4b25ec7f..e1a16fdb 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -430,7 +430,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 @@ -440,6 +440,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')) @@ -485,6 +493,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 From 69ff7dd634970c7aabab2d0f74fd34ff2108899a Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Thu, 14 Nov 2013 12:37:22 -0800 Subject: [PATCH 2/5] Add test for PAGE_ORDER_BY --- .../TestPages/page_used_for_sorting_test.rst | 6 +++ pelican/tests/test_generators.py | 48 +++++++++++++++++-- 2 files changed, 51 insertions(+), 3 deletions(-) create mode 100644 pelican/tests/TestPages/page_used_for_sorting_test.rst 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 150d30f5..f8a5fc3f 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 @@ -47,8 +48,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) + + @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() @@ -223,7 +228,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'], @@ -235,6 +241,42 @@ class TestPageGenerator(unittest.TestCase): self.assertEqual(sorted(pages_expected), sorted(pages)) self.assertEqual(sorted(hidden_pages_expected), sorted(hidden_pages)) + 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): From bf6a4ad74741a954fecefa4536637190ef23fb5e Mon Sep 17 00:00:00 2001 From: Vincent Jousse Date: Wed, 14 May 2014 11:11:58 +0200 Subject: [PATCH 3/5] Add missing methods --- pelican/tests/test_generators.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 659383ac..07871cef 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -58,6 +58,12 @@ class TestArticlesGenerator(unittest.TestCase): cls.generator.generate_context() 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, From 144cddaf39ad6577cfa4a11ef003f6fa44c81d58 Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Tue, 20 May 2014 13:53:02 -0700 Subject: [PATCH 4/5] Address code review comments from PR getpelican/pelican#1348 The text about the sort-by-key function comes from: https://docs.python.org/2/library/stdtypes.html#sequence-types-str-unicode-list-tuple-bytearray-buffer-xrange Minor style cleanup as well. --- docs/settings.rst | 36 +++++++++++++++++++++--------------- pelican/utils.py | 14 +++++--------- 2 files changed, 26 insertions(+), 24 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index 55740296..0c804f1c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -260,16 +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. +``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 @@ -285,13 +288,16 @@ Setting name (followed by default value, if any) What does it do? 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_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 @@ -309,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:: diff --git a/pelican/utils.py b/pelican/utils.py index ecdf5e0d..076c41ea 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -465,17 +465,13 @@ def process_translations(content_list, order_by=None): a.translations = [x for x in items if x != a] if order_by: - if hasattr(order_by, '__call__'): + if callable(order_by): 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 '')) + 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': index.sort(key=attrgetter(order_by)) From b572cbeef15ac2e4fe6107110f52fbdc212cd52a Mon Sep 17 00:00:00 2001 From: Mark Lee Date: Tue, 27 May 2014 11:42:37 -0700 Subject: [PATCH 5/5] Addressed comments from @avaris in PR getpelican/pelican#1348 --- pelican/settings.py | 2 +- pelican/utils.py | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/pelican/settings.py b/pelican/settings.py index f49d3bd4..6845b830 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -70,7 +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_ORDER_BY': 'basename', '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/utils.py b/pelican/utils.py index 076c41ea..c2d6ca22 100644 --- a/pelican/utils.py +++ b/pelican/utils.py @@ -473,7 +473,12 @@ def process_translations(content_list, order_by=None): elif order_by == 'basename': index.sort(key=lambda x: os.path.basename(x.source_path or '')) elif order_by != 'slug': - index.sort(key=attrgetter(order_by)) + 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