From 1fd08d39606d71b9fc2a3be368e3558d65cf8b36 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 17 Apr 2021 07:31:24 +0530 Subject: [PATCH 1/7] refactor: Jinja hooks - Rename hook from "jenv" to "jinja" - You can now pass the path to the module and all of the methods in that module will be added as methods - You can also pass module path of a method BREAKING CHANGE: Previous use of "jenv" hook won't work anymore --- frappe/hooks.py | 4 ++ frappe/utils/jinja.py | 111 +++++++++------------------------- frappe/utils/jinja_globals.py | 71 ++++++++++++++++++++++ 3 files changed, 102 insertions(+), 84 deletions(-) create mode 100644 frappe/utils/jinja_globals.py diff --git a/frappe/hooks.py b/frappe/hooks.py index 74c538c5df..c47afadf58 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -130,6 +130,10 @@ has_website_permission = { "Address": "frappe.contacts.doctype.address.address.has_website_permission" } +jinja = { + "methods": "frappe.utils.jinja_globals" +} + standard_queries = { "User": "frappe.core.doctype.user.user.user_query" } diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index cd74b2a283..1b2ef9f47f 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -18,13 +18,10 @@ def get_jenv(): set_filters(jenv) jenv.globals.update(get_safe_globals()) - jenv.globals.update(get_jenv_customization('methods')) - jenv.globals.update({ - 'resolve_class': resolve_class, - 'inspect': inspect, - 'web_blocks': web_blocks, - 'web_block': web_block - }) + + methods, filters = get_jinja_hooks('methods') + jenv.globals.update(methods or {}) + jenv.filters.update(filters or {}) frappe.local.jenv = jenv @@ -143,88 +140,34 @@ def set_filters(jenv): if frappe.flags.in_setup_help: return - jenv.filters.update(get_jenv_customization('filters')) - - -def get_jenv_customization(customization_type): - '''Returns a dict with filter/method name as key and definition as value''' +def get_jinja_hooks(): + """Returns a tuple of (methods, filters) each containing a dict of method name and method definition pair.""" import frappe - out = {} 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: + 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("jenv", {}).get(customization_type) - if not values: - return out + values = frappe.get_hooks("jinja") + methods, filters = values.get("methods", []), values.get("filters", []) - for value in values: - fn_name, fn_string = value.split(":") - out[fn_name] = frappe.get_attr(fn_string) + method_dict = get_obj_dict_from_paths(methods) + filter_dict = get_obj_dict_from_paths(filters) - return out - - -def resolve_class(classes): - import frappe - - if classes is None: - return '' - - if isinstance(classes, frappe.string_types): - return classes - - if isinstance(classes, (list, tuple)): - return ' '.join([resolve_class(c) for c in classes]).strip() - - if isinstance(classes, dict): - return ' '.join([classname for classname in classes if classes[classname]]).strip() - - return classes - - -def inspect(var, render=True): - context = { "var": var } - if render: - html = "
{{ var | pprint | e }}
" - else: - html = "" - return get_jenv().from_string(html).render(context) - - -def web_block(template, values=None, **kwargs): - options = {"template": template, "values": values} - options.update(kwargs) - return web_blocks([options]) - - -def web_blocks(blocks): - from frappe import throw, _dict - from frappe.website.doctype.web_page.web_page import get_web_blocks_html - - web_blocks = [] - for block in blocks: - if not block.get('template'): - throw('Web Template is not specified') - - doc = _dict({ - 'doctype': 'Web Page Block', - 'web_template': block['template'], - 'web_template_values': block.get('values', {}), - 'add_top_padding': 1, - 'add_bottom_padding': 1, - 'add_container': 1, - 'hide_block': 0, - 'css_class': '' - }) - doc.update(block) - web_blocks.append(doc) - - out = get_web_blocks_html(web_blocks) - - html = out.html - for script in out.scripts: - html += ''.format(script) - - return html + return method_dict, filter_dict diff --git a/frappe/utils/jinja_globals.py b/frappe/utils/jinja_globals.py new file mode 100644 index 0000000000..e63926a109 --- /dev/null +++ b/frappe/utils/jinja_globals.py @@ -0,0 +1,71 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +from frappe.utils.jinja import get_jenv +import frappe + + +def resolve_class(classes): + if classes is None: + return "" + + if isinstance(classes, frappe.string_types): + return classes + + if isinstance(classes, (list, tuple)): + return " ".join([resolve_class(c) for c in classes]).strip() + + if isinstance(classes, dict): + return " ".join([classname for classname in classes if classes[classname]]).strip() + + return classes + + +def inspect(var, render=True): + context = {"var": var} + if render: + html = "
{{ var | pprint | e }}
" + else: + return "" + return get_jenv().from_string(html).render(context) + + +def web_block(template, values=None, **kwargs): + options = {"template": template, "values": values} + options.update(kwargs) + return web_blocks([options]) + + +def web_blocks(blocks): + from frappe import throw, _dict + from frappe.website.doctype.web_page.web_page import get_web_blocks_html + + web_blocks = [] + for block in blocks: + if not block.get("template"): + throw("Web Template is not specified") + + doc = _dict( + { + "doctype": "Web Page Block", + "web_template": block["template"], + "web_template_values": block.get("values", {}), + "add_top_padding": 1, + "add_bottom_padding": 1, + "add_container": 1, + "hide_block": 0, + "css_class": "", + } + ) + doc.update(block) + web_blocks.append(doc) + + out = get_web_blocks_html(web_blocks) + + html = out.html + for script in out.scripts: + html += "".format(script) + + return html + From a62ef80cddedec855e592fbe77a4ab7cdede8bad Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 17 Apr 2021 07:41:04 +0530 Subject: [PATCH 2/7] fix: Add jinja hook boilerplate --- frappe/utils/boilerplate.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index e59f579f75..80eda0af13 100755 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -190,6 +190,15 @@ app_license = "{app_license}" # automatically create page for each record of this doctype # website_generators = ["Web Page"] +# Jinja +# ---------- + +# add methods and filters to jinja environment +# jinja = {{ +# "methods": "{app_name}.utils.jinja_methods", +# "filters": "{app_name}.utils.jinja_filters" +# }} + # Installation # ------------ From a78fed4ffccbe3faa628eb7d6a25affc9887023e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 17 Apr 2021 07:52:04 +0530 Subject: [PATCH 3/7] fix: Move standard filters to jinja hooks --- frappe/hooks.py | 8 +++++++- frappe/utils/jinja.py | 7 +------ 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index c47afadf58..1c78d47755 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -131,7 +131,13 @@ has_website_permission = { } jinja = { - "methods": "frappe.utils.jinja_globals" + "methods": "frappe.utils.jinja_globals", + "filters": [ + "frappe.utils.data.global_date_format", + "frappe.utils.markdown", + "frappe.website.utils.get_shade", + "frappe.website.utils.abs_url", + ] } standard_queries = { diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 1b2ef9f47f..6d25f4a405 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -124,18 +124,13 @@ def get_jloader(): def set_filters(jenv): import frappe - from frappe.utils import global_date_format, cint, cstr, flt, markdown - from frappe.website.utils import get_shade, abs_url + from frappe.utils import cint, cstr, flt - jenv.filters["global_date_format"] = global_date_format - jenv.filters["markdown"] = markdown jenv.filters["json"] = frappe.as_json - jenv.filters["get_shade"] = get_shade jenv.filters["len"] = len jenv.filters["int"] = cint jenv.filters["str"] = cstr jenv.filters["flt"] = flt - jenv.filters["abs_url"] = abs_url if frappe.flags.in_setup_help: return From b32db6e329a006c1e0eedd93ceb53930db1753f5 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sat, 17 Apr 2021 16:03:25 +0530 Subject: [PATCH 4/7] fix: method call --- frappe/utils/jinja.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 6d25f4a405..42ab267381 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -19,7 +19,7 @@ def get_jenv(): jenv.globals.update(get_safe_globals()) - methods, filters = get_jinja_hooks('methods') + methods, filters = get_jinja_hooks() jenv.globals.update(methods or {}) jenv.filters.update(filters or {}) From 976ebd5b2c2d4188db1bfe38b29f9badd709eb5b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 22 Apr 2021 06:03:35 +0530 Subject: [PATCH 5/7] fix: Check if path is a module first then function --- frappe/utils/jinja.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/utils/jinja.py b/frappe/utils/jinja.py index 42ab267381..a77eca4977 100644 --- a/frappe/utils/jinja.py +++ b/frappe/utils/jinja.py @@ -149,7 +149,11 @@ def get_jinja_hooks(): def get_obj_dict_from_paths(object_paths): out = {} for obj_path in object_paths: - obj = frappe.get_attr(obj_path) + 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: From aaa165c7840959e54d2905f9e050b5a765211017 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 22 Apr 2021 17:00:55 +0530 Subject: [PATCH 6/7] fix: Show upgrade message in patch - Will only be shown if the old style "jenv" hook is being used --- frappe/patches.txt | 1 + frappe/patches/v13_0/jinja_hook.py | 13 +++++++++++++ 2 files changed, 14 insertions(+) create mode 100644 frappe/patches/v13_0/jinja_hook.py diff --git a/frappe/patches.txt b/frappe/patches.txt index 516ddb6094..60c3112f4a 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -335,3 +335,4 @@ frappe.patches.v13_0.rename_list_view_setting_to_list_view_settings frappe.patches.v13_0.remove_twilio_settings frappe.patches.v12_0.rename_uploaded_files_with_proper_name frappe.patches.v13_0.queryreport_columns +frappe.patches.v13_0.jinja_hook diff --git a/frappe/patches/v13_0/jinja_hook.py b/frappe/patches/v13_0/jinja_hook.py new file mode 100644 index 0000000000..3e9efc5896 --- /dev/null +++ b/frappe/patches/v13_0/jinja_hook.py @@ -0,0 +1,13 @@ +# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from click import secho + +def execute(): + if frappe.get_hooks('jenv') or True: + print() + secho('WARNING: The hook "jenv" is deprecated. Follow the migration guide to use the new "jinja" hook.', fg='yellow') + secho('https://github.com/frappe/frappe/wiki/Migrating-to-Version-13', fg='yellow') + print() From 87f3038272a33954437d50759caa05ec3916df12 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 22 Apr 2021 17:31:34 +0530 Subject: [PATCH 7/7] fix: remove hardcoding --- frappe/patches/v13_0/jinja_hook.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/patches/v13_0/jinja_hook.py b/frappe/patches/v13_0/jinja_hook.py index 3e9efc5896..84ed6e6cff 100644 --- a/frappe/patches/v13_0/jinja_hook.py +++ b/frappe/patches/v13_0/jinja_hook.py @@ -6,7 +6,7 @@ import frappe from click import secho def execute(): - if frappe.get_hooks('jenv') or True: + if frappe.get_hooks('jenv'): print() secho('WARNING: The hook "jenv" is deprecated. Follow the migration guide to use the new "jinja" hook.', fg='yellow') secho('https://github.com/frappe/frappe/wiki/Migrating-to-Version-13', fg='yellow')