diff --git a/docs/settings.rst b/docs/settings.rst index bf848eee..a1a17e47 100644 --- a/docs/settings.rst +++ b/docs/settings.rst @@ -725,16 +725,13 @@ Template pages tags and category index pages). If the tag and category collections are not needed, set ``DIRECT_TEMPLATES = ['index', 'archives']`` + ``DIRECT_TEMPLATES`` are searched for over paths maintained in + ``THEME_TEMPLATES_OVERRIDES``. + .. data:: PAGINATED_DIRECT_TEMPLATES = ['index'] Provides the direct templates that should be paginated. -.. data:: EXTRA_TEMPLATES_PATHS = [] - - A list of paths you want Jinja2 to search for templates. Can be used to - separate templates from the theme. Example: projects, resume, profile ... - These templates need to use ``DIRECT_TEMPLATES`` setting. - Metadata ======== @@ -1030,6 +1027,21 @@ However, here are the settings that are related to themes. with the same names are included in the paths defined in this settings, they will be progressively overwritten. +.. data:: THEME_TEMPLATES_OVERRIDES = [] + + A list of paths you want Jinja2 to search for templates before searching the + theme's ``templates/`` directory. Allows for overriding individual theme + template files without having to fork an existing theme. Jinja2 searches in + the following order: files in ``THEME_TEMPLATES_OVERRIDES`` first, then the + theme's ``templates/``. + + You can also extend templates from the theme using the ``{% extends %}`` + directive utilizing the ``!theme`` prefix as shown in the following example: + + .. parsed-literal:: + + {% extends '!theme/article.html' %} + .. data:: CSS_FILE = 'main.css' Specify the CSS file you want to load. diff --git a/pelican/generators.py b/pelican/generators.py index d93e740e..0f110070 100644 --- a/pelican/generators.py +++ b/pelican/generators.py @@ -51,20 +51,25 @@ class Generator(object): # templates cache self._templates = {} - self._templates_path = [] - self._templates_path.append(os.path.expanduser( - os.path.join(self.theme, 'templates'))) - self._templates_path += self.settings['EXTRA_TEMPLATES_PATHS'] + self._templates_path = list(self.settings['THEME_TEMPLATES_OVERRIDES']) - theme_path = os.path.dirname(os.path.abspath(__file__)) + theme_templates_path = os.path.expanduser( + os.path.join(self.theme, 'templates')) + self._templates_path.append(theme_templates_path) + theme_loader = FileSystemLoader(theme_templates_path) + + simple_theme_path = os.path.dirname(os.path.abspath(__file__)) + simple_loader = FileSystemLoader( + os.path.join(simple_theme_path, "themes", "simple", "templates")) - simple_loader = FileSystemLoader(os.path.join(theme_path, - "themes", "simple", "templates")) self.env = Environment( loader=ChoiceLoader([ FileSystemLoader(self._templates_path), simple_loader, # implicit inheritance - PrefixLoader({'!simple': simple_loader}) # explicit one + PrefixLoader({ + '!simple': simple_loader, + '!theme': theme_loader + }) # explicit ones ]), **self.settings['JINJA_ENVIRONMENT'] ) diff --git a/pelican/settings.py b/pelican/settings.py index 5b9d4b94..eac6ff19 100644 --- a/pelican/settings.py +++ b/pelican/settings.py @@ -102,7 +102,7 @@ DEFAULT_CONFIG = { 'RELATIVE_URLS': False, 'DEFAULT_LANG': 'en', 'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'], - 'EXTRA_TEMPLATES_PATHS': [], + 'THEME_TEMPLATES_OVERRIDES': [], 'PAGINATED_DIRECT_TEMPLATES': ['index'], 'PELICAN_CLASS': 'pelican.Pelican', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y', @@ -380,12 +380,26 @@ def configure_settings(settings): settings[new_key] = [settings[old_key]] # also make a list del settings[old_key] + # Deprecated warning of EXTRA_TEMPLATES_PATHS + if 'EXTRA_TEMPLATES_PATHS' in settings: + logger.warning('EXTRA_TEMPLATES_PATHS is deprecated use ' + 'THEME_TEMPLATES_OVERRIDES instead.') + if ('THEME_TEMPLATES_OVERRIDES' in settings and + settings['THEME_TEMPLATES_OVERRIDES']): + raise Exception( + 'Setting both EXTRA_TEMPLATES_PATHS and ' + 'THEME_TEMPLATES_OVERRIDES is not permitted. Please move to ' + 'only setting THEME_TEMPLATES_OVERRIDES.') + settings['THEME_TEMPLATES_OVERRIDES'] = \ + settings['EXTRA_TEMPLATES_PATHS'] + del settings['EXTRA_TEMPLATES_PATHS'] + # Save people from accidentally setting a string rather than a list path_keys = ( 'ARTICLE_EXCLUDES', 'DEFAULT_METADATA', 'DIRECT_TEMPLATES', - 'EXTRA_TEMPLATES_PATHS', + 'THEME_TEMPLATES_OVERRIDES', 'FILES_TO_COPY', 'IGNORE_FILES', 'PAGINATED_DIRECT_TEMPLATES', diff --git a/pelican/tests/test_generators.py b/pelican/tests/test_generators.py index 43bc7751..3ff4e6ff 100644 --- a/pelican/tests/test_generators.py +++ b/pelican/tests/test_generators.py @@ -8,7 +8,8 @@ from shutil import copy, rmtree from tempfile import mkdtemp from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator, - StaticGenerator, TemplatePagesGenerator) + PelicanTemplateNotFound, StaticGenerator, + TemplatePagesGenerator) from pelican.tests.support import get_settings, unittest from pelican.writers import Writer @@ -116,6 +117,62 @@ class TestGenerator(unittest.TestCase): self.assertEqual(comment_end_string, generator.env.comment_end_string) + def test_theme_overrides(self): + """ + Test that the THEME_TEMPLATES_OVERRIDES configuration setting is + utilized correctly in the Generator. + """ + override_dirs = (os.path.join(CUR_DIR, 'theme_overrides', 'level1'), + os.path.join(CUR_DIR, 'theme_overrides', 'level2')) + self.settings['THEME_TEMPLATES_OVERRIDES'] = override_dirs + generator = Generator( + context=self.settings.copy(), + settings=self.settings, + path=CUR_DIR, + theme=self.settings['THEME'], + output_path=None) + + filename = generator.get_template('article').filename + self.assertEqual(override_dirs[0], os.path.dirname(filename)) + self.assertEqual('article.html', os.path.basename(filename)) + + filename = generator.get_template('authors').filename + self.assertEqual(override_dirs[1], os.path.dirname(filename)) + self.assertEqual('authors.html', os.path.basename(filename)) + + filename = generator.get_template('taglist').filename + self.assertEqual(os.path.join(self.settings['THEME'], 'templates'), + os.path.dirname(filename)) + self.assertNotIn(os.path.dirname(filename), override_dirs) + self.assertEqual('taglist.html', os.path.basename(filename)) + + def test_simple_prefix(self): + """ + Test `!simple` theme prefix. + """ + filename = self.generator.get_template('!simple/authors').filename + expected_path = os.path.join( + os.path.dirname(CUR_DIR), 'themes', 'simple', 'templates') + self.assertEqual(expected_path, os.path.dirname(filename)) + self.assertEqual('authors.html', os.path.basename(filename)) + + def test_theme_prefix(self): + """ + Test `!theme` theme prefix. + """ + filename = self.generator.get_template('!theme/authors').filename + expected_path = os.path.join( + os.path.dirname(CUR_DIR), 'themes', 'notmyidea', 'templates') + self.assertEqual(expected_path, os.path.dirname(filename)) + self.assertEqual('authors.html', os.path.basename(filename)) + + def test_bad_prefix(self): + """ + Test unknown/bad theme prefix throws exception. + """ + self.assertRaises(PelicanTemplateNotFound, self.generator.get_template, + '!UNKNOWN/authors') + class TestArticlesGenerator(unittest.TestCase): diff --git a/pelican/tests/test_settings.py b/pelican/tests/test_settings.py index 7b1e36df..4ec16c0f 100644 --- a/pelican/tests/test_settings.py +++ b/pelican/tests/test_settings.py @@ -166,3 +166,20 @@ class TestSettingsConfiguration(unittest.TestCase): settings['THEME'] = 'foo' self.assertRaises(Exception, configure_settings, settings) + + def test_deprecated_extra_templates_paths(self): + settings = self.settings + settings['EXTRA_TEMPLATES_PATHS'] = ['/foo/bar', '/ha'] + + configure_settings(settings) + + self.assertEqual(settings['THEME_TEMPLATES_OVERRIDES'], + ['/foo/bar', '/ha']) + self.assertNotIn('EXTRA_TEMPLATES_PATHS', settings) + + def test_theme_and_extra_templates_exception(self): + settings = self.settings + settings['EXTRA_TEMPLATES_PATHS'] = ['/ha'] + settings['THEME_TEMPLATES_OVERRIDES'] = ['/foo/bar'] + + self.assertRaises(Exception, configure_settings, settings) diff --git a/pelican/tests/theme_overrides/level1/article.html b/pelican/tests/theme_overrides/level1/article.html new file mode 100644 index 00000000..12f6b7bf --- /dev/null +++ b/pelican/tests/theme_overrides/level1/article.html @@ -0,0 +1,4 @@ + diff --git a/pelican/tests/theme_overrides/level2/article.html b/pelican/tests/theme_overrides/level2/article.html new file mode 100644 index 00000000..12f6b7bf --- /dev/null +++ b/pelican/tests/theme_overrides/level2/article.html @@ -0,0 +1,4 @@ + diff --git a/pelican/tests/theme_overrides/level2/authors.html b/pelican/tests/theme_overrides/level2/authors.html new file mode 100644 index 00000000..12f6b7bf --- /dev/null +++ b/pelican/tests/theme_overrides/level2/authors.html @@ -0,0 +1,4 @@ +