seitime-frappe/frappe/utils/jinja.py
Gavin D'souza 3446026555 chore: Update header: license.txt => LICENSE
The license.txt file has been replaced with LICENSE for quite a while
now. INAL but it didn't seem accurate to say "hey, checkout license.txt
although there's no such file". Apart from this, there were
inconsistencies in the headers altogether...this change brings
consistency.
2021-09-03 12:02:59 +05:30

170 lines
4.7 KiB
Python

# 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="<pre>{template}</pre><pre>{tb}</pre>".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