Skip to content
This repository was archived by the owner on Apr 26, 2024. It is now read-only.

Commit ae2714c

Browse files
authored
Allow using several custom template directories (#10587)
Allow using several directories in read_templates.
1 parent a933c2c commit ae2714c

File tree

7 files changed

+98
-27
lines changed

7 files changed

+98
-27
lines changed

changelog.d/10587.misc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Allow multiple custom directories in `read_templates`.

synapse/config/_base.py

Lines changed: 25 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -237,43 +237,50 @@ def read_template(self, filename: str) -> jinja2.Template:
237237
def read_templates(
238238
self,
239239
filenames: List[str],
240-
custom_template_directory: Optional[str] = None,
240+
custom_template_directories: Optional[Iterable[str]] = None,
241241
) -> List[jinja2.Template]:
242242
"""Load a list of template files from disk using the given variables.
243243
244244
This function will attempt to load the given templates from the default Synapse
245-
template directory. If `custom_template_directory` is supplied, that directory
246-
is tried first.
245+
template directory. If `custom_template_directories` is supplied, any directory
246+
in this list is tried (in the order they appear in the list) before trying
247+
Synapse's default directory.
247248
248249
Files read are treated as Jinja templates. The templates are not rendered yet
249250
and have autoescape enabled.
250251
251252
Args:
252253
filenames: A list of template filenames to read.
253254
254-
custom_template_directory: A directory to try to look for the templates
255-
before using the default Synapse template directory instead.
255+
custom_template_directories: A list of directory to try to look for the
256+
templates before using the default Synapse template directory instead.
256257
257258
Raises:
258259
ConfigError: if the file's path is incorrect or otherwise cannot be read.
259260
260261
Returns:
261262
A list of jinja2 templates.
262263
"""
263-
search_directories = [self.default_template_dir]
264-
265-
# The loader will first look in the custom template directory (if specified) for the
266-
# given filename. If it doesn't find it, it will use the default template dir instead
267-
if custom_template_directory:
268-
# Check that the given template directory exists
269-
if not self.path_exists(custom_template_directory):
270-
raise ConfigError(
271-
"Configured template directory does not exist: %s"
272-
% (custom_template_directory,)
273-
)
264+
search_directories = []
265+
266+
# The loader will first look in the custom template directories (if specified)
267+
# for the given filename. If it doesn't find it, it will use the default
268+
# template dir instead.
269+
if custom_template_directories is not None:
270+
for custom_template_directory in custom_template_directories:
271+
# Check that the given template directory exists
272+
if not self.path_exists(custom_template_directory):
273+
raise ConfigError(
274+
"Configured template directory does not exist: %s"
275+
% (custom_template_directory,)
276+
)
277+
278+
# Search the custom template directory as well
279+
search_directories.append(custom_template_directory)
274280

275-
# Search the custom template directory as well
276-
search_directories.insert(0, custom_template_directory)
281+
# Append the default directory at the end of the list so Jinja can fallback on it
282+
# if a template is missing from any custom directory.
283+
search_directories.append(self.default_template_dir)
277284

278285
# TODO: switch to synapse.util.templates.build_jinja_env
279286
loader = jinja2.FileSystemLoader(search_directories)

synapse/config/account_validity.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,5 +88,5 @@ def read_config(self, config, **kwargs):
8888
"account_previously_renewed.html",
8989
invalid_token_template_filename,
9090
],
91-
account_validity_template_dir,
91+
(td for td in (account_validity_template_dir,) if td),
9292
)

synapse/config/emailconfig.py

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,9 @@ def read_config(self, config, **kwargs):
257257
registration_template_success_html,
258258
add_threepid_template_success_html,
259259
],
260-
template_dir,
260+
(
261+
td for td in (template_dir,) if td
262+
), # Filter out template_dir if not provided
261263
)
262264

263265
# Render templates that do not contain any placeholders
@@ -297,7 +299,7 @@ def read_config(self, config, **kwargs):
297299
self.email_notif_template_text,
298300
) = self.read_templates(
299301
[notif_template_html, notif_template_text],
300-
template_dir,
302+
(td for td in (template_dir,) if td),
301303
)
302304

