diff sat/tools/common/template.py @ 2624:56f94936df1e

code style reformatting using black
author Goffi <goffi@goffi.org>
date Wed, 27 Jun 2018 20:14:46 +0200
parents 5b26033c49a8
children 0fa217fafabf
line wrap: on
line diff
--- a/sat/tools/common/template.py	Wed Jun 27 07:51:29 2018 +0200
+++ b/sat/tools/common/template.py	Wed Jun 27 20:14:46 2018 +0200
@@ -24,6 +24,7 @@
 from sat.core import exceptions
 from sat.tools.common import date_utils
 from sat.core.log import getLogger
+
 log = getLogger(__name__)
 import os.path
 from xml.sax.saxutils import quoteattr
@@ -35,30 +36,34 @@
 import pygments
 from pygments import lexers
 from pygments import formatters
+
 try:
     import sat_templates
 except ImportError:
-    raise exceptions.MissingModule(u'sat_templates module is not available, please install it or check your path to use template engine')
+    raise exceptions.MissingModule(
+        u"sat_templates module is not available, please install it or check your path to use template engine"
+    )
 else:
     sat_templates  # to avoid pyflakes warning
 
 try:
     import jinja2
 except:
-    raise exceptions.MissingModule(u'Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2')
+    raise exceptions.MissingModule(
+        u"Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2"
+    )
 
 from jinja2 import Markup as safe
 from jinja2 import is_undefined
 from lxml import etree
 
-HTML_EXT = ('html', 'xhtml')
-RE_ATTR_ESCAPE = re.compile(r'[^a-z_-]')
-# TODO: handle external path (an additional search path for templates should be settable by user
+HTML_EXT = ("html", "xhtml")
+RE_ATTR_ESCAPE = re.compile(r"[^a-z_-]")
+#  TODO: handle external path (an additional search path for templates should be settable by user
 # TODO: handle absolute URL (should be used for trusted use cases) only (e.g. jp) for security reason
 
 
 class TemplateLoader(jinja2.FileSystemLoader):
-
     def __init__(self):
         searchpath = os.path.dirname(sat_templates.__file__)
         super(TemplateLoader, self).__init__(searchpath, followlinks=True)
@@ -72,20 +77,20 @@
             relative path is the path from search path with theme specified
             e.g. default/blog/articles.html
         """
-        if template.startswith(u'('):
+        if template.startswith(u"("):
             try:
-                theme_end = template.index(u')')
+                theme_end = template.index(u")")
             except IndexError:
                 raise ValueError(u"incorrect theme in template")
             theme = template[1:theme_end]
-            template = template[theme_end+1:]
-            if not template or template.startswith(u'/'):
+            template = template[theme_end + 1 :]
+            if not template or template.startswith(u"/"):
                 raise ValueError(u"incorrect path after template name")
             template = os.path.join(theme, template)
-        elif template.startswith(u'/'):
+        elif template.startswith(u"/"):
             # absolute path means no template
             theme = None
-            raise NotImplementedError(u'absolute path is not implemented yet')
+            raise NotImplementedError(u"absolute path is not implemented yet")
         else:
             theme = C.TEMPLATE_THEME_DEFAULT
             template = os.path.join(theme, template)
@@ -99,11 +104,11 @@
         @return (unicode, None): default path or None if there is not
         """
         ext = os.path.splitext(template_path)[1][1:]
-        path_elems = template_path.split(u'/')
+        path_elems = template_path.split(u"/")
         if ext in HTML_EXT:
-            if path_elems[1] == u'error':
+            if path_elems[1] == u"error":
                 # if an inexisting error page is requested, we return base page
-                default_path = os.path.join(theme, u'error/base.html')
+                default_path = os.path.join(theme, u"error/base.html")
                 return default_path
         if theme != C.TEMPLATE_THEME_DEFAULT:
             # if template doesn't exists for this theme, we try with default
@@ -124,7 +129,9 @@
             if theme is not None:
                 default_path = self.get_default_template(theme, template_path)
                 if default_path is not None:
-                    return super(TemplateLoader, self).get_source(environment, default_path)
+                    return super(TemplateLoader, self).get_source(
+                        environment, default_path
+                    )
             # if no default template is found, we re-raise the error
             raise e
 
@@ -147,15 +154,14 @@
 
 
 class ScriptsHandler(object):
-
     def __init__(self, renderer, template_path, template_root_dir, root_path):
         self.renderer = renderer
         self.template_root_dir = template_root_dir
         self.root_path = root_path
