Merge branch 'develop' of https://github.com/frappe/frappe into integrated-notifications

This commit is contained in:
Faris Ansari 2019-10-21 18:47:51 +05:30
commit fc5b712a3c
28 changed files with 864 additions and 978 deletions

View file

@ -10,5 +10,6 @@
"admin_password": "admin",
"root_login": "root",
"root_password": "travis",
"host_name": "http://test_site:8000"
"host_name": "http://test_site:8000",
"server_script_enabled": true
}

View file

@ -10,5 +10,6 @@
"admin_password": "admin",
"root_login": "postgres",
"root_password": "travis",
"host_name": "http://test_site:8000"
"host_name": "http://test_site:8000",
"server_script_enabled": true
}

View file

@ -520,7 +520,7 @@ def read_only():
return wrapper_fn
return innfn
def only_for(roles):
def only_for(roles, message=False):
"""Raise `frappe.PermissionError` if the user does not have any of the given **Roles**.
:param roles: List of roles to check."""
@ -532,6 +532,8 @@ def only_for(roles):
roles = set(roles)
myroles = set(get_roles())
if not roles.intersection(myroles):
if message:
msgprint(_('Only for {}'.format(', '.join(roles))))
raise PermissionError
def get_domain_data(module):

View file

@ -13,7 +13,7 @@ common_default_keys = ["__default", "__global"]
global_cache_keys = ("app_hooks", "installed_apps",
"app_modules", "module_app", "system_settings",
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
'active_modules', 'assignment_rule')
'active_modules', 'assignment_rule', 'server_script_map')
user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang",
"defaults", "user_permissions", "home_page", "linked_with",

View file

@ -20,7 +20,7 @@ class TestExporter(unittest.TestCase):
e = Exporter('Web Page', export_fields='All')
csv_array = e.get_csv_array()
header = csv_array[0]
self.assertEqual(len(header), 23)
self.assertEqual(len(header), 24)
def test_exports_selected_fields(self):

View file

