Allow using several custom template directories (#10587)

Allow using several directories in read_templates.
This commit is contained in:
Brendan Abolivier 2021-08-17 12:23:14 +02:00 committed by GitHub
parent a933c2c7d8
commit ae2714c1f3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 97 additions and 26 deletions

1
changelog.d/10587.misc Normal file
View File

@ -0,0 +1 @@
Allow multiple custom directories in `read_templates`.

View File

@ -237,13 +237,14 @@ class Config:
def read_templates( def read_templates(
self, self,
filenames: List[str], filenames: List[str],
custom_template_directory: Optional[str] = None, custom_template_directories: Optional[Iterable[str]] = None,
) -> List[jinja2.Template]: ) -> List[jinja2.Template]:
"""Load a list of template files from disk using the given variables. """Load a list of template files from disk using the given variables.
This function will attempt to load the given templates from the default Synapse This function will attempt to load the given templates from the default Synapse
template directory. If `custom_template_directory` is supplied, that directory template directory. If `custom_template_directories` is supplied, any directory
is tried first. in this list is tried (in the order they appear in the list) before trying
Synapse's default directory.
Files read are treated as Jinja templates. The templates are not rendered yet Files read are treated as Jinja templates. The templates are not rendered yet
and have autoescape enabled. and have autoescape enabled.
@ -251,8 +252,8 @@ class Config:
Args: Args:
filenames: A list of template filenames to read. filenames: A list of template filenames to read.
custom_template_directory: A directory to try to look for the templates custom_template_directories: A list of directory to try to look for the
before using the default Synapse template directory instead. templates before using the default Synapse template directory instead.
Raises: Raises:
ConfigError: if the file's path is incorrect or otherwise cannot be read. ConfigError: if the file's path is incorrect or otherwise cannot be read.
@ -260,11 +261,13 @@ class Config:
Returns: Returns:
A list of jinja2 templates. A list of jinja2 templates.
""" """
search_directories = [self.default_template_dir] search_directories = []
# The loader will first look in the custom template directory (if specified) for the # The loader will first look in the custom template directories (if specified)
# given filename. If it doesn't find it, it will use the default template dir instead # for the given filename. If it doesn't find it, it will use the default
if custom_template_directory: # template dir instead.
if custom_template_directories is not None:
for custom_template_directory in custom_template_directories:
# Check that the given template directory exists # Check that the given template directory exists
if not self.path_exists(custom_template_directory): if not self.path_exists(custom_template_directory):
raise ConfigError( raise ConfigError(
@ -273,7 +276,11 @@ class Config:
) )
# Search the custom template directory as well # Search the custom template directory as well
search_directories.insert(0, custom_template_directory) search_directories.append(custom_template_directory)
# Append the default directory at the end of the list so Jinja can fallback on it
# if a template is missing from any custom directory.
search_directories.append(self.default_template_dir)
# TODO: switch to synapse.util.templates.build_jinja_env # TODO: switch to synapse.util.templates.build_jinja_env
loader = jinja2.FileSystemLoader(search_directories) loader = jinja2.FileSystemLoader(search_directories)

View File

@ -88,5 +88,5 @@ class AccountValidityConfig(Config):
"account_previously_renewed.html", "account_previously_renewed.html",
invalid_token_template_filename, invalid_token_template_filename,
], ],
account_validity_template_dir, (td for td in (account_validity_template_dir,) if td),
) )

View File

@ -257,7 +257,9 @@ class EmailConfig(Config):
registration_template_success_html, registration_template_success_html,
add_threepid_template_success_html, add_threepid_template_success_html,
], ],
template_dir, (
td for td in (template_dir,) if td
), # Filter out template_dir if not provided
) )
# Render templates that do not contain any placeholders # Render templates that do not contain any placeholders
@ -297,7 +299,7 @@ class EmailConfig(Config):
self.email_notif_template_text, self.email_notif_template_text,
) = self.read_templates( ) = self.read_templates(
[notif_template_html, notif_template_text], [notif_template_html, notif_template_text],
template_dir, (td for td in (template_dir,) if td),
) )
self.email_notif_for_new_users = email_config.get( self.email_notif_for_new_users = email_config.get(
@ -320,7 +322,7 @@ class EmailConfig(Config):
self.account_validity_template_text, self.account_validity_template_text,
) = self.read_templates( ) = self.read_templates(
[expiry_template_html, expiry_template_text], [expiry_template_html, expiry_template_text],
template_dir, (td for td in (template_dir,) if td),
) )
subjects_config = email_config.get("subjects", {}) subjects_config = email_config.get("subjects", {})

