Просмотр исходного кода

Merge branch 'develop' into container-js-fix

version-14
Suraj Shetty 3 лет назад
committed by GitHub
Родитель
Сommit
7cb293799a
Не найден GPG ключ соответствующий данной подписи Идентификатор GPG ключа: 4AEE18F83AFDEB23
14 измененных файлов: 568 добавлений и 47 удалений
  1. +14
    -5
      frappe/__init__.py
  2. +1
    -0
      frappe/client.py
  3. +0
    -7
      frappe/core/doctype/server_script/server_script_utils.py
  4. +168
    -0
      frappe/custom/fixtures/temp_doctype.json
  5. +168
    -0
      frappe/custom/fixtures/temp_singles.json
  6. +17
    -3
      frappe/handler.py
  7. +1
    -1
      frappe/integrations/doctype/webhook/__init__.py
  8. +10
    -5
      frappe/model/db_query.py
  9. +3
    -0
      frappe/public/js/frappe/form/controls/datetime.js
  10. +19
    -16
      frappe/public/scss/desk/page.scss
  11. +81
    -0
      frappe/tests/test_fixture_import.py
  12. +24
    -1
      frappe/tests/test_safe_exec.py
  13. +60
    -7
      frappe/utils/safe_exec.py
  14. +2
    -2
      frappe/www/404.html

+ 14
- 5
frappe/__init__.py Просмотреть файл

@@ -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 doc: [optional] Checks User permissions for given doc.
:param user: [optional] Check for given user. Default: current user. :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).""" :param parent_doctype: Required when checking permission for a child DocType (unless doc is specified)."""
import frappe.permissions

if not doctype and doc: if not doctype and doc:
doctype = doc.doctype doctype = doc.doctype


import frappe.permissions
out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user, out = frappe.permissions.has_permission(doctype, ptype, doc=doc, verbose=verbose, user=user,
raise_exception=throw, parent_doctype=parent_doctype) raise_exception=throw, parent_doctype=parent_doctype)

if throw and not out: 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 return out




+ 1
- 0
frappe/client.py Просмотреть файл

@@ -32,6 +32,7 @@ def get_list(doctype, fields=None, filters=None, order_by=None,


args = frappe._dict( args = frappe._dict(
doctype=doctype, doctype=doctype,
parent_doctype=parent,
fields=fields, fields=fields,
filters=filters, filters=filters,
or_filters=or_filters, or_filters=or_filters,


+ 0
- 7
frappe/core/doctype/server_script/server_script_utils.py Просмотреть файл

@@ -19,13 +19,6 @@ EVENT_MAP = {
'on_update_after_submit': 'After Save (Submitted Document)' '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): def run_server_script_for_doc_event(doc, event):
# run document event method # run document event method
if not event in EVENT_MAP: if not event in EVENT_MAP:


+ 168
- 0
frappe/custom/fixtures/temp_doctype.json Просмотреть файл

@@ -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"
}
]
}

+ 168
- 0
frappe/custom/fixtures/temp_singles.json Просмотреть файл

@@ -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"
}
]
}

+ 17
- 3
frappe/handler.py Просмотреть файл

@@ -12,7 +12,7 @@ from frappe.utils.response import build_response
from frappe.utils.csvutils import build_csv_response from frappe.utils.csvutils import build_csv_response
from frappe.utils.image import optimize_image from frappe.utils.image import optimize_image
from mimetypes import guess_type 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', ALLOWED_MIMETYPES = ('image/png', 'image/jpeg', 'application/pdf', 'application/msword',
@@ -49,8 +49,9 @@ def execute_cmd(cmd, from_async=False):
break break


# via server script # 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: try:
method = get_attr(cmd) method = get_attr(cmd)
@@ -66,7 +67,20 @@ def execute_cmd(cmd, from_async=False):


return frappe.call(method, **frappe.form_dict) 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): def is_valid_http_method(method):
if frappe.flags.in_safe_exec:
return

http_method = frappe.local.request.method http_method = frappe.local.request.method


if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]: if http_method not in frappe.allowed_http_methods_for_whitelisted_func[method]:


+ 1
- 1
frappe/integrations/doctype/webhook/__init__.py Просмотреть файл

@@ -7,7 +7,7 @@ import frappe