-        self.scripts = []  # we don't use a set because order may be important
+        self.scripts = []  #  we don't use a set because order may be important
         dummy, self.theme, self.is_default_theme = renderer.getThemeData(template_path)
 
-    def include(self, library_name, attribute='defer'):
+    def include(self, library_name, attribute="defer"):
         """Mark that a script need to be imported.
 
         Must be used before base.html is extended, as <script> are generated there.
@@ -163,13 +169,15 @@
         @param library_name(unicode): name of the library to import
         @param loading:
         """
-        if attribute not in ('defer', 'async', ''):
-            raise exceptions.DataError(_(u'Invalid attribute, please use one of "defer", "async" or ""'))
-        if library_name.endswith('.js'):
+        if attribute not in ("defer", "async", ""):
+            raise exceptions.DataError(
+                _(u'Invalid attribute, please use one of "defer", "async" or ""')
+            )
+        if library_name.endswith(".js"):
             library_name = library_name[:-3]
         if library_name not in self.scripts:
             self.scripts.append((library_name, attribute))
-        return u''
+        return u""
 
     def generate_scripts(self):
         """Generate the <script> elements
@@ -177,68 +185,72 @@
         @return (unicode): <scripts> HTML tags
         """
         scripts = []
-        tpl = u'<script src={src} {attribute}></script>'
+        tpl = u"<script src={src} {attribute}></script>"
         for library, attribute in self.scripts:
-            path = self.renderer.getStaticPath(library, self.template_root_dir, self.theme, self.is_default_theme, '.js')
+            path = self.renderer.getStaticPath(
+                library, self.template_root_dir, self.theme, self.is_default_theme, ".js"
+            )
             if path is None:
                 log.warning(_(u"Can't find {}.js javascript library").format(library))
                 continue
             path = os.path.join(self.root_path, path)
-            scripts.append(tpl.format(
-                src = quoteattr(path),
-                attribute = attribute,
-                ))
-        return safe(u'\n'.join(scripts))
+            scripts.append(tpl.format(src=quoteattr(path), attribute=attribute))
+        return safe(u"\n".join(scripts))
 
 
 class Renderer(object):
-
     def __init__(self, host):
         self.host = host
-        self.base_dir = os.path.dirname(sat_templates.__file__)  # FIXME: should be modified if we handle use extra dirs
+        self.base_dir = os.path.dirname(
+            sat_templates.__file__
+        )  # FIXME: should be modified if we handle use extra dirs
         self.env = jinja2.Environment(
             loader=TemplateLoader(),
-            autoescape=jinja2.select_autoescape(['html', 'xhtml', 'xml']),
+            autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]),
             trim_blocks=True,
             lstrip_blocks=True,
-            extensions=['jinja2.ext.i18n'],
-            )
+            extensions=["jinja2.ext.i18n"],
+        )
         self._locale_str = C.DEFAULT_LOCALE
         self._locale = Locale.parse(self._locale_str)
         self.installTranslations()
         # we want to have access to SàT constants in templates
-        self.env.globals[u'C'] = C
+        self.env.globals[u"C"] = C
         # custom filters
-        self.env.filters['next_gidx'] = self._next_gidx
-        self.env.filters['cur_gidx'] = self._cur_gidx
-        self.env.filters['date_fmt'] = self._date_fmt
-        self.env.filters['xmlui_class'] = self._xmlui_class
-        self.env.filters['attr_escape'] = self.attr_escape
-        self.env.filters['item_filter'] = self._item_filter
-        self.env.filters['adv_format'] = self._adv_format
-        self.env.filters['dict_ext'] = self._dict_ext
-        self.env.filters['highlight'] = self.highlight
+        self.env.filters["next_gidx"] = self._next_gidx
+        self.env.filters["cur_gidx"] = self._cur_gidx
+        self.env.filters["date_fmt"] = self._date_fmt
+        self.env.filters["xmlui_class"] = self._xmlui_class
+        self.env.filters["attr_escape"] = self.attr_escape
+        self.env.filters["item_filter"] = self._item_filter
+        self.env.filters["adv_format"] = self._adv_format
+        self.env.filters["dict_ext"] = self._dict_ext
+        self.env.filters["highlight"] = self.highlight
         # custom tests
