Merge branch 'develop' of https://github.com/frappe/frappe into integrated-notifications
This commit is contained in:
commit
fc5b712a3c
28 changed files with 864 additions and 978 deletions
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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",
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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]])
|
||||
|
||||
|
|
|
|||
0
frappe/core/doctype/server_script/__init__.py
Normal file
0
frappe/core/doctype/server_script/__init__.py
Normal file
8
frappe/core/doctype/server_script/server_script.js
Normal file
8
frappe/core/doctype/server_script/server_script.js
Normal 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) {
|
||||
|
||||
// }
|
||||
});
|
||||
98
frappe/core/doctype/server_script/server_script.json
Normal file
98
frappe/core/doctype/server_script/server_script.json
Normal 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
|
||||
}
|
||||
33
frappe/core/doctype/server_script/server_script.py
Normal file
33
frappe/core/doctype/server_script/server_script.py
Normal 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))
|
||||
|
||||
64
frappe/core/doctype/server_script/server_script_utils.py
Normal file
64
frappe/core/doctype/server_script/server_script_utils.py
Normal 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
|
||||
74
frappe/core/doctype/server_script/test_server_script.py
Normal file
74
frappe/core/doctype/server_script/test_server_script.py
Normal 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"])
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 = []
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
});
|
||||
}
|
||||
});
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
10
frappe/tests/test_safe_exec.py
Normal file
10
frappe/tests/test_safe_exec.py
Normal 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__')
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
141
frappe/utils/safe_exec.py
Normal 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
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -65,3 +65,4 @@ Pygments==2.2.0
|
|||
frontmatter
|
||||
PyYAML==3.13
|
||||
xlrd
|
||||
RestrictedPython==5.0
|
||||
Loading…
Add table
Reference in a new issue