From 2f08932a769cfd077ff0fdbfb21a56d7a593fe44 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Reu=C3=9Fe?= <seb@wirrsal.net> Date: Sun, 15 Jul 2018 19:46:01 +0200 Subject: [PATCH 1/4] Support feed discovery and enumeration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This adds support for a Jinja function/filter, “atom_feeds”, which can be used to enumerate all feeds defined in the project (“atom_feeds()”), or those relevant to the page being generated (“atom_feeds(for_page=this)”). This is convenient, e.g., to define a generic site header which automatically displays a “subscribe” link on those pages which generate feeds. --- README.md | 12 ++++++ lektor_atom.py | 33 ++++++++++++++++ tests/demo-project/configs/atom.ini | 10 +++++ .../content/no-feed-content/contents.lr | 3 ++ tests/test_lektor_atom.py | 39 +++++++++++++++++++ 5 files changed, 97 insertions(+) create mode 100644 tests/demo-project/content/no-feed-content/contents.lr diff --git a/README.md b/README.md index edce238..3c5b9b5 100644 --- a/README.md +++ b/README.md @@ -100,6 +100,18 @@ Link to the feed in a template like this: {{ 'blog@atom/main'|url }} ``` +The plugin also defines a function to enumerate all feeds or a subset of feeds +relevant to the current page. + +``` +{% for feed in atom_feeds(for_page=this) %} + {{ feed | url }} +{% endfor %} +``` + +When the argument `for_page` is omitted, the function will enumerate all feeds +defined in your project. + # Changes 2016-06-02: Version 0.2. Python 3 compatibility (thanks to Dan Bauman), diff --git a/lektor_atom.py b/lektor_atom.py index de80ec6..1400515 100644 --- a/lektor_atom.py +++ b/lektor_atom.py @@ -184,6 +184,9 @@ def get_atom_config(self, feed_id, key): def on_setup_env(self, **extra): self.env.add_build_program(AtomFeedSource, AtomFeedBuilderProgram) + self.env.jinja_env.filters['atom_feeds'] = self.atom_feeds + self.env.jinja_env.globals['atom_feeds'] = self.atom_feeds + @self.env.virtualpathresolver('atom') def feed_path_resolver(node, pieces): if len(pieces) != 1: @@ -204,3 +207,33 @@ def generate_feeds(source): for _id in self.get_config().sections(): if source.path == self.get_atom_config(_id, 'source_path'): yield AtomFeedSource(source, _id, self) + + def _all_feeds(self): + ctx = get_ctx() + + feeds = [] + for feed_id in self.get_config().sections(): + path = self.get_atom_config(feed_id, 'source_path') + feed = ctx.pad.get('%s@atom/%s' % (path, feed_id)) + if feed: + feeds.append(feed) + + return feeds + + def _feeds_for(self, page): + ctx = get_ctx() + record = page.record + + feeds = [] + for section in self.get_config().sections(): + feed = ctx.pad.get('%s@atom/%s' % (record.path, section)) + if feed: + feeds.append(feed) + + return feeds + + def atom_feeds(self, for_page=None): + if not for_page: + return self._all_feeds() + else: + return self._feeds_for(for_page) diff --git a/tests/demo-project/configs/atom.ini b/tests/demo-project/configs/atom.ini index 420cdbc..5d596c2 100644 --- a/tests/demo-project/configs/atom.ini +++ b/tests/demo-project/configs/atom.ini @@ -17,3 +17,13 @@ item_body_field = contents item_author_field = writer item_date_field = published item_model = custom-blog-post + +[feed-four] +name = Feed Three (uncensored) +source_path = /custom-blog +filename = nsfw.xml +item_title_field = headline +item_body_field = contents +item_author_field = writer +item_date_field = published +item_model = custom-blog-post diff --git a/tests/demo-project/content/no-feed-content/contents.lr b/tests/demo-project/content/no-feed-content/contents.lr new file mode 100644 index 0000000..731412a --- /dev/null +++ b/tests/demo-project/content/no-feed-content/contents.lr @@ -0,0 +1,3 @@ +_model: page +--- +contents: Buffalo buffalo Buffalo buffalo buffalo buffalo Buffalo buffalo (cf. Wikipedia) diff --git a/tests/test_lektor_atom.py b/tests/test_lektor_atom.py index c163f25..b6f65cb 100644 --- a/tests/test_lektor_atom.py +++ b/tests/test_lektor_atom.py @@ -1,5 +1,6 @@ import os +from lektor.context import Context from lxml import objectify @@ -113,3 +114,41 @@ def test_dependencies(pad, builder, reporter): 'configs/atom.ini', ]) + +def feeds_from_template(pad, template): + with Context(pad=pad): + return set( + pad.env.jinja_env.from_string(template) + .render() + .split() + ) + + +def test_discover_all(pad): + template = r''' + {% for feed in atom_feeds() %} + {{ feed.feed_id }} + {% endfor %} + ''' + all_feeds = set(['feed-one', 'feed-two', + 'feed-three', 'feed-four']) + feeds_discovered = feeds_from_template(pad, template) + assert feeds_discovered == all_feeds + + +def test_discover_local(pad): + template_blog = r''' + {% for feed in atom_feeds(for_page=site.get('/custom-blog')) %} + {{ feed.feed_id }} + {% endfor %} + ''' + feeds_blog = feeds_from_template(pad, template_blog) + assert feeds_blog == set(['feed-three', 'feed-four']) + + template_noblog = r''' + {% for feed in atom_feeds(for_page=site.get('/no-feed-content')) %} + {{ feed.feed_id }} + {% endfor %} + ''' + feeds_noblog = feeds_from_template(pad, template_noblog) + assert len(feeds_noblog) == 0 From b09d8f9a6ec128b903ff7d56da5a90a69818e669 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Reu=C3=9Fe?= <seb@wirrsal.net> Date: Wed, 8 Aug 2018 17:23:26 +0200 Subject: [PATCH 2/4] Fix: use correct XML base URL in feeds built from page attachments --- lektor_atom.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lektor_atom.py b/lektor_atom.py index 1400515..20f8eec 100644 --- a/lektor_atom.py +++ b/lektor_atom.py @@ -138,10 +138,15 @@ def build_artifact(self, artifact): item_author_field = feed_source.item_author_field item_author = get(item, item_author_field) or blog_author + base_url = url_to( + item.parent if item.is_attachment else item, + external=True + ) + feed.add( get_item_title(item, feed_source.item_title_field), get_item_body(item, feed_source.item_body_field), - xml_base=url_to(item, external=True), + xml_base=base_url, url=url_to(item, external=True), content_type='html', id=get_id(u'%s/%s' % ( From ad6502f5fe19662ff0ac498337cf3a5ce6b29776 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Reu=C3=9Fe?= <seb@wirrsal.net> Date: Wed, 8 Aug 2018 19:15:20 +0200 Subject: [PATCH 3/4] Add alternatives support MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit While lektor-atom currently supports creating feed variants by relying on specially-crafted item query expressions, this is only useful for item model fields that contain natural language strings. When creating feeds based on structured data, this is not helpful, since field values are identical across two alts. E.g., when we would like to publish a feed based on PDF attachments that have a `volume_number` set, we are currently unable to have the English feed display item titles as »Volume 1« in the English feed resp. »Ausgabe 1« in the German alternative. This commit adds support for such use-cases by adding two new mechanisms: 1. Instead of supplying field names to map records to Atom entries, the user supplies a Jinja template. These expressions are evaluated with `this` bound to the blog resp. the item record. 2. For a feed named `feed`, configuration values are first looked-up in the config file section `[feed.ALT]`, where `ALT` is the alternative currently being generated. This allows settings defaults in `[feed]`, and overriding only those settings that are locale-specific by adding them to `[feed.ALT]`. As a side-effect, this also benefits users that don’t use alternatives, since it enables them to compose item titles, bodies, etc. using multiple fields at the same time. Fixes #3, #13. --- README.md | 66 ++++++-- lektor_atom.py | 152 ++++++++++++------ tests/demo-project/Website.lektorproject | 10 ++ tests/demo-project/configs/atom.ini | 24 ++- .../custom-blog/filtered_post/contents.lr | 2 +- .../content/custom-blog/post1/contents.lr | 2 +- .../content/custom-blog/post2/contents.lr | 2 +- .../content/multilang-blog/contents.lr | 6 + .../multilang-blog/post1/contents+de.lr | 5 + .../content/multilang-blog/post1/contents.lr | 5 + .../content/multilang-blog/post2/contents.lr | 7 + .../demo-project/models/custom-blog-post.ini | 2 +- tests/test_lektor_atom.py | 59 ++++++- 13 files changed, 260 insertions(+), 82 deletions(-) create mode 100644 tests/demo-project/content/multilang-blog/contents.lr create mode 100644 tests/demo-project/content/multilang-blog/post1/contents+de.lr create mode 100644 tests/demo-project/content/multilang-blog/post1/contents.lr create mode 100644 tests/demo-project/content/multilang-blog/post2/contents.lr diff --git a/README.md b/README.md index 3c5b9b5..cfe8064 100644 --- a/README.md +++ b/README.md @@ -38,21 +38,21 @@ The section names, like `blog` and `coffee`, are just used as internal identifie ### Options -|Option | Default | Description -|---------------------|------------|------------------------------------------------------------------------- -|source\_path | / | Where in the content directory to find items' parent source -|name | | Feed name: default is section name -|filename | feed.xml | Name of generated Atom feed file -|url\_path | | Feed's URL on your site: default is source's URL path plus the filename -|blog\_author\_field | author | Name of source's author field -|blog\_summary\_field | summary | Name of source's summary field -|items | None | A query expression: default is the source's children -|limit | 50 | How many recent items to include -|item\_title\_field | title | Name of items' title field -|item\_body\_field | body | Name of items' content body field -|item\_author\_field | author | Name of items' author field -|item\_date\_field | pub\_date | Name of items' publication date field -|item\_model | None | Name of items' model +| Option | Default | Description | +|-------------------|--------------------|-------------------------------------------------------------------------| +| source\_path | / | Where in the content directory to find items' parent source | +| name | | Feed name: default is section name | +| filename | feed.xml | Name of generated Atom feed file | +| url\_path | | Feed's URL on your site: default is source's URL path plus the filename | +| blog\_author | {{ this.author }} | Global blog author or blog editor | +| blog\_summary | {{ this.summary }} | Blog summary | +| items | None | A query expression: default is the source's children | +| limit | 50 | How many recent items to include | +| item\_title | {{ this.title }} | Blog post title | +| item\_body | {{ this.body }} | Blog post body | +| item\_author | {{ this.author }} | Blog post author | +| item\_date\_field | pub\_date | Name of items' publication date field | +| item\_model | None | Name of items' model | ### Customizing the plugin for your models @@ -73,8 +73,8 @@ Then add to atom.ini: ``` [main] -blog_author_field = writer -blog_summary_field = short_description +blog_author = {{ this.writer }} +blog_summary = {{ this.short_description }} ``` See [tests/demo-project/configs/atom.ini](https://github.com/ajdavis/lektor-atom/blob/master/tests/demo-project/configs/atom.ini) for a complete example. @@ -112,6 +112,38 @@ relevant to the current page. When the argument `for_page` is omitted, the function will enumerate all feeds defined in your project. +## Alternatives + +If your site is using Lektor’s alternative system, you can set +alternative-specific configuration values in your `configs/atom.ini`: + +``` +[blog] +name = My Blog +source_path = / +item_model = blog-post + +[blog.de] +name = Mein Blog +``` + +When lektor-atom is trying to retrieve a configuration value, it will first +look-up the config file section `[feed.ALT]`, where `ALT` is replaced by the +name of the alternative that is being generated. When such a value does not +exist, lektor-atom will get the value from the global section (`[feed]`), or, if +this does not succeed, lektor-atom will fall back on the hardcoded default. + +If you are using pybabel and have the Jinja i18n extension enabled, you can +alternatively localize your feeds by using `{% trans %}` blocks inside template +expressions in your `atom.ini`. To extract translation strings using babel, just +add the following to your `babel.cfg`: + +``` +[jinja2: site/configs/atom.ini] +encoding=utf-8 +silent=False +``` + # Changes 2016-06-02: Version 0.2. Python 3 compatibility (thanks to Dan Bauman), diff --git a/lektor_atom.py b/lektor_atom.py index 20f8eec..41c0745 100644 --- a/lektor_atom.py +++ b/lektor_atom.py @@ -8,7 +8,7 @@ import pkg_resources from lektor.build_programs import BuildProgram from lektor.db import F -from lektor.environment import Expression +from lektor.environment import Expression, FormatExpression from lektor.pluginsystem import Plugin from lektor.context import get_ctx, url_to from lektor.sourceobj import VirtualSourceObject @@ -37,21 +37,28 @@ def path(self): @property def url_path(self): - p = self.plugin.get_atom_config(self.feed_id, 'url_path') + p = self.plugin.get_atom_config(self.feed_id, 'url_path', + alt=self.alt) if p: + cfg = self.plugin.env.load_config() + primary_alts = '_primary', cfg.primary_alternative + if self.alt not in primary_alts: + p = "/%s%s" % (self.alt, p) return p return build_url([self.parent.url_path, self.filename]) def __getattr__(self, item): try: - return self.plugin.get_atom_config(self.feed_id, item) + return self.plugin.get_atom_config(self.feed_id, item, + alt=self.alt) except KeyError: raise AttributeError(item) @property def feed_name(self): - return self.plugin.get_atom_config(self.feed_id, 'name') or self.feed_id + return self.plugin.get_atom_config(self.feed_id, 'name', alt=self.alt) \ + or self.feed_id def get(item, field, default=None): @@ -71,13 +78,6 @@ def get_item_title(item, field): return item.record_label -def get_item_body(item, field): - if field not in item: - raise RuntimeError('Body field %r not found in %r' % (field, item)) - with get_ctx().changed_base_url(item.url_path): - return text_type(escape(item[field])) - - def get_item_updated(item, field): if field in item: rv = item[field] @@ -89,6 +89,14 @@ def get_item_updated(item, field): class AtomFeedBuilderProgram(BuildProgram): + def format_expression(self, expression, record, env): + with get_ctx().changed_base_url(record.url_path): + return FormatExpression(env, expression).evaluate( + record.pad, + this=record, + alt=record.alt + ) + def produce_artifacts(self): self.declare_artifact( self.source.url_path, @@ -99,13 +107,17 @@ def build_artifact(self, artifact): feed_source = self.source blog = feed_source.parent - summary = get(blog, feed_source.blog_summary_field) or '' - if hasattr(summary, '__html__'): - subtitle_type = 'html' - summary = text_type(summary.__html__()) - else: - subtitle_type = 'text' - blog_author = text_type(get(blog, feed_source.blog_author_field) or '') + summary = self.format_expression( + feed_source.blog_summary, + blog, + ctx.env) + + blog_author = self.format_expression( + feed_source.blog_author, + blog, + ctx.env + ) + generator = ('Lektor Atom Plugin', 'https://github.com/ajdavis/lektor-atom', pkg_resources.get_distribution('lektor-atom').version) @@ -113,10 +125,10 @@ def build_artifact(self, artifact): feed = AtomFeed( title=feed_source.feed_name, subtitle=summary, - subtitle_type=subtitle_type, + subtitle_type='html', author=blog_author, - feed_url=url_to(feed_source, external=True), - url=url_to(blog, external=True), + feed_url=url_to(feed_source, external=True, alt=feed_source.alt), + url=url_to(blog, external=True, alt=feed_source.alt), id=get_id(ctx.env.project.id), generator=generator) @@ -127,6 +139,10 @@ def build_artifact(self, artifact): else: items = blog.children + # Don’t force the user to think about alt when specifying an items + # query. + items.alt = feed_source.alt + if feed_source.item_model: items = items.filter(F._model == feed_source.item_model) @@ -135,17 +151,29 @@ def build_artifact(self, artifact): for item in items: try: - item_author_field = feed_source.item_author_field - item_author = get(item, item_author_field) or blog_author + item_author = self.format_expression( + feed_source.item_author, + item, + ctx.env + ) or blog_author base_url = url_to( item.parent if item.is_attachment else item, external=True ) - + body = self.format_expression( + feed_source.item_body, + item, + ctx.env + ) + title = self.format_expression( + feed_source.item_title, + item, + ctx.env + ) feed.add( - get_item_title(item, feed_source.item_title_field), - get_item_body(item, feed_source.item_body_field), + title, + body, xml_base=base_url, url=url_to(item, external=True), content_type='html', @@ -171,20 +199,30 @@ class AtomPlugin(Plugin): 'name': None, 'url_path': None, 'filename': 'feed.xml', - 'blog_author_field': 'author', - 'blog_summary_field': 'summary', + 'blog_author': '{{ this.author }}', + 'blog_summary': '{{ this.summary }}', 'items': None, 'limit': 50, - 'item_title_field': 'title', - 'item_body_field': 'body', - 'item_author_field': 'author', + 'item_title': '{{ this.title or this.record_label }}', + 'item_body': '{{ this.body }}', + 'item_author': '{{ this.author }}', 'item_date_field': 'pub_date', 'item_model': None, } - def get_atom_config(self, feed_id, key): + def get_atom_config(self, feed_id, key, alt=None): default_value = self.defaults[key] - return self.get_config().get('%s.%s' % (feed_id, key), default_value) + config = self.get_config() + primary_value = config.get( + "%s.%s" % (feed_id, key), + default_value + ) + localized_value = ( + config.get("%s.%s.%s" % (feed_id, alt, key)) + if alt + else None + ) + return localized_value or primary_value def on_setup_env(self, **extra): self.env.add_build_program(AtomFeedSource, AtomFeedBuilderProgram) @@ -199,46 +237,64 @@ def feed_path_resolver(node, pieces): _id = pieces[0] - config = self.get_config() - if _id not in config.sections(): + if _id not in self._feed_ids(): return - source_path = self.get_atom_config(_id, 'source_path') + source_path = self.get_atom_config(_id, 'source_path', + alt=node.alt) if node.path == source_path: return AtomFeedSource(node, _id, plugin=self) @self.env.generator def generate_feeds(source): - for _id in self.get_config().sections(): - if source.path == self.get_atom_config(_id, 'source_path'): + for _id in self._feed_ids(): + if source.path == self.get_atom_config(_id, 'source_path', + alt=source.alt): yield AtomFeedSource(source, _id, self) - def _all_feeds(self): + def _feed_ids(self): + feed_ids = set() + for section in self.get_config().sections(): + if '.' in section: + feed_id, _alt = section.split(".") + else: + feed_id = section + feed_ids.add(feed_id) + + return feed_ids + + def _all_feeds(self, alt=None): ctx = get_ctx() feeds = [] - for feed_id in self.get_config().sections(): - path = self.get_atom_config(feed_id, 'source_path') - feed = ctx.pad.get('%s@atom/%s' % (path, feed_id)) + for feed_id in self._feed_ids(): + path = self.get_atom_config(feed_id, 'source_path', alt=alt) + feed = ctx.pad.get( + '%s@atom/%s' % (path, feed_id), + alt=alt or ctx.record.alt + ) if feed: feeds.append(feed) return feeds - def _feeds_for(self, page): + def _feeds_for(self, page, alt=None): ctx = get_ctx() record = page.record feeds = [] - for section in self.get_config().sections(): - feed = ctx.pad.get('%s@atom/%s' % (record.path, section)) + for section in self._feed_ids(): + feed = ctx.pad.get( + '%s@atom/%s' % (record.path, section), + alt=alt or ctx.record.alt + ) if feed: feeds.append(feed) return feeds - def atom_feeds(self, for_page=None): + def atom_feeds(self, for_page=None, alt=None): if not for_page: - return self._all_feeds() + return self._all_feeds(alt=alt) else: - return self._feeds_for(for_page) + return self._feeds_for(for_page, alt=alt) diff --git a/tests/demo-project/Website.lektorproject b/tests/demo-project/Website.lektorproject index 2744af8..441693b 100644 --- a/tests/demo-project/Website.lektorproject +++ b/tests/demo-project/Website.lektorproject @@ -4,3 +4,13 @@ url = http://x.com [packages] lektor-atom + +[alternatives.en] +name = Elvish +primary = yes +locale = en_US + +[alternatives.de] +name = Dwarvish +locale = de_DE +url_prefix = /de/ diff --git a/tests/demo-project/configs/atom.ini b/tests/demo-project/configs/atom.ini index 5d596c2..2efe1d2 100644 --- a/tests/demo-project/configs/atom.ini +++ b/tests/demo-project/configs/atom.ini @@ -9,12 +9,12 @@ source_path = /typical-blog2 name = Feed Three source_path = /custom-blog filename = atom.xml -blog_author_field = editor -blog_summary_field = description +blog_author = {{ this.editor }} +blog_summary = {{ this.description }} items = site.query('/custom-blog').filter(F.headline != "I'm filtered out") -item_title_field = headline -item_body_field = contents -item_author_field = writer +item_title = {{ this.headline }} +item_body = {{ this.content }} +item_author = {{ this.writer }} item_date_field = published item_model = custom-blog-post @@ -22,8 +22,16 @@ item_model = custom-blog-post name = Feed Three (uncensored) source_path = /custom-blog filename = nsfw.xml -item_title_field = headline -item_body_field = contents -item_author_field = writer +item_title = {{ this.headline }} +item_body = {{ this.content }} +item_author = {{ this.writer }} item_date_field = published item_model = custom-blog-post + +[feed-five] +name = Feed Five +source_path = /multilang-blog +item_title = {{ this.title }} ({{ this.pub_date | dateformat }}) + +[feed-five.de] +name = Feed Fünf diff --git a/tests/demo-project/content/custom-blog/filtered_post/contents.lr b/tests/demo-project/content/custom-blog/filtered_post/contents.lr index 6cc88df..85b9cc1 100644 --- a/tests/demo-project/content/custom-blog/filtered_post/contents.lr +++ b/tests/demo-project/content/custom-blog/filtered_post/contents.lr @@ -2,4 +2,4 @@ headline: I'm filtered out --- published: 2015-12-12 15:00:00 --- -contents: baz +content: baz diff --git a/tests/demo-project/content/custom-blog/post1/contents.lr b/tests/demo-project/content/custom-blog/post1/contents.lr index 63e0ff0..93464bb 100644 --- a/tests/demo-project/content/custom-blog/post1/contents.lr +++ b/tests/demo-project/content/custom-blog/post1/contents.lr @@ -2,4 +2,4 @@ headline: Post 1 --- published: 2015-12-12 12:34:56 --- -contents: foo +content: foo diff --git a/tests/demo-project/content/custom-blog/post2/contents.lr b/tests/demo-project/content/custom-blog/post2/contents.lr index ebdda08..1a4215b 100644 --- a/tests/demo-project/content/custom-blog/post2/contents.lr +++ b/tests/demo-project/content/custom-blog/post2/contents.lr @@ -4,4 +4,4 @@ writer: Armin Ronacher --- published: 2015-12-13 00:00:00 --- -contents: bar +content: bar diff --git a/tests/demo-project/content/multilang-blog/contents.lr b/tests/demo-project/content/multilang-blog/contents.lr new file mode 100644 index 0000000..2f4e07d --- /dev/null +++ b/tests/demo-project/content/multilang-blog/contents.lr @@ -0,0 +1,6 @@ +_model: blog +--- +author: Guy de Maupassant +--- +summary: High-impact multilingual blog +--- diff --git a/tests/demo-project/content/multilang-blog/post1/contents+de.lr b/tests/demo-project/content/multilang-blog/post1/contents+de.lr new file mode 100644 index 0000000..ba35e97 --- /dev/null +++ b/tests/demo-project/content/multilang-blog/post1/contents+de.lr @@ -0,0 +1,5 @@ +title: Post 1 +--- +pub_date: 2015-12-12 +--- +body: Achtung! diff --git a/tests/demo-project/content/multilang-blog/post1/contents.lr b/tests/demo-project/content/multilang-blog/post1/contents.lr new file mode 100644 index 0000000..b76e7f8 --- /dev/null +++ b/tests/demo-project/content/multilang-blog/post1/contents.lr @@ -0,0 +1,5 @@ +title: Post 1 +--- +pub_date: 2015-12-12 +--- +body: foo diff --git a/tests/demo-project/content/multilang-blog/post2/contents.lr b/tests/demo-project/content/multilang-blog/post2/contents.lr new file mode 100644 index 0000000..5e6a905 --- /dev/null +++ b/tests/demo-project/content/multilang-blog/post2/contents.lr @@ -0,0 +1,7 @@ +title: Post 2 +--- +author: Armin Ronacher +--- +pub_date: 2015-12-13 +--- +body: bar diff --git a/tests/demo-project/models/custom-blog-post.ini b/tests/demo-project/models/custom-blog-post.ini index 8beae0b..a66a692 100644 --- a/tests/demo-project/models/custom-blog-post.ini +++ b/tests/demo-project/models/custom-blog-post.ini @@ -10,5 +10,5 @@ type = string [fields.published] type = datetime -[fields.contents] +[fields.content] type = markdown diff --git a/tests/test_lektor_atom.py b/tests/test_lektor_atom.py index b6f65cb..00ffbff 100644 --- a/tests/test_lektor_atom.py +++ b/tests/test_lektor_atom.py @@ -1,3 +1,5 @@ +# -*- coding: utf-8 -*- + import os from lektor.context import Context @@ -12,7 +14,7 @@ def test_typical_feed(pad, builder): assert 'Feed One' == feed.title assert 'My Summary' == feed.subtitle - assert 'text' == feed.subtitle.attrib['type'] + assert 'html' == feed.subtitle.attrib['type'] assert 'A. Jesse Jiryu Davis' == feed.author.name assert 'http://x.com/typical-blog/' == feed.link[0].attrib['href'] assert 'http://x.com/typical-blog/feed.xml' == feed.link[1].attrib['href'] @@ -78,6 +80,39 @@ def test_custom_feed(pad, builder): assert 'A. Jesse Jiryu Davis' == post1.author.name +def test_multilang_feed(pad, builder): + failures = builder.build_all() + assert not failures + + feed_path = os.path.join(builder.destination_path, + 'de/multilang-blog/feed.xml') + feed = objectify.parse(open(feed_path)).getroot() + + assert u'Feed Fünf' == feed.title + assert 'http://x.com/de/multilang-blog/' \ + == feed.link[0].attrib['href'] + assert 'http://x.com/de/multilang-blog/feed.xml' \ + == feed.link[1].attrib['href'] + assert feed.entry.title == 'Post 2 (13.12.2015)' + + base = feed.entry.attrib['{http://www.w3.org/XML/1998/namespace}base'] + assert 'http://x.com/de/multilang-blog/post2/' == base + + feed_path = os.path.join(builder.destination_path, + 'multilang-blog/feed.xml') + feed = objectify.parse(open(feed_path)).getroot() + + assert 'Feed Five' == feed.title + assert 'http://x.com/multilang-blog/' \ + == feed.link[0].attrib['href'] + assert 'http://x.com/multilang-blog/feed.xml' \ + == feed.link[1].attrib['href'] + assert feed.entry.title == 'Post 2 (Dec 13, 2015)' + + base = feed.entry.attrib['{http://www.w3.org/XML/1998/namespace}base'] + assert 'http://x.com/multilang-blog/post2/' == base + + def test_virtual_resolver(pad, builder): # Pass a virtual source path to url_to(). feed_path = '/typical-blog@atom/feed-one' @@ -98,6 +133,10 @@ def test_virtual_resolver(pad, builder): url_path = pad.get('custom-blog/post1').url_to(feed_instance) assert url_path == '../../custom-blog/atom.xml' + feed_instance = pad.get('multilang-blog@atom/feed-five', alt='de') + assert feed_instance and feed_instance.feed_name == u'Feed Fünf' + assert feed_instance.url_path == '/de/multilang-blog/feed.xml' + def test_dependencies(pad, builder, reporter): reporter.clear() @@ -126,19 +165,20 @@ def feeds_from_template(pad, template): def test_discover_all(pad): template = r''' - {% for feed in atom_feeds() %} + {% for feed in atom_feeds(alt='_primary') %} {{ feed.feed_id }} {% endfor %} ''' all_feeds = set(['feed-one', 'feed-two', - 'feed-three', 'feed-four']) + 'feed-three', 'feed-four', + 'feed-five']) feeds_discovered = feeds_from_template(pad, template) assert feeds_discovered == all_feeds def test_discover_local(pad): template_blog = r''' - {% for feed in atom_feeds(for_page=site.get('/custom-blog')) %} + {% for feed in atom_feeds(for_page=site.get('/custom-blog'), alt='_primary') %} {{ feed.feed_id }} {% endfor %} ''' @@ -146,9 +186,18 @@ def test_discover_local(pad): assert feeds_blog == set(['feed-three', 'feed-four']) template_noblog = r''' - {% for feed in atom_feeds(for_page=site.get('/no-feed-content')) %} + {% for feed in atom_feeds(for_page=site.get('/no-feed-content'), alt='_primary') %} {{ feed.feed_id }} {% endfor %} ''' feeds_noblog = feeds_from_template(pad, template_noblog) assert len(feeds_noblog) == 0 + + +def test_localized_config(pad): + plugin = pad.env.plugins['atom'] + assert plugin.get_atom_config('feed-five', 'name') \ + == 'Feed Five' + assert plugin.get_atom_config('feed-five', 'name', alt='de') \ + == u'Feed Fünf' + From 663fa0f679aa57e901ea2fb9f73d9cfd08ec2af4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Sebastian=20Reu=C3=9Fe?= <seb@wirrsal.net> Date: Thu, 9 Aug 2018 11:31:10 +0200 Subject: [PATCH 4/4] Fix: use correct link URL when using attachment items MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Lektor sometimes yields invalid URLs for attachment records that have ‘alt’ set. We work around this by forcing the primary alt when linking to attachments. --- lektor_atom.py | 11 +++++-- tests/demo-project/configs/atom.ini | 9 ++++++ .../content/attachments-feed/1.txt | 0 .../content/attachments-feed/1.txt.lr | 2 ++ .../content/attachments-feed/contents.lr | 6 ++++ tests/demo-project/models/attachment.ini | 6 ++++ .../demo-project/models/attachments-page.ini | 6 ++++ .../templates/attachments-page.html | 0 tests/test_lektor_atom.py | 30 ++++++++++++++++++- 9 files changed, 67 insertions(+), 3 deletions(-) create mode 100644 tests/demo-project/content/attachments-feed/1.txt create mode 100644 tests/demo-project/content/attachments-feed/1.txt.lr create mode 100644 tests/demo-project/content/attachments-feed/contents.lr create mode 100644 tests/demo-project/models/attachment.ini create mode 100644 tests/demo-project/models/attachments-page.ini create mode 100644 tests/demo-project/templates/attachments-page.html diff --git a/lektor_atom.py b/lektor_atom.py index 41c0745..99856de 100644 --- a/lektor_atom.py +++ b/lektor_atom.py @@ -156,7 +156,14 @@ def build_artifact(self, artifact): item, ctx.env ) or blog_author - + # FIXME Work-around Lektor #583. When the item is an attachment, + # we will get an invalid path here unless we force the + # `_primary` alt. + url = ( + item.url_to(item.path, external=True, alt='_primary') + if item.is_attachment + else url_to(item, external=True) + ) base_url = url_to( item.parent if item.is_attachment else item, external=True @@ -175,7 +182,7 @@ def build_artifact(self, artifact): title, body, xml_base=base_url, - url=url_to(item, external=True), + url=url, content_type='html', id=get_id(u'%s/%s' % ( ctx.env.project.id, diff --git a/tests/demo-project/configs/atom.ini b/tests/demo-project/configs/atom.ini index 2efe1d2..5917c21 100644 --- a/tests/demo-project/configs/atom.ini +++ b/tests/demo-project/configs/atom.ini @@ -35,3 +35,12 @@ item_title = {{ this.title }} ({{ this.pub_date | dateformat }}) [feed-five.de] name = Feed Fünf + +[feed-six] +name = Attachments feed +source_path = /attachments-feed +items = site.get("/attachments-feed").attachments +item_title = {{ this.title }} +item_body = +blog_summary = Lots of downloads +blog_author = Kim Dotcom diff --git a/tests/demo-project/content/attachments-feed/1.txt b/tests/demo-project/content/attachments-feed/1.txt new file mode 100644 index 0000000..e69de29 diff --git a/tests/demo-project/content/attachments-feed/1.txt.lr b/tests/demo-project/content/attachments-feed/1.txt.lr new file mode 100644 index 0000000..38d7ae0 --- /dev/null +++ b/tests/demo-project/content/attachments-feed/1.txt.lr @@ -0,0 +1,2 @@ +title: ASCII ramblings +--- diff --git a/tests/demo-project/content/attachments-feed/contents.lr b/tests/demo-project/content/attachments-feed/contents.lr new file mode 100644 index 0000000..bd8ab78 --- /dev/null +++ b/tests/demo-project/content/attachments-feed/contents.lr @@ -0,0 +1,6 @@ +_model: attachments-page +--- +author: A. Jesse Jiryu Davis +--- +summary: My Summary +--- diff --git a/tests/demo-project/models/attachment.ini b/tests/demo-project/models/attachment.ini new file mode 100644 index 0000000..9339b9f --- /dev/null +++ b/tests/demo-project/models/attachment.ini @@ -0,0 +1,6 @@ +[model] +name = Attached document +label = Attachment + +[fields.title] +type = string diff --git a/tests/demo-project/models/attachments-page.ini b/tests/demo-project/models/attachments-page.ini new file mode 100644 index 0000000..cdea8e6 --- /dev/null +++ b/tests/demo-project/models/attachments-page.ini @@ -0,0 +1,6 @@ +[model] +name = Document downloads page +label = Document downloads + +[attachments] +model = attachment diff --git a/tests/demo-project/templates/attachments-page.html b/tests/demo-project/templates/attachments-page.html new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_lektor_atom.py b/tests/test_lektor_atom.py index 00ffbff..5004bbc 100644 --- a/tests/test_lektor_atom.py +++ b/tests/test_lektor_atom.py @@ -171,7 +171,7 @@ def test_discover_all(pad): ''' all_feeds = set(['feed-one', 'feed-two', 'feed-three', 'feed-four', - 'feed-five']) + 'feed-five', 'feed-six']) feeds_discovered = feeds_from_template(pad, template) assert feeds_discovered == all_feeds @@ -201,3 +201,31 @@ def test_localized_config(pad): assert plugin.get_atom_config('feed-five', 'name', alt='de') \ == u'Feed Fünf' + +def test_attachments_feed(pad, builder): + failures = builder.build_all() + assert not failures + + def test_feed(path, prefix=''): + feed_path = os.path.join(builder.destination_path, prefix, path) + feed = objectify.parse(open(feed_path)).getroot() + + assert 'Attachments feed' == feed.title + assert 'Lots of downloads' == str(feed.subtitle).strip() + assert 'html' == feed.subtitle.attrib['type'] + assert 'Kim Dotcom' == feed.author.name + assert 'http://x.com/%sattachments-feed/' % prefix \ + == feed.link[0].attrib['href'] + assert 'http://x.com/%sattachments-feed/feed.xml' % prefix \ + == feed.link[1].attrib['href'] + + assert len(feed.entry) == 1 + attachment1 = feed.entry[0] + assert attachment1.title == "ASCII ramblings" + base = attachment1.attrib['{http://www.w3.org/XML/1998/namespace}base'] + assert base == 'http://x.com/attachments-feed/' + assert attachment1.link.attrib['href'] \ + == 'http://x.com/attachments-feed/1.txt' + + test_feed('attachments-feed/feed.xml') + test_feed('attachments-feed/feed.xml', prefix='de/')