# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE def get_jenv(): import frappe from frappe.utils.safe_exec import get_safe_globals if not getattr(frappe.local, 'jenv', None): from jinja2 import DebugUndefined from jinja2.sandbox import SandboxedEnvironment # frappe will be loaded last, so app templates will get precedence jenv = SandboxedEnvironment( loader=get_jloader(), undefined=DebugUndefined ) set_filters(jenv) jenv.globals.update(get_safe_globals()) methods, filters = get_jinja_hooks() jenv.globals.update(methods or {}) jenv.filters.update(filters or {}) frappe.local.jenv = jenv return frappe.local.jenv def get_template(path): return get_jenv().get_template(path) def get_email_from_template(name, args): from jinja2 import TemplateNotFound args = args or {} try: message = get_template('templates/emails/' + name + '.html').render(args) except TemplateNotFound as e: raise e try: text_content = get_template('templates/emails/' + name + '.txt').render(args) except TemplateNotFound: text_content = None return (message, text_content) def validate_template(html): """Throws exception if there is a syntax error in the Jinja Template""" import frappe from jinja2 import TemplateSyntaxError jenv = get_jenv() try: jenv.from_string(html) except TemplateSyntaxError as e: frappe.msgprint('Line {}: {}'.format(e.lineno, e.message)) frappe.throw(frappe._("Syntax error in template")) def render_template(template, context, is_path=None, safe_render=True): '''Render a template using Jinja :param template: path or HTML containing the jinja template :param context: dict of properties to pass to the template :param is_path: (optional) assert that the `template` parameter is a path :param safe_render: (optional) prevent server side scripting via jinja templating ''' from frappe import _, get_traceback, throw from jinja2 import TemplateError if not template: return "" if (is_path or guess_is_path(template)): return get_jenv().get_template(template).render(context) else: if safe_render and ".__" in template: throw(_("Illegal template")) try: return get_jenv().from_string(template).render(context) except TemplateError: throw(title="Jinja Template Error", msg="
{template}{tb}".format(template=template, tb=get_traceback()))
def guess_is_path(template):
# template can be passed as a path or content
# if its single line and ends with a html, then its probably a path
if '\n' not in template and '.' in template:
extn = template.rsplit('.')[-1]
if extn in ('html', 'css', 'scss', 'py', 'md', 'json', 'js', 'xml'):
return True
return False
def get_jloader():
import frappe
if not getattr(frappe.local, 'jloader', None):
from jinja2 import ChoiceLoader, PackageLoader, PrefixLoader
if frappe.local.flags.in_setup_help:
apps = ['frappe']
else:
apps = frappe.get_hooks('template_apps')
if not apps:
apps = frappe.local.flags.web_pages_apps or frappe.get_installed_apps(sort=True)
apps.reverse()
if "frappe" not in apps:
apps.append('frappe')
frappe.local.jloader = ChoiceLoader(
# search for something like app/templates/...
[PrefixLoader(dict(
(app, PackageLoader(app, ".")) for app in apps
))]
# search for something like templates/...
+ [PackageLoader(app, ".") for app in apps]
)
return frappe.local.jloader
def set_filters(jenv):
import frappe
from frappe.utils import cint, cstr, flt
jenv.filters["json"] = frappe.as_json
jenv.filters["len"] = len
jenv.filters["int"] = cint
jenv.filters["str"] = cstr
jenv.filters["flt"] = flt
if frappe.flags.in_setup_help:
return
def get_jinja_hooks():
"""Returns a tuple of (methods, filters) each containing a dict of method name and method definition pair."""
import frappe
if not getattr(frappe.local, "site", None):
return (None, None)
from types import FunctionType, ModuleType
from inspect import getmembers, isfunction
def get_obj_dict_from_paths(object_paths):
out = {}
for obj_path in object_paths:
try:
obj = frappe.get_module(obj_path)
except ModuleNotFoundError:
obj = frappe.get_attr(obj_path)
if isinstance(obj, ModuleType):
functions = getmembers(obj, isfunction)
for function_name, function in functions:
out[function_name] = function
elif isinstance(obj, FunctionType):
function_name = obj.__name__
out[function_name] = obj
return out
values = frappe.get_hooks("jinja")
methods, filters = values.get("methods", []), values.get("filters", [])
method_dict = get_obj_dict_from_paths(methods)
filter_dict = get_obj_dict_from_paths(filters)
return method_dict, filter_dict