@ -1,777 +1,204 @@
{
"allow_copy": 0,
"allow_events_in_timeline": 0,
"allow_guest_to_view": 0,
"allow_import": 0,
"allow_rename": 0,
"autoname": "field:report_name",
"beta": 0,
"creation": "2013-03-09 15:45:57",
"custom": 0,
"docstatus": 0,
"doctype": "DocType",
"document_type": "System",
"editable_grid": 0,
"engine": "InnoDB",
"fields": [
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "report_name",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Report Name",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 1
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "ref_doctype",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Ref DocType",
"length": 0,
"no_copy": 0,
"options": "DocType",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "reference_report",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Reference Report",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "is_standard",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Is Standard",
"length": 0,
"no_copy": 0,
"options": "No\nYes",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "module",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Module",
"length": 0,
"no_copy": 0,
"options": "Module Def",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "add_total_row",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Add Total Row",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "column_break_4",
"fieldtype": "Column Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "report_type",
"fieldtype": "Select",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Report Type",
"length": 0,
"no_copy": 0,
"options": "Report Builder\nQuery Report\nScript Report\nCustom Report",
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 1,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "disabled",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disabled",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "icon",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Icon",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "color",
"fieldtype": "Data",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Color",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval: doc.is_standard == \"No\"",
"fetch_if_empty": 0,
"fieldname": "letter_head",
"fieldtype": "Link",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Letter Head",
"length": 0,
"no_copy": 0,
"options": "Letter Head",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "section_break_6",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.report_type==\"Query Report\"",
"fetch_if_empty": 0,
"fieldname": "query",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Query",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "",
"description": "JavaScript Format: frappe.query_reports['REPORTNAME'] = {}",
"fetch_if_empty": 0,
"fieldname": "javascript",
"fieldtype": "Code",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Javascript",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.report_type==\"Report Builder\" || \"Custom Report\"",
"fetch_if_empty": 0,
"fieldname": "json",
"fieldtype": "Code",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "JSON",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "permission_rules",
"fieldtype": "Section Break",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"depends_on": "eval:doc.is_standard == 'Yes'",
"fetch_if_empty": 0,
"fieldname": "roles",
"fieldtype": "Table",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Roles",
"length": 0,
"no_copy": 0,
"options": "Has Role",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "disable_prepared_report",
"fieldtype": "Check",
"hidden": 0,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Disable Prepared Report",
"length": 0,
"no_copy": 0,
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 0,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
},
{
"allow_bulk_edit": 0,
"allow_in_quick_entry": 0,
"allow_on_submit": 0,
"bold": 0,
"collapsible": 0,
"columns": 0,
"fetch_if_empty": 0,
"fieldname": "prepared_report",
"fieldtype": "Check",
"hidden": 1,
"ignore_user_permissions": 0,
"ignore_xss_filter": 0,
"in_filter": 0,
"in_global_search": 0,
"in_list_view": 0,
"in_standard_filter": 0,
"label": "Prepared Report",
"length": 0,
"no_copy": 0,
"options": "",
"permlevel": 0,
"precision": "",
"print_hide": 0,
"print_hide_if_no_value": 0,
"read_only": 1,
"remember_last_selected_value": 0,
"report_hide": 0,
"reqd": 0,
"search_index": 0,
"set_only_once": 0,
"translatable": 0,
"unique": 0
}
],
"has_web_view": 0,
"hide_toolbar": 0,
"icon": "",
"idx": 1,
"in_create": 0,
"is_submittable": 0,
"issingle": 0,
"istable": 0,
"max_attachments": 0,
"modified": "2019-04-12 15:53:14.194591",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",
"owner": "Administrator",
"permissions": [
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Report Manager",
"set_user_permissions": 0,
"share": 1,
"submit": 0,
"write": 1
},
{
"amend": 0,
"cancel": 0,
"create": 0,
"delete": 0,
"email": 1,
"export": 0,
"if_owner": 0,
"import": 0,
"permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
"set_user_permissions": 0,
"share": 0,
"submit": 0,
"write": 0
}
],
"quick_entry": 0,
"read_only": 0,
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1,
"track_seen": 0,
"track_views": 0
}
"autoname": "field:report_name",
"creation": "2013-03-09 15:45:57",
"doctype": "DocType",
"document_type": "System",
"engine": "InnoDB",
"field_order": [
"report_name",
"ref_doctype",
"reference_report",
"is_standard",
"module",
"column_break_4",
"report_type",
"letter_head",
"add_total_row",
"disabled",
"disable_prepared_report",
"prepared_report",
"section_break_6",
"query",
"javascript",
"report_script",
"json",
"permission_rules",
"roles"
],
"fields": [
{
"fieldname": "report_name",
"fieldtype": "Data",
"label": "Report Name",
"reqd": 1,
"unique": 1
},
{
"fieldname": "ref_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Ref DocType",
"options": "DocType",
"reqd": 1
},
{
"fieldname": "reference_report",
"fieldtype": "Data",
"label": "Reference Report"
},
{
"fieldname": "is_standard",
"fieldtype": "Select",
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Is Standard",
"options": "No\nYes",
"reqd": 1
},
{
"fieldname": "module",
"fieldtype": "Link",
"label": "Module",
"options": "Module Def"
},
{
"default": "0",
"fieldname": "add_total_row",
"fieldtype": "Check",
"label": "Add Total Row"
},
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "report_type",
"fieldtype": "Select",
"label": "Report Type",
"options": "Report Builder\nQuery Report\nScript Report\nCustom Report",
"reqd": 1
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"depends_on": "eval: doc.is_standard == \"No\"",
"fieldname": "letter_head",
"fieldtype": "Link",
"label": "Letter Head",
"options": "Letter Head"
},
{
"fieldname": "section_break_6",
"fieldtype": "Section Break"
},
{
"depends_on": "eval:doc.report_type==\"Query Report\"",
"fieldname": "query",
"fieldtype": "Code",
"label": "Query"
},
{
"depends_on": "eval:doc.report_type==\"Script Report\" && doc.is_standard===\"No\"",
"description": "JavaScript Format: frappe.query_reports['REPORTNAME'] = {}",
"fieldname": "javascript",
"fieldtype": "Code",
"label": "Javascript"
},
{
"depends_on": "eval:doc.report_type==\"Report Builder\" || \"Custom Report\"",
"fieldname": "json",
"fieldtype": "Code",
"label": "JSON",
"read_only": 1
},
{
"fieldname": "permission_rules",
"fieldtype": "Section Break"
},
{
"depends_on": "eval:doc.is_standard == 'Yes'",
"fieldname": "roles",
"fieldtype": "Table",
"label": "Roles",
"options": "Has Role"
},
{
"default": "0",
"fieldname": "disable_prepared_report",
"fieldtype": "Check",
"label": "Disable Prepared Report"
},
{
"default": "0",
"fieldname": "prepared_report",
"fieldtype": "Check",
"hidden": 1,
"label": "Prepared Report",
"read_only": 1
},
{
"depends_on": "eval:doc.report_type==\"Script Report\" && doc.is_standard===\"No\"",
"description": "output in the form of `data = [columns, result]`",
"fieldname": "report_script",
"fieldtype": "Code",
"label": "Script"
}
],
"idx": 1,
"modified": "2019-10-09 15:43:08.577610",
"modified_by": "Administrator",
"module": "Core",
"name": "Report",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
"share": 1,
"write": 1
},
{
"create": 1,
"delete": 1,
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Report Manager",
"share": 1,
"write": 1
},
{
"email": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "All"
}
],
"show_name_in_global_search": 1,
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -3,8 +3,8 @@
from __future__ import unicode_literals
import frappe
import json
from frappe import _
import json, datetime
from frappe import _, scrub
import frappe.desk.query_report
from frappe.utils import cint
from frappe.model.document import Document
@ -14,6 +14,7 @@ from frappe.core.doctype.page.page import delete_custom_role
from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles
from frappe.desk.reportview import append_totals_row
from six import iteritems
from frappe.utils.safe_exec import safe_exec
class Report(Document):
@ -27,16 +28,17 @@ class Report(Document):
if frappe.session.user=="Administrator" and getattr(frappe.local.conf, 'developer_mode',0)==1:
self.is_standard = "Yes"
if self.is_standard == "No" and frappe.db.get_value("Report", self.name, "is_standard") == "Yes":
frappe.throw(_("Cannot edit a standard report. Please duplicate and create a new report"))
if self.is_standard == "No":
# allow only script manager to edit scripts
if frappe.session.user!="Administrator":
frappe.only_for('Script Manager', True)
if frappe.db.get_value("Report", self.name, "is_standard") == "Yes":
frappe.throw(_("Cannot edit a standard report. Please duplicate and create a new report"))
if self.is_standard == "Yes" and frappe.session.user!="Administrator":
frappe.throw(_("Only Administrator can save a standard report. Please rename and save."))
if self.report_type in ("Query Report", "Script Report") \
and frappe.session.user!="Administrator":
frappe.throw(_("Only Administrator allowed to create Query / Script Reports"))
if self.report_type == "Report Builder":
self.update_report_json()
@ -68,9 +70,7 @@ class Report(Document):
if not allowed:
return True
roles = frappe.get_roles()
if has_common(roles, allowed):
if has_common(frappe.get_roles(), allowed):
return True
def update_report_json(self):
@ -92,6 +92,40 @@ class Report(Document):
make_boilerplate("controller.py", self, {"name": self.name})
make_boilerplate("controller.js", self, {"name": self.name})
def execute_script_report(self, filters):
# save the timestamp to automatically set to prepared
threshold = 30
res = []
start_time = datetime.datetime.now()
# The JOB
if self.is_standard == 'Yes':
res = self.execute_module(filters)
else:
res = self.execute_script(filters)
# automatically set as prepared
execution_time = (datetime.datetime.now() - start_time).total_seconds()
if execution_time > threshold and not self.prepared_report:
self.db_set('prepared_report', 1)
frappe.cache().hset('report_execution_time', self.name, execution_time)
return res
def execute_module(self, filters):
# report in python module
module = self.module or frappe.db.get_value("DocType", self.ref_doctype, "module")
method_name = get_report_module_dotted_path(module, self.name) + ".execute"
return frappe.get_attr(method_name)(frappe._dict(filters))
def execute_script(self, filters):
# server script
loc = {"filters": frappe._dict(filters), 'data':[]}
safe_exec(self.report_script, None, loc)
return loc['data']
def get_data(self, filters=None, limit=None, user=None, as_dict=False):
columns = []
out = []
@ -201,3 +235,7 @@ class Report(Document):
def is_prepared_report_disabled(report):
return frappe.db.get_value('Report',
report, 'disable_prepared_report') or 0
def get_report_module_dotted_path(module, report_name):
return frappe.local.module_app[scrub(module)] + "." + scrub(module) \
+ ".report." + scrub(report_name) + "." + scrub(report_name)

