Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feed enumeration, alt support #16

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
78 changes: 61 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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.
Expand All @@ -100,6 +100,50 @@ 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.

## 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),
Expand Down
181 changes: 141 additions & 40 deletions lektor_atom.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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):
Expand All @@ -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]
Expand All @@ -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,
Expand All @@ -99,24 +107,28 @@ 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)

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)

Expand All @@ -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)

Expand All @@ -135,14 +151,38 @@ 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
# 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
)
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),
xml_base=url_to(item, external=True),
url=url_to(item, external=True),
title,
body,
xml_base=base_url,
url=url,
content_type='html',
id=get_id(u'%s/%s' % (
ctx.env.project.id,
Expand All @@ -166,41 +206,102 @@ 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)

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:
return

_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 _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._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, alt=None):
ctx = get_ctx()
record = page.record

feeds = []
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, alt=None):
if not for_page:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Better to be explicit and use if page is None ?

return self._all_feeds(alt=alt)
else:
return self._feeds_for(for_page, alt=alt)
10 changes: 10 additions & 0 deletions tests/demo-project/Website.lektorproject
Original file line number Diff line number Diff line change
Expand Up @@ -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/
Loading