changeset 3268:85c9cfcd4f5e

tools (common/template): theme settings with possibility to disable default fallback for CSS: - themes can now have a `settings.json` file at their root to modify the renderer behaviour - the `css_default_fallback` boolean (true by default) can be used to disable the fallback mechanism when CSS files are checked. Useful when a theme doesn't follow the CSS naming of default theme. - introduced some type hints
author Goffi <goffi@goffi.org>
date Sun, 03 May 2020 17:01:39 +0200
parents 2eeca6fd08f7
children 1352564e0202
files sat/tools/common/template.py
diffstat 1 files changed, 72 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- a/sat/tools/common/template.py	Sun May 03 13:40:04 2020 +0200
+++ b/sat/tools/common/template.py	Sun May 03 17:01:39 2020 +0200
@@ -24,6 +24,7 @@
 import json
 from pathlib import Path
 from collections import namedtuple
+from typing import Optional, List, Tuple
 from xml.sax.saxutils import quoteattr
 from babel import support
 from babel import Locale
@@ -159,18 +160,22 @@
         return TemplateData(site, theme, template_path)
 
     @staticmethod
-    def getSitesAndThemes(site, theme):
+    def getSitesAndThemes(
+            site: str,
+            theme: str,
+            default_fallback: bool=True
+        ) -> 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
-        @param site(unicode): site requested
-        @param theme(unicode): theme requested
-        @return (list[tuple[unicode, unicode]]): site and theme couples to check
+        @param site: site requested
+        @param theme: theme requested
+        @return: site and theme couples to check
         """
         sites_and_themes = [[site, theme]]
-        if theme != C.TEMPLATE_THEME_DEFAULT:
+        if theme != C.TEMPLATE_THEME_DEFAULT and default_fallback:
             sites_and_themes.append([site, C.TEMPLATE_THEME_DEFAULT])
-        if site:
+        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])
         return sites_and_themes
@@ -383,7 +388,22 @@
                 if not p.is_dir():
                     continue
                 log.debug(f"theme found for {site or 'default site'}: {p.name}")
-                theme_data = self.sites_themes.setdefault(site, {})[p.name] = {'path': p}
+                theme_data = self.sites_themes.setdefault(site, {})[p.name] = {
+                    'path': p,
+                    'settings': {}}
+                theme_settings = p / "settings.json"
+                if theme_settings.is_file:
+                    try:
+                        with theme_settings.open() as f:
+                            settings = json.load(f)
+                    except Exception as e:
+                        log.warning(_(
+                            "Can't load theme settings at {path}").format(
+                            path=theme_settings))
+                    else:
+                        log.debug(
+                            f"found settings for theme {p.name!r} at {theme_settings}")
+                        theme_data['settings'] = settings
                 browser_path = p / BROWSER_DIR
                 if browser_path.is_dir():
                     theme_data['browser_path'] = browser_path
@@ -548,22 +568,30 @@
         except KeyError:
             raise exceptions.NotFound(f"no theme found for {site_name}")
 
-    def getStaticPath(self, template_data, filename):
+    def getStaticPath(
+            self,
+            template_data: TemplateData,
+            filename: str,
+            default_fallback: bool=True
+        ) -> Optional[TemplateData]:
         """Retrieve path of a static file if it exists with current theme or default
 
         File will be looked at <site_root_dir>/<theme_dir>/<static_dir>/filename,
         then <site_root_dir>/<default_theme_dir>/<static_dir>/filename anf finally
         <default_site>/<default_theme_dir>/<static_dir> (i.e. sat_templates).
-        In case of absolue URL, base dir of template is used as base. For instance if
+        In case of absolute URL, base dir of template is used as base. For instance if
         template is an absolute template to "/some/path/template.html", file will be
         checked at "/some/path/<filename>"
-        @param template_data(TemplateData): data of current template
-        @return (TemplateData, None): built template data instance where .path is
+        @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.
+        @return: built template data instance where .path is
             the relative path to the file, from theme root dir.
             None if not found.
         """
         if template_data.site is None:
-            # we have and absolue path
+            # we have an absolue path
             if (not template_data.theme is None
                 or not template_data.path.startswith('/')):
                 raise exceptions.InternalError(
@@ -576,7 +604,8 @@
                 return None
 
         sites_and_themes = TemplateLoader.getSitesAndThemes(template_data.site,
-                                                            template_data.theme)
+                                                            template_data.theme,
+                                                            default_fallback)
         for site, theme in sites_and_themes:
             site_root_dir = self.sites_paths[site]
             relative_path = os.path.join(C.TEMPLATE_STATIC_DIR, filename)
@@ -587,19 +616,28 @@
 
         return None
 
-    def _appendCSSPaths(self, template_data, css_files, css_files_noscript, name_root):
+    def _appendCSSPaths(
+            self,
+            template_data: TemplateData,
+            css_files: list,
+            css_files_noscript: list,
+            name_root: str,
+            default_fallback: bool
+
+        ) -> None:
         """Append found css to css_files and css_files_noscript
 
-        @param css_files(list): list to fill of relative path to found css file
-        @param css_files_noscript(list): list to fill of relative path to found css file
+        @param css_files: list to fill of relative path to found css file
+        @param css_files_noscript: list to fill of relative path to found css file
             with "_noscript" suffix
         """
         name = name_root + ".css"
-        css_path = self.getStaticPath(template_data, name)
+        css_path = self.getStaticPath(template_data, name, default_fallback)
         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)
+            noscript_path = self.getStaticPath(
+                template_data, noscript_name, default_fallback)
             if noscript_path is not None:
                 css_files_noscript.append(self.getFrontURL(noscript_path))
 
@@ -624,6 +662,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.
         @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
@@ -634,17 +675,27 @@
         css_files_noscript = []
         path_elems = template_data.path.split('/')
         path_elems[-1] = os.path.splitext(path_elems[-1])[0]
+        site = template_data.site
+        if site is None:
+            # absolute path
+            default_fallback = True
+        else:
+            default_fallback = (
+                self.sites_themes[site][template_data.theme]['settings']
+            ).get('css_default_fallback', True)
 
-        css_path = self.getStaticPath(template_data, 'fonts.css')
+        css_path = self.getStaticPath(template_data, 'fonts.css', default_fallback)
         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)
+            self._appendCSSPaths(
+                template_data, css_files, css_files_noscript, name_root, default_fallback)
 
         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)
+            self._appendCSSPaths(
+                template_data, css_files, css_files_noscript, name_root, default_fallback)
 
         return css_files, css_files_noscript