changeset 3478:b65175eb7769

tools (common/template): new `fallback` settings: `fallback` can be used to change fallback behaviour. By default, fallback is done on `default` theme, this can be set to an other theme with a string, or to a list of fallback. The list can also be empty is no fallback is desired (notably usefull for "main" themes, on which other themes may fallback). This setting replaces `css_default_fallback`.
author Goffi <goffi@goffi.org>
date Fri, 19 Mar 2021 14:01:52 +0100
parents 9498f32ba6f7
children be6d91572633
files sat/tools/common/template.py
diffstat 1 files changed, 61 insertions(+), 34 deletions(-) [+]
line wrap: on
line diff
--- a/sat/tools/common/template.py	Fri Mar 19 14:01:51 2021 +0100
+++ b/sat/tools/common/template.py	Fri Mar 19 14:01:52 2021 +0100
@@ -82,7 +82,7 @@
     """A template loader which handle site, theme and absolute paths"""
     # TODO: list_templates should be implemented
 
-    def __init__(self, sites_paths, trusted=False):
+    def __init__(self, sites_paths, sites_themes, trusted=False):
         """
         @param trusted(bool): if True, absolue template paths will be allowed
             be careful when using this option and sure that you can trust the template,
@@ -93,6 +93,7 @@
             raise exceptions.InternalError("Invalid sites_paths")
         super(jinja2.BaseLoader, self).__init__()
         self.sites_paths = sites_paths
+        self.sites_themes = sites_themes
         self.trusted = trusted
 
     @staticmethod
@@ -163,21 +164,29 @@
     def getSitesAndThemes(
             site: str,
             theme: str,
-            default_fallback: bool=True
+            settings: Optional[dict] = None,
         ) -> List[Tuple[str, str]]:
         """Get sites and themes to check for template/file
 
-        Will add default theme and default site in search list when suitable
+        Will add default theme and default site in search list when suitable. Settings'
+        `fallback` can be used to modify behaviour: themes in this list will then be used
+        instead of default (it can also be empty list or None, in which case no fallback
+        is used).
+
         @param site: site requested
         @param theme: theme requested
         @return: site and theme couples to check
         """
+        if settings is None:
+            settings = {}
         sites_and_themes = [[site, theme]]
-        if theme != C.TEMPLATE_THEME_DEFAULT and default_fallback:
-            sites_and_themes.append([site, C.TEMPLATE_THEME_DEFAULT])
-        if site and default_fallback:
-            # the site is not the default one, so we add default at the end
-            sites_and_themes.append(['', C.TEMPLATE_THEME_DEFAULT])
+        fallback = settings.get("fallback", [C.TEMPLATE_THEME_DEFAULT])
+        for fb_theme in fallback:
+            if theme != fb_theme:
+                sites_and_themes.append([site, fb_theme])
+        if site:
+            for fb_theme in fallback:
+                sites_and_themes.append(["", fb_theme])
         return sites_and_themes
 
     def _get_template_f(self, site, theme, path_elts):
@@ -194,13 +203,20 @@
         if site is None:
             raise exceptions.InternalError(
                 "_get_template_f must not be used with absolute path")
-        for site, theme in self.getSitesAndThemes(site, theme):
+        settings = self.sites_themes[site][theme]['settings']
+        for site_to_check, theme_to_check in self.getSitesAndThemes(
+                site, theme, settings):
             try:
-                base_path = self.sites_paths[site]
+                base_path = self.sites_paths[site_to_check]
             except KeyError:
-                log.warning(_("Unregistered site requested: {site}").format(
-                    site=site))
-            filepath = os.path.join(base_path, C.TEMPLATE_TPL_DIR, theme, *path_elts)
+                log.warning(_("Unregistered site requested: {site_to_check}").format(
+                    site_to_check=site_to_check))
+            filepath = os.path.join(
+                base_path,
+                C.TEMPLATE_TPL_DIR,
+                theme_to_check,
+                *path_elts
+            )
             f = utils.open_if_exists(filepath, 'r')
             if f is not None:
                 return f, filepath
@@ -398,11 +414,21 @@
                             settings = json.load(f)
                     except Exception as e:
                         log.warning(_(
-                            "Can't load theme settings at {path}").format(
-                            path=theme_settings))
+                            "Can't load theme settings at {path}: {e}").format(
+                            path=theme_settings, e=e))
                     else:
                         log.debug(
                             f"found settings for theme {p.name!r} at {theme_settings}")
+                        fallback = settings.get("fallback")
+                        if fallback is None:
+                            settings["fallback"] = []
+                        elif isinstance(fallback, str):
+                            settings["fallback"] = [fallback]
+                        elif not isinstance(fallback, list):
+                            raise ValueError(
+                                'incorrect type for "fallback" in settings '
+                                f'({type(fallback)}) at {theme_settings}: {fallback}'
+                            )
                         theme_data['settings'] = settings
                 browser_path = p / BROWSER_DIR
                 if browser_path.is_dir():
@@ -419,7 +445,11 @@
                         continue
 
         self.env = Environment(
-            loader=TemplateLoader(sites_paths=self.sites_paths, trusted=trusted),
+            loader=TemplateLoader(
+                sites_paths=self.sites_paths,
+                sites_themes=self.sites_themes,
+                trusted=trusted
+            ),
             autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]),
             trim_blocks=True,
             lstrip_blocks=True,
@@ -550,7 +580,7 @@
         self._locale_str = locale_str
 
     def getThemeAndRoot(self, template):
-        """retrieve theme and root dir of a given tempalte
+        """retrieve theme and root dir of a given template
 
         @param template(unicode): template to parse
         @return (tuple[unicode, unicode]): theme and absolute path to theme's root dir
@@ -577,7 +607,7 @@
             self,
             template_data: TemplateData,
             filename: str,
-            default_fallback: bool=True
+            settings: Optional[dict]=None
         ) -> Optional[TemplateData]:
         """Retrieve path of a static file if it exists with current theme or default
 
