Merge pull request #1247 from paylogic/multiple-authors

Multiple authors implementation for #956
This commit is contained in:
Justin Mayer 2014-02-13 19:08:34 -08:00
commit 826ff4df50
16 changed files with 122 additions and 59 deletions

View file

@ -7,6 +7,7 @@ Next release
* Added the `:modified:` metadata field to complement `:date:`.
Used to specify the last date and time an article was updated independently from the date and time it was published.
* Produce inline links instead of reference-style links when importing content.
* Multiple authors support added via new `:authors:` metadata field.
3.3.0 (2013-09-24)
==================

View file

@ -311,7 +311,7 @@ this metadata in text files via the following syntax (give your file the
:tags: thats, awesome
:category: yeah
:slug: my-super-post
:author: Alexis Metaireau
:authors: Alexis Metaireau, Conan Doyle
:summary: Short version for index and feeds
Pelican implements an extension to reStructuredText to enable support for the
@ -331,7 +331,7 @@ pattern::
Category: Python
Tags: pelican, publishing
Slug: my-super-post
Author: Alexis Metaireau
Authors: Alexis Metaireau, Conan Doyle
Summary: Short version for index and feeds
This is the content of my super blog post.
@ -351,7 +351,7 @@ interprets the HTML in a very straightforward manner, reading metadata from
<meta name="date" content="2012-07-09 22:28" />
<meta name="modified" content="2012-07-10 20:14" />
<meta name="category" content="yeah" />
<meta name="author" content="Alexis Métaireau" />
<meta name="authors" content="Alexis Métaireau, Conan Doyle" />
<meta name="summary" content="Short version for index and feeds" />
</head>
<body>
@ -380,6 +380,9 @@ __ `W3C ISO 8601`_
Besides you can show ``modified`` in the templates, feed entries in feed readers will be updated automatically
when you set ``modified`` to the current date after you modified your article.
``authors`` is a comma-separated list of article authors. If there's only one author you
can use ``author`` field.
If you do not explicitly specify summary metadata for a given post, the
``SUMMARY_MAX_LENGTH`` setting can be used to specify how many words from the
beginning of an article are used as the summary.

View file

@ -74,11 +74,17 @@ class Content(object):
#default template if it's not defined in page
self.template = self._get_template()
# default author to the one in settings if not defined
# First, read the authors from "authors", if not, fallback to "author"
# and if not use the settings defined one, if any.
if not hasattr(self, 'author'):
if 'AUTHOR' in settings:
if hasattr(self, 'authors'):
self.author = self.authors[0]
elif 'AUTHOR' in settings:
self.author = Author(settings['AUTHOR'], settings)
if not hasattr(self, 'authors') and hasattr(self, 'author'):
self.authors = [self.author]
# XXX Split all the following code into pieces, there is too much here.
# manage languages

View file

@ -444,9 +444,9 @@ class ArticlesGenerator(Generator):
for tag in article.tags:
self.tags[tag].append(article)
# ignore blank authors as well as undefined
if hasattr(article, 'author') and article.author.name != '':
self.authors[article.author].append(article)
for author in getattr(article, 'authors', []):
if author.name != '':
self.authors[author].append(article)
# sort the articles by date
self.articles.sort(key=attrgetter('date'), reverse=True)
self.dates = list(self.articles)

View file

@ -46,6 +46,7 @@ METADATA_PROCESSORS = {
'status': lambda x, y: x.strip(),
'category': Category,
'author': Author,
'authors': lambda x, y: [Author(author, y) for author in x],
}
logger = logging.getLogger(__name__)
@ -144,6 +145,9 @@ class RstReader(BaseReader):
value = render_node_to_html(document, body_elem)
else:
value = body_elem.astext()
elif element.tagname == 'authors': # author list
name = element.tagname
value = [element.astext() for element in element.children]
else: # standard fields (e.g. address)
name = element.tagname
value = element.astext()

View file

@ -0,0 +1,6 @@
This is an article with multiple authors!
#########################################
:date: 2014-02-09 02:20
:modified: 2014-02-09 02:20
:authors: First Author, Second Author

View file

@ -346,6 +346,17 @@ class TestPage(unittest.TestCase):
'<a href="http://notmyidea.org/article-spaces.html">link</a>'
)
def test_multiple_authors(self):
"""Test article with multiple authors."""
args = self.page_kwargs.copy()
content = Page(**args)
assert content.authors == [content.author]
args['metadata'].pop('author')
args['metadata']['authors'] = ['First Author', 'Second Author']
content = Page(**args)
assert content.authors
assert content.author == content.authors[0]
class TestArticle(TestPage):
def test_template(self):

View file

@ -93,6 +93,7 @@ class TestArticlesGenerator(unittest.TestCase):
['This is a super article !', 'published', 'Default', 'article'],
['This is an article with category !', 'published', 'yeah',
'article'],
['This is an article with multiple authors!', 'published', 'Default', 'article'],
['This is an article without category !', 'published', 'Default',
'article'],
['This is an article without category !', 'published',
@ -257,6 +258,16 @@ class TestArticlesGenerator(unittest.TestCase):
settings,
blog=True, dates=dates)
def test_generate_authors(self):
"""Check authors generation."""
authors = [author.name for author, _ in self.generator.authors]
authors_expected = sorted(['Alexis Métaireau', 'First Author', 'Second Author'])
self.assertEqual(sorted(authors), authors_expected)
# test for slug
authors = [author.slug for author, _ in self.generator.authors]
authors_expected = ['alexis-metaireau', 'first-author', 'second-author']
self.assertEqual(sorted(authors), sorted(authors_expected))
class TestPageGenerator(unittest.TestCase):
# Note: Every time you want to test for a new field; Make sure the test