View file

@ -6,6 +6,7 @@ import frappe, json, os
import unittest
test_records = frappe.get_test_records('Report')
test_dependencies = ['User']
class TestReport(unittest.TestCase):
def test_report_builder(self):
@ -28,7 +29,8 @@ class TestReport(unittest.TestCase):
self.assertEqual(columns[1].get('label'), 'Module')
self.assertTrue('User' in [d[0] for d in data])
def test_report_permisisons(self):
def test_report_permissions(self):
frappe.set_user('test@example.com')
frappe.db.sql("""delete from `tabHas Role` where parent = %s
and role = 'Test Has Role'""", frappe.session.user, auto_commit=1)
@ -53,6 +55,7 @@ class TestReport(unittest.TestCase):
report = frappe.get_doc('Report', 'Test Report')
self.assertNotEquals(report.is_permitted(), True)
frappe.set_user('Administrator')
# test for the `_format` method if report data doesn't have sort_by parameter
def test_format_method(self):
@ -68,3 +71,32 @@ class TestReport(unittest.TestCase):
self.assertEqual(columns[1].get('label'), 'User Type')
self.assertTrue('Administrator' in [d[0] for d in data])
frappe.delete_doc('Report', 'User Activity Report Without Sort')
def test_non_standard_script_report(self):
report_name = 'Test Non Standard Script Report'
if not frappe.db.exists("Report", report_name):
report = frappe.get_doc({
'doctype': 'Report',
'ref_doctype': 'User',
'report_name': report_name,
'report_type': 'Script Report',
'is_standard': 'No',
}).insert(ignore_permissions=True)
else:
report = frappe.get_doc('Report', report_name)
report.report_script = '''
data = [
[{'fieldname': 'name', 'label': 'ID'}],
[frappe.db.get_all('User', dict(user_type="System User"))]
]
'''
report.save()
data = report.get_data()
# check columns
self.assertEqual(data[0][0]['label'], 'ID')
# check values
self.assertTrue('Administrator' in [d.get('name') for d in data[1][0]])

View file

@ -0,0 +1,8 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
frappe.ui.form.on('Server Script', {
// refresh: function(frm) {
// }
});

View file

