From 182fb11c80935f2ef1661beaf1151969a24e833f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Thu, 26 Oct 2017 18:23:17 +0200 Subject: [PATCH] Allow to use page URL in pagination pattern. Currently it was only possible to use page "save as" name or part of it for generating pagination links. That's not sufficient when page URLs differ a lot from actual filenames. With this patch it's possible to use the `{url}` placeholder in PAGINATION_PATTERNS setting. For example, the paginated archives would be saved to: blog/index.html blog/2/index.html blog/3/index.html while the actual URLs would be like this (with the help of Apache's mod_rewrite): http://blog.my.site/ http://blog.my.site/2/ http://blog.my.site/3/ The configuration that corresponds to this is roughly the following: ARCHIVES_SAVE_AS = 'blog/index.html' ARCHIVES_URL = 'http://blog.my.site/' PAGINATION_PATTERNS = [ (1, '/{url}', '{base_name}/index.html'), (2, '/{url}{number}/', '{base_name}/{number}/index.html') ] Also added YEAR_ARCHIVE_URL, MONTH_ARCHIVE_URL and DAY_ARCHIVE_URL settings, as they were missing and now they make sense. --- pelican/generators.py | 33 +++++++++++++++++++++----------- pelican/paginator.py | 11 +++++++---- pelican/settings.py | 3 +++ pelican/tests/test_generators.py | 23 +++++++++++++++------- pelican/tests/test_paginator.py | 24 ++++++++++++++++++++++- pelican/writers.py | 5 +++-- 6 files changed, 74 insertions(+), 25 deletions(-) diff --git a/pelican/generators.py b/pelican/generators.py index eb97c115..877eefe9 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -255,7 +255,7 @@ class TemplatePagesGenerator(Generator): template = self.env.get_template(source) rurls = self.settings['RELATIVE_URLS'] writer.write_file(dest, template, self.context, rurls, - override_output=True) + override_output=True, url='') finally: del self.env.loader.loaders[0] @@ -369,7 +369,7 @@ class ArticlesGenerator(CachingGenerator): write(article.save_as, self.get_template(article.template), self.context, article=article, category=article.category, override_output=hasattr(article, 'override_save_as'), - blog=True) + url=article.url, blog=True) def generate_period_archives(self, write): """Generate per-year, per-month, and per-day archives.""" @@ -384,13 +384,19 @@ class ArticlesGenerator(CachingGenerator): 'day': self.settings['DAY_ARCHIVE_SAVE_AS'], } + period_url = { + 'year': self.settings['YEAR_ARCHIVE_URL'], + 'month': self.settings['MONTH_ARCHIVE_URL'], + 'day': self.settings['DAY_ARCHIVE_URL'], + } + period_date_key = { 'year': attrgetter('date.year'), 'month': attrgetter('date.year', 'date.month'), 'day': attrgetter('date.year', 'date.month', 'date.day') } - def _generate_period_archives(dates, key, save_as_fmt): + def _generate_period_archives(dates, key, save_as_fmt, url_fmt): """Generate period archives from `dates`, grouped by `key` and written to `save_as`. """ @@ -402,6 +408,7 @@ class ArticlesGenerator(CachingGenerator): # period archive dates date = archive[0].date save_as = save_as_fmt.format(date=date) + url = url_fmt.format(date=date) context = self.context.copy() if key == period_date_key['year']: @@ -419,13 +426,14 @@ class ArticlesGenerator(CachingGenerator): _period[2]) write(save_as, template, context, - dates=archive, blog=True) + dates=archive, blog=True, url=url) for period in 'year', 'month', 'day': save_as = period_save_as[period] + url = period_url[period] if save_as: key = period_date_key[period] - _generate_period_archives(self.dates, key, save_as) + _generate_period_archives(self.dates, key, save_as, url) def generate_direct_templates(self, write): """Generate direct templates pages""" @@ -436,12 +444,14 @@ class ArticlesGenerator(CachingGenerator): paginated = {'articles': self.articles, 'dates': self.dates} save_as = self.settings.get("%s_SAVE_AS" % template.upper(), '%s.html' % template) + url = self.settings.get("%s_URL" % template.upper(), + '%s.html' % template) if not save_as: continue write(save_as, self.get_template(template), self.context, blog=True, paginated=paginated, - page_name=os.path.splitext(save_as)[0]) + page_name=os.path.splitext(save_as)[0], url=url) def generate_tags(self, write): """Generate Tags pages.""" @@ -450,7 +460,7 @@ class ArticlesGenerator(CachingGenerator): articles.sort(key=attrgetter('date'), reverse=True) dates = [article for article in self.dates if article in articles] write(tag.save_as, tag_template, self.context, tag=tag, - articles=articles, dates=dates, + url=tag.url, articles=articles, dates=dates, paginated={'articles': articles, 'dates': dates}, blog=True, page_name=tag.page_name, all_articles=self.articles) @@ -461,7 +471,7 @@ class ArticlesGenerator(CachingGenerator): articles.sort(key=attrgetter('date'), reverse=True) dates = [article for article in self.dates if article in articles] write(cat.save_as, category_template, self.context, - category=cat, articles=articles, dates=dates, + url=cat.url, category=cat, articles=articles, dates=dates, paginated={'articles': articles, 'dates': dates}, blog=True, page_name=cat.page_name, all_articles=self.articles) @@ -472,7 +482,7 @@ class ArticlesGenerator(CachingGenerator): articles.sort(key=attrgetter('date'), reverse=True) dates = [article for article in self.dates if article in articles] write(aut.save_as, author_template, self.context, - author=aut, articles=articles, dates=dates, + url=aut.url, author=aut, articles=articles, dates=dates, paginated={'articles': articles, 'dates': dates}, blog=True, page_name=aut.page_name, all_articles=self.articles) @@ -482,7 +492,7 @@ class ArticlesGenerator(CachingGenerator): write(draft.save_as, self.get_template(draft.template), self.context, article=draft, category=draft.category, override_output=hasattr(draft, 'override_save_as'), - blog=True, all_articles=self.articles) + blog=True, all_articles=self.articles, url=draft.url) def generate_pages(self, writer): """Generate the pages on the disk""" @@ -646,7 +656,8 @@ class PagesGenerator(CachingGenerator): page.save_as, self.get_template(page.template), self.context, page=page, relative_urls=self.settings['RELATIVE_URLS'], - override_output=hasattr(page, 'override_save_as')) + override_output=hasattr(page, 'override_save_as'), + url=page.url) signals.page_writer_finalized.send(self, writer=writer) diff --git a/pelican/paginator.py b/pelican/paginator.py index 9aca550b..6996cae9 100644 --- a/pelican/paginator.py +++ b/pelican/paginator.py @@ -17,8 +17,9 @@ PaginationRule = namedtuple( class Paginator(object): - def __init__(self, name, object_list, settings): + def __init__(self, name, url, object_list, settings): self.name = name + self.url = url self.object_list = object_list self.settings = settings @@ -37,8 +38,8 @@ class Paginator(object): top = bottom + self.per_page if top + self.orphans >= self.count: top = self.count - return Page(self.name, self.object_list[bottom:top], number, self, - self.settings) + return Page(self.name, self.url, self.object_list[bottom:top], number, + self, self.settings) def _get_count(self): "Returns the total number of objects, across all pages." @@ -65,8 +66,9 @@ class Paginator(object): class Page(object): - def __init__(self, name, object_list, number, paginator, settings): + def __init__(self, name, url, object_list, number, paginator, settings): self.name, self.extension = os.path.splitext(name) + self.base_url = url self.object_list = object_list self.number = number self.paginator = paginator @@ -134,6 +136,7 @@ class Page(object): # URL or SAVE_AS is a string, format it with a controlled context context = { 'name': self.name.replace(os.sep, '/'), + 'url': self.base_url, 'object_list': self.object_list, 'number': self.number, 'paginator': self.paginator, diff --git a/pelican/settings.py b/pelican/settings.py index 417de79a..b1ec3b0f 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -93,8 +93,11 @@ DEFAULT_CONFIG = { 'PAGINATION_PATTERNS': [ (0, '{name}{number}{extension}', '{name}{number}{extension}'), ], + 'YEAR_ARCHIVE_URL': '', 'YEAR_ARCHIVE_SAVE_AS': '', + 'MONTH_ARCHIVE_URL': '', 'MONTH_ARCHIVE_SAVE_AS': '', + 'DAY_ARCHIVE_URL': '', 'DAY_ARCHIVE_SAVE_AS': '', 'RELATIVE_URLS': False, 'DEFAULT_LANG': 'en', diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 5f2151c3..1f4c895e 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -255,7 +255,7 @@ class TestArticlesGenerator(unittest.TestCase): self.assertEqual(sorted(categories), sorted(categories_expected)) @unittest.skipUnless(MagicMock, 'Needs Mock module') - def test_direct_templates_save_as_default(self): + def test_direct_templates_save_as_url_default(self): settings = get_settings(filenames={}) settings['CACHE_PATH'] = self.temp_cache @@ -266,14 +266,16 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_direct_templates(write) write.assert_called_with("archives.html", generator.get_template("archives"), settings, - blog=True, paginated={}, page_name='archives') + blog=True, paginated={}, page_name='archives', + url="archives.html") @unittest.skipUnless(MagicMock, 'Needs Mock module') - def test_direct_templates_save_as_modified(self): + def test_direct_templates_save_as_url_modified(self): settings = get_settings() settings['DIRECT_TEMPLATES'] = ['archives'] settings['ARCHIVES_SAVE_AS'] = 'archives/index.html' + settings['ARCHIVES_URL'] = 'archives/' settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, @@ -283,7 +285,8 @@ class TestArticlesGenerator(unittest.TestCase): write.assert_called_with("archives/index.html", generator.get_template("archives"), settings, blog=True, paginated={}, - page_name='archives/index') + page_name='archives/index', + url="archives/") @unittest.skipUnless(MagicMock, 'Needs Mock module') def test_direct_templates_save_as_false(self): @@ -321,6 +324,7 @@ class TestArticlesGenerator(unittest.TestCase): settings = get_settings(filenames={}) settings['YEAR_ARCHIVE_SAVE_AS'] = 'posts/{date:%Y}/index.html' + settings['YEAR_ARCHIVE_URL'] = 'posts/{date:%Y}/' settings['CACHE_PATH'] = self.temp_cache generator = ArticlesGenerator( context=settings, settings=settings, @@ -335,11 +339,13 @@ class TestArticlesGenerator(unittest.TestCase): write.assert_called_with("posts/1970/index.html", generator.get_template("period_archives"), settings, - blog=True, dates=dates) + blog=True, dates=dates, url="posts/1970/") del settings["period"] settings['MONTH_ARCHIVE_SAVE_AS'] = \ 'posts/{date:%Y}/{date:%b}/index.html' + settings['MONTH_ARCHIVE_URL'] = \ + 'posts/{date:%Y}/{date:%b}/' generator = ArticlesGenerator( context=settings, settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) @@ -354,11 +360,13 @@ class TestArticlesGenerator(unittest.TestCase): write.assert_called_with("posts/1970/Jan/index.html", generator.get_template("period_archives"), settings, - blog=True, dates=dates) + blog=True, dates=dates, url="posts/1970/Jan/") del settings["period"] settings['DAY_ARCHIVE_SAVE_AS'] = \ 'posts/{date:%Y}/{date:%b}/{date:%d}/index.html' + settings['DAY_ARCHIVE_URL'] = \ + 'posts/{date:%Y}/{date:%b}/{date:%d}/' generator = ArticlesGenerator( context=settings, settings=settings, path=CONTENT_DIR, theme=settings['THEME'], output_path=None) @@ -377,7 +385,8 @@ class TestArticlesGenerator(unittest.TestCase): write.assert_called_with("posts/1970/Jan/01/index.html", generator.get_template("period_archives"), settings, - blog=True, dates=dates) + blog=True, dates=dates, + url="posts/1970/Jan/01/") locale.setlocale(locale.LC_ALL, old_locale) def test_nonexistent_template(self): diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py index 903a0305..12403410 100644 --- a/pelican/tests/test_paginator.py +++ b/pelican/tests/test_paginator.py @@ -54,6 +54,28 @@ class TestPage(unittest.TestCase): self.page_kwargs['metadata']['author'] = Author('Blogger', settings) object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)] - paginator = Paginator('foobar.foo', object_list, settings) + paginator = Paginator('foobar.foo', 'foobar/foo', object_list, + settings) page = paginator.page(1) self.assertEqual(page.save_as, 'foobar.foo') + + def test_custom_pagination_pattern(self): + from pelican.paginator import PaginationRule + settings = get_settings() + settings['PAGINATION_PATTERNS'] = [PaginationRule(*r) for r in [ + (1, '/{url}', '{base_name}/index.html'), + (2, '/{url}{number}/', '{base_name}/{number}/index.html') + ]] + settings['DEFAULT_PAGINATION'] = 1 + + self.page_kwargs['metadata']['author'] = Author('Blogger', settings) + object_list = [Article(**self.page_kwargs), + Article(**self.page_kwargs)] + paginator = Paginator('blog/index.html', '//blog.my.site/', + object_list, settings) + page1 = paginator.page(1) + self.assertEqual(page1.save_as, 'blog/index.html') + self.assertEqual(page1.url, '//blog.my.site/') + page2 = paginator.page(2) + self.assertEqual(page2.save_as, 'blog/2/index.html') + self.assertEqual(page2.url, '//blog.my.site/2/') diff --git a/pelican/writers.py b/pelican/writers.py index fef0d5ca..6c8b97d2 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -141,7 +141,7 @@ class Writer(object): return feed def write_file(self, name, template, context, relative_urls=False, - paginated=None, override_output=False, **kwargs): + paginated=None, override_output=False, url=None, **kwargs): """Render the template and write the file. :param name: name of the file to output @@ -153,6 +153,7 @@ class Writer(object): :param override_output: boolean telling if we can override previous output with the same name (and if next files written with the same name should be skipped to keep that one) + :param url: url of the file (needed by the paginator) :param **kwargs: additional variables to pass to the templates """ @@ -202,7 +203,7 @@ class Writer(object): if paginated: # pagination needed, init paginators - paginators = {key: Paginator(name, val, self.settings) + paginators = {key: Paginator(name, url, val, self.settings) for key, val in paginated.items()} # generated pages, and write