Repository: getpelican/pelican

Issue:
`category.slug` is not honored when determining URLs for articles
I'm trying to write a plugin which allows me to manually adjust the slug for a category, i.e. the thing which goes into the URL when you put `{category}` in `ARTICLE_URL` and friends.  I have it working, except that `category.slug` is _ignored_ by `Content.url_format` (in favor of `slugify(category)`, which uses the _name_, so the generated pathname for the category itself honors the slug setting, but the generated pathnames for articles in the category don't.

It appears to me that this is a straight-up bug in `Content.url_format`, possibly due to its having been written before `URLWrapper` objects were a thing?



Discussion:

Resolving patch:
diff --git a/pelican/contents.py b/pelican/contents.py
--- a/pelican/contents.py
+++ b/pelican/contents.py
@@ -53,7 +53,7 @@ def __init__(self, content, metadata=None, settings=None,
         self._context = context
         self.translations = []
 
-        local_metadata = dict(settings['DEFAULT_METADATA'])
+        local_metadata = dict()
         local_metadata.update(metadata)
 
         # set metadata as attributes
@@ -166,21 +166,13 @@ def url_format(self):
         """Returns the URL, formatted with the proper values"""
         metadata = copy.copy(self.metadata)
         path = self.metadata.get('path', self.get_relative_source_path())
-        default_category = self.settings['DEFAULT_CATEGORY']
-        slug_substitutions = self.settings.get('SLUG_SUBSTITUTIONS', ())
         metadata.update({
             'path': path_to_url(path),
             'slug': getattr(self, 'slug', ''),
             'lang': getattr(self, 'lang', 'en'),
             'date': getattr(self, 'date', SafeDatetime.now()),
-            'author': slugify(
-                getattr(self, 'author', ''),
-                slug_substitutions
-            ),
-            'category': slugify(
-                getattr(self, 'category', default_category),
-                slug_substitutions
-            )
+            'author': self.author.slug if hasattr(self, 'author') else '',
+            'category': self.category.slug if hasattr(self, 'category') else ''
         })
         return metadata
 
diff --git a/pelican/readers.py b/pelican/readers.py
--- a/pelican/readers.py
+++ b/pelican/readers.py
@@ -537,6 +537,10 @@ def find_empty_alt(content, path):
 def default_metadata(settings=None, process=None):
     metadata = {}
     if settings:
+        for name, value in dict(settings.get('DEFAULT_METADATA', {})).items():
+            if process:
+                value = process(name, value)
+            metadata[name] = value
         if 'DEFAULT_CATEGORY' in settings:
             value = settings['DEFAULT_CATEGORY']
             if process:


Test patch:
diff --git a/pelican/tests/test_contents.py b/pelican/tests/test_contents.py
--- a/pelican/tests/test_contents.py
+++ b/pelican/tests/test_contents.py
@@ -8,7 +8,7 @@
 
 from pelican.tests.support import unittest, get_settings
 
-from pelican.contents import Page, Article, Static, URLWrapper
+from pelican.contents import Page, Article, Static, URLWrapper, Author, Category
 from pelican.settings import DEFAULT_CONFIG
 from pelican.utils import path_to_url, truncate_html_words, SafeDatetime, posix_join
 from pelican.signals import content_object_init
@@ -33,7 +33,7 @@ def setUp(self):
             'metadata': {
                 'summary': TEST_SUMMARY,
                 'title': 'foo bar',
-                'author': 'Blogger',
+                'author': Author('Blogger', DEFAULT_CONFIG),
             },
             'source_path': '/path/to/file/foo.ext'
         }
@@ -374,7 +374,8 @@ def test_multiple_authors(self):
         content = Page(**args)
         assert content.authors == [content.author]
         args['metadata'].pop('author')
-        args['metadata']['authors'] = ['First Author', 'Second Author']
+        args['metadata']['authors'] = [Author('First Author', DEFAULT_CONFIG),
+                                       Author('Second Author', DEFAULT_CONFIG)]
         content = Page(**args)
         assert content.authors
         assert content.author == content.authors[0]