@ -0,0 +1,98 @@
{
"autoname": "Prompt",
"creation": "2019-09-30 11:56:57.943241",
"doctype": "DocType",
"editable_grid": 1,
"engine": "InnoDB",
"field_order": [
"script_type",
"disabled",
"column_break_3",
"reference_doctype",
"doctype_event",
"api_method",
"allow_guest",
"section_break_8",
"script"
],
"fields": [
{
"fieldname": "script_type",
"fieldtype": "Select",
"in_list_view": 1,
"label": "Script Type",
"options": "DocType Event\nAPI",
"reqd": 1
},
{
"fieldname": "script",
"fieldtype": "Code",
"label": "Script",
"reqd": 1
},
{
"depends_on": "eval:doc.script_type==='DocType Event'",
"fieldname": "reference_doctype",
"fieldtype": "Link",
"in_list_view": 1,
"label": "Reference Document Type",
"options": "DocType"
},
{
"depends_on": "eval:doc.script_type==='DocType Event'",
"fieldname": "doctype_event",
"fieldtype": "Select",
"label": "DocType Event",
"options": "Before Insert\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete"
},
{
"depends_on": "eval:doc.script_type==='API'",
"fieldname": "api_method",
"fieldtype": "Data",
"label": "API Method"
},
{
"default": "0",
"depends_on": "eval:doc.script_type==='API'",
"fieldname": "allow_guest",
"fieldtype": "Check",
"label": "Allow Guest"
},
{
"default": "0",
"fieldname": "disabled",
"fieldtype": "Check",
"label": "Disabled"
},
{
"fieldname": "column_break_3",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_8",
"fieldtype": "Section Break"
}
],
"modified": "2019-10-09 15:08:40.085059",
"modified_by": "Administrator",
"module": "Core",
"name": "Server Script",
"owner": "Administrator",
"permissions": [
{
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
"print": 1,
"read": 1,
"report": 1,
"role": "Script Manager",
"share": 1,
"write": 1
}
],
"sort_field": "modified",
"sort_order": "DESC",
"track_changes": 1
}

View file

@ -0,0 +1,33 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and contributors
# For license information, please see license.txt
from __future__ import unicode_literals
import frappe
from frappe.model.document import Document
from frappe.utils.safe_exec import safe_exec
class ServerScript(Document):
@staticmethod
def validate():
frappe.only_for('Script Manager', True)
@staticmethod
def on_update():
frappe.cache().delete_value('server_script_map')
def execute_method(self):
if self.script_type == 'API':
# validate if guest is allowed
if frappe.session.user == 'Guest' and not self.allow_guest:
raise frappe.PermissionError
safe_exec(self.script)
else:
# wrong report type!
raise frappe.DoesNotExistError
def execute_doc(self, doc):
# execute event
safe_exec(self.script, None, dict(doc = doc))

View file

@ -0,0 +1,64 @@
import frappe
# this is a separate file since it is imported in frappe.model.document
# to avoid circular imports
EVENT_MAP = {
'before_insert': 'Before Insert',
'after_insert': 'After Insert',
'validate': 'Before Save',
'on_update': 'After Save',
'before_submit': 'Before Submit',
'on_submit': 'After Submit',
'before_cancel': 'Before Cancel',
'on_cancel': 'After Cancel',
'on_trash': 'Before Delete',
'after_delete': 'After Delete',
}
def run_server_script_api(method):
# called via handler, execute an API script
script_name = get_server_script_map().get('_api', {}).get(method)
if script_name:
frappe.get_doc('Server Script', script_name).execute_method()
return True
def run_server_script_for_doc_event(doc, event):
# run document event method
if not event in EVENT_MAP:
return
if frappe.flags.in_install:
return
scripts = get_server_script_map().get(doc.doctype, {}).get(EVENT_MAP[event], None)
if scripts:
# run all scripts for this doctype + event
for script_name in scripts:
frappe.get_doc('Server Script', script_name).execute_doc(doc)
def get_server_script_map():
# fetch cached server script methods
# {
# '[doctype]': {
# 'Before Insert': ['[server script 1]', '[server script 2]']
# },
# '_api': {
# '[path]': '[server script]'
# }
# }
if frappe.flags.in_patch and not frappe.db.table_exists('Server Script'):
return {}
script_map = frappe.cache().get_value('server_script_map')
if script_map is None:
script_map = {}
for script in frappe.get_all('Server Script', ('name', 'reference_doctype', 'doctype_event',
'api_method', 'script_type')):
if script.script_type == 'DocType Event':
script_map.setdefault(script.reference_doctype, {}).setdefault(script.doctype_event, []).append(script.name)
else:
script_map.setdefault('_api', {})[script.api_method] = script.name
frappe.cache().set_value('server_script_map', script_map)
return script_map

