1
0
Fork 0
forked from github/pelican

multiple authors implemented

This commit is contained in:
Anatoly Bubenkov 2013-07-05 01:08:45 +02:00
commit 2c25e488c4
16 changed files with 122 additions and 59 deletions

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

@ -434,7 +434,7 @@ class ArticlesGenerator(Generator):
self.articles, self.translations = process_translations(all_articles)
signals.article_generator_pretaxonomy.send(self)
signals.article_generator_pretaxonomy.send(self)
for article in self.articles:
# only main articles are listed in categories and tags
@ -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',
@ -75,7 +75,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
out_name = fnames[index]
self.assertTrue(out_name.endswith(filename))
index += 1
def test_unless_custom_post_all_items_should_be_pages_or_posts(self):
self.assertTrue(self.posts)
pages_data = []
@ -85,7 +85,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
else:
pages_data.append((title, fname))
self.assertEqual(0, len(pages_data))
def test_recognise_custom_post_type(self):
self.assertTrue(self.custposts)
cust_data = []
@ -98,7 +98,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
self.assertEqual(('A custom post in category 4', 'custom1'), cust_data[0])
self.assertEqual(('A custom post in category 5', 'custom1'), cust_data[1])
self.assertEqual(('A 2nd custom post type also in category 5', 'custom2'), cust_data[2])
def test_custom_posts_put_in_own_dir(self):
silent_f2p = mute(True)(fields2pelican)
test_posts = []
@ -130,7 +130,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
else:
test_posts.append(post)
with temporary_folder() as temp:
fnames = list(silent_f2p(test_posts, 'markdown', temp,
fnames = list(silent_f2p(test_posts, 'markdown', temp,
wp_custpost=True, dircat=True))
index = 0
for post in test_posts:
@ -152,7 +152,7 @@ class TestWordpressXmlImporter(unittest.TestCase):
if post[7] == 'page':
test_posts.append(post)
with temporary_folder() as temp:
fnames = list(silent_f2p(test_posts, 'markdown', temp,
fnames = list(silent_f2p(test_posts, 'markdown', temp,
wp_custpost=True, dirpage=False))
index = 0
for post in test_posts:
@ -161,8 +161,8 @@ class TestWordpressXmlImporter(unittest.TestCase):
filename = os.path.join('pages', name)
out_name = fnames[index]
self.assertFalse(out_name.endswith(filename))
def test_can_toggle_raw_html_code_parsing(self):
def r(f):
with open(f) as infile:
@ -247,9 +247,9 @@ class TestBuildHeader(unittest.TestCase):
'##############################################\n\n')
def test_galleries_added_to_header(self):
header = build_header('test', None, None, None, None,
header = build_header('test', None, None, None, None,
None, ['output/test1', 'output/test2'])
self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, '
self.assertEqual(header, 'test\n####\n' + ':attachments: output/test1, '
+ 'output/test2\n\n')
def test_galleries_added_to_markdown_header(self):
@ -258,11 +258,11 @@ class TestBuildHeader(unittest.TestCase):
self.assertEqual(header, 'Title: test\n' + 'Attachments: output/test1, '
+ 'output/test2\n\n')
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
class TestWordpressXMLAttachements(unittest.TestCase):
@unittest.skipUnless(BeautifulSoup, 'Needs BeautifulSoup module')
class TestWordpressXMLAttachements(unittest.TestCase):
def setUp(self):
self.attachments = get_attachments(WORDPRESS_XML_SAMPLE)
def test_recognise_attachments(self):
self.assertTrue(self.attachments)
self.assertTrue(len(self.attachments.keys()) == 3)
@ -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>

View file

@ -124,7 +124,7 @@ def get_filename(filename, post_id):
def wp2fields(xml, wp_custpost=False):
"""Opens a wordpress XML file, and yield Pelican fields"""
items = get_items(xml)
for item in items:
@ -140,7 +140,7 @@ def wp2fields(xml, wp_custpost=False):
filename = item.find('post_name').string
post_id = item.find('post_id').string
filename = get_filename(filename, post_id)
content = item.find('encoded').string
raw_date = item.find('post_date').string
date_object = time.strptime(raw_date, "%Y-%m-%d %H:%M:%S")
@ -161,7 +161,7 @@ def wp2fields(xml, wp_custpost=False):
pass
# Old behaviour was to name everything not a page as an article.
# Theoretically all attachments have status == inherit so
# no attachments should be here. But this statement is to
# no attachments should be here. But this statement is to
# maintain existing behaviour in case that doesn't hold true.
elif post_type == 'attachment':
pass
@ -469,7 +469,7 @@ def build_header(title, date, author, categories, tags, slug, attachments=None):
header += '\n'
return header
def build_markdown_header(title, date, author, categories, tags, slug,
def build_markdown_header(title, date, author, categories, tags, slug,
attachments=None):
"""Build a header from a list of fields"""
header = 'Title: %s\n' % title
@ -494,8 +494,8 @@ def get_ext(out_markup, in_markup='html'):
else:
ext = '.rst'
return ext
def get_out_filename(output_path, filename, ext, kind,
def get_out_filename(output_path, filename, ext, kind,
dirpage, dircat, categories, wp_custpost):
filename = os.path.basename(filename)
@ -516,7 +516,7 @@ def get_out_filename(output_path, filename, ext, kind,
os.mkdir(pages_dir)
out_filename = os.path.join(pages_dir, filename+ext)
elif not dirpage and kind == 'page':
pass
pass
# option to put wp custom post types in directories with post type
# names. Custom post types can also have categories so option to
# create subdirectories with category names
@ -530,7 +530,7 @@ def get_out_filename(output_path, filename, ext, kind,
catname = slugify(categories[0])
else:
catname = ''
out_filename = os.path.join(output_path, typename,
out_filename = os.path.join(output_path, typename,
catname, filename+ext)
if not os.path.isdir(os.path.join(output_path, typename, catname)):
os.makedirs(os.path.join(output_path, typename, catname))
@ -544,20 +544,20 @@ def get_out_filename(output_path, filename, ext, kind,
return out_filename
def get_attachments(xml):
"""returns a dictionary of posts that have attachments with a list
"""returns a dictionary of posts that have attachments with a list
of the attachment_urls
"""
items = get_items(xml)
names = {}
attachments = []
for item in items:
kind = item.find('post_type').string
filename = item.find('post_name').string
post_id = item.find('post_id').string
if kind == 'attachment':
attachments.append((item.find('post_parent').string,
attachments.append((item.find('post_parent').string,
item.find('attachment_url').string))
else:
filename = get_filename(filename, post_id)
@ -569,7 +569,7 @@ def get_attachments(xml):
except KeyError:
#attachment's parent is not a valid post
parent_name = None
try:
attachedposts[parent_name].append(url)
except KeyError:
@ -578,13 +578,13 @@ def get_attachments(xml):
return attachedposts
def download_attachments(output_path, urls):
"""Downloads wordpress attachments and returns a list of paths to
attachments that can be associated with a post (relative path to output
"""Downloads wordpress attachments and returns a list of paths to
attachments that can be associated with a post (relative path to output
directory). Files that fail to download, will not be added to posts"""
locations = []
for url in urls:
path = urlparse(url).path
#teardown path and rebuild to negate any errors with
#teardown path and rebuild to negate any errors with
#os.path.join and leading /'s
path = path.split('/')
filename = path.pop(-1)
@ -625,16 +625,16 @@ def fields2pelican(fields, out_markup, output_path,
attached_files = download_attachments(output_path, urls)
except KeyError:
attached_files = None
else:
else:
attached_files = None
ext = get_ext(out_markup, in_markup)
if ext == '.md':
header = build_markdown_header(title, date, author, categories,
header = build_markdown_header(title, date, author, categories,
tags, slug, attached_files)
else:
out_markup = "rst"
header = build_header(title, date, author, categories,
header = build_header(title, date, author, categories,
tags, slug, attached_files)
out_filename = get_out_filename(output_path, filename, ext,
@ -690,7 +690,7 @@ def fields2pelican(fields, out_markup, output_path,
print("downloading attachments that don't have a parent post")
urls = attachments[None]
orphan_galleries = download_attachments(output_path, urls)
def main():
parser = argparse.ArgumentParser(
description="Transform feed, WordPress, Tumblr, Dotclear, or Posterous "
@ -723,7 +723,7 @@ def main():
parser.add_argument('--strip-raw', action='store_true', dest='strip_raw',
help="Strip raw HTML code that can't be converted to "
"markup such as flash embeds or iframes (wordpress import only)")
parser.add_argument('--wp-custpost', action='store_true',
parser.add_argument('--wp-custpost', action='store_true',
dest='wp_custpost',
help='Put wordpress custom post types in directories. If used with '
'--dir-cat option directories will be created as '
@ -775,7 +775,7 @@ def main():
if args.wp_attach and input_type != 'wordpress':
error = "You must be importing a wordpress xml to use the --wp-attach option"
exit(error)
if input_type == 'wordpress':
fields = wp2fields(args.input, args.wp_custpost or False)
elif input_type == 'dotclear':