@@ -740,17 +740,26 @@ def has_permission(doctype=None, ptype="read", doc=None, user=None, verbose=Fals | |||
:param doc: [optional] Checks User permissions for given doc. | |||
:param user: [optional] Check for given user. Default: current user. | |||
:param parent_doctype: Required when checking permission for a child DocType (unless doc is specified).""" | |||
import frappe.permissions | |||
if not doctype and doc: | |||
doctype = doc.doctype | |||
import frappe.permissions | |||
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user, | |||
raise_exception=throw, parent_doctype=parent_doctype) | |||
if throw and not out: | |||
if doc: | |||
frappe.throw(_("No permission for {0}").format(doc.doctype + " " + doc.name)) | |||
else: | |||
frappe.throw(_("No permission for {0}").format(doctype)) | |||
# mimics frappe.throw | |||
document_label = f"{doc.doctype} {doc.name}" if doc else doctype | |||
msgprint( | |||
_("No permission for {0}").format(document_label), | |||
raise_exception=ValidationError, | |||
title=None, | |||
indicator='red', | |||
is_minimizable=None, | |||
wide=None, | |||
as_list=False | |||
) | |||
return out | |||
@@ -32,6 +32,7 @@ def get_list(doctype, fields=None, filters=None, order_by=None, | |||
args = frappe._dict( | |||
doctype=doctype, | |||
parent_doctype=parent, | |||
fields=fields, | |||
filters=filters, | |||
or_filters=or_filters, | |||
@@ -19,13 +19,6 @@ EVENT_MAP = { | |||
'on_update_after_submit': 'After Save (Submitted Document)' | |||
} | |||
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: | |||
@@ -0,0 +1,168 @@ | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"name": "new-doctype-2", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"is_submittable": 0, | |||
"istable": 0, | |||
"issingle": 0, | |||
"is_tree": 0, | |||
"editable_grid": 1, | |||
"quick_entry": 1, | |||
"track_changes": 1, | |||
"track_seen": 0, | |||
"track_views": 0, | |||
"custom": 1, | |||
"beta": 0, | |||
"is_virtual": 0, | |||
"naming_rule": "", | |||
"name_case": "", | |||
"allow_rename": 1, | |||
"hide_toolbar": 0, | |||
"allow_copy": 0, | |||
"allow_import": 0, | |||
"allow_events_in_timeline": 0, | |||
"allow_auto_repeat": 0, | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"document_type": "", | |||
"show_preview_popup": 0, | |||
"show_name_in_global_search": 0, | |||
"email_append_to": 0, | |||
"read_only": 0, | |||
"in_create": 0, | |||
"has_web_view": 0, | |||
"allow_guest_to_view": 0, | |||
"index_web_pages_for_search": 1, | |||
"engine": "InnoDB", | |||
"permissions": [ | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocPerm", | |||
"name": "new-docperm-2", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"if_owner": 0, | |||
"permlevel": 0, | |||
"select": 0, | |||
"read": 1, | |||
"write": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"submit": 0, | |||
"cancel": 0, | |||
"amend": 0, | |||
"report": 1, | |||
"export": 1, | |||
"import": 0, | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"print": 1, | |||
"email": 1, | |||
"parent": "new-doctype-2", | |||
"parentfield": "permissions", | |||
"parenttype": "DocType", | |||
"idx": 1, | |||
"role": "System Manager" | |||
} | |||
], | |||
"__newname": "temp_doctype", | |||
"module": "Custom", | |||
"fields": [ | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocField", | |||
"name": "new-docfield-1", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"fieldtype": "Data", | |||
"precision": "", | |||
"non_negative": 0, | |||
"hide_days": 0, | |||
"hide_seconds": 0, | |||
"reqd": 1, | |||
"search_index": 0, | |||
"fetch_if_empty": 0, | |||
"hidden": 0, | |||
"bold": 0, | |||
"allow_in_quick_entry": 0, | |||
"translatable": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"report_hide": 0, | |||
"collapsible": 0, | |||
"hide_border": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"in_preview": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"read_only": 0, | |||
"allow_on_submit": 0, | |||
"ignore_user_permissions": 0, | |||
"allow_bulk_edit": 0, | |||
"permlevel": 0, | |||
"ignore_xss_filter": 0, | |||
"unique": 0, | |||
"no_copy": 0, | |||
"set_only_once": 0, | |||
"remember_last_selected_value": 0, | |||
"parent": "new-doctype-2", | |||
"parentfield": "fields", | |||
"parenttype": "DocType", | |||
"idx": 1, | |||
"__unedited": false, | |||
"label": "member_name" | |||
}, | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocField", | |||
"name": "new-docfield-2", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"fieldtype": "Data", | |||
"precision": "", | |||
"non_negative": 0, | |||
"hide_days": 0, | |||
"hide_seconds": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fetch_if_empty": 0, | |||
"hidden": 0, | |||
"bold": 0, | |||
"allow_in_quick_entry": 0, | |||
"translatable": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"report_hide": 0, | |||
"collapsible": 0, | |||
"hide_border": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"in_preview": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"read_only": 0, | |||
"allow_on_submit": 0, | |||
"ignore_user_permissions": 0, | |||
"allow_bulk_edit": 0, | |||
"permlevel": 0, | |||
"ignore_xss_filter": 0, | |||
"unique": 0, | |||
"no_copy": 0, | |||
"set_only_once": 0, | |||
"remember_last_selected_value": 0, | |||
"parent": "new-doctype-2", | |||
"parentfield": "fields", | |||
"parenttype": "DocType", | |||
"idx": 2, | |||
"__unedited": false, | |||
"label": "email" | |||
} | |||
] | |||
} |
@@ -0,0 +1,168 @@ | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocType", | |||
"name": "new-doctype-1", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"is_submittable": 0, | |||
"istable": 0, | |||
"issingle": 1, | |||
"is_tree": 0, | |||
"editable_grid": 1, | |||
"quick_entry": 0, | |||
"track_changes": 1, | |||
"track_seen": 0, | |||
"track_views": 0, | |||
"custom": 1, | |||
"beta": 0, | |||
"is_virtual": 0, | |||
"naming_rule": "", | |||
"name_case": "", | |||
"allow_rename": 1, | |||
"hide_toolbar": 0, | |||
"allow_copy": 0, | |||
"allow_import": 0, | |||
"allow_events_in_timeline": 0, | |||
"allow_auto_repeat": 0, | |||
"sort_field": "modified", | |||
"sort_order": "DESC", | |||
"document_type": "", | |||
"show_preview_popup": 0, | |||
"show_name_in_global_search": 0, | |||
"email_append_to": 0, | |||
"read_only": 0, | |||
"in_create": 0, | |||
"has_web_view": 0, | |||
"allow_guest_to_view": 0, | |||
"index_web_pages_for_search": 1, | |||
"engine": "InnoDB", | |||
"permissions": [ | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocPerm", | |||
"name": "new-docperm-1", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"if_owner": 0, | |||
"permlevel": 0, | |||
"select": 0, | |||
"read": 1, | |||
"write": 1, | |||
"create": 1, | |||
"delete": 1, | |||
"submit": 0, | |||
"cancel": 0, | |||
"amend": 0, | |||
"report": 1, | |||
"export": 1, | |||
"import": 0, | |||
"set_user_permissions": 0, | |||
"share": 1, | |||
"print": 1, | |||
"email": 1, | |||
"parent": "new-doctype-1", | |||
"parentfield": "permissions", | |||
"parenttype": "DocType", | |||
"idx": 1, | |||
"role": "System Manager" | |||
} | |||
], | |||
"__newname": "temp_singles", | |||
"module": "Custom", | |||
"fields": [ | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocField", | |||
"name": "new-docfield-1", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"fieldtype": "Data", | |||
"precision": "", | |||
"non_negative": 0, | |||
"hide_days": 0, | |||
"hide_seconds": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fetch_if_empty": 0, | |||
"hidden": 0, | |||
"bold": 0, | |||
"allow_in_quick_entry": 0, | |||
"translatable": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"report_hide": 0, | |||
"collapsible": 0, | |||
"hide_border": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"in_preview": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"read_only": 0, | |||
"allow_on_submit": 0, | |||
"ignore_user_permissions": 0, | |||
"allow_bulk_edit": 0, | |||
"permlevel": 0, | |||
"ignore_xss_filter": 0, | |||
"unique": 0, | |||
"no_copy": 0, | |||
"set_only_once": 0, | |||
"remember_last_selected_value": 0, | |||
"parent": "new-doctype-1", | |||
"parentfield": "fields", | |||
"parenttype": "DocType", | |||
"idx": 1, | |||
"__unedited": false, | |||
"label": "member_name" | |||
}, | |||
{ | |||
"docstatus": 0, | |||
"doctype": "DocField", | |||
"name": "new-docfield-2", | |||
"__islocal": 1, | |||
"__unsaved": 1, | |||
"owner": "Administrator", | |||
"fieldtype": "Data", | |||
"precision": "", | |||
"non_negative": 0, | |||
"hide_days": 0, | |||
"hide_seconds": 0, | |||
"reqd": 0, | |||
"search_index": 0, | |||
"fetch_if_empty": 0, | |||
"hidden": 0, | |||
"bold": 0, | |||
"allow_in_quick_entry": 0, | |||
"translatable": 0, | |||
"print_hide": 0, | |||
"print_hide_if_no_value": 0, | |||
"report_hide": 0, | |||
"collapsible": 0, | |||
"hide_border": 0, | |||
"in_list_view": 0, | |||
"in_standard_filter": 0, | |||
"in_preview": 0, | |||
"in_filter": 0, | |||
"in_global_search": 0, | |||
"read_only": 0, | |||
"allow_on_submit": 0, | |||
"ignore_user_permissions": 0, | |||
"allow_bulk_edit": 0, | |||
"permlevel": 0, | |||
"ignore_xss_filter": 0, | |||
"unique": 0, | |||
"no_copy": 0, | |||
"set_only_once": 0, | |||
"remember_last_selected_value": 0, | |||
"parent": "new-doctype-1", | |||
"parentfield": "fields", | |||
"parenttype": "DocType", | |||
"idx": 2, | |||
"__unedited": false, | |||
"label": "email" | |||
} | |||
] | |||
} |
@@ -12,7 +12,7 @@ from frappe.utils.response import build_response | |||
from frappe.utils.csvutils import build_csv_response | |||
from frappe.utils.image import optimize_image | |||
from mimetypes import guess_type | |||
from frappe.core.doctype.server_script.server_script_utils import run_server_script_api | |||
from frappe.core.doctype.server_script.server_script_utils import get_server_script_map | |||
ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/msword', | |||
@@ -49,8 +49,9 @@ def execute_cmd(cmd, from_async=False): | |||
break | |||
# via server script | |||
if run_server_script_api(cmd): | |||
return None | |||
server_script = get_server_script_map().get('_api', {}).get(cmd) | |||
if server_script: | |||
return run_server_script(server_script) | |||
try: | |||
method = get_attr(cmd) | |||
@@ -66,7 +67,20 @@ def execute_cmd(cmd, from_async=False): | |||
return frappe.call(method, **frappe.form_dict) | |||
def run_server_script(server_script): | |||
response = frappe.get_doc('Server Script', server_script).execute_method() | |||
# some server scripts return output using flags (empty dict by default), | |||
# while others directly modify frappe.response | |||
# return flags if not empty dict (this overwrites frappe.response.message) | |||
if response != {}: | |||
return response | |||
def is_valid_http_method(method): | |||
if frappe.flags.in_safe_exec: | |||
return | |||
http_method = frappe.local.request.method | |||
if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]: | |||
@@ -7,7 +7,7 @@ import frappe | |||
def run_webhooks(doc, method): | |||
'''Run webhooks for this method''' | |||
if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install: | |||
if frappe.flags.in_import or frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_migrate: | |||
return | |||
if frappe.flags.webhooks_executed is None: | |||
@@ -36,10 +36,12 @@ class DatabaseQuery(object): | |||
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False, | |||
update=None, add_total_row=None, user_settings=None, reference_doctype=None, | |||
run=True, strict=True, pluck=None, ignore_ddl=False, parent_doctype=None) -> List: | |||
if not ignore_permissions and \ | |||
not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype) and \ | |||
not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype): | |||
if ( | |||
not ignore_permissions | |||
and not frappe.has_permission(self.doctype, "select", user=user, parent_doctype=parent_doctype) | |||
and not frappe.has_permission(self.doctype, "read", user=user, parent_doctype=parent_doctype) | |||
): | |||
frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype)) | |||
raise frappe.PermissionError(self.doctype) | |||
@@ -787,12 +789,15 @@ class DatabaseQuery(object): | |||
def check_parent_permission(parent, child_doctype): | |||
if parent: | |||
# User may pass fake parent and get the information from the child table | |||
if child_doctype and not frappe.db.exists('DocField', | |||
{'parent': parent, 'options': child_doctype}): | |||
if child_doctype and not ( | |||
frappe.db.exists('DocField', {'parent': parent, 'options': child_doctype}) | |||
or frappe.db.exists('Custom Field', {'dt': parent, 'options': child_doctype}) | |||
): | |||
raise frappe.PermissionError | |||
if frappe.permissions.has_permission(parent): | |||
return | |||
# Either parent not passed or the user doesn't have permission on parent doctype of child table! | |||
raise frappe.PermissionError | |||
@@ -81,6 +81,9 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co | |||
get_model_value() { | |||
let value = super.get_model_value(); | |||
if (!value && !this.doc) { | |||
value = this.last_value; | |||
} | |||
return frappe.datetime.get_datetime_as_string(value); | |||
} | |||
}; |
@@ -156,26 +156,29 @@ | |||
.result, .no-result, .freeze { | |||
min-height: #{"calc(100vh - 284px)"}; | |||
} | |||
} | |||
.msg-box { | |||
margin-bottom: 4em; | |||
font-size: var(--text-sm); | |||
.msg-box { | |||
margin-bottom: 4em; | |||
font-size: var(--text-sm); | |||
// To compensate for perceived centering | |||
.null-state { | |||
height: 85px; | |||
width: auto; | |||
margin-bottom: var(--margin-md); | |||
img { | |||
fill: var(--fg-color); | |||
} | |||
// To compensate for perceived centering | |||
.null-state { | |||
height: 85px; | |||
width: auto; | |||
margin-bottom: var(--margin-md); | |||
img { | |||
fill: var(--fg-color); | |||
} | |||
} | |||
p { | |||
font-size: var(--text-md); | |||
} | |||
.meta-description { | |||
width: 45%; | |||
margin-right: auto; | |||
margin-left: auto; | |||
} | |||
.meta-description { | |||
width: 45%; | |||
margin-right: auto; | |||
margin-left: auto; | |||
} | |||
} | |||
} | |||
@@ -0,0 +1,81 @@ | |||
import os | |||
import unittest | |||
from typing import List | |||
import frappe | |||
from frappe.core.doctype.data_import.data_import import export_json, import_doc | |||
from frappe.desk.form.save import savedocs | |||
from frappe.model.delete_doc import delete_doc | |||
class TestFixtureImport(unittest.TestCase): | |||
def create_new_doctype(self, DocType: str) -> None: | |||
file = frappe.get_app_path("frappe", "custom", "fixtures", f"{DocType}.json") | |||
file = open(file, "r") | |||
doc = file.read() | |||
file.close() | |||
savedocs(doc, "Save") | |||
def insert_dummy_data_and_export(self, DocType: str, dummy_name_list: List[str]) -> str: | |||
for name in dummy_name_list: | |||
doc = frappe.get_doc({"doctype": DocType, "member_name": name}) | |||
doc.insert() | |||
path_to_exported_fixtures = os.path.join(os.getcwd(), f"{DocType}_data.json") | |||
export_json(DocType, path_to_exported_fixtures) | |||
return path_to_exported_fixtures | |||
def test_fixtures_import(self): | |||
self.assertFalse(frappe.db.exists("DocType", "temp_doctype")) | |||
self.create_new_doctype("temp_doctype") | |||
dummy_name_list = ["jhon", "jane"] | |||
path_to_exported_fixtures = self.insert_dummy_data_and_export("temp_doctype", dummy_name_list) | |||
frappe.db.truncate("temp_doctype") | |||
import_doc(path_to_exported_fixtures) | |||
delete_doc("DocType", "temp_doctype", delete_permanently=True) | |||
os.remove(path_to_exported_fixtures) | |||
self.assertEqual(frappe.db.count("temp_doctype"), len(dummy_name_list)) | |||
data = frappe.get_all("temp_doctype", "member_name") | |||
frappe.db.truncate("temp_doctype") | |||
imported_data = set() | |||
for item in data: | |||
imported_data.add(item["member_name"]) | |||
self.assertEqual(set(dummy_name_list), imported_data) | |||
def test_singles_fixtures_import(self): | |||
self.assertFalse(frappe.db.exists("DocType", "temp_singles")) | |||
self.create_new_doctype("temp_singles") | |||
dummy_name_list = ["Phoebe"] | |||
path_to_exported_fixtures = self.insert_dummy_data_and_export("temp_singles", dummy_name_list) | |||
singles_doctype = frappe.qb.DocType("Singles") | |||
truncate_query = ( | |||
frappe.qb.from_(singles_doctype) | |||
.delete() | |||
.where(singles_doctype.doctype == "temp_singles") | |||
) | |||
truncate_query.run() | |||
import_doc(path_to_exported_fixtures) | |||
delete_doc("DocType", "temp_singles", delete_permanently=True) | |||
os.remove(path_to_exported_fixtures) | |||
data = frappe.db.get_single_value("temp_singles", "member_name") | |||
truncate_query.run() | |||
self.assertEqual(data, dummy_name_list[0]) |
@@ -31,4 +31,27 @@ class TestSafeExec(unittest.TestCase): | |||
self.assertEqual(frappe.db.sql("SELECT Max(name) FROM tabUser"), _locals["out"]) | |||
def test_safe_query_builder(self): | |||
self.assertRaises(frappe.PermissionError, safe_exec, '''frappe.qb.from_("User").delete().run()''') | |||
self.assertRaises(frappe.PermissionError, safe_exec, '''frappe.qb.from_("User").delete().run()''') | |||
def test_call(self): | |||
# call non whitelisted method | |||
self.assertRaises( | |||
frappe.PermissionError, | |||
safe_exec, | |||
"""frappe.call("frappe.get_user")""" | |||
) | |||
# call whitelisted method | |||
safe_exec("""frappe.call("ping")""") | |||
def test_enqueue(self): | |||
# enqueue non whitelisted method | |||
self.assertRaises( | |||
frappe.PermissionError, | |||
safe_exec, | |||
"""frappe.enqueue("frappe.get_user", now=True)""" | |||
) | |||
# enqueue whitelisted method | |||
safe_exec("""frappe.enqueue("ping", now=True)""") |
@@ -14,11 +14,12 @@ import frappe.integrations.utils | |||
import frappe.utils | |||
import frappe.utils.data | |||
from frappe import _ | |||
from frappe.handler import execute_cmd | |||
from frappe.frappeclient import FrappeClient | |||
from frappe.modules import scrub | |||
from frappe.website.utils import get_next_link, get_shade, get_toc | |||
from frappe.www.printview import get_visible_columns | |||
from frappe.utils.background_jobs import enqueue, get_jobs | |||
class ServerScriptNotEnabled(frappe.PermissionError): | |||
pass | |||
@@ -74,7 +75,9 @@ def get_safe_globals(): | |||
add_data_utils(datautils) | |||
if "_" in getattr(frappe.local, 'form_dict', {}): | |||
form_dict = getattr(frappe.local, 'form_dict', frappe._dict()) | |||
if "_" in form_dict: | |||
del frappe.local.form_dict["_"] | |||
user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest" | |||
@@ -89,14 +92,16 @@ def get_safe_globals(): | |||
dict=dict, | |||
log=frappe.log, | |||
_dict=frappe._dict, | |||
args=form_dict, | |||
frappe=NamespaceDict( | |||
call=call_whitelisted_function, | |||
flags=frappe._dict(), | |||
format=frappe.format_value, | |||
format_value=frappe.format_value, | |||
date_format=date_format, | |||
time_format=time_format, | |||
format_date=frappe.utils.data.global_date_format, | |||
form_dict=getattr(frappe.local, 'form_dict', {}), | |||
form_dict=form_dict, | |||
bold=frappe.bold, | |||
copy_doc=frappe.copy_doc, | |||
errprint=frappe.errprint, | |||
@@ -132,6 +137,7 @@ def get_safe_globals(): | |||
make_post_request=frappe.integrations.utils.make_post_request, | |||
socketio_port=frappe.conf.socketio_port, | |||
get_hooks=get_hooks, | |||
enqueue=safe_enqueue, | |||
sanitize_html=frappe.utils.sanitize_html, | |||
log_error=frappe.log_error | |||
), | |||
@@ -147,7 +153,8 @@ def get_safe_globals(): | |||
guess_mimetype=mimetypes.guess_type, | |||
html2text=html2text, | |||
dev_server=1 if frappe._dev_server else 0, | |||
run_script=run_script | |||
run_script=run_script, | |||
is_job_queued=is_job_queued, | |||
) | |||
add_module_properties(frappe.exceptions, out.frappe, lambda obj: inspect.isclass(obj) and issubclass(obj, Exception)) | |||
@@ -190,6 +197,55 @@ def get_safe_globals(): | |||
return out | |||
def is_job_queued(job_name, queue="default"): | |||
''' | |||
:param job_name: used to identify a queued job, usually dotted path to function | |||
:param queue: should be either long, default or short | |||
''' | |||
site = frappe.local.site | |||
queued_jobs = get_jobs(site=site, queue=queue, key='job_name').get(site) | |||
return queued_jobs and job_name in queued_jobs | |||
def safe_enqueue(function, **kwargs): | |||
''' | |||
Enqueue function to be executed using a background worker | |||
Accepts frappe.enqueue params like job_name, queue, timeout, etc. | |||
in addition to params to be passed to function | |||
:param function: whitelised function or API Method set in Server Script | |||
''' | |||
return enqueue( | |||
'frappe.utils.safe_exec.call_whitelisted_function', | |||
function=function, | |||
**kwargs | |||
) | |||
def call_whitelisted_function(function, **kwargs): | |||
'''Executes a whitelisted function or Server Script of type API''' | |||
return call_with_form_dict(lambda: execute_cmd(function), kwargs) | |||
def run_script(script, **kwargs): | |||
'''run another server script''' | |||
return call_with_form_dict( | |||
lambda: frappe.get_doc('Server Script', script).execute_method(), | |||
kwargs | |||
) | |||
def call_with_form_dict(function, kwargs): | |||
# temporarily update form_dict, to use inside below call | |||
form_dict = getattr(frappe.local, 'form_dict', frappe._dict()) | |||
if kwargs: | |||
frappe.local.form_dict = form_dict.copy().update(kwargs) | |||
try: | |||
return function() | |||
finally: | |||
frappe.local.form_dict = form_dict | |||
def get_python_builtins(): | |||
return { | |||
'abs': abs, | |||
@@ -221,9 +277,6 @@ def read_sql(query, *args, **kwargs): | |||
raise frappe.PermissionError('Only SELECT SQL allowed in scripting') | |||
return frappe.db.sql(query, *args, **kwargs) | |||
def run_script(script): | |||
'''run another server script''' | |||
return frappe.get_doc('Server Script', script).execute_method() | |||
def _getitem(obj, key): | |||
# guard function for RestrictedPython | |||
@@ -15,10 +15,10 @@ | |||
{{ _("There's nothing here") }} | |||
</h2> | |||
<div class="text-muted error-text"> | |||
{{ _("The page you are looking for have gone missing.") }} | |||
{{ _("The page you are looking for has gone missing.") }} | |||
</div> | |||
<div class="mt-6 back-to-home"><a href='/' class='btn btn-primary'>{{ _("Back to Home") }}</a></div> | |||
</div> | |||
</div> | |||
{% endblock %} | |||
{% endblock %} |