View file

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019, Frappe Technologies and Contributors
# See license.txt
from __future__ import unicode_literals
import frappe
import unittest
import requests
from frappe.utils import get_site_url
scripts = [
dict(
name='test_todo',
script_type = 'DocType Event',
doctype_event = 'Before Insert',
reference_doctype = 'ToDo',
script = '''
if "test" in doc.description:
doc.status = 'Closed'
'''
),
dict(
name='test_todo_validate',
script_type = 'DocType Event',
doctype_event = 'Before Insert',
reference_doctype = 'ToDo',
script = '''
if "validate" in doc.description:
raise frappe.ValidationError
'''
),
dict(
name='test_api',
script_type = 'API',
api_method = 'test_server_script',
allow_guest = 1,
script = '''
frappe.response['message'] = 'hello'
'''
)
]
class TestServerScript(unittest.TestCase):
@classmethod
def setUpClass(cls):
frappe.db.commit()
frappe.db.sql('truncate `tabServer Script`')
frappe.get_doc('User', 'Administrator').add_roles('Script Manager')
for script in scripts:
script_doc = frappe.get_doc(doctype ='Server Script')
script_doc.update(script)
script_doc.insert()
frappe.db.commit()
# @classmethod
# def tearDownClass(cls):
# frappe.db.sql('truncate `tabServer Script`')
def setUp(self):
frappe.cache().delete_value('server_script_map')
def test_doctype_event(self):
todo = frappe.get_doc(dict(doctype='ToDo', description='hello')).insert()
self.assertEqual(todo.status, 'Open')
todo = frappe.get_doc(dict(doctype='ToDo', description='test todo')).insert()
self.assertEqual(todo.status, 'Closed')
self.assertRaises(frappe.ValidationError, frappe.get_doc(dict(doctype='ToDo', description='validate me')).insert)
def test_api(self):
response = requests.post(get_site_url(frappe.local.site) + "/api/method/test_server_script")
self.assertEqual(response.status_code, 200)
self.assertEqual("hello", response.json()["message"])

View file

@ -3,7 +3,6 @@
from __future__ import unicode_literals
import frappe, unittest
import requests
from frappe.model.delete_doc import delete_doc
from frappe.utils.data import today, add_to_date

View file

@ -4,7 +4,7 @@
from __future__ import unicode_literals
import frappe
import os, json, datetime
import os, json
from frappe import _
from frappe.modules import scrub, get_module_path
@ -63,38 +63,21 @@ def generate_report_result(report, filters=None, user=None):
result = [list(t) for t in frappe.db.sql(report.query, filters)]
columns = [cstr(c[0]) for c in frappe.db.get_description()]
else:
module = report.module or frappe.db.get_value("DocType", report.ref_doctype, "module")
if report.is_standard == "Yes":
method_name = get_report_module_dotted_path(module, report.name) + ".execute"
threshold = 30
res = []
start_time = datetime.datetime.now()
# The JOB
res = frappe.get_attr(method_name)(frappe._dict(filters))
elif report.report_type == 'Script Report':
res = report.execute_script_report(filters)
end_time = datetime.datetime.now()
columns, result = res[0], res[1]
if len(res) > 2:
message = res[2]
if len(res) > 3:
chart = res[3]
if len(res) > 4:
data_to_be_printed = res[4]
execution_time = (end_time - start_time).seconds
if execution_time > threshold and not report.prepared_report:
report.db_set('prepared_report', 1)
frappe.cache().hset('report_execution_time', report.name, execution_time)
columns, result = res[0], res[1]
if len(res) > 2:
message = res[2]
if len(res) > 3:
chart = res[3]
if len(res) > 4:
data_to_be_printed = res[4]
if report.custom_columns:
columns = json.loads(report.custom_columns)
result = add_data_to_custom_columns(columns, result)
if report.custom_columns:
columns = json.loads(report.custom_columns)
result = add_data_to_custom_columns(columns, result)
if result:
result = get_filtered_data(report.ref_doctype, columns, result, user)
@ -355,11 +338,6 @@ def build_xlsx_data(columns, data, visible_idx,include_indentation):
return result
def get_report_module_dotted_path(module, report_name):
return frappe.local.module_app[scrub(module)] + "." + scrub(module) \
+ ".report." + scrub(report_name) + "." + scrub(report_name)
def add_total_row(result, columns, meta = None):
total_row = [""]*len(columns)
has_percent = []

View file

@ -9,6 +9,7 @@ import frappe.sessions
import frappe.desk.form.run_method
from frappe.utils.response import build_response
from frappe.utils import cint
from frappe.core.doctype.server_script.server_script_utils import run_server_script_api
from werkzeug.wrappers import Response
from six import string_types
@ -38,6 +39,10 @@ def execute_cmd(cmd, from_async=False):
cmd = hook
break
# via server script
if run_server_script_api(cmd):
return None
try:
method = get_attr(cmd)
except Exception as e:

View file

@ -17,6 +17,7 @@ from frappe.model.workflow import validate_workflow
from frappe.utils.global_search import update_global_search
from frappe.integrations.doctype.webhook import run_webhooks
from frappe.desk.form.document_follow import follow_document
from frappe.core.doctype.server_script.server_script_utils import run_server_script_for_doc_event
# once_only validation
# methods
@ -787,6 +788,7 @@ class Document(BaseDocument):
self.run_notifications(method)
run_webhooks(self, method)
run_server_script_for_doc_event(self, method)
return out

View file