@@ -589,8 +619,7 @@
         checked at "/some/path/<filename>"
         @param template_data: data of current template
         @param filename: name of the file to retrieve
-        @param default_fallback: if True, default theme will be checked if the file is
-            not found in current theme, then default site with default theme will be used.
+        @param settings: theme settings, can be used to modify behaviour
         @return: built template data instance where .path is
             the relative path to the file, from theme root dir.
             None if not found.
@@ -610,7 +639,7 @@
 
         sites_and_themes = TemplateLoader.getSitesAndThemes(template_data.site,
                                                             template_data.theme,
-                                                            default_fallback)
+                                                            settings)
         for site, theme in sites_and_themes:
             site_root_dir = self.sites_paths[site]
             relative_path = os.path.join(C.TEMPLATE_STATIC_DIR, filename)
@@ -627,7 +656,7 @@
             css_files: list,
             css_files_noscript: list,
             name_root: str,
-            default_fallback: bool
+            settings: dict
 
         ) -> None:
         """Append found css to css_files and css_files_noscript
@@ -637,12 +666,12 @@
             with "_noscript" suffix
         """
         name = name_root + ".css"
-        css_path = self.getStaticPath(template_data, name, default_fallback)
+        css_path = self.getStaticPath(template_data, name, settings)
         if css_path is not None:
             css_files.append(self.getFrontURL(css_path))
             noscript_name = name_root + "_noscript.css"
             noscript_path = self.getStaticPath(
-                template_data, noscript_name, default_fallback)
+                template_data, noscript_name, settings)
             if noscript_path is not None:
                 css_files_noscript.append(self.getFrontURL(noscript_path))
 
@@ -667,9 +696,9 @@
               else default/static/blog_articles.css (if it exists too)
         and for each found files, if same file with _noscript suffix exists, it is put
         in noscript list (for instance (some_theme/static/styles_noscript.css)).
-        The behaviour may be changed with theme settings: if "css_default_fallback" is
-        False, only CSS from the theme is returned if it exists, default CSS is never
-        used.
+        The behaviour may be changed with theme settings: if "fallback" is set, specified
+        themes will be checked instead of default. The theme will be checked in given
+        order, and "fallback" may be None or empty list to not check anything.
         @param template_data(TemplateData): data of the current template
         @return (tuple[list[unicode], list[unicode]]): a tuple with:
             - front URLs of CSS files to use
@@ -683,24 +712,22 @@
         site = template_data.site
         if site is None:
             # absolute path
-            default_fallback = True
+            settings = {}
         else:
-            default_fallback = (
-                self.sites_themes[site][template_data.theme]['settings']
-            ).get('css_default_fallback', True)
+            settings = self.sites_themes[site][template_data.theme]['settings']
 
-        css_path = self.getStaticPath(template_data, 'fonts.css', default_fallback)
+        css_path = self.getStaticPath(template_data, 'fonts.css', settings)
         if css_path is not None:
             css_files.append(self.getFrontURL(css_path))
 
         for name_root in ('styles', 'styles_extra', 'highlight'):
             self._appendCSSPaths(
-                template_data, css_files, css_files_noscript, name_root, default_fallback)
+                template_data, css_files, css_files_noscript, name_root, settings)
 
         for idx in range(len(path_elems)):
             name_root = "_".join(path_elems[:idx+1])
             self._appendCSSPaths(
-                template_data, css_files, css_files_noscript, name_root, default_fallback)
+                template_data, css_files, css_files_noscript, name_root, settings)
 
         return css_files, css_files_noscript