-        self.env.tests['in_the_past'] = self._in_the_past
-        self.icons_path = os.path.join(host.media_dir, u'fonts/fontello/svg')
+        self.env.tests["in_the_past"] = self._in_the_past
+        self.icons_path = os.path.join(host.media_dir, u"fonts/fontello/svg")
 
     def installTranslations(self):
-        i18n_dir = os.path.join(self.base_dir, 'i18n')
+        i18n_dir = os.path.join(self.base_dir, "i18n")
         self.translations = {}
         for lang_dir in os.listdir(i18n_dir):
             lang_path = os.path.join(i18n_dir, lang_dir)
             if not os.path.isdir(lang_path):
                 continue
-            po_path = os.path.join(lang_path, 'LC_MESSAGES/sat.mo')
+            po_path = os.path.join(lang_path, "LC_MESSAGES/sat.mo")
             try:
-                with open(po_path, 'rb') as f:
-                    self.translations[Locale.parse(lang_dir)] = support.Translations(f, 'sat')
+                with open(po_path, "rb") as f:
+                    self.translations[Locale.parse(lang_dir)] = support.Translations(
+                        f, "sat"
+                    )
             except EnvironmentError:
-                log.error(_(u"Can't find template translation at {path}").format(path = po_path))
+                log.error(
+                    _(u"Can't find template translation at {path}").format(path=po_path)
+                )
             except UnknownLocaleError as e:
                 log.error(_(u"Invalid locale name: {msg}").format(msg=e))
             else:
-                log.info(_(u'loaded {lang} templates translations').format(lang=lang_dir))
+                log.info(_(u"loaded {lang} templates translations").format(lang=lang_dir))
         self.env.install_null_translations(True)
 
     def setLocale(self, locale_str):
@@ -248,10 +260,10 @@
         """
         if locale_str == self._locale_str:
             return
-        if locale_str == 'en':
+        if locale_str == "en":
             # we default to GB English when it's not specified
             # one of the main reason is to avoid the nonsense U.S. short date format
-            locale_str = 'en_GB'
+            locale_str = "en_GB"
         try:
             locale = Locale.parse(locale_str)
         except ValueError as e:
@@ -269,7 +281,7 @@
                 locale = Locale.parse(self._locale_str)
             else:
                 self.env.install_gettext_translations(translations, True)
-                log.debug(_(u'Switched to {lang}').format(lang=locale.english_name))
+                log.debug(_(u"Switched to {lang}").format(lang=locale.english_name))
 
         if locale_str == C.DEFAULT_LOCALE:
             self.env.install_null_translations(True)
@@ -286,7 +298,7 @@
         theme, dummy = self.env.loader.parse_template(template)
         return theme, os.path.join(self.base_dir, theme)
 
-    def getStaticPath(self, name, template_root_dir, theme, is_default, ext='.css'):
+    def getStaticPath(self, name, template_root_dir, theme, is_default, ext=".css"):
         """retrieve path of a static file if it exists with current theme or default
 
         File will be looked at [theme]/static/[name][ext], and then default
@@ -302,7 +314,9 @@
         if os.path.exists(os.path.join(template_root_dir, path)):
             file_ = path
         elif not is_default:
-            path = os.path.join(C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext)
+            path = os.path.join(
+                C.TEMPLATE_THEME_DEFAULT, C.TEMPLATE_STATIC_DIR, name + ext
+            )
             if os.path.exists(os.path.join(template_root_dir, path)):
                 file_.append(path)
         return file_