@ -3,13 +3,29 @@
from __future__ import unicode_literals, print_function
import frappe
from frappe import _
from frappe import _, bold
from frappe.utils import cint
from frappe.model.naming import validate_name
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.utils.password import rename_password
from frappe.model.utils.user_settings import sync_user_settings, update_user_settings_data
@frappe.whitelist()
def update_document_title(doctype, docname, title_field, old_title, new_title, old_name, new_name):
"""
Update title from header in form view
"""
if new_title and old_title != new_title:
frappe.db.set_value(doctype, docname, title_field, new_title)
frappe.msgprint(_('Saved'), alert=True, indicator='green')
if new_name and old_name != new_name:
return rename_doc(doctype, old_name, new_name)
return old_name
@frappe.whitelist()
def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=False, ignore_if_exists=False):
"""
@ -83,6 +99,7 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F
frappe.clear_cache()
frappe.enqueue('frappe.utils.global_search.rebuild_for_doctype', doctype=doctype)
frappe.msgprint(_('Document renamed from {0} to {1}').format(bold(old), bold(new)), alert=True, indicator='green')
return new

View file

@ -52,10 +52,14 @@ frappe.ui.form.Toolbar = Class.extend({
this.set_indicator();
},
is_title_editable: function() {
if (this.frm.meta.title_field==="title"
let title_field = this.frm.meta.title_field;
let doc_field = this.frm.get_docfield(title_field);
if (title_field
&& this.frm.perm[0].write
&& !this.frm.get_docfield("title").options
&& !this.frm.doc.__islocal) {
&& !this.frm.doc.__islocal
&& doc_field.fieldtype === "Data"
&& !doc_field.read_only) {
return true;
} else {
return false;
@ -64,25 +68,82 @@ frappe.ui.form.Toolbar = Class.extend({
can_rename: function() {
return this.frm.perm[0].write && this.frm.meta.allow_rename && !this.frm.doc.__islocal;
},
setup_editable_title: function() {
var me = this;
this.page.$title_area.find(".title-text").on("click", function() {
if(me.is_title_editable()) {
frappe.prompt({fieldname: "title", fieldtype:"Data",
label: __("Title"), reqd: 1, "default": me.frm.doc.title },
function(data) {
if(data.title) {
me.frm.set_value("title", data.title);
if(!me.frm.doc.__islocal) {
me.frm.save_or_update();
} else {
me.set_title();
}
}
}, __("Edit Title"), __("Update"));
setup_editable_title: function () {
let me = this;
this.page.$title_area.find(".title-text").on("click", () => {
let fields = [];
let doctype = me.frm.doctype;
let docname = me.frm.doc.name;
let title_field = me.frm.meta.title_field || '';
// check if title is updateable
if (me.is_title_editable()) {
let title_field_label = me.frm.get_docfield(title_field).label;
fields.push({
label: __("New {0}", [__(title_field_label)]),
fieldname: "title",
fieldtype: "Data",
reqd: 1,
default: me.frm.doc[title_field]
});
}
if(me.can_rename()) {
me.frm.rename_doc();
// check if docname is updateable
if (me.can_rename()) {
fields.push(...[{
label: __("New Name"),
fieldname: "name",
fieldtype: "Data",
reqd: 1,
default: docname
}, {
label: __("Merge with existing"),
fieldname: "merge",
fieldtype: "Check",
default: 0
}]);
}
// create dialog
if (fields.length > 0) {
let d = new frappe.ui.Dialog({
title: __("Rename"),
fields: fields
});
d.show();
d.set_primary_action(__("Rename"), function () {
let args = d.get_values();
if (args.title != me.frm.doc[title_field] || args.name != docname) {
frappe.call({
method: "frappe.model.rename_doc.update_document_title",
args: {
doctype,
docname,
title_field,
old_title: me.frm.doc[title_field],
new_title: args.title,
old_name: docname,
new_name: args.name
},
btn: d.get_primary_btn()
}).then((res) => {
me.frm.reload_doc();
if (!res.exc && (args.name != docname)) {
$(document).trigger("rename", [doctype, docname, res.message || args.name]);
if (locals[doctype] && locals[doctype][docname]) delete locals[doctype][docname];
}
});
} else {
frappe.show_alert({
indicator: "yellow",
message: __("Unchanged")
});
}
d.hide();
});
}
});
},

View file

@ -215,19 +215,16 @@ $.extend(frappe.ui.toolbar, {
},
});
frappe.ui.toolbar.clear_cache = function() {
frappe.ui.toolbar.clear_cache = frappe.utils.throttle(function() {
frappe.assets.clear_local_storage();
frappe.call({
method: 'frappe.sessions.clear',
callback: function(r) {
if(!r.exc) {
frappe.show_alert({message:r.message, indicator:'green'});
location.reload(true);
}
}
frappe.xcall('frappe.sessions.clear').then(message => {
frappe.show_alert({
message: message,
indicator: 'green'
});
location.reload(true);
});
return false;
};
}, 10000);
frappe.ui.toolbar.show_about = function() {
try {

View file

@ -0,0 +1,10 @@
from __future__ import unicode_literals
import unittest
from frappe.utils.safe_exec import safe_exec
class TestSafeExec(unittest.TestCase):
def test_import_fails(self):
self.assertRaises(ImportError, safe_exec, 'import os')
def test_internal_attributes(self):
self.assertRaises(SyntaxError, safe_exec, '().__class__.__call__')

View file

@ -63,7 +63,7 @@ class BackupGenerator:
site = site.replace('.', '_')
#Generate a random name using today's date and a 8 digit random number
for_db = todays_date + "-" + site + "-database.sql"
for_db = todays_date + "-" + site + "-database.sql.gz"
for_public_files = todays_date + "-" + site + "-files.tar"
for_private_files = todays_date + "-" + site + "-private-files.tar"
backup_path = get_backup_path()
@ -111,14 +111,9 @@ class BackupGenerator:
args = dict([item[0], frappe.utils.esc(item[1], '$ ')]
for item in self.__dict__.copy().items())
cmd_string = """mysqldump --single-transaction --quick --lock-tables=false -u %(user)s -p%(password)s %(db_name)s -h %(db_host)s > %(backup_path_db)s """ % args
cmd_string = """mysqldump --single-transaction --quick --lock-tables=false -u %(user)s -p%(password)s %(db_name)s -h %(db_host)s | gzip > %(backup_path_db)s """ % args
err, out = frappe.utils.execute_in_shell(cmd_string)
cmd_string = 'gzip %(backup_path_db)s '% args
err, out = frappe.utils.execute_in_shell(cmd_string)
self.backup_path_db = "{0}.gz".format(self.backup_path_db)
def send_email(self):
"""
Sends the link to backup file located at erpnext/backups