303305
self.email_notif_for_new_users = email_config.get(
@@ -320,7 +322,7 @@ def read_config(self, config, **kwargs):
320322
self.account_validity_template_text,
321323
) = self.read_templates(
322324
[expiry_template_html, expiry_template_text],
323-
template_dir,
325+
(td for td in (template_dir,) if td),
324326
)
325327

326328
subjects_config = email_config.get("subjects", {})

synapse/config/sso.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def read_config(self, config, **kwargs):
6363
"sso_auth_success.html",
6464
"sso_auth_bad_user.html",
6565
],
66-
self.sso_template_dir,
66+
(td for td in (self.sso_template_dir,) if td),
6767
)
6868

6969
# These templates have no placeholders, so render them here

synapse/module_api/__init__.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -677,7 +677,10 @@ def read_templates(
677677
A list containing the loaded templates, with the orders matching the one of
678678
the filenames parameter.
679679
"""
680-
return self._hs.config.read_templates(filenames, custom_template_directory)
680+
return self._hs.config.read_templates(
681+
filenames,
682+
(td for td in (custom_template_directory,) if td),
683+
)
681684

682685

683686
class PublicRoomListManager:

tests/config/test_base.py

Lines changed: 61 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ def test_loading_missing_templates(self):
3030
# contain template files
3131
with tempfile.TemporaryDirectory() as tmp_dir:
3232
# Attempt to load an HTML template from our custom template directory
33-
template = self.hs.config.read_templates(["sso_error.html"], tmp_dir)[0]
33+
template = self.hs.config.read_templates(["sso_error.html"], (tmp_dir,))[0]
3434

3535
# If no errors, we should've gotten the default template instead
3636

@@ -60,7 +60,7 @@ def test_loading_custom_templates(self):
6060

6161
# Attempt to load the template from our custom template directory
6262
template = (
63-
self.hs.config.read_templates([template_filename], tmp_dir)
63+
self.hs.config.read_templates([template_filename], (tmp_dir,))
6464
)[0]
6565

6666
# Render the template
@@ -74,8 +74,66 @@ def test_loading_custom_templates(self):
7474
"Template file did not contain our test string",
7575
)
7676

77+
def test_multiple_custom_template_directories(self):
78+
"""Tests that directories are searched in the right order if multiple custom
79+
template directories are provided.
80+
"""
81+
# Create two temporary directories on the filesystem.
82+
tempdirs = [
83+
tempfile.TemporaryDirectory(),
84+
tempfile.TemporaryDirectory(),
85+
]
86+
87+
# Create one template in each directory, whose content is the index of the
88+
# directory in the list.
89+
template_filename = "my_template.html.j2"
90+
for i in range(len(tempdirs)):
91+
tempdir = tempdirs[i]
92+
template_path = os.path.join(tempdir.name, template_filename)
93+
94+
with open(template_path, "w") as fp:
95+
fp.write(str(i))
96+
fp.flush()
97+
98+
# Retrieve the template.
99+
template = (
100+
self.hs.config.read_templates(
101+
[template_filename],
102+
(td.name for td in tempdirs),
103+
)
104+
)[0]
105+
106+
# Test that we got the template we dropped in the first directory in the list.
107+
self.assertEqual(template.render(), "0")
108+
109+
# Add another template, this one only in the second directory in the list, so we
110+
# can test that the second directory is still searched into when no matching file
111+
# could be found in the first one.
112+
other_template_name = "my_other_template.html.j2"
113+
other_template_path = os.path.join(tempdirs[1].name, other_template_name)
114+
115+
with open(other_template_path, "w") as fp:
116+
fp.write("hello world")
117+
fp.flush()
118+
119+
# Retrieve the template.
120+
template = (
121+
self.hs.config.read_templates(
122+
[other_template_name],
123+
(td.name for td in tempdirs),
124+
)
125+
)[0]
126+
127+
# Test that the file has the expected content.
128+
self.assertEqual(template.render(), "hello world")
129+
130+
# Cleanup the temporary directories manually since we're not using a context
131+
# manager.
132+
for td in tempdirs:
133+
td.cleanup()
134+
77135
def test_loading_template_from_nonexistent_custom_directory(self):
78136
with self.assertRaises(ConfigError):
79137
self.hs.config.read_templates(
80-
["some_filename.html"], "a_nonexistent_directory"
138+
["some_filename.html"], ("a_nonexistent_directory",)
81139
)

0 commit comments

Comments
 (0)