@@ -396,8 +397,8 @@ def test_slugify_category_author(self):
         settings['ARTICLE_URL'] = '{author}/{category}/{slug}/'
         settings['ARTICLE_SAVE_AS'] = '{author}/{category}/{slug}/index.html'
         article_kwargs = self._copy_page_kwargs()
-        article_kwargs['metadata']['author'] = "O'Brien"
-        article_kwargs['metadata']['category'] = 'C# & stuff'
+        article_kwargs['metadata']['author'] = Author("O'Brien", settings)
+        article_kwargs['metadata']['category'] = Category('C# & stuff', settings)
         article_kwargs['metadata']['title'] = 'fnord'
         article_kwargs['settings'] = settings
         article = Article(**article_kwargs)
diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py
--- a/pelican/tests/test_generators.py
+++ b/pelican/tests/test_generators.py
@@ -413,6 +413,38 @@ def test_ignore_cache(self):
         generator.generate_context()
         generator.readers.read_file.assert_called_count == orig_call_count
 
+    def test_standard_metadata_in_default_metadata(self):
+        settings = get_settings(filenames={})
+        settings['CACHE_CONTENT'] = False
+        settings['DEFAULT_CATEGORY'] = 'Default'
+        settings['DEFAULT_DATE'] = (1970, 1, 1)
+        settings['DEFAULT_METADATA'] = (('author', 'Blogger'),
+                                        # category will be ignored in favor of
+                                        # DEFAULT_CATEGORY
+                                        ('category', 'Random'),
+                                        ('tags', 'general, untagged'))
+        generator = ArticlesGenerator(
+            context=settings.copy(), settings=settings,
+            path=CONTENT_DIR, theme=settings['THEME'], output_path=None)
+        generator.generate_context()
+
+        authors = sorted([author.name for author, _ in generator.authors])
+        authors_expected = sorted(['Alexis Métaireau', 'Blogger',
+                                   'First Author', 'Second Author'])
+        self.assertEqual(authors, authors_expected)
+
+        categories = sorted([category.name
+                             for category, _ in generator.categories])
+        categories_expected = [
+            sorted(['Default', 'TestCategory', 'yeah', 'test', '指導書']),
+            sorted(['Default', 'TestCategory', 'Yeah', 'test', '指導書'])]
+        self.assertIn(categories, categories_expected)
+
+        tags = sorted([tag.name for tag in generator.tags])
+        tags_expected = sorted(['bar', 'foo', 'foobar', 'general', 'untagged',
+                                'パイソン', 'マック'])
+        self.assertEqual(tags, tags_expected)
+
 
 class TestPageGenerator(unittest.TestCase):
     # Note: Every time you want to test for a new field; Make sure the test
diff --git a/pelican/tests/test_paginator.py b/pelican/tests/test_paginator.py
--- a/pelican/tests/test_paginator.py
+++ b/pelican/tests/test_paginator.py
@@ -5,7 +5,7 @@
 from pelican.tests.support import unittest, get_settings
 
 from pelican.paginator import Paginator
-from pelican.contents import Article
+from pelican.contents import Article, Author
 from pelican.settings import DEFAULT_CONFIG
 from jinja2.utils import generate_lorem_ipsum
 
@@ -26,7 +26,6 @@ def setUp(self):
             'metadata': {
                 'summary': TEST_SUMMARY,
                 'title': 'foo bar',
-                'author': 'Blogger',
             },
             'source_path': '/path/to/file/foo.ext'
         }
@@ -49,6 +48,7 @@ def test_save_as_preservation(self):
             key=lambda r: r[0],
         )
 
+        self.page_kwargs['metadata']['author'] = Author('Blogger', settings)
         object_list = [Article(**self.page_kwargs), Article(**self.page_kwargs)]
         paginator = Paginator('foobar.foo', object_list, settings)
         page = paginator.page(1)