View file

@ -4,6 +4,7 @@ from __future__ import unicode_literals
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
@ -14,7 +15,7 @@ def get_jenv():
undefined=DebugUndefined)
set_filters(jenv)
jenv.globals.update(get_allowed_functions_for_jenv())
jenv.globals.update(get_safe_globals())
frappe.local.jenv = jenv
@ -80,98 +81,6 @@ def render_template(template, context, is_path=None, safe_render=True):
throw(title="Jinja Template Error", msg="<pre>{template}</pre><pre>{tb}</pre>".format(template=template, tb=get_traceback()))
def get_allowed_functions_for_jenv():
import os, json
import frappe
import frappe.utils
import frappe.utils.data
from frappe.model.document import get_controller
from frappe.website.utils import (get_shade, get_toc, get_next_link)
from frappe.modules import scrub
import mimetypes
from html2text import html2text
from frappe.www.printview import get_visible_columns
datautils = {}
if frappe.db:
date_format = frappe.db.get_default("date_format") or "yyyy-mm-dd"
else:
date_format = 'yyyy-mm-dd'
for key, obj in frappe.utils.data.__dict__.items():
if key.startswith("_"):
# ignore
continue
if hasattr(obj, "__call__"):
# only allow functions
datautils[key] = obj
if "_" in getattr(frappe.local, 'form_dict', {}):
del frappe.local.form_dict["_"]
user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest"
out = {
# make available limited methods of frappe
"frappe": {
"_": frappe._,
"get_url": frappe.utils.get_url,
'format': frappe.format_value,
"format_value": frappe.format_value,
'date_format': date_format,
"format_date": frappe.utils.data.global_date_format,
"form_dict": getattr(frappe.local, 'form_dict', {}),
"get_hooks": frappe.get_hooks,
"get_meta": frappe.get_meta,
"get_doc": frappe.get_doc,
"get_cached_doc": frappe.get_cached_doc,
"get_list": frappe.get_list,
"get_all": frappe.get_all,
'get_system_settings': frappe.get_system_settings,
"utils": datautils,
"user": user,
"get_fullname": frappe.utils.get_fullname,
"get_gravatar": frappe.utils.get_gravatar_url,
"full_name": frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest",
"render_template": frappe.render_template,
"request": getattr(frappe.local, 'request', {}),
'session': {
'user': user,
'csrf_token': frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else ''
},
"socketio_port": frappe.conf.socketio_port,
},
'style': {
'border_color': '#d1d8dd'
},
'get_toc': get_toc,
'get_next_link': get_next_link,
"_": frappe._,
"get_shade": get_shade,
"scrub": scrub,
"guess_mimetype": mimetypes.guess_type,
'html2text': html2text,
'json': json,
"dev_server": 1 if os.environ.get('DEV_SERVER', False) else 0
}
if not frappe.flags.in_setup_help:
out['get_visible_columns'] = get_visible_columns
out['frappe']['date_format'] = date_format
out['frappe']["db"] = {
"get_value": frappe.db.get_value,
"get_single_value": frappe.db.get_single_value,
"get_default": frappe.db.get_default,
"escape": frappe.db.escape,
}
# load jenv methods from hooks.py
for method_name, method_definition in get_jenv_customization("methods"):
out[method_name] = frappe.get_attr(method_definition)
return out
def get_jloader():
import frappe
if not getattr(frappe.local, 'jloader', None):
@ -216,18 +125,3 @@ def set_filters(jenv):
jenv.filters["abs_url"] = abs_url
if frappe.flags.in_setup_help: return
# load jenv_filters from hooks.py
for filter_name, filter_function in get_jenv_customization("filters"):
jenv.filters[filter_name] = frappe.get_attr(filter_function)
def get_jenv_customization(customizable_type):
import frappe
if getattr(frappe.local, "site", None):
for app in frappe.get_installed_apps():
for jenv_customizable, jenv_customizable_definition in frappe.get_hooks(app_name=app).get("jenv", {}).items():
if customizable_type == jenv_customizable:
for data in jenv_customizable_definition:
split_data = data.split(":")
yield split_data[0], split_data[1]

141
frappe/utils/safe_exec.py Normal file
View file

@ -0,0 +1,141 @@
import os, json, inspect
import mimetypes
from html2text import html2text
from RestrictedPython import compile_restricted, safe_globals
import frappe
import frappe.utils
import frappe.utils.data
from frappe.website.utils import (get_shade, get_toc, get_next_link)
from frappe.modules import scrub
from frappe.www.printview import get_visible_columns
import frappe.exceptions
class ServerScriptNotEnabled(frappe.PermissionError): pass
def safe_exec(script, _globals=None, _locals=None):
# script reports must be enabled via site_config.json
if not frappe.conf.server_script_enabled:
frappe.msgprint('Please Enable Server Scripts')
raise ServerScriptNotEnabled
# build globals
exec_globals = get_safe_globals()
if _globals:
exec_globals.update(_globals)
# execute script compiled by RestrictedPython
exec(compile_restricted(script), exec_globals, _locals) # pylint: disable=exec-used
def get_safe_globals():
datautils = {}
if frappe.db:
date_format = frappe.db.get_default("date_format") or "yyyy-mm-dd"
else:
date_format = 'yyyy-mm-dd'
add_module_properties(frappe.utils.data, datautils, lambda obj: hasattr(obj, "__call__"))
if "_" in getattr(frappe.local, 'form_dict', {}):
del frappe.local.form_dict["_"]
user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest"
out = frappe._dict(
# make available limited methods of frappe
json = json,
dict = dict,
frappe = frappe._dict(
_ = frappe._,
_dict = frappe._dict,
flags = frappe.flags,
format = frappe.format_value,
format_value = frappe.format_value,
date_format = date_format,
format_date = frappe.utils.data.global_date_format,
form_dict = getattr(frappe.local, 'form_dict', {}),
get_meta = frappe.get_meta,
get_doc = frappe.get_doc,
get_cached_doc = frappe.get_cached_doc,
get_list = frappe.get_list,
get_all = frappe.get_all,
get_system_settings = frappe.get_system_settings,
utils = datautils,
get_url = frappe.utils.get_url,
render_template = frappe.render_template,
msgprint = frappe.msgprint,
user = user,
get_fullname = frappe.utils.get_fullname,
get_gravatar = frappe.utils.get_gravatar_url,
full_name = frappe.local.session.data.full_name if getattr(frappe.local, "session", None) else "Guest",
request = getattr(frappe.local, 'request', {}),
session = frappe._dict(
user = user,
csrf_token = frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else ''
),
socketio_port = frappe.conf.socketio_port,
get_hooks = frappe.get_hooks,
),
style = frappe._dict(
border_color = '#d1d8dd'
),
get_toc = get_toc,
get_next_link = get_next_link,
_ = frappe._,
get_shade = get_shade,
scrub = scrub,
guess_mimetype = mimetypes.guess_type,
html2text = html2text,
dev_server = 1 if os.environ.get('DEV_SERVER', False) else 0
)
add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception))
if not frappe.flags.in_setup_help:
out.get_visible_columns = get_visible_columns
out.frappe.date_format = date_format
out.frappe.db = frappe._dict(
get_list = frappe.get_list,
get_all = frappe.get_all,
get_value = frappe.db.get_value,
get_single_value = frappe.db.get_single_value,
get_default = frappe.db.get_default,
escape = frappe.db.escape,
)
if frappe.response:
out.frappe.response = frappe.response
out.update(safe_globals)
# default writer allows write access
out._write_ = _write
out._getitem_ = _getitem
return out
def _getitem(obj, key):
# guard function for RestrictedPython
# allow any key to be accessed as long as it does not start with underscore
if isinstance(key, str) and key.startswith('_'):
raise SyntaxError('Key starts with _')
return obj[key]
def _write(obj):
# guard function for RestrictedPython
# allow writing to any object
return obj
def add_module_properties(module, data, filter_method):
for key, obj in module.__dict__.items():
if key.startswith("_"):
# ignore
continue
if filter_method(obj):
# only allow functions
data[key] = obj

View file

@ -18,6 +18,7 @@
"end_date",
"sb1",
"content_type",
"dynamic_template",
"main_section",
"main_section_md",
"main_section_html",
@ -235,6 +236,12 @@
"fieldname": "set_meta_tags",
"fieldtype": "Button",
"label": "Set Meta Tags"
},
{
"default": "0",
"fieldname": "dynamic_template",
"fieldtype": "Check",
"label": "Dynamic Template"
}
],
"has_web_view": 1,

View file

@ -30,6 +30,7 @@ class WebPage(WebsiteGenerator):
def get_context(self, context):
context.main_section = get_html_content_based_on_type(self, 'main_section', self.content_type)
self.render_dynamic(context)
# if static page, get static content
if context.slideshow:
@ -57,7 +58,7 @@ class WebPage(WebsiteGenerator):
def render_dynamic(self, context):
# dynamic
is_jinja = "<!-- jinja -->" in context.main_section
is_jinja = context.dynamic_template or "<!-- jinja -->" in context.main_section
if is_jinja or ("{{" in context.main_section):
try:
context["main_section"] = render_template(context.main_section,

View file

@ -65,3 +65,4 @@ Pygments==2.2.0
frontmatter
PyYAML==3.13
xlrd
RestrictedPython==5.0