@@ -315,7 +329,7 @@
             theme: theme of the page
             is_default: True if the theme is the default theme
         """
-        path_elems = [os.path.splitext(p)[0] for p in template_path.split(u'/')]
+        path_elems = [os.path.splitext(p)[0] for p in template_path.split(u"/")]
         theme = path_elems.pop(0)
         is_default = theme == C.TEMPLATE_THEME_DEFAULT
         return (path_elems, theme, is_default)
@@ -336,39 +350,44 @@
         # TODO: some caching would be nice
         css_files = []
         path_elems, theme, is_default = self.getThemeData(template_path)
-        for css in (u'fonts', u'styles'):
+        for css in (u"fonts", u"styles"):
             css_path = self.getStaticPath(css, template_root_dir, theme, is_default)
             if css_path is not None:
                 css_files.append(css_path)
 
         for idx, path in enumerate(path_elems):
-            css_path = self.getStaticPath(u'_'.join(path_elems[:idx+1]), template_root_dir, theme, is_default)
+            css_path = self.getStaticPath(
+                u"_".join(path_elems[: idx + 1]), template_root_dir, theme, is_default
+            )
             if css_path is not None:
                 css_files.append(css_path)
 
         return css_files
 
-
     ## custom filters ##
 
     @jinja2.contextfilter
     def _next_gidx(self, ctx, value):
         """Use next current global index as suffix"""
-        next_ = ctx['gidx'].next(value)
+        next_ = ctx["gidx"].next(value)
         return value if next_ == 0 else u"{}_{}".format(value, next_)
 
     @jinja2.contextfilter
     def _cur_gidx(self, ctx, value):
         """Use current current global index as suffix"""
-        current = ctx['gidx'].current(value)
+        current = ctx["gidx"].current(value)
         return value if not current else u"{}_{}".format(value, current)
 
-    def _date_fmt(self, timestamp, fmt='short', date_only=False, auto_limit=None, auto_old_fmt=None):
+    def _date_fmt(
+        self, timestamp, fmt="short", date_only=False, auto_limit=None, auto_old_fmt=None
+    ):
         if is_undefined(fmt):
-            fmt = u'short'
+            fmt = u"short"
 
         try:
-            return date_utils.date_fmt(timestamp, fmt, date_only, auto_limit, auto_old_fmt)
+            return date_utils.date_fmt(
+                timestamp, fmt, date_only, auto_limit, auto_old_fmt
+            )
         except Exception as e:
             log.warning(_(u"Can't parse date: {msg}").format(msg=e))
             return timestamp
@@ -378,7 +397,7 @@
 
         remove spaces, and put in lower case
         """
-        return RE_ATTR_ESCAPE.sub(u'_', text.strip().lower())[:50]
+        return RE_ATTR_ESCAPE.sub(u"_", text.strip().lower())[:50]
 
     def _xmlui_class(self, xmlui_item, fields):
         """return classes computed from XMLUI fields name
@@ -394,11 +413,13 @@
             escaped_name = self.attr_escape(name)
             try:
                 for value in xmlui_item.widgets[name].values:
-                    classes.append(escaped_name + '_' + self.attr_escape(value))
+                    classes.append(escaped_name + "_" + self.attr_escape(value))
             except KeyError:
-                log.debug(_(u"ignoring field \"{name}\": it doesn't exists").format(name=name))
+                log.debug(
+                    _(u'ignoring field "{name}": it doesn\'t exists').format(name=name)
+                )
                 continue
-        return u' '.join(classes) or None
+        return u" ".join(classes) or None
 
     @jinja2.contextfilter
     def _item_filter(self, ctx, item, filters):
@@ -420,8 +441,8 @@
         if filter_ is None:
             return value
         elif isinstance(filter_, dict):
-            filters_args = filter_.get(u'filters_args')
-            for idx, f_name in enumerate(filter_.get(u'filters', [])):
+            filters_args = filter_.get(u"filters_args")
+            for idx, f_name in enumerate(filter_.get(u"filters", [])):
                 kwargs = filters_args[idx] if filters_args is not None else {}
                 filter_func = self.env.filters[f_name]
                 try:
@@ -433,7 +454,7 @@
                     value = filter_func(ctx.eval_ctx, value, **kwargs)
                 else:
                     value = filter_func(value, **kwargs)
-            template = filter_.get(u'template')
+            template = filter_.get(u"template")
             if template:
                 # format will return a string, so we need to check first
                 # if the value is safe or not, and re-mark it after formatting
@@ -455,7 +476,7 @@
         """
         if template is None:
             return value
-        # jinja use string when no special char is used, so we have to convert to unicode
+        #  jinja use string when no special char is used, so we have to convert to unicode
         return unicode(template).format(value=value, **kwargs)
 
     def _dict_ext(self, source_dict, extra_dict, key=None):
@@ -511,29 +532,45 @@
 
     def _icon_defs(self, *names):
         """Define svg icons which will be used in the template, and use their name as id"""
-        svg_elt = etree.Element('svg', nsmap={None: 'http://www.w3.org/2000/svg'},
-                                width='0', height='0', style='display: block'
-                                )
-        defs_elt = etree.SubElement(svg_elt, 'defs')
+        svg_elt = etree.Element(
+            "svg",
+            nsmap={None: "http://www.w3.org/2000/svg"},
+            width="0",
+            height="0",
+            style="display: block",
+        )
+        defs_elt = etree.SubElement(svg_elt, "defs")
         for name in names:
-            path = os.path.join(self.icons_path, name + u'.svg')
+            path = os.path.join(self.icons_path, name + u".svg")
             icon_svg_elt = etree.parse(path).getroot()
             # we use icon name as id, so we can retrieve them easily
-            icon_svg_elt.set('id', name)
-            if not icon_svg_elt.tag == '{http://www.w3.org/2000/svg}svg':
-                raise exceptions.DataError(u'invalid SVG element')
+            icon_svg_elt.set("id", name)
+            if not icon_svg_elt.tag == "{http://www.w3.org/2000/svg}svg":
+                raise exceptions.DataError(u"invalid SVG element")
             defs_elt.append(icon_svg_elt)
-        return safe(etree.tostring(svg_elt, encoding='unicode'))
+        return safe(etree.tostring(svg_elt, encoding="unicode"))
 
-    def _icon_use(self, name, cls=''):
-        return safe(u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
+    def _icon_use(self, name, cls=""):
+        return safe(
+            u"""<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100">
           <use href="#{name}"/>
         </svg>
         """.format(
-            name=name,
-            cls=(' ' + cls) if cls else ''))
+                name=name, cls=(" " + cls) if cls else ""
+            )
+        )
 
-    def render(self, template, theme=None, locale=C.DEFAULT_LOCALE, root_path=u'', media_path=u'', css_files=None, css_inline=False, **kwargs):
+    def render(
+        self,
+        template,
+        theme=None,
+        locale=C.DEFAULT_LOCALE,
+        root_path=u"",
+        media_path=u"",
+        css_files=None,
+        css_inline=False,
+        **kwargs
+    ):
         """render a template
 .
         @param template(unicode): template to render (e.g. blog/articles.html)
@@ -553,24 +590,28 @@
             raise ValueError(u"template can't be empty")
         if theme is not None:
             # use want to set a theme, we add it to the template path
-            if template[0] == u'(':
-                raise ValueError(u"you can't specify theme in template path and in argument at the same time")
-            elif template[0] == u'/':
+            if template[0] == u"(":
+                raise ValueError(
+                    u"you can't specify theme in template path and in argument at the same time"
+                )
+            elif template[0] == u"/":
                 raise ValueError(u"you can't specify theme with absolute paths")
-            template= u'(' + theme + u')' + template
+            template = u"(" + theme + u")" + template
         else:
             theme, dummy = self.env.loader.parse_template(template)
 
         template_source = self.env.get_template(template)
-        template_root_dir = os.path.normpath(self.base_dir)  # FIXME: should be modified if we handle use extra dirs
-        # XXX: template_path may have a different theme as first element than theme if a default page is used
-        template_path = template_source.filename[len(template_root_dir)+1:]
+        template_root_dir = os.path.normpath(
+            self.base_dir
+        )  # FIXME: should be modified if we handle use extra dirs
+        #  XXX: template_path may have a different theme as first element than theme if a default page is used
+        template_path = template_source.filename[len(template_root_dir) + 1 :]
 
         if css_files is None:
             css_files = self.getCSSFiles(template_path, template_root_dir)
 
-        kwargs['icon_defs'] = self._icon_defs
-        kwargs['icon'] = self._icon_use
+        kwargs["icon_defs"] = self._icon_defs
+        kwargs["icon"] = self._icon_use
 
         if css_inline:
             css_contents = []
@@ -579,13 +620,21 @@
                 with open(css_file_path) as f:
                     css_contents.append(f.read())
             if css_contents:
-                kwargs['css_content'] = '\n'.join(css_contents)
+                kwargs["css_content"] = "\n".join(css_contents)
 
-        scripts_handler = ScriptsHandler(self, template_path, template_root_dir, root_path)
+        scripts_handler = ScriptsHandler(
+            self, template_path, template_root_dir, root_path
+        )
         self.setLocale(locale)
         # XXX: theme used in template arguments is the requested theme, which may differ from actual theme
         #      if the template doesn't exist in the requested theme.
-        return template_source.render(theme=theme, root_path=root_path, media_path=media_path,
-                                      css_files=css_files, locale=self._locale,
-                                      gidx=Indexer(), script=scripts_handler,
-                                      **kwargs)
+        return template_source.render(
+            theme=theme,
+            root_path=root_path,
+            media_path=media_path,
+            css_files=css_files,
+            locale=self._locale,
+            gidx=Indexer(),
+            script=scripts_handler,
+            **kwargs
+        )