View File

@ -63,7 +63,7 @@ class SSOConfig(Config):
"sso_auth_success.html", "sso_auth_success.html",
"sso_auth_bad_user.html", "sso_auth_bad_user.html",
], ],
self.sso_template_dir, (td for td in (self.sso_template_dir,) if td),
) )
# These templates have no placeholders, so render them here # These templates have no placeholders, so render them here

View File

@ -677,7 +677,10 @@ class ModuleApi:
A list containing the loaded templates, with the orders matching the one of A list containing the loaded templates, with the orders matching the one of
the filenames parameter. the filenames parameter.
""" """
return self._hs.config.read_templates(filenames, custom_template_directory) return self._hs.config.read_templates(
filenames,
(td for td in (custom_template_directory,) if td),
)
class PublicRoomListManager: class PublicRoomListManager:

View File

@ -30,7 +30,7 @@ class BaseConfigTestCase(unittest.HomeserverTestCase):
# contain template files # contain template files
with tempfile.TemporaryDirectory() as tmp_dir: with tempfile.TemporaryDirectory() as tmp_dir:
# Attempt to load an HTML template from our custom template directory # Attempt to load an HTML template from our custom template directory
template = self.hs.config.read_templates(["sso_error.html"], tmp_dir)[0] template = self.hs.config.read_templates(["sso_error.html"], (tmp_dir,))[0]
# If no errors, we should've gotten the default template instead # If no errors, we should've gotten the default template instead
@ -60,7 +60,7 @@ class BaseConfigTestCase(unittest.HomeserverTestCase):
# Attempt to load the template from our custom template directory # Attempt to load the template from our custom template directory
template = ( template = (
self.hs.config.read_templates([template_filename], tmp_dir) self.hs.config.read_templates([template_filename], (tmp_dir,))
)[0] )[0]
# Render the template # Render the template
@ -74,8 +74,66 @@ class BaseConfigTestCase(unittest.HomeserverTestCase):
"Template file did not contain our test string", "Template file did not contain our test string",
) )
def test_multiple_custom_template_directories(self):
"""Tests that directories are searched in the right order if multiple custom
template directories are provided.
"""
# Create two temporary directories on the filesystem.
tempdirs = [
tempfile.TemporaryDirectory(),
tempfile.TemporaryDirectory(),
]
# Create one template in each directory, whose content is the index of the
# directory in the list.
template_filename = "my_template.html.j2"
for i in range(len(tempdirs)):
tempdir = tempdirs[i]
template_path = os.path.join(tempdir.name, template_filename)
with open(template_path, "w") as fp:
fp.write(str(i))
fp.flush()
# Retrieve the template.
template = (
self.hs.config.read_templates(
[template_filename],
(td.name for td in tempdirs),
)
)[0]
# Test that we got the template we dropped in the first directory in the list.
self.assertEqual(template.render(), "0")
# Add another template, this one only in the second directory in the list, so we
# can test that the second directory is still searched into when no matching file
# could be found in the first one.
other_template_name = "my_other_template.html.j2"
other_template_path = os.path.join(tempdirs[1].name, other_template_name)
with open(other_template_path, "w") as fp:
fp.write("hello world")
fp.flush()
# Retrieve the template.
template = (
self.hs.config.read_templates(
[other_template_name],
(td.name for td in tempdirs),
)
)[0]
# Test that the file has the expected content.
self.assertEqual(template.render(), "hello world")
# Cleanup the temporary directories manually since we're not using a context
# manager.
for td in tempdirs:
td.cleanup()
def test_loading_template_from_nonexistent_custom_directory(self): def test_loading_template_from_nonexistent_custom_directory(self):
with self.assertRaises(ConfigError): with self.assertRaises(ConfigError):
self.hs.config.read_templates( self.hs.config.read_templates(
["some_filename.html"], "a_nonexistent_directory" ["some_filename.html"], ("a_nonexistent_directory",)
) )