Mercurial > libervia-backend
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 + )