# -*- coding: utf-8 -*-
#
# This file is part of INGInious. See the LICENSE and the COPYRIGHTS files for
# more information about the licensing of this file.
""" TemplateManager """
import os
from functools import lru_cache
from jinja2 import Environment, FileSystemLoader, select_autoescape
import inginious
import json
[docs]class TemplateHelper(object):
""" Class accessible from templates that calls function defined in the Python part of the code. """
def __init__(self, plugin_manager, user_manager, use_minified=True):
"""
Init the Template Helper
:param plugin_manager: an instance of a PluginManager
:param user_manager: an instance of UserManager.
:param default_template_dir: the path to the template dir. If it is not absolute, it will be taken from the root of the inginious package.
:param default_layout: the path to the layout. If it is not absolute, it will be taken from the root of the inginious package.
:param use_minified: weither to use minified js/css or not. Use True in production, False in dev envs.
"""
self._base_helpers = {"header_hook": (lambda **kwargs: self._generic_hook('header_html', **kwargs)),
"main_menu": (lambda **kwargs: self._generic_hook('main_menu', **kwargs)),
"course_menu": (lambda **kwargs: self._generic_hook('course_menu', **kwargs)),
"submission_admin_menu": (lambda **kwargs: self._generic_hook('submission_admin_menu', **kwargs)),
"task_list_item": (lambda **kwargs: self._generic_hook('task_list_item', **kwargs)),
"task_menu": (lambda **kwargs: self._generic_hook('task_menu', **kwargs)),
"welcome_text": (lambda **kwargs: self._generic_hook('welcome_text', **kwargs)),
"javascript_header": (lambda **_: self._javascript_helper("header")),
"javascript_footer": (lambda **_: self._javascript_helper("footer")),
"css": (lambda **_: self._css_helper())}
self._plugin_manager = plugin_manager
self._template_dir = 'frontend/templates'
self._user_manager = user_manager # can be None!
self._layout_old = 'frontend/templates/layout_old'
self._template_globals = {}
self._ctx = {"javascript": {"footer": [], "header": []}, "css": []}
self.add_to_template_globals("template_helper", self)
self.add_to_template_globals("plugin_manager", plugin_manager)
self.add_to_template_globals("use_minified", use_minified)
self.add_to_template_globals("is_lti", self.is_lti)
self.add_to_template_globals("json", self._json_safe_dump)
[docs] def is_lti(self):
""" True if the current session is an LTI one """
return self._user_manager is not None and self._user_manager.session_lti_info() is not None
[docs] def add_to_template_globals(self, name, value):
""" Add a variable to will be accessible in the templates """
self._template_globals[name] = value
[docs] def render(self, path, template_folder="", **tpl_kwargs):
"""
Parse the Jinja template named "path" and render it with args ``*tpl_args`` and ``**tpl_kwargs``
:param path: Path of the template, relative to the base folder
:param template_folder: add the specified folder to the templates PATH.
:param tpl_kwargs: named args sent to the template
:return: the rendered template, as a str
"""
return self._get_jinja_renderer(template_folder).get_template(path).render(**tpl_kwargs)
@lru_cache(None)
def _get_jinja_renderer(self, template_folder=""):
# Always include the main template folder
template_folders = [os.path.join(inginious.get_root_path(), self._template_dir)]
# Include the additional template folder if specified
if template_folder:
template_folders += [os.path.join(inginious.get_root_path(), template_folder)]
env = Environment(loader=FileSystemLoader(template_folders),
autoescape=select_autoescape(['html', 'htm', 'xml']))
env.globals.update(self._template_globals)
return env
[docs] def call(self, name, **kwargs):
helpers = dict(list(self._base_helpers.items()) + self._plugin_manager.call_hook("template_helper"))
if helpers.get(name, None) is None:
return ""
else:
return helpers[name](**kwargs)
[docs] def add_javascript(self, link, position="footer"):
""" Add a javascript file to load. Position can either be "header" or "footer" """
self._get_ctx()["javascript"][position].append(link)
[docs] def add_css(self, link):
""" Add a css file to load """
self._get_ctx()["css"].append(link)
[docs] def add_other(self, name, func):
""" Add another callback to the template helper """
self._base_helpers[name] = func
def _javascript_helper(self, position):
""" Add javascript links for the current page and for the plugins """
if position not in ["header", "footer"]:
position = "footer"
# Load javascript files from plugins
if position == "header":
entries = [entry for entry in self._plugin_manager.call_hook("javascript_header") if entry is not None]
else:
entries = [entry for entry in self._plugin_manager.call_hook("javascript_footer") if entry is not None]
# Load javascript for the current page
entries += self._get_ctx()["javascript"][position]
entries = ["<script src='" + entry + "' type='text/javascript' charset='utf-8'></script>" for entry in entries]
return "\n".join(entries)
def _css_helper(self):
""" Add CSS links for the current page and for the plugins """
entries = [entry for entry in self._plugin_manager.call_hook("css") if entry is not None]
# Load javascript for the current page
entries += self._get_ctx()["css"]
entries = ["<link href='" + entry + "' rel='stylesheet'>" for entry in entries]
return "\n".join(entries)
def _get_ctx(self):
""" Get web.ctx object for the Template helper """
return self._ctx
def _generic_hook(self, name, **kwargs):
""" A generic hook that links the TemplateHelper with PluginManager """
entries = [entry for entry in self._plugin_manager.call_hook(name, **kwargs) if entry is not None]
return "\n".join(entries)
def _json_safe_dump(self, data):
""" Make a json dump of `data`, that can be used directly in a `<script>` tag. Available as json() inside templates """
return json.dumps(data).replace(u'<', u'\\u003c') \
.replace(u'>', u'\\u003e') \
.replace(u'&', u'\\u0026') \
.replace(u"'", u'\\u0027')