mirror of
https://github.com/getpelican/pelican.git
synced 2025-10-15 20:28:56 +02:00
Merge pull request #1348 from vjousse/davidmarble-page-order-by
Support ordering pages and articles when iterating in templates.
This commit is contained in:
commit
1d9981b4f9
6 changed files with 105 additions and 9 deletions
|
|
@ -264,11 +264,19 @@ posts for the month at ``posts/2011/Aug/index.html``.
|
||||||
arrive at an appropriate archive of posts, without having to specify
|
arrive at an appropriate archive of posts, without having to specify
|
||||||
a page name.
|
a page name.
|
||||||
|
|
||||||
====================================================== ========================================================
|
====================================================== ==============================================================
|
||||||
Setting name (followed by default value, if any) What does it do?
|
Setting name (followed by default value, if any) What does it do?
|
||||||
====================================================== ========================================================
|
====================================================== ==============================================================
|
||||||
``ARTICLE_URL = '{slug}.html'`` The URL to refer to an article.
|
``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_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
|
``ARTICLE_LANG_URL = '{slug}-{lang}.html'`` The URL to refer to an article which doesn't use the
|
||||||
default language.
|
default language.
|
||||||
``ARTICLE_LANG_SAVE_AS = '{slug}-{lang}.html'`` The place where we will save an article which
|
``ARTICLE_LANG_SAVE_AS = '{slug}-{lang}.html'`` The place where we will save an article which
|
||||||
|
|
@ -283,6 +291,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
|
``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
|
the same as PAGE_URL or you need to use a rewrite in
|
||||||
your server config.
|
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
|
``PAGE_LANG_URL = 'pages/{slug}-{lang}.html'`` The URL we will use to link to a page which doesn't
|
||||||
use the default language.
|
use the default language.
|
||||||
``PAGE_LANG_SAVE_AS = 'pages/{slug}-{lang}.html'`` The location we will save the page which doesn't
|
``PAGE_LANG_SAVE_AS = 'pages/{slug}-{lang}.html'`` The location we will save the page which doesn't
|
||||||
|
|
@ -300,7 +319,7 @@ Setting name (followed by default value, if any) What does it do?
|
||||||
non-alphanumerics when generating slugs. Specified
|
non-alphanumerics when generating slugs. Specified
|
||||||
as a list of 2-tuples of ``(from, to)`` which are
|
as a list of 2-tuples of ``(from, to)`` which are
|
||||||
applied in order.
|
applied in order.
|
||||||
====================================================== ========================================================
|
====================================================== ==============================================================
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -501,7 +501,8 @@ class ArticlesGenerator(CachingGenerator):
|
||||||
logger.error("Unknown status '%s' for file %s, skipping it.",
|
logger.error("Unknown status '%s' for file %s, skipping it.",
|
||||||
article.status, f)
|
article.status, 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 = \
|
self.drafts, self.drafts_translations = \
|
||||||
process_translations(all_drafts)
|
process_translations(all_drafts)
|
||||||
|
|
||||||
|
|
@ -618,7 +619,8 @@ class PagesGenerator(CachingGenerator):
|
||||||
logger.error("Unknown status '%s' for file %s, skipping it.",
|
logger.error("Unknown status '%s' for file %s, skipping it.",
|
||||||
page.status, f)
|
page.status, 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 = (
|
self.hidden_pages, self.hidden_translations = (
|
||||||
process_translations(hidden_pages))
|
process_translations(hidden_pages))
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ DEFAULT_CONFIG = {
|
||||||
'OUTPUT_RETENTION': (),
|
'OUTPUT_RETENTION': (),
|
||||||
'ARTICLE_URL': '{slug}.html',
|
'ARTICLE_URL': '{slug}.html',
|
||||||
'ARTICLE_SAVE_AS': '{slug}.html',
|
'ARTICLE_SAVE_AS': '{slug}.html',
|
||||||
|
'ARTICLE_ORDER_BY': 'slug',
|
||||||
'ARTICLE_LANG_URL': '{slug}-{lang}.html',
|
'ARTICLE_LANG_URL': '{slug}-{lang}.html',
|
||||||
'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html',
|
'ARTICLE_LANG_SAVE_AS': '{slug}-{lang}.html',
|
||||||
'DRAFT_URL': 'drafts/{slug}.html',
|
'DRAFT_URL': 'drafts/{slug}.html',
|
||||||
|
|
@ -69,6 +70,7 @@ DEFAULT_CONFIG = {
|
||||||
'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'),
|
'DRAFT_LANG_SAVE_AS': os.path.join('drafts', '{slug}-{lang}.html'),
|
||||||
'PAGE_URL': 'pages/{slug}.html',
|
'PAGE_URL': 'pages/{slug}.html',
|
||||||
'PAGE_SAVE_AS': os.path.join('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_URL': 'pages/{slug}-{lang}.html',
|
||||||
'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'),
|
'PAGE_LANG_SAVE_AS': os.path.join('pages', '{slug}-{lang}.html'),
|
||||||
'STATIC_URL': '{path}',
|
'STATIC_URL': '{path}',
|
||||||
|
|
|
||||||
6
pelican/tests/TestPages/page_used_for_sorting_test.rst
Normal file
6
pelican/tests/TestPages/page_used_for_sorting_test.rst
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
A Page (Test) for sorting
|
||||||
|
#########################
|
||||||
|
|
||||||
|
:slug: zzzz
|
||||||
|
|
||||||
|
When using title, should be first. When using slug, should be last.
|
||||||
|
|
@ -7,6 +7,7 @@ try:
|
||||||
from unittest.mock import MagicMock
|
from unittest.mock import MagicMock
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from mock import MagicMock
|
from mock import MagicMock
|
||||||
|
from operator import itemgetter
|
||||||
from shutil import rmtree
|
from shutil import rmtree
|
||||||
from tempfile import mkdtemp
|
from tempfile import mkdtemp
|
||||||
|
|
||||||
|
|
@ -55,8 +56,7 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
context=settings.copy(), settings=settings,
|
context=settings.copy(), settings=settings,
|
||||||
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
|
||||||
cls.generator.generate_context()
|
cls.generator.generate_context()
|
||||||
cls.articles = [[page.title, page.status, page.category.name,
|
cls.articles = cls.distill_articles(cls.generator.articles)
|
||||||
page.template] for page in cls.generator.articles]
|
|
||||||
|
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
self.temp_cache = mkdtemp(prefix='pelican_cache.')
|
self.temp_cache = mkdtemp(prefix='pelican_cache.')
|
||||||
|
|
@ -64,6 +64,11 @@ class TestArticlesGenerator(unittest.TestCase):
|
||||||
def tearDown(self):
|
def tearDown(self):
|
||||||
rmtree(self.temp_cache)
|
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):
|
def test_generate_feeds(self):
|
||||||
settings = get_settings()
|
settings = get_settings()
|
||||||
settings['CACHE_PATH'] = self.temp_cache
|
settings['CACHE_PATH'] = self.temp_cache
|
||||||
|
|
@ -390,7 +395,8 @@ class TestPageGenerator(unittest.TestCase):
|
||||||
['This is a test page', 'published', 'page'],
|
['This is a test page', 'published', 'page'],
|
||||||
['This is a markdown test page', 'published', 'page'],
|
['This is a markdown test page', 'published', 'page'],
|
||||||
['This is a test page with a preset template', 'published',
|
['This is a test page with a preset template', 'published',
|
||||||
'custom']
|
'custom'],
|
||||||
|
['A Page (Test) for sorting', 'published', 'page'],
|
||||||
]
|
]
|
||||||
hidden_pages_expected = [
|
hidden_pages_expected = [
|
||||||
['This is a test hidden page', 'hidden', 'page'],
|
['This is a test hidden page', 'hidden', 'page'],
|
||||||
|
|
@ -468,6 +474,42 @@ class TestPageGenerator(unittest.TestCase):
|
||||||
generator.generate_context()
|
generator.generate_context()
|
||||||
generator.readers.read_file.assert_called_count == orig_call_count
|
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):
|
class TestTemplatePagesGenerator(unittest.TestCase):
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -440,7 +440,7 @@ def truncate_html_words(s, num, end_text='...'):
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
|
||||||
def process_translations(content_list):
|
def process_translations(content_list, order_by=None):
|
||||||
""" Finds translation and returns them.
|
""" Finds translation and returns them.
|
||||||
|
|
||||||
Returns a tuple with two lists (index, translations). Index list includes
|
Returns a tuple with two lists (index, translations). Index list includes
|
||||||
|
|
@ -450,6 +450,14 @@ def process_translations(content_list):
|
||||||
the same slug have that metadata.
|
the same slug have that metadata.
|
||||||
|
|
||||||
For each content_list item, sets the 'translations' attribute.
|
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'))
|
content_list.sort(key=attrgetter('slug'))
|
||||||
grouped_by_slugs = groupby(content_list, attrgetter('slug'))
|
grouped_by_slugs = groupby(content_list, attrgetter('slug'))
|
||||||
|
|
@ -495,6 +503,23 @@ def process_translations(content_list):
|
||||||
translations.extend([x for x in items if x not in default_lang_items])
|
translations.extend([x for x in items if x not in default_lang_items])
|
||||||
for a in items:
|
for a in items:
|
||||||
a.translations = [x for x in items if x != a]
|
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
|
return index, translations
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue