tools (common/template), jp: refactoring to handle multiple sites:
- site can now be specified in template header before theme, for instance: (some_site/some_theme)path/to/template.ext - absolute template paths are now implemented, but Renderer must be instanciated with trusted to True for security reason (it's the case for jp) - a new "front_url_filter" callable can be given to Renderer, which will convert template path to URL seen by end-user (default to real path). - the "front_url_filter" can be used in templates with… "front_url" filter - template_data is a new named tuple available in templates, which give site, theme and template relative URL - search order is site/theme, site/default_theme, and default/default_theme where default link to sat_pubsub templates - when loading CSS files, files with _noscript suffixes are now loaded, and used when javascript is not available - "styles_extra.css" is also loaded before "styles.css", useful when a theme want to reuse default style, and just override some rules - new site can be specified in sat.conf [DEFAULT] section, using sites_path_public_dict or sites_path_private_dict (where sites_path_private_dict won't be used in public frontends, like Libervia) - "private" argument of Renderer tells the renderer to load private sites or not - templates are now loaded from "templates" subdirectory, to differenciate them from other data like i18n - jp template output has been updated to handle those changes, and to manage absolute templates
This commit is contained in:
parent
1a770a48ee
commit
e70405cfc2
6 changed files with 507 additions and 249 deletions
|
|
@ -225,6 +225,7 @@ class Const(object):
|
|||
]
|
||||
|
||||
## Templates ##
|
||||
TEMPLATE_TPL_DIR = u"templates"
|
||||
TEMPLATE_THEME_DEFAULT = u"default"
|
||||
TEMPLATE_STATIC_DIR = u"static"
|
||||
|
||||
|
|
|
|||
|
|
@ -158,13 +158,16 @@ class FilterName(object):
|
|||
|
||||
class ConfigureBase(object):
|
||||
LOGGER_CLASS = Logger
|
||||
_color_location = False # True if color location is specified in fmt (with COLOR_START)
|
||||
# True if color location is specified in fmt (with COLOR_START)
|
||||
_color_location = False
|
||||
|
||||
def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False, levels_taints_dict=None, force_colors=False, backend_data=None):
|
||||
def __init__(self, level=None, fmt=None, output=None, logger=None, colors=False,
|
||||
levels_taints_dict=None, force_colors=False, backend_data=None):
|
||||
"""Configure a backend
|
||||
|
||||
@param level: one of C.LOG_LEVELS
|
||||
@param fmt: format string, pretty much as in std logging. Accept the following keywords (maybe more depending on backend):
|
||||
@param fmt: format string, pretty much as in std logging.
|
||||
Accept the following keywords (maybe more depending on backend):
|
||||
- "message"
|
||||
- "levelname"
|
||||
- "name" (logger name)
|
||||
|
|
|
|||
|
|
@ -17,16 +17,16 @@
|
|||
# You should have received a copy of the GNU Affero General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
""" template generation """
|
||||
"""Template generation"""
|
||||
|
||||
import os.path
|
||||
from collections import namedtuple
|
||||
from sat.core.constants import Const as C
|
||||
from sat.core.i18n import _
|
||||
from sat.core import exceptions
|
||||
from sat.tools import config
|
||||
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
|
||||
import time
|
||||
import re
|
||||
|
|
@ -41,7 +41,8 @@ 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"
|
||||
u"sat_templates module is not available, please install it or check your path to "
|
||||
u"use template engine"
|
||||
)
|
||||
else:
|
||||
sat_templates # to avoid pyflakes warning
|
||||
|
|
@ -50,90 +51,206 @@ try:
|
|||
import jinja2
|
||||
except:
|
||||
raise exceptions.MissingModule(
|
||||
u"Missing module jinja2, please install it from http://jinja.pocoo.org or with pip install jinja2"
|
||||
u"Missing module jinja2, please install it from http://jinja.pocoo.org or with "
|
||||
u"pip install jinja2"
|
||||
)
|
||||
|
||||
from jinja2 import Markup as safe
|
||||
from jinja2 import is_undefined
|
||||
from jinja2 import utils
|
||||
from jinja2 import TemplateNotFound
|
||||
from jinja2.loaders import split_template_path
|
||||
from lxml import etree
|
||||
|
||||
log = getLogger(__name__)
|
||||
|
||||
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
|
||||
SITE_RESERVED_NAMES = (u"sat",)
|
||||
TPL_RESERVED_CHARS = ur"()/."
|
||||
RE_TPL_RESERVED_CHARS = re.compile(u"[" + TPL_RESERVED_CHARS + u"]")
|
||||
|
||||
TemplateData = namedtuple("TemplateData", ['site', 'theme', 'path'])
|
||||
|
||||
|
||||
class TemplateLoader(jinja2.FileSystemLoader):
|
||||
def __init__(self):
|
||||
searchpath = os.path.dirname(sat_templates.__file__)
|
||||
super(TemplateLoader, self).__init__(searchpath, followlinks=True)
|
||||
class TemplateLoader(jinja2.BaseLoader):
|
||||
"""A template loader which handle site, theme and absolute paths"""
|
||||
# TODO: list_templates should be implemented
|
||||
|
||||
def parse_template(self, template):
|
||||
"""parse template path and return theme and relative URL
|
||||
def __init__(self, sites_paths, 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,
|
||||
as this allow the template to open any file on the system that the
|
||||
launching user can access.
|
||||
"""
|
||||
if not sites_paths or not u"" in sites_paths:
|
||||
raise exceptions.InternalError(u"Invalid sites_paths")
|
||||
super(jinja2.BaseLoader, self).__init__()
|
||||
self.sites_paths = sites_paths
|
||||
self.trusted = trusted
|
||||
|
||||
@staticmethod
|
||||
def parse_template(template):
|
||||
"""Parse template path and return site, theme and path
|
||||
|
||||
@param template_path(unicode): path to template with parenthesis syntax
|
||||
@return (tuple[(unicode,None),unicode]): theme and template_path
|
||||
theme can be None if relative path is used
|
||||
relative path is the path from search path with theme specified
|
||||
e.g. default/blog/articles.html
|
||||
The site and/or theme can be specified in parenthesis just before the path
|
||||
e.g.: (some_theme)path/to/template.html
|
||||
(/some_theme)path/to/template.html (equivalent to previous one)
|
||||
(other_site/other_theme)path/to/template.html
|
||||
(other_site/)path/to/template.html (defaut theme for other_site)
|
||||
/absolute/path/to/template.html (in trusted environment only)
|
||||
@return (TemplateData):
|
||||
site, theme and template_path.
|
||||
if site is empty, SàT Templates are used
|
||||
site and theme can be both None if absolute path is used
|
||||
Relative path is the path from theme root dir e.g. blog/articles.html
|
||||
"""
|
||||
if template.startswith(u"("):
|
||||
# site and/or theme are specified
|
||||
try:
|
||||
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"/"):
|
||||
raise ValueError(u"incorrect path after template name")
|
||||
template = os.path.join(theme, template)
|
||||
raise ValueError(u"incorrect site/theme in template")
|
||||
theme_data = template[1:theme_end]
|
||||
theme_splitted = theme_data.split(u'/')
|
||||
if len(theme_splitted) == 1:
|
||||
site, theme = u"", theme_splitted
|
||||
elif len(theme_splitted) == 2:
|
||||
site, theme = theme_splitted
|
||||
else:
|
||||
raise ValueError(u"incorrect site/theme in template")
|
||||
template_path = template[theme_end+1:]
|
||||
if not template_path or template_path.startswith(u"/"):
|
||||
raise ValueError(u"incorrect template path")
|
||||
elif template.startswith(u"/"):
|
||||
# absolute path means no template
|
||||
# this is an absolute path, so we have no site and no theme
|
||||
site = None
|
||||
theme = None
|
||||
raise NotImplementedError(u"absolute path is not implemented yet")
|
||||
template_path = template
|
||||
else:
|
||||
# a default template
|
||||
site = u""
|
||||
theme = C.TEMPLATE_THEME_DEFAULT
|
||||
template = os.path.join(theme, template)
|
||||
return theme, template
|
||||
template_path = template
|
||||
|
||||
def get_default_template(self, theme, template_path):
|
||||
"""return default template path
|
||||
if site is not None:
|
||||
site = site.strip()
|
||||
if not site:
|
||||
site = u""
|
||||
elif site in SITE_RESERVED_NAMES:
|
||||
raise ValueError(_(u"{site} can't be used as site name, "
|
||||
u"it's reserved.").format(site=site))
|
||||
|
||||
@param theme(unicode): theme used
|
||||
@param template_path(unicode): path to the not found template
|
||||
@return (unicode, None): default path or None if there is not
|
||||
if theme is not None:
|
||||
theme = theme.strip()
|
||||
if not theme:
|
||||
theme = C.TEMPLATE_THEME_DEFAULT
|
||||
if RE_TPL_RESERVED_CHARS.search(theme):
|
||||
raise ValueError(_(u"{theme} contain forbidden char. Following chars "
|
||||
u"are forbidden: {reserved}").format(
|
||||
theme=theme, reserved=TPL_RESERVED_CHARS))
|
||||
|
||||
return TemplateData(site, theme, template_path)
|
||||
|
||||
@staticmethod
|
||||
def getSitesAndThemes(site, theme):
|
||||
"""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
|
||||
"""
|
||||
ext = os.path.splitext(template_path)[1][1:]
|
||||
path_elems = template_path.split(u"/")
|
||||
if ext in HTML_EXT:
|
||||
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")
|
||||
return default_path
|
||||
sites_and_themes = [[site, theme]]
|
||||
if theme != C.TEMPLATE_THEME_DEFAULT:
|
||||
# if template doesn't exists for this theme, we try with default
|
||||
return os.path.join(C.TEMPLATE_THEME_DEFAULT, path_elems[1:])
|
||||
sites_and_themes.append([site, C.TEMPLATE_THEME_DEFAULT])
|
||||
if site:
|
||||
# the site is not the default one, so we add default at the end
|
||||
sites_and_themes.append([u'', C.TEMPLATE_THEME_DEFAULT])
|
||||
return sites_and_themes
|
||||
|
||||
def _get_template_f(self, site, theme, path_elts):
|
||||
"""Look for template and return opened file if found
|
||||
|
||||
@param site(unicode): names of site to check
|
||||
(default site will also checked)
|
||||
@param theme(unicode): theme to check (default theme will also be checked)
|
||||
@param path_elts(iterable[str]): elements of template path
|
||||
@return (tuple[(File, None), (str, None)]): a tuple with:
|
||||
- opened template, or None if not found
|
||||
- absolute file path, or None if not found
|
||||
"""
|
||||
if site is None:
|
||||
raise exceptions.InternalError(
|
||||
u"_get_template_f must not be used with absolute path")
|
||||
for site, theme in self.getSitesAndThemes(site, theme):
|
||||
try:
|
||||
base_path = self.sites_paths[site]
|
||||
except KeyError:
|
||||
log.warning(_(u"Unregistered site requested: {site}").format(
|
||||
site=site))
|
||||
filepath = os.path.join(base_path, C.TEMPLATE_TPL_DIR, theme, *path_elts)
|
||||
f = utils.open_if_exists(filepath)
|
||||
if f is not None:
|
||||
return f, filepath
|
||||
return None, None
|
||||
|
||||
def get_source(self, environment, template):
|
||||
"""relative path to template dir, with special theme handling
|
||||
"""Retrieve source handling site and themes
|
||||
|
||||
If the path is absolute it is used directly if in trusted environment
|
||||
else and exception is raised.
|
||||
if the path is just relative, "default" theme is used.
|
||||
The theme can be specified in parenthesis just before the path
|
||||
e.g.: (some_theme)path/to/template.html
|
||||
@raise PermissionError: absolute path used in untrusted environment
|
||||
"""
|
||||
theme, template_path = self.parse_template(template)
|
||||
try:
|
||||
return super(TemplateLoader, self).get_source(environment, template_path)
|
||||
except jinja2.exceptions.TemplateNotFound as e:
|
||||
# in some special cases, a defaut template is returned if nothing is found
|
||||
site, theme, template_path = self.parse_template(template)
|
||||
|
||||
if site is None:
|
||||
# we have an abolute template
|
||||
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
|
||||
)
|
||||
# if no default template is found, we re-raise the error
|
||||
raise e
|
||||
raise exceptions.InternalError(u"We can't have a theme with absolute "
|
||||
u"template.")
|
||||
if not self.trusted:
|
||||
log.error(_(u"Absolute template used while unsecure is disabled, hack "
|
||||
u"attempt? Template: {template}").format(template=template))
|
||||
raise exceptions.PermissionError(u"absolute template is not allowed")
|
||||
filepath = template_path
|
||||
f = utils.open_if_exists(filepath)
|
||||
else:
|
||||
# relative path, we have to deal with site and theme
|
||||
assert theme and template_path
|
||||
path_elts = split_template_path(template_path)
|
||||
# if we have non default site, we check it first, else we only check default
|
||||
f, filepath = self._get_template_f(site, theme, path_elts)
|
||||
|
||||
if f is None:
|
||||
if (site is not None and path_elts[0] == u"error"
|
||||
and os.path.splitext(template_path)[1][1:] in HTML_EXT):
|
||||
# if an HTML error is requested but doesn't exist, we try again
|
||||
# with base error.
|
||||
f, filepath = self._get_template_f(
|
||||
site, theme, ("error", "base.html"))
|
||||
if f is None:
|
||||
raise exceptions.InternalError(u"error/base.html should exist")
|
||||
else:
|
||||
raise TemplateNotFound(template)
|
||||
|
||||
try:
|
||||
contents = f.read().decode('utf-8')
|
||||
finally:
|
||||
f.close()
|
||||
|
||||
mtime = os.path.getmtime(filepath)
|
||||
|
||||
def uptodate():
|
||||
try:
|
||||
return os.path.getmtime(filepath) == mtime
|
||||
except OSError:
|
||||
return False
|
||||
|
||||
return contents, filepath, uptodate
|
||||
|
||||
|
||||
class Indexer(object):
|
||||
|
|
@ -154,12 +271,10 @@ class Indexer(object):
|
|||
|
||||
|
||||
class ScriptsHandler(object):
|
||||
def __init__(self, renderer, template_path, template_root_dir, root_path):
|
||||
def __init__(self, renderer, template_data):
|
||||
self.renderer = renderer
|
||||
self.template_root_dir = template_root_dir
|
||||
self.root_path = root_path
|
||||
self.template_data = template_data
|
||||
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"):
|
||||
"""Mark that a script need to be imported.
|
||||
|
|
@ -169,13 +284,13 @@ class ScriptsHandler(object):
|
|||
@param library_name(unicode): name of the library to import
|
||||
@param loading:
|
||||
"""
|
||||
if attribute not in ("defer", "async", ""):
|
||||
if attribute not in (u"defer", u"async", u""):
|
||||
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:
|
||||
if not library_name.endswith(u".js"):
|
||||
library_name = library_name + u".js"
|
||||
if (library_name, attribute) not in self.scripts:
|
||||
self.scripts.append((library_name, attribute))
|
||||
return u""
|
||||
|
||||
|
|
@ -187,25 +302,61 @@ class ScriptsHandler(object):
|
|||
scripts = []
|
||||
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"
|
||||
)
|
||||
if path is None:
|
||||
log.warning(_(u"Can't find {}.js javascript library").format(library))
|
||||
library_path = self.renderer.getStaticPath(self.template_data, library)
|
||||
if library_path is None:
|
||||
log.warning(_(u"Can't find {libary} javascript library").format(
|
||||
library=library))
|
||||
continue
|
||||
path = os.path.join(self.root_path, path)
|
||||
path = self.renderer.getFrontURL(library_path)
|
||||
scripts.append(tpl.format(src=quoteattr(path), attribute=attribute))
|
||||
return safe(u"\n".join(scripts))
|
||||
|
||||
|
||||
class Renderer(object):
|
||||
def __init__(self, host):
|
||||
|
||||
def __init__(self, host, front_url_filter=None, trusted=False, private=False):
|
||||
"""
|
||||
@param front_url_filter(callable): filter to retrieve real url of a directory/file
|
||||
The callable will get a two arguments:
|
||||
- a dict with a "template_data" key containing TemplateData instance of
|
||||
current template. Only site and theme should be necessary.
|
||||
- the relative URL of the file to retrieve, relative from theme root
|
||||
None to use default filter which return real path on file
|
||||
Need to be specified for web rendering, to reflect URL seen by end user
|
||||
@param trusted(bool): if True, allow to access absolute path
|
||||
Only set to True if environment is safe (e.g. command line tool)
|
||||
@param private(bool): if True, also load sites from sites_path_private_dict
|
||||
"""
|
||||
# TODO:
|
||||
self.host = host
|
||||
self.base_dir = os.path.dirname(
|
||||
sat_templates.__file__
|
||||
) # FIXME: should be modified if we handle use extra dirs
|
||||
self.trusted = trusted
|
||||
self.sites_paths = {
|
||||
u"": os.path.dirname(sat_templates.__file__),
|
||||
}
|
||||
conf = config.parseMainConf()
|
||||
public_sites = config.getConfig(conf, None, u"sites_path_public_dict", {})
|
||||
sites_data = [public_sites]
|
||||
if private:
|
||||
private_sites = config.getConfig(conf, None, u"sites_path_private_dict", {})
|
||||
sites_data.append(private_sites)
|
||||
for sites in sites_data:
|
||||
normalised = {}
|
||||
for name, path in sites.iteritems():
|
||||
if RE_TPL_RESERVED_CHARS.search(name):
|
||||
log.warning(_(u"Can't add \"{name}\" site, it contains forbidden "
|
||||
u"characters. Forbidden characters are {forbidden}.")
|
||||
.format(name=name, forbidden=TPL_RESERVED_CHARS))
|
||||
continue
|
||||
path = os.path.expanduser(os.path.normpath(path))
|
||||
if not path or not path.startswith(u"/"):
|
||||
log.warning(_(u"Can't add \"{name}\" site, it should map to an "
|
||||
u"absolute path").format(name=name))
|
||||
continue
|
||||
normalised[name] = path
|
||||
self.sites_paths.update(normalised)
|
||||
|
||||
self.env = jinja2.Environment(
|
||||
loader=TemplateLoader(),
|
||||
loader=TemplateLoader(sites_paths=self.sites_paths, trusted=trusted),
|
||||
autoescape=jinja2.select_autoescape(["html", "xhtml", "xml"]),
|
||||
trim_blocks=True,
|
||||
lstrip_blocks=True,
|
||||
|
|
@ -217,41 +368,64 @@ class Renderer(object):
|
|||
# we want to have access to SàT constants in templates
|
||||
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[u"next_gidx"] = self._next_gidx
|
||||
self.env.filters[u"cur_gidx"] = self._cur_gidx
|
||||
self.env.filters[u"date_fmt"] = self._date_fmt
|
||||
self.env.filters[u"xmlui_class"] = self._xmlui_class
|
||||
self.env.filters[u"attr_escape"] = self.attr_escape
|
||||
self.env.filters[u"item_filter"] = self._item_filter
|
||||
self.env.filters[u"adv_format"] = self._adv_format
|
||||
self.env.filters[u"dict_ext"] = self._dict_ext
|
||||
self.env.filters[u"highlight"] = self.highlight
|
||||
self.env.filters[u"front_url"] = (self._front_url if front_url_filter is None
|
||||
else front_url_filter)
|
||||
# custom tests
|
||||
self.env.tests["in_the_past"] = self._in_the_past
|
||||
self.env.tests[u"in_the_past"] = self._in_the_past
|
||||
self.icons_path = os.path.join(host.media_dir, u"fonts/fontello/svg")
|
||||
|
||||
def getFrontURL(self, template_data, path=None):
|
||||
"""Give front URL (i.e. URL seen by end-user) of a path
|
||||
|
||||
@param template_data[TemplateData]: data of current template
|
||||
@param path(unicode, None): relative path of file to get,
|
||||
if set, will remplate template_data.path
|
||||
"""
|
||||
return self.env.filters[u"front_url"]({u"template_data": template_data},
|
||||
path or template_data.path)
|
||||
|
||||
def installTranslations(self):
|
||||
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")
|
||||
try:
|
||||
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)
|
||||
)
|
||||
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))
|
||||
self.env.install_null_translations(True)
|
||||
# TODO: support multi translation
|
||||
# for now, only translations in sat_templates are handled
|
||||
for site_key, site_path in self.sites_paths.iteritems():
|
||||
site_prefix = u"[{}] ".format(site_key) if site_key else u''
|
||||
i18n_dir = os.path.join(site_path, "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")
|
||||
try:
|
||||
locale = Locale.parse(lang_dir)
|
||||
with open(po_path, "rb") as f:
|
||||
try:
|
||||
translations = self.translations[locale]
|
||||
except KeyError:
|
||||
self.translations[locale] = support.Translations(f, "sat")
|
||||
else:
|
||||
translations.merge(support.Translations(f, "sat"))
|
||||
except EnvironmentError:
|
||||
log.error(
|
||||
_(u"Can't find template translation at {path}").format(
|
||||
path=po_path))
|
||||
except UnknownLocaleError as e:
|
||||
log.error(_(u"{site}Invalid locale name: {msg}").format(
|
||||
site=site_prefix, msg=e))
|
||||
else:
|
||||
log.info(_(u"{site}loaded {lang} templates translations").format(
|
||||
site = site_prefix,
|
||||
lang=lang_dir))
|
||||
self.env.install_null_translations(True)
|
||||
|
||||
def setLocale(self, locale_str):
|
||||
"""set current locale
|
||||
|
|
@ -294,78 +468,136 @@ class Renderer(object):
|
|||
|
||||
@param template(unicode): template to parse
|
||||
@return (tuple[unicode, unicode]): theme and absolute path to theme's root dir
|
||||
@raise NotFound: requested site has not been found
|
||||
"""
|
||||
theme, dummy = self.env.loader.parse_template(template)
|
||||
return theme, os.path.join(self.base_dir, theme)
|
||||
# FIXME: check use in jp, and include site
|
||||
site, theme, __ = self.env.loader.parse_template(template)
|
||||
if site is None:
|
||||
# absolute template
|
||||
return u"", os.path.dirname(template)
|
||||
try:
|
||||
site_root_dir = self.sites_paths[site]
|
||||
except KeyError:
|
||||
raise exceptions.NotFound
|
||||
return theme, os.path.join(site_root_dir, C.TEMPLATE_TPL_DIR, theme)
|
||||
|
||||
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
|
||||
def getStaticPath(self, template_data, filename):
|
||||
"""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
|
||||
if not found.
|
||||
@param name(unicode): name of the file to look for
|
||||
@param template_root_dir(unicode): absolute path to template root used
|
||||
@param theme(unicode): name of the template theme used
|
||||
@param is_default(bool): True if theme is the default theme
|
||||
@return (unicode, None): relative path if found, else None
|
||||
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
|
||||
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
|
||||
the relative path to the file, from theme root dir.
|
||||
None if not found.
|
||||
"""
|
||||
file_ = None
|
||||
path = os.path.join(theme, C.TEMPLATE_STATIC_DIR, name + ext)
|
||||
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
|
||||
)
|
||||
if os.path.exists(os.path.join(template_root_dir, path)):
|
||||
file_.append(path)
|
||||
return file_
|
||||
if template_data.site is None:
|
||||
# we have and absolue path
|
||||
if (not template_data.theme is None
|
||||
or not template_data.path.startswith(u'/')):
|
||||
raise exceptions.InternalError(
|
||||
u"invalid template data, was expecting absolute URL")
|
||||
static_dir = os.path.dirname(template_data.path)
|
||||
file_path = os.path.join(static_dir, filename)
|
||||
if os.path.exists(file_path):
|
||||
return TemplateData(site=None, theme=None, path=file_path)
|
||||
else:
|
||||
return None
|
||||
|
||||
def getThemeData(self, template_path):
|
||||
"""return template data got from template_path
|
||||
sites_and_themes = TemplateLoader.getSitesAndThemes(template_data.site,
|
||||
template_data.theme)
|
||||
for site, theme in sites_and_themes:
|
||||
site_root_dir = self.sites_paths[site]
|
||||
relative_path = os.path.join(C.TEMPLATE_STATIC_DIR, filename)
|
||||
absolute_path = os.path.join(site_root_dir, C.TEMPLATE_TPL_DIR,
|
||||
theme, relative_path)
|
||||
if os.path.exists(absolute_path):
|
||||
return TemplateData(site=site, theme=theme, path=relative_path)
|
||||
|
||||
@return tuple(unicode, unicode, bool):
|
||||
path_elems: elements of the path
|
||||
theme: theme of the page
|
||||
is_default: True if the theme is the default theme
|
||||
return None
|
||||
|
||||
def _appendCSSPaths(self, template_data, css_files, css_files_noscript, name_root):
|
||||
"""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
|
||||
with "_noscript" suffix
|
||||
"""
|
||||
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)
|
||||
name = name_root + u".css"
|
||||
css_path = self.getStaticPath(template_data, name)
|
||||
if css_path is not None:
|
||||
css_files.append(self.getFrontURL(css_path))
|
||||
noscript_name = name_root + u"_noscript.css"
|
||||
noscript_path = self.getStaticPath(template_data, noscript_name)
|
||||
if noscript_path is not None:
|
||||
css_files_noscript.append(self.getFrontURL(noscript_path))
|
||||
|
||||
def getCSSFiles(self, template_path, template_root_dir):
|
||||
"""retrieve CSS files to use according to theme and template path
|
||||
def getCSSFiles(self, template_data):
|
||||
"""Retrieve CSS files to use according template_data
|
||||
|
||||
for each element of the path, a .css file is looked for in /static, and returned if it exists.
|
||||
previous element are kept by replacing '/' with '_', and styles.css is always returned.
|
||||
For instance, if template_path is some_theme/blog/articles.html:
|
||||
some_theme/static/styles.css is returned if it exists else default/static/styles.css
|
||||
some_theme/static/blog.css is returned if it exists else default/static/blog.css (if it exists too)
|
||||
some_theme/static/blog_articles.css is returned if it exists else default/static/blog_articles.css (if it exists too)
|
||||
@param template_path(unicode): relative path to template file (e.g. some_theme/blog/articles.html)
|
||||
@param template_root_dir(unicode): absolute path of the theme root dir used
|
||||
@return list[unicode]: relative path to CSS files to use
|
||||
For each element of the path, a .css file is looked for in /static, and returned
|
||||
if it exists.
|
||||
Previous element are kept by replacing '/' with '_'.
|
||||
styles_extra.css, styles.css and fonts.css are always used if they exist.
|
||||
For each found file, if a file with the same name and "_noscript" suffix exists,
|
||||
it will be return is second part of resulting tuple.
|
||||
For instance, if template_data is (some_site, some_theme, blog/articles.html),
|
||||
following files are returned, earch time trying [some_site root] first,
|
||||
then default site (i.e. sat_templates) root:
|
||||
- some_theme/static/styles.css is returned if it exists
|
||||
else default/static/styles.css
|
||||
- some_theme/static/blog.css is returned if it exists
|
||||
else default/static/blog.css (if it exists too)
|
||||
- some_theme/static/blog_articles.css is returned if it exists
|
||||
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)).
|
||||
@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
|
||||
- front URLs of CSS files to use when scripts are not enabled
|
||||
"""
|
||||
# TODO: some caching would be nice
|
||||
css_files = []
|
||||
path_elems, theme, is_default = self.getThemeData(template_path)
|
||||
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)
|
||||
css_files_noscript = []
|
||||
path_elems = template_data.path.split(u'/')
|
||||
path_elems[-1] = os.path.splitext(path_elems[-1])[0]
|
||||
|
||||
for idx, path in enumerate(path_elems):
|
||||
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)
|
||||
for name_root in (u'styles_extra', u'styles'):
|
||||
self._appendCSSPaths(template_data, css_files, css_files_noscript, name_root)
|
||||
|
||||
return css_files
|
||||
css_path = self.getStaticPath(template_data, u'fonts.css')
|
||||
if css_path is not None:
|
||||
css_files.append(self.getFrontURL(css_path))
|
||||
|
||||
for idx in xrange(len(path_elems)):
|
||||
name_root = u"_".join(path_elems[:idx+1])
|
||||
self._appendCSSPaths(template_data, css_files, css_files_noscript, name_root)
|
||||
|
||||
return css_files, css_files_noscript
|
||||
|
||||
## custom filters ##
|
||||
|
||||
@jinja2.contextfilter
|
||||
def _front_url(self, ctx, relative_url):
|
||||
"""Get front URL (URL seen by end-user) from a relative URL
|
||||
|
||||
This default method return absolute full path
|
||||
"""
|
||||
template_data = ctx[u'template_data']
|
||||
if template_data.site is None:
|
||||
assert template_data.theme is None
|
||||
assert template_data.path.startswith(u"/")
|
||||
return os.path.join(os.path.dirname(template_data.path, relative_url))
|
||||
|
||||
site_root_dir = self.sites_paths[template_data.site]
|
||||
return os.path.join(site_root_dir, C.TEMPLATE_TPL_DIR, template_data.theme,
|
||||
relative_url)
|
||||
|
||||
@jinja2.contextfilter
|
||||
def _next_gidx(self, ctx, value):
|
||||
"""Use next current global index as suffix"""
|
||||
|
|
@ -433,7 +665,8 @@ class Renderer(object):
|
|||
if filter is a callable, apply it
|
||||
if filter is a dict, it can have following keys:
|
||||
- filters: iterable of filters to apply
|
||||
- filters_args: kwargs of filters in the same order as filters (use empty dict if needed)
|
||||
- filters_args: kwargs of filters in the same order as filters (use empty
|
||||
dict if needed)
|
||||
- template: template to format where {value} is the filtered value
|
||||
"""
|
||||
value = item.value
|
||||
|
|
@ -500,7 +733,8 @@ class Renderer(object):
|
|||
def highlight(self, code, lexer_name=None, lexer_opts=None, html_fmt_opts=None):
|
||||
"""Do syntax highlighting on code
|
||||
|
||||
under the hood, pygments is used, check its documentation for options possible values
|
||||
Under the hood, pygments is used, check its documentation for options possible
|
||||
values.
|
||||
@param code(unicode): code or markup to highlight
|
||||
@param lexer_name(unicode, None): name of the lexer to use
|
||||
None to autodetect it
|
||||
|
|
@ -531,7 +765,10 @@ class Renderer(object):
|
|||
## template methods ##
|
||||
|
||||
def _icon_defs(self, *names):
|
||||
"""Define svg icons which will be used in the template, and use their name as id"""
|
||||
"""Define svg icons which will be used in the template.
|
||||
|
||||
Their name is used as id
|
||||
"""
|
||||
svg_elt = etree.Element(
|
||||
"svg",
|
||||
nsmap={None: "http://www.w3.org/2000/svg"},
|
||||
|
|
@ -551,88 +788,78 @@ class Renderer(object):
|
|||
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">
|
||||
<use href="#{name}"/>
|
||||
</svg>
|
||||
""".format(
|
||||
name=name, cls=(" " + cls) if cls else ""
|
||||
)
|
||||
)
|
||||
return safe(u'<svg class="svg-icon{cls}" xmlns="http://www.w3.org/2000/svg" '
|
||||
u'viewBox="0 0 100 100">\n'
|
||||
u' <use href="#{name}"/>'
|
||||
u'</svg>\n'.format(name=name, cls=(" " + cls) if cls else ""))
|
||||
|
||||
def render(self, template, site=None, theme=None, locale=C.DEFAULT_LOCALE,
|
||||
media_path=u"", css_files=None, css_inline=False, **kwargs):
|
||||
"""Render a template
|
||||
|
||||
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)
|
||||
@param site(unicide): site name
|
||||
None or empty string for defaut site (i.e. SàT templates)
|
||||
@param theme(unicode): template theme
|
||||
@param root_path(unicode): prefix of the path/URL to use for template root
|
||||
must end with a u'/'
|
||||
@param media_path(unicode): prefix of the SàT media path/URL to use for template root
|
||||
must end with a u'/'
|
||||
@param media_path(unicode): prefix of the SàT media path/URL to use for
|
||||
template root. Must end with a u'/'
|
||||
@param css_files(list[unicode],None): CSS files to used
|
||||
CSS files must be in static dir of the template
|
||||
use None for automatic selection of CSS files based on template category
|
||||
None is recommended. General static/style.css and theme file name will be used.
|
||||
None is recommended. General static/style.css and theme file name will be
|
||||
used.
|
||||
@param css_inline(bool): if True, CSS will be embedded in the HTML page
|
||||
@param **kwargs: variable to transmit to the template
|
||||
"""
|
||||
if not template:
|
||||
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 site is not None or theme is not None:
|
||||
# user wants to set site and/or theme, so we add it to the template path
|
||||
if site is None:
|
||||
site = u''
|
||||
if theme is None:
|
||||
theme = C.TEMPLATE_THEME_DEFAULT
|
||||
if template[0] == u"(":
|
||||
raise ValueError(
|
||||
u"you can't specify theme in template path and in argument at the same time"
|
||||
u"you can't specify site or theme in template path and in argument "
|
||||
u"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_data = TemplateData(site, theme, template)
|
||||
template = u"({site}/{theme}){template}".format(
|
||||
site=site, theme=theme, template=template)
|
||||
else:
|
||||
theme, dummy = self.env.loader.parse_template(template)
|
||||
template_data = 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 :]
|
||||
|
||||
if css_files is None:
|
||||
css_files = self.getCSSFiles(template_path, template_root_dir)
|
||||
css_files, css_files_noscript = self.getCSSFiles(template_data)
|
||||
|
||||
kwargs["icon_defs"] = self._icon_defs
|
||||
kwargs["icon"] = self._icon_use
|
||||
|
||||
if css_inline:
|
||||
css_contents = []
|
||||
for css_file in css_files:
|
||||
css_file_path = os.path.join(template_root_dir, css_file)
|
||||
with open(css_file_path) as f:
|
||||
css_contents.append(f.read())
|
||||
if css_contents:
|
||||
kwargs["css_content"] = "\n".join(css_contents)
|
||||
for files, suffix in ((css_files, u""),
|
||||
(css_files_noscript, u"_noscript")):
|
||||
site_root_dir = self.sites_paths[template_data.site]
|
||||
for css_file in files:
|
||||
css_file_path = os.path.join(site_root_dir, css_file)
|
||||
with open(css_file_path) as f:
|
||||
css_contents.append(f.read())
|
||||
if css_contents:
|
||||
kwargs[u"css_content" + suffix] = u"\n".join(css_contents)
|
||||
|
||||
scripts_handler = ScriptsHandler(
|
||||
self, template_path, template_root_dir, root_path
|
||||
)
|
||||
scripts_handler = ScriptsHandler(self, template_data)
|
||||
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.
|
||||
# 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,
|
||||
template_data=template_data,
|
||||
media_path=media_path,
|
||||
css_files=css_files,
|
||||
css_files_noscript=css_files,
|
||||
locale=self._locale,
|
||||
gidx=Indexer(),
|
||||
script=scripts_handler,
|
||||
|
|
|
|||
|
|
@ -47,7 +47,8 @@ def fixConfigOption(section, option, value, silent=True):
|
|||
config = SafeConfigParser()
|
||||
target_file = None
|
||||
for file_ in C.CONFIG_FILES[::-1]:
|
||||
# we will eventually update the existing file with the highest priority, if it's a user personal file...
|
||||
# we will eventually update the existing file with the highest priority,
|
||||
# if it's a user personal file...
|
||||
if not silent:
|
||||
log.debug(_(u"Testing file %s") % file_)
|
||||
if os.path.isfile(file_):
|
||||
|
|
@ -66,12 +67,9 @@ def fixConfigOption(section, option, value, silent=True):
|
|||
if not silent:
|
||||
if option in ("passphrase",): # list here the options storing a password
|
||||
value = "******"
|
||||
log.warning(
|
||||
_(
|
||||
u"Config auto-update: %(option)s set to %(value)s in the file %(config_file)s"
|
||||
)
|
||||
% {"option": option, "value": value, "config_file": target_file}
|
||||
)
|
||||
log.warning(_(u"Config auto-update: {option} set to {value} in the file "
|
||||
u"{config_file}.").format(option=option, value=value,
|
||||
config_file=target_file))
|
||||
|
||||
|
||||
def parseMainConf():
|
||||
|
|
|
|||
|
|
@ -554,7 +554,7 @@ class Jp(object):
|
|||
if self._need_loop or self._auto_loop:
|
||||
self._start_loop()
|
||||
except KeyboardInterrupt:
|
||||
log.info(_("User interruption: good bye"))
|
||||
self.disp(_("User interruption: good bye"))
|
||||
|
||||
def _start_loop(self):
|
||||
self.loop = JPLoop()
|
||||
|
|
|
|||
|
|
@ -21,7 +21,10 @@
|
|||
|
||||
from sat_frontends.jp.constants import Const as C
|
||||
from sat.core.i18n import _
|
||||
from sat.core import log
|
||||
from sat.tools.common import template
|
||||
from functools import partial
|
||||
import logging
|
||||
import webbrowser
|
||||
import tempfile
|
||||
import os.path
|
||||
|
|
@ -38,6 +41,19 @@ class Template(object):
|
|||
self.host = jp
|
||||
jp.register_output(C.OUTPUT_COMPLEX, TEMPLATE, self.render)
|
||||
|
||||
def _front_url_tmp_dir(self, ctx, relative_url, tmp_dir):
|
||||
"""Get front URL for temporary directory"""
|
||||
template_data = ctx[u'template_data']
|
||||
return u"file://" + os.path.join(tmp_dir, template_data.theme, relative_url)
|
||||
|
||||
def _do_render(self, template_path, css_inline, **kwargs):
|
||||
try:
|
||||
return self.renderer.render(template_path, css_inline=css_inline, **kwargs)
|
||||
except template.TemplateNotFound:
|
||||
self.host.disp(_(u"Can't find requested template: {template_path}")
|
||||
.format(template_path=template_path), error=True)
|
||||
self.host.quit(C.EXIT_NOT_FOUND)
|
||||
|
||||
def render(self, data):
|
||||
"""render output data using requested template
|
||||
|
||||
|
|
@ -55,23 +71,23 @@ class Template(object):
|
|||
template_path = cmd.TEMPLATE
|
||||
except AttributeError:
|
||||
if not "template" in cmd.args.output_opts:
|
||||
self.host.disp(
|
||||
u"no default template set for this command, "
|
||||
u"you need to specify a template using --oo template=[path/to/template.html]",
|
||||
self.host.disp(_(
|
||||
u"no default template set for this command, you need to specify a "
|
||||
u"template using --oo template=[path/to/template.html]"),
|
||||
error=True,
|
||||
)
|
||||
self.host.quit(C.EXIT_BAD_ARG)
|
||||
|
||||
options = self.host.parse_output_options()
|
||||
self.host.check_output_options(OPTIONS, options)
|
||||
self.renderer = template.Renderer(self.host)
|
||||
try:
|
||||
template_path = options["template"]
|
||||
except KeyError:
|
||||
# template is not specified, we use default one
|
||||
pass
|
||||
if template_path is None:
|
||||
self.host.disp(u"Can't parse template, please check its syntax", error=True)
|
||||
self.host.disp(_(u"Can't parse template, please check its syntax"),
|
||||
error=True)
|
||||
self.host.quit(C.EXIT_BAD_ARG)
|
||||
|
||||
try:
|
||||
|
|
@ -82,28 +98,41 @@ class Template(object):
|
|||
kwargs = mapping_cb(data)
|
||||
|
||||
css_inline = u"inline-css" in options
|
||||
rendered = self.renderer.render(template_path, css_inline=css_inline, **kwargs)
|
||||
|
||||
if "browser" in options:
|
||||
template_name = os.path.basename(template_path)
|
||||
tmp_dir = tempfile.mkdtemp()
|
||||
self.host.disp(
|
||||
_(
|
||||
u"Browser opening requested.\nTemporary files are put in the following directory, "
|
||||
u"you'll have to delete it yourself once finished viewing: {}"
|
||||
).format(tmp_dir)
|
||||
)
|
||||
front_url_filter = partial(self._front_url_tmp_dir, tmp_dir=tmp_dir)
|
||||
self.renderer = template.Renderer(
|
||||
self.host, front_url_filter=front_url_filter, trusted=True)
|
||||
rendered = self._do_render(template_path, css_inline=css_inline, **kwargs)
|
||||
self.host.disp(_(
|
||||
u"Browser opening requested.\n"
|
||||
u"Temporary files are put in the following directory, you'll have to "
|
||||
u"delete it yourself once finished viewing: {}").format(tmp_dir))
|
||||
tmp_file = os.path.join(tmp_dir, template_name)
|
||||
with open(tmp_file, "w") as f:
|
||||
f.write(rendered.encode("utf-8"))
|
||||
theme, theme_root_path = self.renderer.getThemeAndRoot(template_path)
|
||||
if theme is None:
|
||||
# we have an absolute path
|
||||
webbrowser
|
||||
static_dir = os.path.join(theme_root_path, C.TEMPLATE_STATIC_DIR)
|
||||
if os.path.exists(static_dir):
|
||||
# we have to copy static files in a subdirectory, to avoid file download
|
||||
# to be blocked by same origin policy
|
||||
import shutil
|
||||
|
||||
shutil.copytree(
|
||||
static_dir, os.path.join(tmp_dir, theme, C.TEMPLATE_STATIC_DIR)
|
||||
)
|
||||
webbrowser.open(tmp_file)
|
||||
else:
|
||||
# FIXME: Q&D way to disable template logging
|
||||
# logs are overcomplicated, and need to be reworked
|
||||
template_logger = log.getLogger(u"sat.tools.common.template")
|
||||
template_logger.log = lambda *args: None
|
||||
|
||||
logging.disable(logging.WARNING)
|
||||
self.renderer = template.Renderer(self.host, trusted=True)
|
||||
rendered = self._do_render(template_path, css_inline=css_inline, **kwargs)
|
||||
self.host.disp(rendered)
|
||||
|
|
|
|||
Loading…
Reference in a new issue