From 343e24a26f28eb15c2b189d0fca233797dffcfc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Vladim=C3=ADr=20Vondru=C5=A1?= Date: Sun, 29 Oct 2017 19:58:26 +0100 Subject: [PATCH] Make it possible to specify custom URLs for feeds. With this patch, there are new FEED_*_URL configuration options that allow to specify custom URLs for feeds, which is helpful in case the feed filename and the actual URL differ a lot -- for example if a feed is saved to blog/feeds/all.atom.xml but the actual URL from the user PoV is http://blog.your.site/feeds/all.atom.xml This setting currently affects only the generated feed XML. This change is also fully backwards compatible, so if the FEED_*_URL setting is not present, the value of FEED_* is used for both file location and URL. --- docs/settings.rst | 87 +++++++++++++++++++++++++++----- pelican/generators.py | 51 +++++++++++++++++-- pelican/tests/test_generators.py | 15 ++++++ pelican/writers.py | 8 +-- 4 files changed, 140 insertions(+), 21 deletions(-) diff --git a/docs/settings.rst b/docs/settings.rst index e5ce19f5..07069d2c 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -811,46 +811,95 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings: .. data:: FEED_ATOM = None, i.e. no Atom feed - Relative URL to output the Atom feed. + The location to save the Atom feed. + +.. data:: FEED_ATOM_URL = None + + Relative URL of the Atom feed. If not set, ``FEED_ATOM`` is used both for + save location and URL. .. data:: FEED_RSS = None, i.e. no RSS - Relative URL to output the RSS feed. + The location to save the RSS feed. + +.. data:: FEED_RSS_URL = None + + Relative URL of the RSS feed. If not set, ``FEED_RSS`` is used both for save + location and URL. .. data:: FEED_ALL_ATOM = 'feeds/all.atom.xml' - Relative URL to output the all-posts Atom feed: this feed will contain all + The location to save the all-posts Atom feed: this feed will contain all posts regardless of their language. +.. data:: FEED_ALL_ATOM_URL = None + + Relative URL of the all-posts Atom feed. If not set, ``FEED_ALL_ATOM`` is + used both for save location and URL. + .. data:: FEED_ALL_RSS = None, i.e. no all-posts RSS - Relative URL to output the all-posts RSS feed: this feed will contain all + The location to save the the all-posts RSS feed: this feed will contain all posts regardless of their language. +.. data:: FEED_ALL_RSS_URL = None + + Relative URL of the all-posts RSS feed. If not set, ``FEED_ALL_RSS`` is used + both for save location and URL. + .. data:: CATEGORY_FEED_ATOM = 'feeds/%s.atom.xml' - Where to put the category Atom feeds. [2]_ + The location to save the category Atom feeds. [2]_ + +.. data:: CATEGORY_FEED_ATOM_URL = None + + Relative URL of the category Atom feeds, including the ``%s`` placeholder. + [2]_ If not set, ``CATEGORY_FEED_ATOM`` is used both for save location and + URL. .. data:: CATEGORY_FEED_RSS = None, i.e. no RSS - Where to put the category RSS feeds. + The location to save the category RSS feeds, including the ``%s`` + placeholder. [2]_ + +.. data:: CATEGORY_FEED_RSS_URL = None + + Relative URL of the category RSS feeds, including the ``%s`` placeholder. + [2]_ If not set, ``CATEGORY_FEED_RSS`` is used both for save location and + URL. .. data:: AUTHOR_FEED_ATOM = 'feeds/%s.atom.xml' - Where to put the author Atom feeds. [2]_ + The location to save the author Atom feeds. [2]_ + +.. data:: AUTHOR_FEED_ATOM_URL = None + + Relative URL of the author Atom feeds, including the ``%s`` placeholder. + [2]_ If not set, ``AUTHOR_FEED_ATOM`` is used both for save location and + URL. .. data:: AUTHOR_FEED_RSS = 'feeds/%s.rss.xml' - Where to put the author RSS feeds. [2]_ + The location to save the author RSS feeds. [2]_ + +.. data:: AUTHOR_FEED_RSS_URL = None + + Relative URL of the author RSS feeds, including the ``%s`` placeholder. [2]_ + If not set, ``AUTHOR_FEED_RSS`` is used both for save location and URL. .. data:: TAG_FEED_ATOM = None, i.e. no tag feed - Relative URL to output the tag Atom feed. It should be defined using a "%s" - match in the tag name. + The location to save the tag Atom feed, including the ``%s`` placeholder. + [2]_ + +.. data:: TAG_FEED_ATOM_URL = None + + Relative URL of the tag Atom feed, including the ``%s`` placeholder. [2]_ .. data:: TAG_FEED_RSS = None, i.e. no RSS tag feed - Relative URL to output the tag RSS feed + Relative URL to output the tag RSS feed, including the ``%s`` placeholder. + If not set, ``TAG_FEED_RSS`` is used both for save location and URL. .. data:: FEED_MAX_ITEMS @@ -865,7 +914,7 @@ the ``TAG_FEED_ATOM`` and ``TAG_FEED_RSS`` settings: If you don't want to generate some or any of these feeds, set the above variables to ``None``. -.. [2] %s is the name of the category. +.. [2] ``%s`` is replaced by name of the category / author / tag. FeedBurner @@ -948,12 +997,24 @@ more information. .. data:: TRANSLATION_FEED_ATOM = 'feeds/all-%s.atom.xml' - Where to put the Atom feed for translations. [3]_ + The location to save the Atom feed for translations. [3]_ + +.. data:: TRANSLATION_FEED_ATOM_URL = None + + Relative URL of the Atom feed for translations, including the ``%s`` + placeholder. [3]_ If not set, ``TRANSLATION_FEED_ATOM`` is used both for + save location and URL. .. data:: TRANSLATION_FEED_RSS = None, i.e. no RSS Where to put the RSS feed for translations. +.. data:: TRANSLATION_FEED_RSS_URL = None + + Relative URL of the RSS feed for translations, including the ``%s`` + placeholder. [3]_ If not set, ``TRANSLATION_FEED_RSS`` is used both for save + location and URL. + .. [3] %s is the language diff --git a/pelican/generators.py b/pelican/generators.py index eb97c115..1f84b2b5 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -282,11 +282,16 @@ class ArticlesGenerator(CachingGenerator): if self.settings.get('FEED_ATOM'): writer.write_feed(self.articles, self.context, - self.settings['FEED_ATOM']) + self.settings['FEED_ATOM'], + self.settings.get('FEED_ATOM_URL', + self.settings['FEED_ATOM'])) if self.settings.get('FEED_RSS'): writer.write_feed(self.articles, self.context, - self.settings['FEED_RSS'], feed_type='rss') + self.settings['FEED_RSS'], + self.settings.get('FEED_RSS_URL', + self.settings['FEED_RSS']), + feed_type='rss') if (self.settings.get('FEED_ALL_ATOM') or self.settings.get('FEED_ALL_RSS')): @@ -297,11 +302,17 @@ class ArticlesGenerator(CachingGenerator): if self.settings.get('FEED_ALL_ATOM'): writer.write_feed(all_articles, self.context, - self.settings['FEED_ALL_ATOM']) + self.settings['FEED_ALL_ATOM'], + self.settings.get( + 'FEED_ALL_ATOM_URL', + self.settings['FEED_ALL_ATOM'])) if self.settings.get('FEED_ALL_RSS'): writer.write_feed(all_articles, self.context, self.settings['FEED_ALL_RSS'], + self.settings.get( + 'FEED_ALL_RSS_URL', + self.settings['FEED_ALL_RSS']), feed_type='rss') for cat, arts in self.categories: @@ -309,11 +320,19 @@ class ArticlesGenerator(CachingGenerator): if self.settings.get('CATEGORY_FEED_ATOM'): writer.write_feed(arts, self.context, self.settings['CATEGORY_FEED_ATOM'] + % cat.slug, + self.settings.get( + 'CATEGORY_FEED_ATOM_URL', + self.settings['CATEGORY_FEED_ATOM']) % cat.slug, feed_title=cat.name) if self.settings.get('CATEGORY_FEED_RSS'): writer.write_feed(arts, self.context, self.settings['CATEGORY_FEED_RSS'] + % cat.slug, + self.settings.get( + 'CATEGORY_FEED_RSS_URL', + self.settings['CATEGORY_FEED_RSS']) % cat.slug, feed_title=cat.name, feed_type='rss') @@ -322,11 +341,19 @@ class ArticlesGenerator(CachingGenerator): if self.settings.get('AUTHOR_FEED_ATOM'): writer.write_feed(arts, self.context, self.settings['AUTHOR_FEED_ATOM'] + % auth.slug, + self.settings.get( + 'AUTHOR_FEED_ATOM_URL', + self.settings['AUTHOR_FEED_ATOM']) % auth.slug, feed_title=auth.name) if self.settings.get('AUTHOR_FEED_RSS'): writer.write_feed(arts, self.context, self.settings['AUTHOR_FEED_RSS'] + % auth.slug, + self.settings.get( + 'AUTHOR_FEED_RSS_URL', + self.settings['AUTHOR_FEED_RSS']) % auth.slug, feed_title=auth.name, feed_type='rss') @@ -337,12 +364,20 @@ class ArticlesGenerator(CachingGenerator): if self.settings.get('TAG_FEED_ATOM'): writer.write_feed(arts, self.context, self.settings['TAG_FEED_ATOM'] + % tag.slug, + self.settings.get( + 'TAG_FEED_ATOM_URL', + self.settings['TAG_FEED_ATOM']) % tag.slug, feed_title=tag.name) if self.settings.get('TAG_FEED_RSS'): writer.write_feed(arts, self.context, self.settings['TAG_FEED_RSS'] % tag.slug, - feed_title=tag.name, feed_type='rss') + self.settings.get( + 'TAG_FEED_RSS_URL', + self.settings['TAG_FEED_RSS']) + % tag.slug, feed_title=tag.name, + feed_type='rss') if (self.settings.get('TRANSLATION_FEED_ATOM') or self.settings.get('TRANSLATION_FEED_RSS')): @@ -355,11 +390,17 @@ class ArticlesGenerator(CachingGenerator): if self.settings.get('TRANSLATION_FEED_ATOM'): writer.write_feed( items, self.context, - self.settings['TRANSLATION_FEED_ATOM'] % lang) + self.settings['TRANSLATION_FEED_ATOM'] % lang, + self.settings.get( + 'TRANSLATION_FEED_ATOM_URL', + self.settings['TRANSLATION_FEED_ATOM']) % lang) if self.settings.get('TRANSLATION_FEED_RSS'): writer.write_feed( items, self.context, self.settings['TRANSLATION_FEED_RSS'] % lang, + self.settings.get( + 'TRANSLATION_FEED_RSS_URL', + self.settings['TRANSLATION_FEED_RSS']) % lang, feed_type='rss') def generate_articles(self, write): diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 5f2151c3..8fc3f3de 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -155,6 +155,7 @@ class TestArticlesGenerator(unittest.TestCase): writer = MagicMock() generator.generate_feeds(writer) writer.write_feed.assert_called_with([], settings, + 'feeds/all.atom.xml', 'feeds/all.atom.xml') generator = ArticlesGenerator( @@ -164,6 +165,20 @@ class TestArticlesGenerator(unittest.TestCase): generator.generate_feeds(writer) self.assertFalse(writer.write_feed.called) + @unittest.skipUnless(MagicMock, 'Needs Mock module') + def test_generate_feeds_override_url(self): + settings = get_settings() + settings['CACHE_PATH'] = self.temp_cache + settings['FEED_ALL_ATOM_URL'] = 'feeds/atom/all/' + generator = ArticlesGenerator( + context=settings, settings=settings, + path=None, theme=settings['THEME'], output_path=None) + writer = MagicMock() + generator.generate_feeds(writer) + writer.write_feed.assert_called_with([], settings, + 'feeds/all.atom.xml', + 'feeds/atom/all/') + def test_generate_context(self): articles_expected = [ ['Article title', 'published', 'Default', 'article'], diff --git a/pelican/writers.py b/pelican/writers.py index fef0d5ca..ba77e7b5 100644 --- a/pelican/writers.py +++ b/pelican/writers.py @@ -89,8 +89,8 @@ class Writer(object): self._written_files.add(filename) return open(filename, 'w', encoding=encoding) - def write_feed(self, elements, context, path=None, feed_type='atom', - override_output=False, feed_title=None): + def write_feed(self, elements, context, path=None, url=None, + feed_type='atom', override_output=False, feed_title=None): """Generate a feed with the list of articles provided Return the feed. If no path or output_path is specified, just @@ -99,6 +99,8 @@ class Writer(object): :param elements: the articles to put on the feed. :param context: the context to get the feed metadata. :param path: the path to output. + :param url: the publicly visible feed URL; if None, path is used + instead :param feed_type: the feed type to use (atom or rss) :param override_output: boolean telling if we can override previous output with the same name (and if next files written with the same @@ -112,7 +114,7 @@ class Writer(object): 'SITEURL', path_to_url(get_relative_path(path))) self.feed_domain = context.get('FEED_DOMAIN') - self.feed_url = '{}/{}'.format(self.feed_domain, path) + self.feed_url = '{}/{}'.format(self.feed_domain, url if url else path) feed = self._create_new_feed(feed_type, feed_title, context)