Add THEME_TEMPLATE_OVERRIDES. Refs 2021

Allow for overriding individual templates from the theme by configuring
the Jinja2 `Environment` loader to search for templates in the
`THEME_TEMPLATES_OVERRIDES` path before the theme's `templates/`
directory.
This commit is contained in:
Pedro H 2016-10-16 15:47:22 +08:00
commit 50af2ed45d
8 changed files with 134 additions and 17 deletions

View file

@ -706,16 +706,13 @@ Template pages
tags and category index pages). If the tag and category collections are not tags and category index pages). If the tag and category collections are not
needed, set ``DIRECT_TEMPLATES = ['index', 'archives']`` needed, set ``DIRECT_TEMPLATES = ['index', 'archives']``
``DIRECT_TEMPLATES`` are searched for over paths maintained in
``THEME_TEMPLATES_OVERRIDES``.
.. data:: PAGINATED_DIRECT_TEMPLATES = ['index'] .. data:: PAGINATED_DIRECT_TEMPLATES = ['index']
Provides the direct templates that should be paginated. 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 Metadata
======== ========
@ -1011,6 +1008,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 with the same names are included in the paths defined in this settings, they
will be progressively overwritten. 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' .. data:: CSS_FILE = 'main.css'
Specify the CSS file you want to load. Specify the CSS file you want to load.

View file

@ -51,20 +51,25 @@ class Generator(object):
# templates cache # templates cache
self._templates = {} self._templates = {}
self._templates_path = [] self._templates_path = list(self.settings['THEME_TEMPLATES_OVERRIDES'])
self._templates_path.append(os.path.expanduser(
os.path.join(self.theme, 'templates')))
self._templates_path += self.settings['EXTRA_TEMPLATES_PATHS']
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( self.env = Environment(
loader=ChoiceLoader([ loader=ChoiceLoader([
FileSystemLoader(self._templates_path), FileSystemLoader(self._templates_path),
simple_loader, # implicit inheritance simple_loader, # implicit inheritance
PrefixLoader({'!simple': simple_loader}) # explicit one PrefixLoader({
'!simple': simple_loader,
'!theme': theme_loader
}) # explicit ones
]), ]),
**self.settings['JINJA_ENVIRONMENT'] **self.settings['JINJA_ENVIRONMENT']
) )

View file

@ -99,7 +99,7 @@ DEFAULT_CONFIG = {
'RELATIVE_URLS': False, 'RELATIVE_URLS': False,
'DEFAULT_LANG': 'en', 'DEFAULT_LANG': 'en',
'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'], 'DIRECT_TEMPLATES': ['index', 'tags', 'categories', 'authors', 'archives'],
'EXTRA_TEMPLATES_PATHS': [], 'THEME_TEMPLATES_OVERRIDES': [],
'PAGINATED_DIRECT_TEMPLATES': ['index'], 'PAGINATED_DIRECT_TEMPLATES': ['index'],
'PELICAN_CLASS': 'pelican.Pelican', 'PELICAN_CLASS': 'pelican.Pelican',
'DEFAULT_DATE_FORMAT': '%a %d %B %Y', 'DEFAULT_DATE_FORMAT': '%a %d %B %Y',
@ -376,12 +376,26 @@ def configure_settings(settings):
settings[new_key] = [settings[old_key]] # also make a list settings[new_key] = [settings[old_key]] # also make a list
del settings[old_key] 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 # Save people from accidentally setting a string rather than a list
path_keys = ( path_keys = (
'ARTICLE_EXCLUDES', 'ARTICLE_EXCLUDES',
'DEFAULT_METADATA', 'DEFAULT_METADATA',
'DIRECT_TEMPLATES', 'DIRECT_TEMPLATES',
'EXTRA_TEMPLATES_PATHS', 'THEME_TEMPLATES_OVERRIDES',
'FILES_TO_COPY', 'FILES_TO_COPY',
'IGNORE_FILES', 'IGNORE_FILES',
'PAGINATED_DIRECT_TEMPLATES', 'PAGINATED_DIRECT_TEMPLATES',

View file

@ -9,7 +9,8 @@ from shutil import copy, rmtree
from tempfile import mkdtemp from tempfile import mkdtemp
from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator, from pelican.generators import (ArticlesGenerator, Generator, PagesGenerator,
StaticGenerator, TemplatePagesGenerator) PelicanTemplateNotFound, StaticGenerator,
TemplatePagesGenerator)
from pelican.tests.support import get_settings, unittest from pelican.tests.support import get_settings, unittest
from pelican.writers import Writer from pelican.writers import Writer
@ -117,6 +118,62 @@ class TestGenerator(unittest.TestCase):
self.assertEqual(comment_end_string, self.assertEqual(comment_end_string,
generator.env.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): class TestArticlesGenerator(unittest.TestCase):

View file

@ -166,3 +166,20 @@ class TestSettingsConfiguration(unittest.TestCase):
settings['THEME'] = 'foo' settings['THEME'] = 'foo'
self.assertRaises(Exception, configure_settings, settings) 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)

View file

@ -0,0 +1,4 @@
<!--
This file is only here to test the `THEME_TEMPLATES_OVERRIDES` configuration
setting.
-->

View file

@ -0,0 +1,4 @@
<!--
This file is only here to test the `THEME_TEMPLATES_OVERRIDES` configuration
setting.
-->

View file

@ -0,0 +1,4 @@
<!--
This file is only here to test the `THEME_TEMPLATES_OVERRIDES` configuration
setting.
-->