aboutsummaryrefslogtreecommitdiffstats
path: root/nikola/plugins/task/authors.py
diff options
context:
space:
mode:
Diffstat (limited to 'nikola/plugins/task/authors.py')
-rw-r--r--nikola/plugins/task/authors.py316
1 files changed, 316 insertions, 0 deletions
diff --git a/nikola/plugins/task/authors.py b/nikola/plugins/task/authors.py
new file mode 100644
index 0000000..081d21d
--- /dev/null
+++ b/nikola/plugins/task/authors.py
@@ -0,0 +1,316 @@
+# -*- coding: utf-8 -*-
+
+# Copyright © 2015 Juanjo Conti.
+
+# Permission is hereby granted, free of charge, to any
+# person obtaining a copy of this software and associated
+# documentation files (the "Software"), to deal in the
+# Software without restriction, including without limitation
+# the rights to use, copy, modify, merge, publish,
+# distribute, sublicense, and/or sell copies of the
+# Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice
+# shall be included in all copies or substantial portions of
+# the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY
+# KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
+# WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
+# PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
+# OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
+# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
+# OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+# SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+"""Render the author pages and feeds."""
+
+from __future__ import unicode_literals
+import os
+import natsort
+try:
+ from urlparse import urljoin
+except ImportError:
+ from urllib.parse import urljoin # NOQA
+from collections import defaultdict
+
+from nikola.plugin_categories import Task
+from nikola import utils
+
+
+class RenderAuthors(Task):
+ """Render the author pages and feeds."""
+
+ name = "render_authors"
+ posts_per_author = None
+
+ def set_site(self, site):
+ """Set Nikola site."""
+ if site.config["ENABLE_AUTHOR_PAGES"]:
+ site.register_path_handler('author_index', self.author_index_path)
+ site.register_path_handler('author', self.author_path)
+ site.register_path_handler('author_atom', self.author_atom_path)
+ site.register_path_handler('author_rss', self.author_rss_path)
+ return super(RenderAuthors, self).set_site(site)
+
+ def gen_tasks(self):
+ """Render the author pages and feeds."""
+ kw = {
+ "translations": self.site.config["TRANSLATIONS"],
+ "blog_title": self.site.config["BLOG_TITLE"],
+ "site_url": self.site.config["SITE_URL"],
+ "base_url": self.site.config["BASE_URL"],
+ "messages": self.site.MESSAGES,
+ "output_folder": self.site.config['OUTPUT_FOLDER'],
+ "filters": self.site.config['FILTERS'],
+ 'author_path': self.site.config['AUTHOR_PATH'],
+ "author_pages_are_indexes": self.site.config['AUTHOR_PAGES_ARE_INDEXES'],
+ "generate_rss": self.site.config['GENERATE_RSS'],
+ "feed_teasers": self.site.config["FEED_TEASERS"],
+ "feed_plain": self.site.config["FEED_PLAIN"],
+ "feed_link_append_query": self.site.config["FEED_LINKS_APPEND_QUERY"],
+ "show_untranslated_posts": self.site.config['SHOW_UNTRANSLATED_POSTS'],
+ "feed_length": self.site.config['FEED_LENGTH'],
+ "tzinfo": self.site.tzinfo,
+ "pretty_urls": self.site.config['PRETTY_URLS'],
+ "strip_indexes": self.site.config['STRIP_INDEXES'],
+ "index_file": self.site.config['INDEX_FILE'],
+ }
+
+ yield self.group_task()
+ self.site.scan_posts()
+
+ generate_author_pages = self.site.config["ENABLE_AUTHOR_PAGES"] and len(self._posts_per_author()) > 1
+ self.site.GLOBAL_CONTEXT["author_pages_generated"] = generate_author_pages
+ if generate_author_pages:
+ yield self.list_authors_page(kw)
+
+ if not self._posts_per_author(): # this may be self.site.posts_per_author
+ return
+
+ author_list = list(self._posts_per_author().items())
+
+ def render_lists(author, posts):
+ """Render author pages as RSS files and lists/indexes."""
+ post_list = sorted(posts, key=lambda a: a.date)
+ post_list.reverse()
+ for lang in kw["translations"]:
+ if kw["show_untranslated_posts"]:
+ filtered_posts = post_list
+ else:
+ filtered_posts = [x for x in post_list if x.is_translation_available(lang)]
+ if kw["generate_rss"]:
+ yield self.author_rss(author, lang, filtered_posts, kw)
+ # Render HTML
+ if kw['author_pages_are_indexes']:
+ yield self.author_page_as_index(author, lang, filtered_posts, kw)
+ else:
+ yield self.author_page_as_list(author, lang, filtered_posts, kw)
+
+ for author, posts in author_list:
+ for task in render_lists(author, posts):
+ yield task
+
+ def _create_authors_page(self, kw):
+ """Create a global "all authors" page for each language."""
+ template_name = "authors.tmpl"
+ kw = kw.copy()
+ for lang in kw["translations"]:
+ authors = natsort.natsorted([author for author in self._posts_per_author().keys()],
+ alg=natsort.ns.F | natsort.ns.IC)
+ has_authors = (authors != [])
+ kw['authors'] = authors
+ output_name = os.path.join(
+ kw['output_folder'], self.site.path('author_index', None, lang))
+ context = {}
+ if has_authors:
+ context["title"] = kw["messages"][lang]["Authors"]
+ context["items"] = [(author, self.site.link("author", author, lang)) for author
+ in authors]
+ context["description"] = context["title"]
+ else:
+ context["items"] = None
+ context["permalink"] = self.site.link("author_index", None, lang)
+ context["pagekind"] = ["list", "authors_page"]
+ task = self.site.generic_post_list_renderer(
+ lang,
+ [],
+ output_name,
+ template_name,
+ kw['filters'],
+ context,
+ )
+ task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.authors:page')]
+ task['basename'] = str(self.name)
+ yield task
+
+ def list_authors_page(self, kw):
+ """Create a global "all authors" page for each language."""
+ yield self._create_authors_page(kw)
+
+ def _get_title(self, author):
+ return author
+
+ def _get_description(self, author, lang):
+ descriptions = self.site.config['AUTHOR_PAGES_DESCRIPTIONS']
+ return descriptions[lang][author] if lang in descriptions and author in descriptions[lang] else None
+
+ def author_page_as_index(self, author, lang, post_list, kw):
+ """Render a sort of index page collection using only this author's posts."""
+ kind = "author"
+
+ def page_link(i, displayed_i, num_pages, force_addition, extension=None):
+ feed = "_atom" if extension == ".atom" else ""
+ return utils.adjust_name_for_index_link(self.site.link(kind + feed, author, lang), i, displayed_i, lang, self.site, force_addition, extension)
+
+ def page_path(i, displayed_i, num_pages, force_addition, extension=None):
+ feed = "_atom" if extension == ".atom" else ""
+ return utils.adjust_name_for_index_path(self.site.path(kind + feed, author, lang), i, displayed_i, lang, self.site, force_addition, extension)
+
+ context_source = {}
+ title = self._get_title(author)
+ if kw["generate_rss"]:
+ # On a author page, the feeds include the author's feeds
+ rss_link = ("""<link rel="alternate" type="application/rss+xml" """
+ """title="RSS for author """
+ """{0} ({1})" href="{2}">""".format(
+ title, lang, self.site.link(kind + "_rss", author, lang)))
+ context_source['rss_link'] = rss_link
+ context_source["author"] = title
+ indexes_title = kw["messages"][lang]["Posts by %s"] % title
+ context_source["description"] = self._get_description(author, lang)
+ context_source["pagekind"] = ["index", "author_page"]
+ template_name = "authorindex.tmpl"
+
+ yield self.site.generic_index_renderer(lang, post_list, indexes_title, template_name, context_source, kw, str(self.name), page_link, page_path)
+
+ def author_page_as_list(self, author, lang, post_list, kw):
+ """Render a single flat link list with this author's posts."""
+ kind = "author"
+ template_name = "author.tmpl"
+ output_name = os.path.join(kw['output_folder'], self.site.path(
+ kind, author, lang))
+ context = {}
+ context["lang"] = lang
+ title = self._get_title(author)
+ context["author"] = title
+ context["title"] = kw["messages"][lang]["Posts by %s"] % title
+ context["posts"] = post_list
+ context["permalink"] = self.site.link(kind, author, lang)
+ context["kind"] = kind
+ context["description"] = self._get_description(author, lang)
+ context["pagekind"] = ["list", "author_page"]
+ task = self.site.generic_post_list_renderer(
+ lang,
+ post_list,
+ output_name,
+ template_name,
+ kw['filters'],
+ context,
+ )
+ task['uptodate'] = task['uptodate'] + [utils.config_changed(kw, 'nikola.plugins.task.authors:list')]
+ task['basename'] = str(self.name)
+ yield task
+
+ def author_rss(self, author, lang, posts, kw):
+ """Create a RSS feed for a single author in a given language."""
+ kind = "author"
+ # Render RSS
+ output_name = os.path.normpath(
+ os.path.join(kw['output_folder'],
+ self.site.path(kind + "_rss", author, lang)))
+ feed_url = urljoin(self.site.config['BASE_URL'], self.site.link(kind + "_rss", author, lang).lstrip('/'))
+ deps = []
+ deps_uptodate = []
+ post_list = sorted(posts, key=lambda a: a.date)
+ post_list.reverse()
+ for post in post_list:
+ deps += post.deps(lang)
+ deps_uptodate += post.deps_uptodate(lang)
+ task = {
+ 'basename': str(self.name),
+ 'name': output_name,
+ 'file_dep': deps,
+ 'targets': [output_name],
+ 'actions': [(utils.generic_rss_renderer,
+ (lang, "{0} ({1})".format(kw["blog_title"](lang), self._get_title(author)),
+ kw["site_url"], None, post_list,
+ output_name, kw["feed_teasers"], kw["feed_plain"], kw['feed_length'],
+ feed_url, None, kw["feed_link_append_query"]))],
+ 'clean': True,
+ 'uptodate': [utils.config_changed(kw, 'nikola.plugins.task.authors:rss')] + deps_uptodate,
+ 'task_dep': ['render_posts'],
+ }
+ return utils.apply_filters(task, kw['filters'])
+
+ def slugify_author_name(self, name):
+ """Slugify an author name."""
+ if self.site.config['SLUG_AUTHOR_PATH']:
+ name = utils.slugify(name)
+ return name
+
+ def author_index_path(self, name, lang):
+ """Link to the author's index.
+
+ Example:
+
+ link://authors/ => /authors/index.html
+ """
+ return [_f for _f in [self.site.config['TRANSLATIONS'][lang],
+ self.site.config['AUTHOR_PATH'],
+ self.site.config['INDEX_FILE']] if _f]
+
+ def author_path(self, name, lang):
+ """Link to an author's page.
+
+ Example:
+
+ link://author/joe => /authors/joe.html
+ """
+ if self.site.config['PRETTY_URLS']:
+ return [_f for _f in [
+ self.site.config['TRANSLATIONS'][lang],
+ self.site.config['AUTHOR_PATH'],
+ self.slugify_author_name(name),
+ self.site.config['INDEX_FILE']] if _f]
+ else:
+ return [_f for _f in [
+ self.site.config['TRANSLATIONS'][lang],
+ self.site.config['AUTHOR_PATH'],
+ self.slugify_author_name(name) + ".html"] if _f]
+
+ def author_atom_path(self, name, lang):
+ """Link to an author's Atom feed.
+
+ Example:
+
+ link://author_atom/joe => /authors/joe.atom
+ """
+ return [_f for _f in [self.site.config['TRANSLATIONS'][lang],
+ self.site.config['AUTHOR_PATH'], self.slugify_author_name(name) + ".atom"] if
+ _f]
+
+ def author_rss_path(self, name, lang):
+ """Link to an author's RSS feed.
+
+ Example:
+
+ link://author_rss/joe => /authors/joe.rss
+ """
+ return [_f for _f in [self.site.config['TRANSLATIONS'][lang],
+ self.site.config['AUTHOR_PATH'], self.slugify_author_name(name) + ".xml"] if
+ _f]
+
+ def _add_extension(self, path, extension):
+ path[-1] += extension
+ return path
+
+ def _posts_per_author(self):
+ """Return a dict of posts per author."""
+ if self.posts_per_author is None:
+ self.posts_per_author = defaultdict(list)
+ for post in self.site.timeline:
+ if post.is_post:
+ self.posts_per_author[post.author()].append(post)
+ return self.posts_per_author