View file

@ -10,7 +10,7 @@ from pelican.tests.support import (unittest, temporary_folder, mute,
from pelican.utils import slugify
CUR_DIR = os.path.dirname(__file__)
CUR_DIR = os.path.abspath(os.path.dirname(__file__))
WORDPRESS_XML_SAMPLE = os.path.join(CUR_DIR, 'content', 'wordpressexport.xml')
WORDPRESS_ENCODED_CONTENT_SAMPLE = os.path.join(CUR_DIR,
'content',
@ -283,7 +283,7 @@ class TestWordpressXMLAttachements(unittest.TestCase):
def test_download_attachments(self):
real_file = os.path.join(CUR_DIR, 'content/article.rst')
good_url = 'file://' + real_file
bad_url = 'http://www.notarealsite.notarealdomain/not_a_file.txt'
bad_url = 'http://localhost:1/not_a_file.txt'
silent_da = mute()(download_attachments)
with temporary_folder() as temp:
#locations = download_attachments(temp, [good_url, bad_url])

View file

@ -2,11 +2,11 @@
from __future__ import unicode_literals, print_function
import os
from filecmp import dircmp
from tempfile import mkdtemp
from shutil import rmtree
import locale
import logging
import subprocess
from pelican import Pelican
from pelican.settings import read_settings
@ -64,6 +64,13 @@ class TestPelican(LoggedTestCase):
self.assertEqual(diff['right_only'], [], msg=msg)
self.assertEqual(diff['diff_files'], [], msg=msg)
def assertDirsEqual(self, left_path, right_path):
out, err = subprocess.Popen(
['git', 'diff', '--no-ext-diff', '--exit-code', '-w', left_path, right_path], env={'PAGER': ''},
stdout=subprocess.PIPE, stderr=subprocess.PIPE).communicate()
assert not out, out
assert not err, err
def test_basic_generation_works(self):
# when running pelican without settings, it should pick up the default
# ones and generate correct output without raising any exception
@ -74,8 +81,7 @@ class TestPelican(LoggedTestCase):
})
pelican = Pelican(settings=settings)
mute(True)(pelican.run)()
dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'basic'))
self.assertFilesEqual(recursiveDiff(dcmp))
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'basic'))
self.assertLogCountEqual(
count=4,
msg="Unable to find.*skipping url replacement",
@ -90,8 +96,7 @@ class TestPelican(LoggedTestCase):
})
pelican = Pelican(settings=settings)
mute(True)(pelican.run)()
dcmp = dircmp(self.temp_path, os.path.join(OUTPUT_PATH, 'custom'))
self.assertFilesEqual(recursiveDiff(dcmp))
self.assertDirsEqual(self.temp_path, os.path.join(OUTPUT_PATH, 'custom'))
def test_theme_static_paths_copy(self):
# the same thing with a specified set of settings should work

View file

@ -360,6 +360,15 @@ class HTMLReaderTest(ReaderTest):
for key, value in expected.items():
self.assertEqual(value, page.metadata[key], key)
def test_article_with_multiple_authors(self):
page = self.read_file(path='article_with_multiple_authors.rst')
expected = {
'authors': ['First Author', 'Second Author']
}
for key, value in expected.items():
self.assertEqual(value, page.metadata[key], key)
def test_article_with_metadata_and_contents_attrib(self):
page = self.read_file(path='article_with_metadata_and_contents.html')
expected = {

View file

@ -9,9 +9,11 @@
</abbr>
{% endif %}
{% if article.author %}
{% if article.authors %}
<address class="vcard author">
By <a class="url fn" href="{{ SITEURL }}/{{ article.author.url }}">{{ article.author }}</a>
By {% for author in article.authors %}
<a class="url fn" href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
{% endfor %}
</address>
{% endif %}
<p>In <a href="{{ SITEURL }}/{{ article.category.url }}">{{ article.category }}</a>. {% if PDF_PROCESSOR %}<a href="{{ SITEURL }}/pdf/{{ article.slug }}.pdf">get the pdf</a>{% endif %}</p>

View file

@ -6,7 +6,6 @@
<section id="content" class="body">
<h1>Authors on {{ SITENAME }}</h1>
{%- for author, articles in authors|sort %}
<li><a href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a> ({{ articles|count }})</li>
{% endfor %}

View file

@ -33,9 +33,11 @@
{{ article.locale_modified }}
</abbr>
{% endif %}
{% if article.author %}
{% if article.authors %}
<address class="vcard author">
By <a class="url fn" href="{{ SITEURL }}/{{ article.author.url }}">{{ article.author }}</a>
By {% for author in article.authors %}
<a class="url fn" href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
{% endfor %}
</address>
{% endif %}
</footer><!-- /.post-info -->

View file

@ -11,7 +11,11 @@
<header> <h2 class="entry-title"><a href="{{ SITEURL }}/{{ article.url }}" rel="bookmark" title="Permalink to {{ article.title|striptags }}">{{ article.title }}</a></h2> </header>
<footer class="post-info">
<abbr class="published" title="{{ article.date.isoformat() }}"> {{ article.locale_date }} </abbr>
{% if article.author %}<address class="vcard author">By <a class="url fn" href="{{ SITEURL }}/{{ article.author.url }}">{{ article.author }}</a></address>{% endif %}
<address class="vcard author">By
{% for author in article.authors %}
<a class="url fn" href="{{ SITEURL }}/{{ author.url }}">{{ author }}</a>
{% endfor %}
</address>
</footer><!-- /.post-info -->
<div class="entry-content"> {{ article.summary }} </div><!-- /.entry-content -->
</article></li>