def run_webhooks(doc, method): def run_webhooks(doc, method):
'''Run webhooks for this 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 return


if frappe.flags.webhooks_executed is None: if frappe.flags.webhooks_executed is None:


+ 10
- 5
frappe/model/db_query.py Просмотреть файл

@@ -36,10 +36,12 @@ class DatabaseQuery(object):
ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False, ignore_ifnull=False, save_user_settings=False, save_user_settings_fields=False,
update=None, add_total_row=None, user_settings=None, reference_doctype=None, 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: 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)) frappe.flags.error_message = _('Insufficient Permission for {0}').format(frappe.bold(self.doctype))
raise frappe.PermissionError(self.doctype) raise frappe.PermissionError(self.doctype)


@@ -787,12 +789,15 @@ class DatabaseQuery(object):
def check_parent_permission(parent, child_doctype): def check_parent_permission(parent, child_doctype):
if parent: if parent:
# User may pass fake parent and get the information from the child table # 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 raise frappe.PermissionError


if frappe.permissions.has_permission(parent): if frappe.permissions.has_permission(parent):
return return

# Either parent not passed or the user doesn't have permission on parent doctype of child table! # Either parent not passed or the user doesn't have permission on parent doctype of child table!
raise frappe.PermissionError raise frappe.PermissionError




+ 3
- 0
frappe/public/js/frappe/form/controls/datetime.js Просмотреть файл

@@ -81,6 +81,9 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co


get_model_value() { get_model_value() {
let value = super.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); return frappe.datetime.get_datetime_as_string(value);
} }
}; };

+ 19
- 16
frappe/public/scss/desk/page.scss Просмотреть файл

@@ -156,26 +156,29 @@
.result, .no-result, .freeze { .result, .no-result, .freeze {
min-height: #{"calc(100vh - 284px)"}; 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;
} }
} }
} }


+ 81
- 0
frappe/tests/test_fixture_import.py Просмотреть файл

@@ -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])

+ 24
- 1
frappe/tests/test_safe_exec.py Просмотреть файл

@@ -31,4 +31,27 @@ class TestSafeExec(unittest.TestCase):
self.assertEqual(frappe.db.sql("SELECT Max(name) FROM tabUser"), _locals["out"]) self.assertEqual(frappe.db.sql("SELECT Max(name) FROM tabUser"), _locals["out"])


def test_safe_query_builder(self): 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)""")

+ 60
- 7
frappe/utils/safe_exec.py Просмотреть файл

@@ -14,11 +14,12 @@ import frappe.integrations.utils
import frappe.utils import frappe.utils
import frappe.utils.data import frappe.utils.data
from frappe import _ from frappe import _
from frappe.handler import execute_cmd
from frappe.frappeclient import FrappeClient from frappe.frappeclient import FrappeClient
from frappe.modules import scrub from frappe.modules import scrub
from frappe.website.utils import get_next_link, get_shade, get_toc from frappe.website.utils import get_next_link, get_shade, get_toc
from frappe.www.printview import get_visible_columns from frappe.www.printview import get_visible_columns
from frappe.utils.background_jobs import enqueue, get_jobs


class ServerScriptNotEnabled(frappe.PermissionError): class ServerScriptNotEnabled(frappe.PermissionError):
pass pass
@@ -74,7 +75,9 @@ def get_safe_globals():


add_data_utils(datautils) 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["_"] del frappe.local.form_dict["_"]


user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest" user = getattr(frappe.local, "session", None) and frappe.local.session.user or "Guest"
@@ -89,14 +92,16 @@ def get_safe_globals():
dict=dict, dict=dict,
log=frappe.log, log=frappe.log,
_dict=frappe._dict, _dict=frappe._dict,
args=form_dict,
frappe=NamespaceDict( frappe=NamespaceDict(
call=call_whitelisted_function,
flags=frappe._dict(), flags=frappe._dict(),
format=frappe.format_value, format=frappe.format_value,
format_value=frappe.format_value, format_value=frappe.format_value,
date_format=date_format, date_format=date_format,
time_format=time_format, time_format=time_format,
format_date=frappe.utils.data.global_date_format, format_date=frappe.utils.data.global_date_format,
form_dict=getattr(frappe.local, 'form_dict', {}),
form_dict=form_dict,
bold=frappe.bold, bold=frappe.bold,
copy_doc=frappe.copy_doc, copy_doc=frappe.copy_doc,
errprint=frappe.errprint, errprint=frappe.errprint,
@@ -132,6 +137,7 @@ def get_safe_globals():
make_post_request=frappe.integrations.utils.make_post_request, make_post_request=frappe.integrations.utils.make_post_request,
socketio_port=frappe.conf.socketio_port, socketio_port=frappe.conf.socketio_port,
get_hooks=get_hooks, get_hooks=get_hooks,
enqueue=safe_enqueue,
sanitize_html=frappe.utils.sanitize_html, sanitize_html=frappe.utils.sanitize_html,
log_error=frappe.log_error log_error=frappe.log_error
), ),
@@ -147,7 +153,8 @@ def get_safe_globals():
guess_mimetype=mimetypes.guess_type, guess_mimetype=mimetypes.guess_type,
html2text=html2text, html2text=html2text,
dev_server=1 if frappe._dev_server else 0, 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)) 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 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(): def get_python_builtins():
return { return {
'abs': abs, 'abs': abs,
@@ -221,9 +277,6 @@ def read_sql(query, *args, **kwargs):
raise frappe.PermissionError('Only SELECT SQL allowed in scripting') raise frappe.PermissionError('Only SELECT SQL allowed in scripting')
return frappe.db.sql(query, *args, **kwargs) 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): def _getitem(obj, key):
# guard function for RestrictedPython # guard function for RestrictedPython


+ 2
- 2
frappe/www/404.html Просмотреть файл

@@ -15,10 +15,10 @@
{{ _("There's nothing here") }} {{ _("There's nothing here") }}
</h2> </h2>
<div class="text-muted error-text"> <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>
<div class="mt-6 back-to-home"><a href='/' class='btn btn-primary'>{{ _("Back to Home") }}</a></div> <div class="mt-6 back-to-home"><a href='/' class='btn btn-primary'>{{ _("Back to Home") }}</a></div>
</div> </div>
</div> </div>


{% endblock %}
{% endblock %}

Загрузка…
Отмена
Сохранить