From d7abbe0e104aba30a96c94833ee9b0d2f9b49d13 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 14 Apr 2022 07:52:06 +0530
Subject: [PATCH 0001/2449] refactor: frappe.modules.utils
* Simplified logic and got rid of multi-level indents
* Better error & debug messages
* Better code readability
* Optimized conditions
* Added type hints
* Refactored raw query to QB
---
frappe/modules/utils.py | 323 ++++++++++++++++++++--------------------
1 file changed, 158 insertions(+), 165 deletions(-)
diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py
index 60eaefddc5..b7b9819093 100644
--- a/frappe/modules/utils.py
+++ b/frappe/modules/utils.py
@@ -1,75 +1,77 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
"""
Utilities for using modules
"""
import json
import os
+from glob import glob
+from textwrap import dedent
+from typing import TYPE_CHECKING, Dict, Optional, Tuple, Union
import frappe
-import frappe.utils
-from frappe import _
-from frappe.utils import cint
+from frappe import _, get_module_path, scrub
+from frappe.utils import cint, cstr, now_datetime
+
+if TYPE_CHECKING:
+ from types import ModuleType
+
+ from frappe.model.document import Document
-def export_module_json(doc, is_standard, module):
+doctype_python_modules = {}
+
+
+def export_module_json(doc: "Document", is_standard: bool, module: str) -> str:
"""Make a folder for the given doc and add its json file (make it a standard
- object that will be synced)"""
- if not frappe.flags.in_import and getattr(frappe.get_conf(), "developer_mode", 0) and is_standard:
- from frappe.modules.export_file import export_to_files
+ object that will be synced)
- # json
- export_to_files(
- record_list=[[doc.doctype, doc.name]], record_module=module, create_init=is_standard
- )
+ Returns the absolute file_path without the extension.
+ Eg: For exporting a Print Format "_Test Print Format 1", the return value will be
+ `/home/gavin/frappe-bench/apps/frappe/frappe/core/print_format/_test_print_format_1/_test_print_format_1`
+ """
+ if is_standard and frappe.flags.in_import and frappe.get_conf().developer_mode:
+ return
- path = os.path.join(
- frappe.get_module_path(module), scrub(doc.doctype), scrub(doc.name), scrub(doc.name)
- )
+ from frappe.modules.export_file import export_to_files
- return path
-
-
-def get_doc_module(module, doctype, name):
- """Get custom module for given document"""
- module_name = "{app}.{module}.{doctype}.{name}.{name}".format(
- app=frappe.local.module_app[scrub(module)],
- doctype=scrub(doctype),
- module=scrub(module),
- name=scrub(name),
+ export_to_files(
+ record_list=[[doc.doctype, doc.name]], record_module=module, create_init=is_standard
)
- return frappe.get_module(module_name)
+
+ return os.path.join(get_module_path(module), scrub(doc.doctype), scrub(doc.name), scrub(doc.name))
+
+
+def get_doc_module(module: str, doctype: str, name: str) -> "ModuleType":
+ """Get custom module for given document"""
+ name = scrub(name)
+ module = scrub(module)
+ doctype = scrub(doctype)
+ app = frappe.local.module_app[module]
+ return frappe.get_module(f"{app}.{module}.{doctype}.{name}.{name}")
@frappe.whitelist()
-def export_customizations(module, doctype, sync_on_migrate=0, with_permissions=0):
+def export_customizations(
+ module: str, doctype: str, sync_on_migrate: bool = False, with_permissions: bool = False
+):
"""Export Custom Field and Property Setter for the current document to the app folder.
This will be synced with bench migrate"""
+ if not frappe.get_conf().developer_mode:
+ frappe.throw(_("Only allowed to export customizations in developer mode"))
+
sync_on_migrate = cint(sync_on_migrate)
with_permissions = cint(with_permissions)
-
- if not frappe.get_conf().developer_mode:
- raise Exception("Not developer mode")
-
custom = {
- "custom_fields": [],
- "property_setters": [],
+ "custom_fields": frappe.get_all("Custom Field", fields="*", filters={"dt": doctype}),
+ "property_setters": frappe.get_all("Property Setter", fields="*", filters={"doc_type": doctype}),
"custom_perms": [],
- "links": [],
+ "links": frappe.get_all("DocType Link", fields="*", filters={"parent": doctype}),
"doctype": doctype,
"sync_on_migrate": sync_on_migrate,
}
- def add(_doctype):
- custom["custom_fields"] += frappe.get_all("Custom Field", fields="*", filters={"dt": _doctype})
- custom["property_setters"] += frappe.get_all(
- "Property Setter", fields="*", filters={"doc_type": _doctype}
- )
- custom["links"] += frappe.get_all("DocType Link", fields="*", filters={"parent": _doctype})
-
- add(doctype)
-
if with_permissions:
custom["custom_perms"] = frappe.get_all(
"Custom DocPerm", fields="*", filters={"parent": doctype}
@@ -77,41 +79,45 @@ def export_customizations(module, doctype, sync_on_migrate=0, with_permissions=0
# also update the custom fields and property setters for all child tables
for d in frappe.get_meta(doctype).get_table_fields():
- export_customizations(module, d.options, sync_on_migrate, with_permissions)
+ export_customizations(
+ module=module,
+ doctype=d.options,
+ sync_on_migrate=sync_on_migrate,
+ with_permissions=with_permissions,
+ )
if custom["custom_fields"] or custom["property_setters"] or custom["custom_perms"]:
folder_path = os.path.join(get_module_path(module), "custom")
- if not os.path.exists(folder_path):
- os.makedirs(folder_path)
+ file_path = os.path.join(folder_path, f"{scrub(doctype)}.json")
- path = os.path.join(folder_path, scrub(doctype) + ".json")
- with open(path, "w") as f:
+ os.makedirs(folder_path, exist_ok=True)
+ with open(file_path, "w") as f:
f.write(frappe.as_json(custom))
- frappe.msgprint(_("Customizations for {0} exported to: {1}").format(doctype, path))
+ frappe.msgprint(
+ _("Customizations for {0} exported to: {1}").format(doctype, file_path)
+ )
-def sync_customizations(app=None):
+def sync_customizations(app: Optional[str] = None):
"""Sync custom fields and property setters from custom folder in each app module"""
-
- if app:
- apps = [app]
- else:
- apps = frappe.get_installed_apps()
+ apps = frappe.get_installed_apps() if not app else [app]
for app_name in apps:
for module_name in frappe.local.app_modules.get(app_name) or []:
- folder = frappe.get_app_path(app_name, module_name, "custom")
- if os.path.exists(folder):
- for fname in os.listdir(folder):
- if fname.endswith(".json"):
- with open(os.path.join(folder, fname), "r") as f:
- data = json.loads(f.read())
- if data.get("sync_on_migrate"):
- sync_customizations_for_doctype(data, folder)
+ module_custom_folder = frappe.get_app_path(app_name, module_name, "custom")
+ if not os.path.exists(module_custom_folder):
+ continue
+
+ for json_file in glob(os.path.join(module_custom_folder, "*.json")):
+ with open(os.path.join(module_custom_folder, json_file), "r") as f:
+ data = json.loads(f.read())
+
+ if data.get("sync_on_migrate"):
+ sync_customizations_for_doctype(data, module_custom_folder)
-def sync_customizations_for_doctype(data, folder):
+def sync_customizations_for_doctype(data: Dict, folder: str):
"""Sync doctype customzations for a particular data set"""
from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype
@@ -149,9 +155,7 @@ def sync_customizations_for_doctype(data, folder):
for doc_type in doctypes:
# only sync the parent doctype and child doctype if there isn't any other child table json file
- if doc_type == doctype or not os.path.exists(
- os.path.join(folder, frappe.scrub(doc_type) + ".json")
- ):
+ if doc_type == doctype or not os.path.exists(os.path.join(folder, scrub(doc_type) + ".json")):
sync_single_doctype(doc_type)
if data["custom_fields"]:
@@ -164,33 +168,31 @@ def sync_customizations_for_doctype(data, folder):
if data.get("custom_perms"):
sync("custom_perms", "Custom DocPerm", "parent")
- print("Updating customizations for {0}".format(doctype))
+ print(f"Updating customizations for {doctype}")
validate_fields_for_doctype(doctype)
if update_schema and not frappe.db.get_value("DocType", doctype, "issingle"):
frappe.db.updatedb(doctype)
-def scrub(txt):
- return frappe.scrub(txt)
-
-
-def scrub_dt_dn(dt, dn):
+def scrub_dt_dn(dt: str, dn: str) -> Tuple[str, str]:
"""Returns in lowercase and code friendly names of doctype and name for certain types"""
return scrub(dt), scrub(dn)
-def get_module_path(module):
- """Returns path of the given module"""
- return frappe.get_module_path(module)
+def get_doc_path(module: str, doctype: str, name: str) -> str:
+ """Returns path of a doc in a module"""
+ return os.path.join(get_module_path(module), *scrub_dt_dn(doctype, name))
-def get_doc_path(module, doctype, name):
- dt, dn = scrub_dt_dn(doctype, name)
- return os.path.join(get_module_path(module), dt, dn)
-
-
-def reload_doc(module, dt=None, dn=None, force=False, reset_permissions=False):
+def reload_doc(
+ module: str,
+ dt: str = None,
+ dn: str = None,
+ force: bool = False,
+ reset_permissions: bool = False,
+):
+ """Reload Document from model (`[module]//[name]/[name].json`) files"""
from frappe.modules.import_file import import_files
return import_files(module, dt, dn, force=force, reset_permissions=reset_permissions)
@@ -200,128 +202,119 @@ def export_doc(doctype, name, module=None):
"""Write a doc to standard path."""
from frappe.modules.export_file import write_document_file
- print(doctype, name)
-
- if not module:
- module = frappe.db.get_value("DocType", name, "module")
+ print(f"Exporting Document {doctype} {name}")
+ module = module or frappe.db.get_value("DocType", name, "module")
write_document_file(frappe.get_doc(doctype, name), module)
-def get_doctype_module(doctype):
+def get_doctype_module(doctype: str) -> str:
"""Returns **Module Def** name of given doctype."""
-
- def make_modules_dict():
- return dict(frappe.db.sql("select name, module from tabDocType"))
-
- return frappe.cache().get_value("doctype_modules", make_modules_dict)[doctype]
-
-
-doctype_python_modules = {}
+ return frappe.cache().get_value(
+ "doctype_modules",
+ generator=lambda: dict(frappe.qb.from_("DocType").select("name", "module").run()),
+ )[doctype]
def load_doctype_module(doctype, module=None, prefix="", suffix=""):
- """Returns the module object for given doctype."""
- if not module:
- module = get_doctype_module(doctype)
+ """Returns the module object for given doctype.
+ Note: This will return the standard defined module object for the doctype irrespective
+ of the `override_doctype_class` hook.
+ """
+ module = module or get_doctype_module(doctype)
app = get_module_app(module)
-
key = (app, doctype, prefix, suffix)
-
module_name = get_module_name(doctype, module, prefix, suffix)
- try:
- if key not in doctype_python_modules:
+ if key not in doctype_python_modules:
+ try:
doctype_python_modules[key] = frappe.get_module(module_name)
- except ImportError as e:
- raise ImportError(
- "Module import failed for {0} ({1})".format(doctype, module_name + " Error: " + str(e))
- )
+ except ImportError as e:
+ raise ImportError(f"Module import failed for {doctype} ({module_name} Error: {e})")
return doctype_python_modules[key]
-def get_module_name(doctype, module, prefix="", suffix="", app=None):
- return "{app}.{module}.doctype.{doctype}.{prefix}{doctype}{suffix}".format(
- app=scrub(app or get_module_app(module)),
- module=scrub(module),
- doctype=scrub(doctype),
- prefix=prefix,
- suffix=suffix,
- )
+def get_module_name(
+ doctype: str, module: str, prefix: str = "", suffix: str = "", app: Optional[str] = None
+):
+ app = scrub(app or get_module_app(module))
+ module = scrub(module)
+ doctype = scrub(doctype)
+ return f"{app}.{module}.doctype.{doctype}.{prefix}{doctype}{suffix}"
-def get_module_app(module):
+def get_module_app(module: str) -> str:
return frappe.local.module_app[scrub(module)]
-def get_app_publisher(module):
+def get_app_publisher(module: str) -> str:
app = frappe.local.module_app[scrub(module)]
if not app:
- frappe.throw(_("App not found"))
- app_publisher = frappe.get_hooks(hook="app_publisher", app_name=app)[0]
- return app_publisher
+ frappe.throw(_("App not found for module: {0}").format(module))
+ return frappe.get_hooks(hook="app_publisher", app_name=app)[0]
-def make_boilerplate(template, doc, opts=None):
+def make_boilerplate(
+ template: str, doc: Union["Document", "frappe._dict"], opts: Union[Dict, "frappe._dict"] = None
+):
target_path = get_doc_path(doc.module, doc.doctype, doc.name)
template_name = template.replace("controller", scrub(doc.name))
if template_name.endswith("._py"):
template_name = template_name[:-4] + ".py"
target_file_path = os.path.join(target_path, template_name)
+ template_file_path = os.path.join(
+ get_module_path("core"), "doctype", scrub(doc.doctype), "boilerplate", template
+ )
- if not doc:
- doc = {}
+ if os.path.exists(target_file_path):
+ print(f"{target_file_path} already exists, skipping...")
+ return
+ doc = doc or frappe._dict()
+ opts = opts or frappe._dict()
app_publisher = get_app_publisher(doc.module)
+ base_class = "Document"
+ base_class_import = "from frappe.model.document import Document"
+ controller_body = "pass"
- if not os.path.exists(target_file_path):
- if not opts:
- opts = {}
+ if doc.get("is_tree"):
+ base_class = "NestedSet"
+ base_class_import = "from frappe.utils.nestedset import NestedSet"
- base_class = "Document"
- base_class_import = "from frappe.model.document import Document"
- if doc.get("is_tree"):
- base_class = "NestedSet"
- base_class_import = "from frappe.utils.nestedset import NestedSet"
+ if doc.get("is_virtual"):
+ controller_body = dedent(
+ """
+ def db_insert(self):
+ pass
- custom_controller = "pass"
- if doc.get("is_virtual"):
- custom_controller = """
- def db_insert(self):
- pass
+ def load_from_db(self):
+ pass
- def load_from_db(self):
- pass
+ def db_update(self):
+ pass
- def db_update(self):
- pass
+ def get_list(self, args):
+ pass
- def get_list(self, args):
- pass
+ def get_count(self, args):
+ pass
- def get_count(self, args):
- pass
+ def get_stats(self, args):
+ pass
+ """
+ )
- def get_stats(self, args):
- pass"""
-
- with open(target_file_path, "w") as target:
- with open(
- os.path.join(get_module_path("core"), "doctype", scrub(doc.doctype), "boilerplate", template),
- "r",
- ) as source:
- target.write(
- frappe.as_unicode(
- frappe.utils.cstr(source.read()).format(
- app_publisher=app_publisher,
- year=frappe.utils.nowdate()[:4],
- classname=doc.name.replace(" ", "").replace("-", ""),
- base_class_import=base_class_import,
- base_class=base_class,
- doctype=doc.name,
- **opts,
- custom_controller=custom_controller
- )
- )
- )
+ with open(target_file_path, "w") as target, open(template_file_path, "r") as source:
+ template = source.read()
+ controller_file_content = cstr(template).format(
+ app_publisher=app_publisher,
+ year=now_datetime().year,
+ classname=doc.name.replace(" ", "").replace("-", ""),
+ base_class_import=base_class_import,
+ base_class=base_class,
+ doctype=doc.name,
+ **opts,
+ custom_controller=controller_body,
+ )
+ target.write(frappe.as_unicode(controller_file_content))
From 0a854ddd2a6aecfc5b5afce262da41c812b1511e Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 14 Apr 2022 09:57:03 +0530
Subject: [PATCH 0002/2449] chore(frappe): Add typing hints for init methods
---
frappe/__init__.py | 13 ++++++-------
1 file changed, 6 insertions(+), 7 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 37c282f04a..9d85f7adfa 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -25,7 +25,7 @@ import importlib
import inspect
import json
import sys
-from typing import TYPE_CHECKING, Dict, List, Union
+from typing import TYPE_CHECKING, Dict, List, Optional, Union
import click
from werkzeug.local import Local, release_local
@@ -80,7 +80,7 @@ class _dict(dict):
return _dict(dict(self).copy())
-def _(msg, lang=None, context=None):
+def _(msg: str, lang: Optional[str] = None, context: str = None) -> str:
"""Returns translated string in current lang, if exists.
Usage:
_('Change')
@@ -317,14 +317,13 @@ def get_site_config(sites_path=None, site_path=None):
return _dict(config)
-def get_conf(site=None):
+def get_conf(site: Optional[str] = None) -> _dict:
if hasattr(local, "conf"):
return local.conf
- else:
- # if no site, get from common_site_config.json
- with init_site(site):
- return local.conf
+ # if no site, get from common_site_config.json
+ with init_site(site):
+ return local.conf
class init_site:
From 7e51f51f11c85101bdc3392fee2df980bb3f736c Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 14 Apr 2022 09:58:35 +0530
Subject: [PATCH 0003/2449] test: Added test for PrintFormat.export_doc
Other Changes
* Added return value for export_doc method
---
.../doctype/print_format/print_format.py | 2 +-
.../doctype/print_format/test_print_format.py | 22 +++++++++++++++++++
2 files changed, 23 insertions(+), 1 deletion(-)
diff --git a/frappe/printing/doctype/print_format/print_format.py b/frappe/printing/doctype/print_format/print_format.py
index e54f46b487..e1e043beae 100644
--- a/frappe/printing/doctype/print_format/print_format.py
+++ b/frappe/printing/doctype/print_format/print_format.py
@@ -96,7 +96,7 @@ class PrintFormat(Document):
def export_doc(self):
from frappe.modules.utils import export_module_json
- export_module_json(self, self.standard == "Yes", self.module)
+ return export_module_json(self, self.standard == "Yes", self.module)
def on_trash(self):
if self.doc_type:
diff --git a/frappe/printing/doctype/print_format/test_print_format.py b/frappe/printing/doctype/print_format/test_print_format.py
index fbfeecb3ab..e97b07ed6e 100644
--- a/frappe/printing/doctype/print_format/test_print_format.py
+++ b/frappe/printing/doctype/print_format/test_print_format.py
@@ -1,10 +1,15 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
+import os
import re
import unittest
+from typing import TYPE_CHECKING
import frappe
+if TYPE_CHECKING:
+ from frappe.printing.doctype.print_format.print_format import PrintFormat
+
test_records = frappe.get_test_records("Print Format")
@@ -30,3 +35,20 @@ class TestPrintFormat(unittest.TestCase):
def test_print_user_classic(self):
print_html = self.test_print_user("Classic")
self.assertTrue("/* classic format: for-test */" in print_html)
+
+ def test_export_doc(self):
+ doc: "PrintFormat" = frappe.get_doc("Print Format", test_records[0]["name"])
+ doc.standard = "Yes" # this is only to make export_doc happy
+ export_path = doc.export_doc()
+ exported_doc_path = f"{export_path}.json"
+ doc.reload()
+ doc_dict = doc.as_dict(no_nulls=True, convert_dates_to_str=True)
+
+ self.assertTrue(os.path.exists(exported_doc_path))
+
+ with open(exported_doc_path, "r") as f:
+ exported_doc = frappe.parse_json(f.read())
+
+ for key, value in exported_doc.items():
+ if key in doc_dict:
+ self.assertEqual(value, doc_dict[key])
From 7aff38c4690d91dc83e60afe6aa30bf76c9b249f Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 14 Apr 2022 13:28:36 +0530
Subject: [PATCH 0004/2449] test: Added tests for frappe.modules
---
frappe/tests/test_modules.py | 162 +++++++++++++++++++++++++++++++++++
1 file changed, 162 insertions(+)
create mode 100644 frappe/tests/test_modules.py
diff --git a/frappe/tests/test_modules.py b/frappe/tests/test_modules.py
new file mode 100644
index 0000000000..0ea7549bc0
--- /dev/null
+++ b/frappe/tests/test_modules.py
@@ -0,0 +1,162 @@
+import os
+import shutil
+import unittest
+
+import frappe
+from frappe import scrub
+from frappe.core.doctype.doctype.test_doctype import new_doctype
+from frappe.custom.doctype.custom_field.custom_field import create_custom_field
+from frappe.model.meta import trim_table
+from frappe.modules import export_customizations, export_module_json, get_module_path
+from frappe.modules.utils import export_doc, sync_customizations
+from frappe.utils import now_datetime
+
+
+def write_file(path, content):
+ with open(path, "w") as f:
+ f.write(content)
+
+
+def delete_file(path):
+ os.remove(path)
+
+
+def delete_path(path):
+ shutil.rmtree(path)
+
+
+class TestUtils(unittest.TestCase):
+ def setUp(self):
+ if self._testMethodName == "test_export_module_json_no_export":
+ self._dev_mode = frappe.local.conf.developer_mode
+ self._in_import = frappe.local.flags.in_import
+ frappe.local.flags.in_import = True
+ frappe.local.conf.developer_mode = True
+
+ if self._testMethodName in ("test_export_customizations", "test_sync_customizations"):
+ df = {
+ "fieldname": "test_export_customizations_field",
+ "label": "Custom Data Field",
+ "fieldtype": "Data",
+ }
+ self.custom_field = create_custom_field("Note", df=df)
+
+ if self._testMethodName == "test_export_doc":
+ self.note = frappe.new_doc("Note")
+ self.note.title = frappe.generate_hash("Note", length=10)
+ self.note.save()
+
+ if self._testMethodName == "test_make_boilerplate":
+ self.doctype = new_doctype("Test DocType Boilerplate")
+ self.doctype.insert()
+
+ def tearDown(self):
+ if self._testMethodName == "test_export_module_json_no_export":
+ frappe.local.conf.developer_mode = self._dev_mode
+ frappe.local.flags.in_import = self._in_import
+
+ if self._testMethodName in ("test_export_customizations", "test_sync_customizations"):
+ self.custom_field.delete()
+ trim_table("Note", dry_run=False)
+ delattr(self, "custom_field")
+
+ if self._testMethodName == "test_export_doc":
+ self.note.delete()
+ delattr(self, "note")
+
+ if self._testMethodName == "test_make_boilerplate":
+ self.doctype.delete()
+ frappe.db.sql_ddl("DROP TABLE `tabTest DocType Boilerplate`")
+ delattr(self, "doctype")
+
+ def test_export_module_json_no_export(self):
+ doc = frappe.get_last_doc("DocType")
+ self.assertIsNone(export_module_json(doc=doc, is_standard=True, module=doc.module))
+
+ def test_export_module_json(self):
+ doc = frappe.get_last_doc("DocType")
+ export_doc_path = os.path.join(
+ get_module_path(doc.module),
+ scrub(doc.doctype),
+ scrub(doc.name),
+ f"{scrub(doc.name)}.json",
+ )
+ with open(export_doc_path, "r") as f:
+ export_doc_before = frappe.parse_json(f.read())
+ last_modified_before = os.path.getmtime(export_doc_path)
+ self.addCleanup(write_file, path=export_doc_path, content=frappe.as_json(export_doc_before))
+ export_path = export_module_json(doc=doc, is_standard=True, module=doc.module)
+ last_modified_after = os.path.getmtime(export_doc_path)
+ with open(f"{export_path}.json", "r") as f:
+ export_doc_after = frappe.parse_json(f.read())
+ self.assertTrue(last_modified_after > last_modified_before)
+
+ def test_export_customizations(self):
+ file_path = export_customizations(module="Custom", doctype="Note")
+ self.addCleanup(delete_file, path=file_path)
+ self.assertTrue(file_path.endswith("/custom/custom/note.json"))
+ self.assertTrue(os.path.exists(file_path))
+
+ def test_sync_customizations(self):
+ custom_field = frappe.get_doc(
+ "Custom Field", {"dt": "Note", "fieldname": "test_export_customizations_field"}
+ )
+
+ file_path = export_customizations(module="Custom", doctype="Note", sync_on_migrate=True)
+ custom_field.db_set("modified", now_datetime())
+ custom_field.reload()
+
+ self.assertTrue(file_path.endswith("/custom/custom/note.json"))
+ self.assertTrue(os.path.exists(file_path))
+ last_modified_before = custom_field.modified
+
+ sync_customizations(app="frappe")
+
+ self.assertTrue(file_path.endswith("/custom/custom/note.json"))
+ self.assertTrue(os.path.exists(file_path))
+ custom_field.reload()
+ last_modified_after = custom_field.modified
+
+ self.assertNotEqual(last_modified_after, last_modified_before)
+ self.addCleanup(delete_file, path=file_path)
+
+ def test_reload_doc(self):
+ frappe.db.set_value("DocType", "Note", "migration_hash", "", update_modified=False)
+ self.assertFalse(frappe.db.get_value("DocType", "Note", "migration_hash"))
+ frappe.db.set_value(
+ "DocField",
+ {"parent": "Note", "fieldname": "title"},
+ "fieldtype",
+ "Text",
+ update_modified=False,
+ )
+ self.assertEqual(
+ frappe.db.get_value("DocField", {"parent": "Note", "fieldname": "title"}, "fieldtype"),
+ "Text",
+ )
+ frappe.reload_doctype("Note")
+ self.assertEqual(
+ frappe.db.get_value("DocField", {"parent": "Note", "fieldname": "title"}, "fieldtype"),
+ "Data",
+ )
+ self.assertTrue(frappe.db.get_value("DocType", "Note", "migration_hash"))
+
+ def test_export_doc(self):
+ exported_doc_path = frappe.get_app_path(
+ "frappe", "desk", "note", self.note.name, f"{self.note.name}.json"
+ )
+ folder_path = os.path.abspath(os.path.dirname(exported_doc_path))
+ export_doc(doctype="Note", name=self.note.name)
+ self.addCleanup(delete_path, path=folder_path)
+ self.assertTrue(os.path.exists(exported_doc_path))
+
+ def test_make_boilerplate(self):
+ scrubbed = frappe.scrub(self.doctype.name)
+ self.assertFalse(
+ os.path.exists(frappe.get_app_path("frappe", "core", "doctype", scrubbed, f"{scrubbed}.json"))
+ )
+ self.doctype.custom = False
+ self.doctype.save()
+ self.assertTrue(
+ os.path.exists(frappe.get_app_path("frappe", "core", "doctype", scrubbed, f"{scrubbed}.json"))
+ )
From 6538419e5d0fe013146939b362430e34de6d7f6d Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 14 Apr 2022 13:29:20 +0530
Subject: [PATCH 0005/2449] fix: Add return values to enable better testing ;)
---
frappe/custom/doctype/custom_field/custom_field.py | 1 +
frappe/modules/export_file.py | 4 +++-
frappe/modules/utils.py | 1 +
3 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py
index 10ee4a503f..8a2a2663de 100644
--- a/frappe/custom/doctype/custom_field/custom_field.py
+++ b/frappe/custom/doctype/custom_field/custom_field.py
@@ -161,6 +161,7 @@ def create_custom_field(doctype, df, ignore_validate=False, is_system_generated=
custom_field.update(df)
custom_field.flags.ignore_validate = ignore_validate
custom_field.insert()
+ return custom_field
def create_custom_fields(custom_fields, ignore_validate=False, update=True):
diff --git a/frappe/modules/export_file.py b/frappe/modules/export_file.py
index 8eac7a9229..a1d527c91d 100644
--- a/frappe/modules/export_file.py
+++ b/frappe/modules/export_file.py
@@ -47,8 +47,10 @@ def write_document_file(doc, record_module=None, create_init=True, folder_name=N
write_code_files(folder, fname, doc, doc_export)
# write the data file
- with open(os.path.join(folder, fname + ".json"), "w+") as txtfile:
+ path = os.path.join(folder, f"{fname}.json")
+ with open(path, "w+") as txtfile:
txtfile.write(frappe.as_json(doc_export))
+ print(f"Wrote document file for {doc.doctype} {doc.name} at {path}")
def strip_default_fields(doc, doc_export):
diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py
index b7b9819093..1f89dae716 100644
--- a/frappe/modules/utils.py
+++ b/frappe/modules/utils.py
@@ -97,6 +97,7 @@ def export_customizations(
frappe.msgprint(
_("Customizations for {0} exported to: {1}").format(doctype, file_path)
)
+ return file_path
def sync_customizations(app: Optional[str] = None):
From 67478e25c341109a301582431a65777a6897ad82 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 14 Apr 2022 13:29:44 +0530
Subject: [PATCH 0006/2449] fix: Drop dead suspicious looking API
---
frappe/custom/doctype/custom_field/custom_field.py | 6 ------
1 file changed, 6 deletions(-)
diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py
index 8a2a2663de..3f00828439 100644
--- a/frappe/custom/doctype/custom_field/custom_field.py
+++ b/frappe/custom/doctype/custom_field/custom_field.py
@@ -198,9 +198,3 @@ def create_custom_fields(custom_fields, ignore_validate=False, update=True):
frappe.clear_cache(doctype=doctype)
frappe.db.updatedb(doctype)
-
-
-@frappe.whitelist()
-def add_custom_field(doctype, df):
- df = json.loads(df)
- return create_custom_field(doctype, df)
From c9fa6931ffc5dfbc9cd792904b7cba395cd03ca8 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 14 Apr 2022 13:30:35 +0530
Subject: [PATCH 0007/2449] perf: Glob filesystem instead of DB reads etc
Why speak more word when less word do trick
---
frappe/modules/utils.py | 15 +++++----------
1 file changed, 5 insertions(+), 10 deletions(-)
diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py
index 1f89dae716..b8ab6756a5 100644
--- a/frappe/modules/utils.py
+++ b/frappe/modules/utils.py
@@ -105,17 +105,12 @@ def sync_customizations(app: Optional[str] = None):
apps = frappe.get_installed_apps() if not app else [app]
for app_name in apps:
- for module_name in frappe.local.app_modules.get(app_name) or []:
- module_custom_folder = frappe.get_app_path(app_name, module_name, "custom")
- if not os.path.exists(module_custom_folder):
- continue
+ for json_file in glob(frappe.get_app_path(app_name, "**", "custom", "*.json")):
+ with open(json_file, "r") as f:
+ data = json.loads(f.read())
- for json_file in glob(os.path.join(module_custom_folder, "*.json")):
- with open(os.path.join(module_custom_folder, json_file), "r") as f:
- data = json.loads(f.read())
-
- if data.get("sync_on_migrate"):
- sync_customizations_for_doctype(data, module_custom_folder)
+ if data.get("sync_on_migrate"):
+ sync_customizations_for_doctype(data, os.path.dirname(json_file))
def sync_customizations_for_doctype(data: Dict, folder: str):
From f463d45a613787cc4da07b121903e149234ad590 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 21 Apr 2022 15:24:40 +0530
Subject: [PATCH 0008/2449] Revert "perf: Glob filesystem instead of DB reads
etc"
This reverts commit c9fa6931ffc5dfbc9cd792904b7cba395cd03ca8.
---
frappe/modules/utils.py | 15 ++++++++++-----
1 file changed, 10 insertions(+), 5 deletions(-)
diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py
index b8ab6756a5..1f89dae716 100644
--- a/frappe/modules/utils.py
+++ b/frappe/modules/utils.py
@@ -105,12 +105,17 @@ def sync_customizations(app: Optional[str] = None):
apps = frappe.get_installed_apps() if not app else [app]
for app_name in apps:
- for json_file in glob(frappe.get_app_path(app_name, "**", "custom", "*.json")):
- with open(json_file, "r") as f:
- data = json.loads(f.read())
+ for module_name in frappe.local.app_modules.get(app_name) or []:
+ module_custom_folder = frappe.get_app_path(app_name, module_name, "custom")
+ if not os.path.exists(module_custom_folder):
+ continue
- if data.get("sync_on_migrate"):
- sync_customizations_for_doctype(data, os.path.dirname(json_file))
+ for json_file in glob(os.path.join(module_custom_folder, "*.json")):
+ with open(os.path.join(module_custom_folder, json_file), "r") as f:
+ data = json.loads(f.read())
+
+ if data.get("sync_on_migrate"):
+ sync_customizations_for_doctype(data, module_custom_folder)
def sync_customizations_for_doctype(data: Dict, folder: str):
From 314e61179420084c98cc6a31d3fa5c468434b271 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 26 Apr 2022 12:50:23 +0530
Subject: [PATCH 0009/2449] test: Run test_modules in developer mode
Update cleanup for test_make_boilerplate
---
frappe/tests/test_modules.py | 18 ++++++++++++------
1 file changed, 12 insertions(+), 6 deletions(-)
diff --git a/frappe/tests/test_modules.py b/frappe/tests/test_modules.py
index 0ea7549bc0..85bd4fddda 100644
--- a/frappe/tests/test_modules.py
+++ b/frappe/tests/test_modules.py
@@ -27,11 +27,13 @@ def delete_path(path):
class TestUtils(unittest.TestCase):
def setUp(self):
+ self._dev_mode = frappe.local.conf.developer_mode
+ self._in_import = frappe.local.flags.in_import
+
+ frappe.local.conf.developer_mode = True
+
if self._testMethodName == "test_export_module_json_no_export":
- self._dev_mode = frappe.local.conf.developer_mode
- self._in_import = frappe.local.flags.in_import
frappe.local.flags.in_import = True
- frappe.local.conf.developer_mode = True
if self._testMethodName in ("test_export_customizations", "test_sync_customizations"):
df = {
@@ -51,9 +53,8 @@ class TestUtils(unittest.TestCase):
self.doctype.insert()
def tearDown(self):
- if self._testMethodName == "test_export_module_json_no_export":
- frappe.local.conf.developer_mode = self._dev_mode
- frappe.local.flags.in_import = self._in_import
+ frappe.local.conf.developer_mode = self._dev_mode
+ frappe.local.flags.in_import = self._in_import
if self._testMethodName in ("test_export_customizations", "test_sync_customizations"):
self.custom_field.delete()
@@ -66,6 +67,11 @@ class TestUtils(unittest.TestCase):
if self._testMethodName == "test_make_boilerplate":
self.doctype.delete()
+ scrubbed = frappe.scrub(self.doctype.name)
+ self.addCleanup(
+ delete_path,
+ path=frappe.get_app_path("frappe", "core", "doctype", scrubbed),
+ )
frappe.db.sql_ddl("DROP TABLE `tabTest DocType Boilerplate`")
delattr(self, "doctype")
From d99f75e64b6fbf3d2fb594e48d6f5d9ef5511e4e Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 26 Apr 2022 14:53:12 +0530
Subject: [PATCH 0010/2449] test: Use FrappeTestCase for modules' TestUtils
---
frappe/tests/test_modules.py | 9 +++++++--
1 file changed, 7 insertions(+), 2 deletions(-)
diff --git a/frappe/tests/test_modules.py b/frappe/tests/test_modules.py
index 85bd4fddda..ff5eb34d7b 100644
--- a/frappe/tests/test_modules.py
+++ b/frappe/tests/test_modules.py
@@ -9,6 +9,7 @@ from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.model.meta import trim_table
from frappe.modules import export_customizations, export_module_json, get_module_path
from frappe.modules.utils import export_doc, sync_customizations
+from frappe.tests.utils import FrappeTestCase
from frappe.utils import now_datetime
@@ -22,10 +23,10 @@ def delete_file(path):
def delete_path(path):
- shutil.rmtree(path)
+ shutil.rmtree(path, ignore_errors=True)
-class TestUtils(unittest.TestCase):
+class TestUtils(FrappeTestCase):
def setUp(self):
self._dev_mode = frappe.local.conf.developer_mode
self._in_import = frappe.local.flags.in_import
@@ -89,12 +90,16 @@ class TestUtils(unittest.TestCase):
)
with open(export_doc_path, "r") as f:
export_doc_before = frappe.parse_json(f.read())
+
last_modified_before = os.path.getmtime(export_doc_path)
self.addCleanup(write_file, path=export_doc_path, content=frappe.as_json(export_doc_before))
+
export_path = export_module_json(doc=doc, is_standard=True, module=doc.module)
last_modified_after = os.path.getmtime(export_doc_path)
+
with open(f"{export_path}.json", "r") as f:
export_doc_after = frappe.parse_json(f.read())
+
self.assertTrue(last_modified_after > last_modified_before)
def test_export_customizations(self):
From d3900485a61c45d34e1bffe7307f8dba42737607 Mon Sep 17 00:00:00 2001
From: gavin
Date: Sat, 21 May 2022 01:03:57 +0530
Subject: [PATCH 0011/2449] refactor: Use mariadb client for creating
connections
Move to use MariaDB's official Python client written in C instead of
the PyMySQL library. This change doesn't rid Frappe of the PyMySQL
library. Instead, it continues to utilize it for the ER module and
converter methods until the MariaDB library adds support for the same.
Ticket: https://jira.mariadb.org/projects/CONPY/issues/CONPY-203
---
frappe/database/database.py | 1 -
frappe/database/mariadb/database.py | 105 +++++++++---------
frappe/geo/utils.py | 5 +-
.../patches/v12_0/delete_duplicate_indexes.py | 4 +-
requirements.txt | 1 +
5 files changed, 55 insertions(+), 61 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 42135f3cd5..ab1d8ea4fa 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -183,7 +183,6 @@ class Database(object):
except Exception as e:
if self.is_syntax_error(e):
- # only for mariadb
frappe.errprint("Syntax error in query:")
frappe.errprint(query)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 7505ef3a7f..57e9576143 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -1,8 +1,9 @@
from typing import List, Tuple, Union
-import pymysql
-from pymysql.constants import ER, FIELD_TYPE
-from pymysql.converters import conversions, escape_string
+import mariadb
+from mariadb.constants import FIELD_TYPE
+from pymysql.constants import ER
+from pymysql.converters import escape_string
import frappe
from frappe.database.database import Database
@@ -11,12 +12,12 @@ from frappe.utils import UnicodeWithAttrs, cstr, get_datetime, get_table_name
class MariaDBDatabase(Database):
- ProgrammingError = pymysql.err.ProgrammingError
- TableMissingError = pymysql.err.ProgrammingError
- OperationalError = pymysql.err.OperationalError
- InternalError = pymysql.err.InternalError
- SQLError = pymysql.err.ProgrammingError
- DataError = pymysql.err.DataError
+ ProgrammingError = mariadb.ProgrammingError
+ TableMissingError = mariadb.ProgrammingError
+ OperationalError = mariadb.OperationalError
+ InternalError = mariadb.InternalError
+ SQLError = mariadb.ProgrammingError
+ DataError = mariadb.DataError
REGEX_CHARACTER = "regexp"
# NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
@@ -68,42 +69,34 @@ class MariaDBDatabase(Database):
}
def get_connection(self):
- usessl = 0
- if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key:
- usessl = 1
- ssl_params = {
- "ca": frappe.conf.db_ssl_ca,
- "cert": frappe.conf.db_ssl_cert,
- "key": frappe.conf.db_ssl_key,
- }
-
- conversions.update(
- {
+ conn_settings = {
+ "host": self.host,
+ "user": self.user,
+ "password": self.password,
+ "database": self.user,
+ "converter": {
FIELD_TYPE.NEWDECIMAL: float,
FIELD_TYPE.DATETIME: get_datetime,
- UnicodeWithAttrs: conversions[str],
+ UnicodeWithAttrs: escape_string,
+ },
+ }
+
+ if self.port:
+ conn_settings["port"] = int(self.port)
+
+ if frappe.conf.local_infile:
+ conn_settings["local_infile"] = frappe.conf.local_infile
+
+ if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key:
+ ssl_params = {
+ "ssl": True,
+ "ssl_ca": frappe.conf.db_ssl_ca,
+ "ssl_cert": frappe.conf.db_ssl_cert,
+ "ssl_key": frappe.conf.db_ssl_key,
}
- )
+ conn_settings.update(ssl_params)
- conn = pymysql.connect(
- user=self.user or "",
- password=self.password or "",
- host=self.host,
- port=self.port,
- charset="utf8mb4",
- use_unicode=True,
- ssl=ssl_params if usessl else None,
- conv=conversions,
- local_infile=frappe.conf.local_infile,
- )
-
- # MYSQL_OPTION_MULTI_STATEMENTS_OFF = 1
- # # self._conn.set_server_option(MYSQL_OPTION_MULTI_STATEMENTS_OFF)
-
- if self.user != "root":
- conn.select_db(self.user)
-
- return conn
+ return mariadb.connect(**conn_settings)
def get_database_size(self):
"""'Returns database size in MB"""
@@ -122,6 +115,10 @@ class MariaDBDatabase(Database):
@staticmethod
def escape(s, percent=True):
"""Excape quotes and percent in given string."""
+ # Update: We've scrapped PyMySQL in favour of MariaDB's official Python client
+ # Also, given we're promoting use of the PyPika builder via frappe.qb, the use
+ # of this method should be limited.
+
# pymysql expects unicode argument to escape_string with Python 3
s = frappe.as_unicode(escape_string(frappe.as_unicode(s)), "utf-8").replace("`", "\\`")
@@ -138,11 +135,11 @@ class MariaDBDatabase(Database):
# column type
@staticmethod
def is_type_number(code):
- return code == pymysql.NUMBER
+ return code == mariadb.NUMBER
@staticmethod
def is_type_datetime(code):
- return code in (pymysql.DATE, pymysql.DATETIME)
+ return code == mariadb.DATETIME
def rename_table(self, old_name: str, new_name: str) -> Union[List, Tuple]:
old_name = get_table_name(old_name)
@@ -163,15 +160,15 @@ class MariaDBDatabase(Database):
# exception types
@staticmethod
def is_deadlocked(e):
- return e.args[0] == ER.LOCK_DEADLOCK
+ return getattr(e, "errno", 0) == ER.LOCK_DEADLOCK
@staticmethod
def is_timedout(e):
- return e.args[0] == ER.LOCK_WAIT_TIMEOUT
+ return getattr(e, "errno", 0) == ER.LOCK_WAIT_TIMEOUT
@staticmethod
def is_table_missing(e):
- return e.args[0] == ER.NO_SUCH_TABLE
+ return getattr(e, "errno", 0) == ER.NO_SUCH_TABLE
@staticmethod
def is_missing_table(e):
@@ -179,35 +176,37 @@ class MariaDBDatabase(Database):
@staticmethod
def is_missing_column(e):
- return e.args[0] == ER.BAD_FIELD_ERROR
+ return getattr(e, "errno", 0) == ER.BAD_FIELD_ERROR
@staticmethod
def is_duplicate_fieldname(e):
- return e.args[0] == ER.DUP_FIELDNAME
+ return getattr(e, "errno", 0) == ER.DUP_FIELDNAME
@staticmethod
def is_duplicate_entry(e):
- return e.args[0] == ER.DUP_ENTRY
+ return getattr(e, "errno", 0) == ER.DUP_ENTRY
@staticmethod
def is_access_denied(e):
- return e.args[0] == ER.ACCESS_DENIED_ERROR
+ return getattr(e, "errno", 0) == ER.ACCESS_DENIED_ERROR
@staticmethod
def cant_drop_field_or_key(e):
- return e.args[0] == ER.CANT_DROP_FIELD_OR_KEY
+ return getattr(e, "errno", 0) == ER.CANT_DROP_FIELD_OR_KEY
@staticmethod
def is_syntax_error(e):
- return e.args[0] == ER.PARSE_ERROR
+ return getattr(e, "errno", 0) == ER.PARSE_ERROR
@staticmethod
def is_data_too_long(e):
- return e.args[0] == ER.DATA_TOO_LONG
+ return getattr(e, "errno", 0) == ER.DATA_TOO_LONG
+ @staticmethod
def is_primary_key_violation(self, e):
return self.is_duplicate_entry(e) and "PRIMARY" in cstr(e.args[1])
+ @staticmethod
def is_unique_key_violation(self, e):
return self.is_duplicate_entry(e) and "Duplicate" in cstr(e.args[1])
diff --git a/frappe/geo/utils.py b/frappe/geo/utils.py
index 577c5de2ff..9340e28a36 100644
--- a/frappe/geo/utils.py
+++ b/frappe/geo/utils.py
@@ -1,9 +1,6 @@
-# -*- coding: utf-8 -*-
# Copyright (c) 2020, Frappe Technologies and contributors
# License: MIT. See LICENSE
-from pymysql import InternalError
-
import frappe
@@ -67,7 +64,7 @@ def return_location(doctype, filters_sql):
coords = frappe.db.sql(
"""SELECT name, location FROM `tab{}` WHERE {}""".format(doctype, filters_sql), as_dict=True
)
- except InternalError:
+ except frappe.db.InternalError:
frappe.msgprint(frappe._("This Doctype does not contain location fields"), raise_exception=True)
return
else:
diff --git a/frappe/patches/v12_0/delete_duplicate_indexes.py b/frappe/patches/v12_0/delete_duplicate_indexes.py
index 6a6b0b3204..96ebd237d8 100644
--- a/frappe/patches/v12_0/delete_duplicate_indexes.py
+++ b/frappe/patches/v12_0/delete_duplicate_indexes.py
@@ -1,5 +1,3 @@
-from pymysql import InternalError
-
import frappe
# This patch deletes all the duplicate indexes created for same column
@@ -51,5 +49,5 @@ def execute():
for query in query_list:
try:
frappe.db.sql(query)
- except InternalError:
+ except frappe.db.InternalError:
pass
diff --git a/requirements.txt b/requirements.txt
index 88ad0562b7..040bd72498 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -24,6 +24,7 @@ html5lib~=1.1
ipython~=7.31.1
Jinja2~=3.0.1
ldap3~=2.9
+mariadb~=1.0.11
markdown2~=2.4.0
maxminddb-geolite2==2018.703
num2words~=0.5.10
From cac25f1229212e9be5a50d9e445c0ea192e37e18 Mon Sep 17 00:00:00 2001
From: gavin
Date: Sat, 21 May 2022 01:08:13 +0530
Subject: [PATCH 0012/2449] feat(minor): Add support for `for_update` in
get_last_doc
---
frappe/__init__.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 17a945c875..c97dc42a9b 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -1112,11 +1112,11 @@ def get_doc(*args, **kwargs):
return doc
-def get_last_doc(doctype, filters=None, order_by="creation desc"):
+def get_last_doc(doctype, filters=None, order_by="creation desc", for_update=False):
"""Get last created document of this type."""
d = get_all(doctype, filters=filters, limit_page_length=1, order_by=order_by, pluck="name")
if d:
- return get_doc(doctype, d[0])
+ return get_doc(doctype, d[0], for_update=for_update)
else:
raise DoesNotExistError
From 1fe3624c430cee9b7ee75403475ea51c1cd5dc08 Mon Sep 17 00:00:00 2001
From: gavin
Date: Sat, 21 May 2022 01:26:15 +0530
Subject: [PATCH 0013/2449] fix: DB error detection API usage
* Make all methods static
* Add typing hints
* Don't safe fetch attribute value for errno - MariaDB exceptions if
raised will have errno attr in them. If the class doesn't have one,
it's not meant to be passed in these methods.
---
frappe/database/mariadb/database.py | 59 ++++++++++++++++-------------
1 file changed, 33 insertions(+), 26 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 57e9576143..7b13f575e1 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -157,58 +157,65 @@ class MariaDBDatabase(Database):
null_constraint = "NOT NULL" if not nullable else ""
return self.sql_ddl(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} {null_constraint}")
- # exception types
@staticmethod
- def is_deadlocked(e):
- return getattr(e, "errno", 0) == ER.LOCK_DEADLOCK
+ def is_deadlocked(e: mariadb.Error) -> bool:
+ return e.errno == ER.LOCK_DEADLOCK
@staticmethod
- def is_timedout(e):
- return getattr(e, "errno", 0) == ER.LOCK_WAIT_TIMEOUT
+ def is_timedout(e: mariadb.Error) -> bool:
+ return e.errno == ER.LOCK_WAIT_TIMEOUT
@staticmethod
- def is_table_missing(e):
- return getattr(e, "errno", 0) == ER.NO_SUCH_TABLE
+ def is_table_missing(e: mariadb.Error) -> bool:
+ return e.errno == ER.NO_SUCH_TABLE
@staticmethod
- def is_missing_table(e):
+ def is_missing_table(e: mariadb.Error) -> bool:
return MariaDBDatabase.is_table_missing(e)
@staticmethod
- def is_missing_column(e):
- return getattr(e, "errno", 0) == ER.BAD_FIELD_ERROR
+ def is_missing_column(e: mariadb.Error) -> bool:
+ return e.errno == ER.BAD_FIELD_ERROR
@staticmethod
- def is_duplicate_fieldname(e):
- return getattr(e, "errno", 0) == ER.DUP_FIELDNAME
+ def is_duplicate_fieldname(e: mariadb.Error) -> bool:
+ return e.errno == ER.DUP_FIELDNAME
@staticmethod
- def is_duplicate_entry(e):
- return getattr(e, "errno", 0) == ER.DUP_ENTRY
+ def is_duplicate_entry(e: mariadb.Error) -> bool:
+ return e.errno == ER.DUP_ENTRY
@staticmethod
- def is_access_denied(e):
- return getattr(e, "errno", 0) == ER.ACCESS_DENIED_ERROR
+ def is_access_denied(e: mariadb.Error) -> bool:
+ return e.errno == ER.ACCESS_DENIED_ERROR
@staticmethod
- def cant_drop_field_or_key(e):
- return getattr(e, "errno", 0) == ER.CANT_DROP_FIELD_OR_KEY
+ def cant_drop_field_or_key(e: mariadb.Error) -> bool:
+ return e.errno == ER.CANT_DROP_FIELD_OR_KEY
@staticmethod
- def is_syntax_error(e):
- return getattr(e, "errno", 0) == ER.PARSE_ERROR
+ def is_syntax_error(e: mariadb.Error) -> bool:
+ return e.errno == ER.PARSE_ERROR
@staticmethod
- def is_data_too_long(e):
- return getattr(e, "errno", 0) == ER.DATA_TOO_LONG
+ def is_data_too_long(e: mariadb.Error) -> bool:
+ return e.errno == ER.DATA_TOO_LONG
@staticmethod
- def is_primary_key_violation(self, e):
- return self.is_duplicate_entry(e) and "PRIMARY" in cstr(e.args[1])
+ def is_primary_key_violation(e: mariadb.Error) -> bool:
+ return (
+ MariaDBDatabase.is_duplicate_entry(e)
+ and "PRIMARY" in e.errmsg
+ and isinstance(e, mariadb.IntegrityError)
+ )
@staticmethod
- def is_unique_key_violation(self, e):
- return self.is_duplicate_entry(e) and "Duplicate" in cstr(e.args[1])
+ def is_unique_key_violation(e: mariadb.Error) -> bool:
+ return (
+ MariaDBDatabase.is_duplicate_entry(e)
+ and "Duplicate" in e.errmsg
+ and isinstance(e, mariadb.IntegrityError)
+ )
def create_auth_table(self):
self.sql_ddl(
From 756e385362e67bf32923731762e01940cc097ad4 Mon Sep 17 00:00:00 2001
From: gavin
Date: Sat, 21 May 2022 01:42:05 +0530
Subject: [PATCH 0014/2449] refactor: Move exception detection & abstraction in
separate class
This change has only been done to separate and club only like methods /
utils together
---
frappe/database/mariadb/database.py | 127 ++++++++++++++--------------
1 file changed, 65 insertions(+), 62 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 7b13f575e1..304ea1e470 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -8,16 +8,79 @@ from pymysql.converters import escape_string
import frappe
from frappe.database.database import Database
from frappe.database.mariadb.schema import MariaDBTable
-from frappe.utils import UnicodeWithAttrs, cstr, get_datetime, get_table_name
+from frappe.utils import UnicodeWithAttrs, get_datetime, get_table_name
-class MariaDBDatabase(Database):
+class MariaDBExceptionUtil:
ProgrammingError = mariadb.ProgrammingError
TableMissingError = mariadb.ProgrammingError
OperationalError = mariadb.OperationalError
InternalError = mariadb.InternalError
SQLError = mariadb.ProgrammingError
DataError = mariadb.DataError
+
+ @staticmethod
+ def is_deadlocked(e: mariadb.Error) -> bool:
+ return e.errno == ER.LOCK_DEADLOCK
+
+ @staticmethod
+ def is_timedout(e: mariadb.Error) -> bool:
+ return e.errno == ER.LOCK_WAIT_TIMEOUT
+
+ @staticmethod
+ def is_table_missing(e: mariadb.Error) -> bool:
+ return e.errno == ER.NO_SUCH_TABLE
+
+ @staticmethod
+ def is_missing_table(e: mariadb.Error) -> bool:
+ return MariaDBDatabase.is_table_missing(e)
+
+ @staticmethod
+ def is_missing_column(e: mariadb.Error) -> bool:
+ return e.errno == ER.BAD_FIELD_ERROR
+
+ @staticmethod
+ def is_duplicate_fieldname(e: mariadb.Error) -> bool:
+ return e.errno == ER.DUP_FIELDNAME
+
+ @staticmethod
+ def is_duplicate_entry(e: mariadb.Error) -> bool:
+ return e.errno == ER.DUP_ENTRY
+
+ @staticmethod
+ def is_access_denied(e: mariadb.Error) -> bool:
+ return e.errno == ER.ACCESS_DENIED_ERROR
+
+ @staticmethod
+ def cant_drop_field_or_key(e: mariadb.Error) -> bool:
+ return e.errno == ER.CANT_DROP_FIELD_OR_KEY
+
+ @staticmethod
+ def is_syntax_error(e: mariadb.Error) -> bool:
+ return e.errno == ER.PARSE_ERROR
+
+ @staticmethod
+ def is_data_too_long(e: mariadb.Error) -> bool:
+ return e.errno == ER.DATA_TOO_LONG
+
+ @staticmethod
+ def is_primary_key_violation(e: mariadb.Error) -> bool:
+ return (
+ MariaDBDatabase.is_duplicate_entry(e)
+ and "PRIMARY" in e.errmsg
+ and isinstance(e, mariadb.IntegrityError)
+ )
+
+ @staticmethod
+ def is_unique_key_violation(e: mariadb.Error) -> bool:
+ return (
+ MariaDBDatabase.is_duplicate_entry(e)
+ and "Duplicate" in e.errmsg
+ and isinstance(e, mariadb.IntegrityError)
+ )
+
+
+class MariaDBDatabase(Database, MariaDBExceptionUtil):
REGEX_CHARACTER = "regexp"
# NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
@@ -157,66 +220,6 @@ class MariaDBDatabase(Database):
null_constraint = "NOT NULL" if not nullable else ""
return self.sql_ddl(f"ALTER TABLE `{table_name}` MODIFY `{column}` {type} {null_constraint}")
- @staticmethod
- def is_deadlocked(e: mariadb.Error) -> bool:
- return e.errno == ER.LOCK_DEADLOCK
-
- @staticmethod
- def is_timedout(e: mariadb.Error) -> bool:
- return e.errno == ER.LOCK_WAIT_TIMEOUT
-
- @staticmethod
- def is_table_missing(e: mariadb.Error) -> bool:
- return e.errno == ER.NO_SUCH_TABLE
-
- @staticmethod
- def is_missing_table(e: mariadb.Error) -> bool:
- return MariaDBDatabase.is_table_missing(e)
-
- @staticmethod
- def is_missing_column(e: mariadb.Error) -> bool:
- return e.errno == ER.BAD_FIELD_ERROR
-
- @staticmethod
- def is_duplicate_fieldname(e: mariadb.Error) -> bool:
- return e.errno == ER.DUP_FIELDNAME
-
- @staticmethod
- def is_duplicate_entry(e: mariadb.Error) -> bool:
- return e.errno == ER.DUP_ENTRY
-
- @staticmethod
- def is_access_denied(e: mariadb.Error) -> bool:
- return e.errno == ER.ACCESS_DENIED_ERROR
-
- @staticmethod
- def cant_drop_field_or_key(e: mariadb.Error) -> bool:
- return e.errno == ER.CANT_DROP_FIELD_OR_KEY
-
- @staticmethod
- def is_syntax_error(e: mariadb.Error) -> bool:
- return e.errno == ER.PARSE_ERROR
-
- @staticmethod
- def is_data_too_long(e: mariadb.Error) -> bool:
- return e.errno == ER.DATA_TOO_LONG
-
- @staticmethod
- def is_primary_key_violation(e: mariadb.Error) -> bool:
- return (
- MariaDBDatabase.is_duplicate_entry(e)
- and "PRIMARY" in e.errmsg
- and isinstance(e, mariadb.IntegrityError)
- )
-
- @staticmethod
- def is_unique_key_violation(e: mariadb.Error) -> bool:
- return (
- MariaDBDatabase.is_duplicate_entry(e)
- and "Duplicate" in e.errmsg
- and isinstance(e, mariadb.IntegrityError)
- )
-
def create_auth_table(self):
self.sql_ddl(
"""create table if not exists `__Auth` (
From a17c978065515c264901207f9320f8d2048fa203 Mon Sep 17 00:00:00 2001
From: gavin
Date: Sat, 21 May 2022 01:43:17 +0530
Subject: [PATCH 0015/2449] refactor!: frappe.db.get_database_list
* Drop mandatory unused arg in method call
* Use pluck instead of list comprehension + subscripting
---
frappe/database/mariadb/database.py | 4 ++--
frappe/database/postgres/database.py | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 304ea1e470..7afa265810 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -362,5 +362,5 @@ class MariaDBDatabase(Database, MariaDBExceptionUtil):
db_table.sync()
self.begin()
- def get_database_list(self, target):
- return [d[0] for d in self.sql("SHOW DATABASES;")]
+ def get_database_list(self):
+ return self.sql("SHOW DATABASES", pluck=True)
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 8bd4113823..84a237e702 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -375,8 +375,8 @@ class PostgresDatabase(Database):
as_dict=1,
)
- def get_database_list(self, target):
- return [d[0] for d in self.sql("SELECT datname FROM pg_database;")]
+ def get_database_list(self):
+ return self.sql("SELECT datname FROM pg_database", pluck=True)
def modify_query(query):
From 0d80f6ac52e34a4005c75c2e4fef7729083e6480 Mon Sep 17 00:00:00 2001
From: gavin
Date: Sat, 21 May 2022 02:37:51 +0530
Subject: [PATCH 0016/2449] feat: mariadb connection pooling
Start initial pool of 4 (_POOL_SIZE) connections for a given site. When
the pool is exhausted, generate new connections upon request and add
them to the pool. Max allowed size for each pool is 64 (_MAX_POOL_SIZE)
connections. However, you may have more than 64 active connections, just
that they won't be pooled but destroyed upon job completion/end of
request/similar cycle.
---
frappe/database/mariadb/database.py | 56 +++++++++++++++++++++++++++--
1 file changed, 53 insertions(+), 3 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 7afa265810..f43f068e40 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -1,4 +1,4 @@
-from typing import List, Tuple, Union
+from typing import TYPE_CHECKING, Dict, List, Tuple, Union
import mariadb
from mariadb.constants import FIELD_TYPE
@@ -10,6 +10,19 @@ from frappe.database.database import Database
from frappe.database.mariadb.schema import MariaDBTable
from frappe.utils import UnicodeWithAttrs, get_datetime, get_table_name
+if TYPE_CHECKING:
+ from mariadb import ConnectionPool
+
+_SITE_POOLS: Dict[str, "ConnectionPool"] = {}
+_MAX_POOL_SIZE = 64 # max pool size supported by MariaDB client
+_POOL_SIZE = (
+ 4 # selected arbitrarily to avoid overloading the server and being mindful of multitenancy
+)
+
+# init size of connection pool will be _POOL_SIZE for each site. This pool may expand up to _MAX_POOL_SIZE
+# as per requirement. This cannot be a function of @@global.max_connections, # of sites since there may be
+# multiple processes holding connections; and this defines the size for each of those processes/workers
+
class MariaDBExceptionUtil:
ProgrammingError = mariadb.ProgrammingError
@@ -132,6 +145,44 @@ class MariaDBDatabase(Database, MariaDBExceptionUtil):
}
def get_connection(self):
+ # get pooled connection
+ global _SITE_POOLS
+
+ if frappe.local.site not in _SITE_POOLS:
+ pool = mariadb.ConnectionPool(
+ pool_name=f"{frappe.local.site}_conn_pool",
+ pool_size=_MAX_POOL_SIZE,
+ pool_reset_connection=False,
+ )
+ pool.set_config(**self.get_connection_settings())
+
+ for _ in range(_POOL_SIZE):
+ pool.add_connection()
+
+ _SITE_POOLS[frappe.local.site] = pool
+
+ site_pool = _SITE_POOLS[frappe.local.site]
+
+ try:
+ conn = site_pool.get_connection()
+ except mariadb.PoolError:
+ # PoolError is raised when the pool is exhausted
+ conn = self.create_connection()
+ try:
+ site_pool.add_connection(conn)
+ # log this via frappe.logger & continue - site needs bigger pool...over _POOL_SIZE
+ except mariadb.PoolError:
+ # PoolError is raised when size limit is reached
+ # log this via frappe.logger & continue - site needs a much bigger pool...over _MAX_POOL_SIZE
+ pass
+
+ return conn
+
+ def create_connection(self):
+ # get new connection
+ return mariadb.connect(**self.get_connection_settings())
+
+ def get_connection_settings(self) -> Dict:
conn_settings = {
"host": self.host,
"user": self.user,
@@ -158,8 +209,7 @@ class MariaDBDatabase(Database, MariaDBExceptionUtil):
"ssl_key": frappe.conf.db_ssl_key,
}
conn_settings.update(ssl_params)
-
- return mariadb.connect(**conn_settings)
+ return conn_settings
def get_database_size(self):
"""'Returns database size in MB"""
From dae57d0de2dabf22dbc876a3819dcdbe7e397d1e Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 12:45:42 +0530
Subject: [PATCH 0017/2449] fix(db)!: Base methods should throw
NotImplementedError
---
frappe/database/database.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index ab1d8ea4fa..c6cb162fa0 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -82,10 +82,11 @@ class Database(object):
self._conn.select_db(db_name)
def get_connection(self):
- pass
+ """Returns a Database connection object that conforms with https://peps.python.org/pep-0249/#connection-objects"""
+ raise NotImplementedError
def get_database_size(self):
- pass
+ raise NotImplementedError
def sql(
self,
From 363708d7f2e161b0becb41e90c03bfb31a5f822d Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 12:46:56 +0530
Subject: [PATCH 0018/2449] fix:
frappe.conf.disable_database_connection_pooling
For the times you don't want to use pooling ;) Calling
get_connection/connect will close all pooled connections
for the current process / worker!
---
frappe/database/mariadb/database.py | 15 +++++++++++++++
1 file changed, 15 insertions(+)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index f43f068e40..3f2580490d 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -145,9 +145,24 @@ class MariaDBDatabase(Database, MariaDBExceptionUtil):
}
def get_connection(self):
+ """Return MariaDB connection object.
+
+ If frappe.conf.disable_database_connection_pooling is set, return a new connection
+ object and close existing pool if exists. Else, return a connection from the pool.
+ """
# get pooled connection
global _SITE_POOLS
+ if frappe.conf.disable_database_connection_pooling:
+ if frappe.local.site in _SITE_POOLS:
+ pool = _SITE_POOLS[frappe.local.site]
+ try:
+ pool.close()
+ except Exception:
+ pass
+ _SITE_POOLS.pop(frappe.local.site, None)
+ return self.create_connection()
+
if frappe.local.site not in _SITE_POOLS:
pool = mariadb.ConnectionPool(
pool_name=f"{frappe.local.site}_conn_pool",
From 993b935097a6289b8325b982d980b6dd2c861bb2 Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 13:57:24 +0530
Subject: [PATCH 0019/2449] fix: Separate pool for replica setup
_SITE_POOLS[frappe.local.site].read_only will hold ConnectionPool for
replica database connections. _SITE_POOLS[frappe.local.site].default
will hold pool for connections that allow read+writes
---
frappe/database/mariadb/database.py | 76 +++++++++++++++++++----------
1 file changed, 50 insertions(+), 26 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 3f2580490d..e5cf1cd34a 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -1,3 +1,4 @@
+from collections import defaultdict
from typing import TYPE_CHECKING, Dict, List, Tuple, Union
import mariadb
@@ -13,12 +14,11 @@ from frappe.utils import UnicodeWithAttrs, get_datetime, get_table_name
if TYPE_CHECKING:
from mariadb import ConnectionPool
-_SITE_POOLS: Dict[str, "ConnectionPool"] = {}
-_MAX_POOL_SIZE = 64 # max pool size supported by MariaDB client
-_POOL_SIZE = (
- 4 # selected arbitrarily to avoid overloading the server and being mindful of multitenancy
-)
+_SITE_POOLS = defaultdict(frappe._dict)
+_MAX_POOL_SIZE = 64
+_POOL_SIZE = 4
+# _POOL_SIZE is selected "arbitrarily" to avoid overloading the server and being mindful of multitenancy
# init size of connection pool will be _POOL_SIZE for each site. This pool may expand up to _MAX_POOL_SIZE
# as per requirement. This cannot be a function of @@global.max_connections, # of sites since there may be
# multiple processes holding connections; and this defines the size for each of those processes/workers
@@ -154,29 +154,15 @@ class MariaDBDatabase(Database, MariaDBExceptionUtil):
global _SITE_POOLS
if frappe.conf.disable_database_connection_pooling:
- if frappe.local.site in _SITE_POOLS:
- pool = _SITE_POOLS[frappe.local.site]
- try:
- pool.close()
- except Exception:
- pass
- _SITE_POOLS.pop(frappe.local.site, None)
+ self.close_connection_pools()
return self.create_connection()
+ is_read_only_conn = hasattr(frappe.local, "primary_db")
+
if frappe.local.site not in _SITE_POOLS:
- pool = mariadb.ConnectionPool(
- pool_name=f"{frappe.local.site}_conn_pool",
- pool_size=_MAX_POOL_SIZE,
- pool_reset_connection=False,
- )
- pool.set_config(**self.get_connection_settings())
-
- for _ in range(_POOL_SIZE):
- pool.add_connection()
-
- _SITE_POOLS[frappe.local.site] = pool
-
- site_pool = _SITE_POOLS[frappe.local.site]
+ site_pool = self.create_connection_pool(read_only=is_read_only_conn)
+ else:
+ site_pool = self.get_connection_pool(read_only=is_read_only_conn)
try:
conn = site_pool.get_connection()
@@ -193,8 +179,46 @@ class MariaDBDatabase(Database, MariaDBExceptionUtil):
return conn
+ def close_connection_pools(self):
+ if frappe.local.site in _SITE_POOLS:
+ pools = _SITE_POOLS[frappe.local.site]
+ for pool in pools.values():
+ try:
+ pool.close()
+ except Exception:
+ pass
+ _SITE_POOLS.pop(frappe.local.site, None)
+
+ def get_pool_name(self, read_only=False) -> str:
+ pool_type = "read-only" if read_only else "default"
+ return f"{frappe.local.site}-{pool_type}"
+
+ def get_connection_pool(self, read_only=False) -> "ConnectionPool":
+ """Return MariaDB connection pool object.
+
+ If `read_only` is True, return a read only pool.
+ """
+ return _SITE_POOLS[frappe.local.site]["read_only" if read_only else "default"]
+
+ def create_connection_pool(self, read_only=False):
+ pool = mariadb.ConnectionPool(
+ pool_name=self.get_pool_name(read_only=read_only),
+ pool_size=_MAX_POOL_SIZE,
+ pool_reset_connection=False,
+ )
+ pool.set_config(**self.get_connection_settings())
+
+ for _ in range(_POOL_SIZE):
+ pool.add_connection()
+
+ if read_only:
+ _SITE_POOLS[frappe.local.site].read_only = pool
+ else:
+ _SITE_POOLS[frappe.local.site].default = pool
+
+ return pool
+
def create_connection(self):
- # get new connection
return mariadb.connect(**self.get_connection_settings())
def get_connection_settings(self) -> Dict:
From f02125030882b28404229401ed341e24143aa60f Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 14:07:34 +0530
Subject: [PATCH 0020/2449] refactor: Move connection & pool management out,
inherit instead
---
frappe/database/mariadb/database.py | 112 ++++++++++++++--------------
1 file changed, 58 insertions(+), 54 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index e5cf1cd34a..f9616761b1 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -19,9 +19,11 @@ _MAX_POOL_SIZE = 64
_POOL_SIZE = 4
# _POOL_SIZE is selected "arbitrarily" to avoid overloading the server and being mindful of multitenancy
-# init size of connection pool will be _POOL_SIZE for each site. This pool may expand up to _MAX_POOL_SIZE
-# as per requirement. This cannot be a function of @@global.max_connections, # of sites since there may be
-# multiple processes holding connections; and this defines the size for each of those processes/workers
+# init size of connection pool will be _POOL_SIZE for each site. Replica setups will have separate pool.
+# This means each site with a replica setup can have 2 active pools of size _POOL_SIZE each. Each pool may
+# expand up to _MAX_POOL_SIZE as per requirement. This cannot be a function of @@global.max_connections,
+# no. of sites since there may be multiple processes holding connections; and this defines the size for each
+# of those processes/workers. Check MariaDBConnectionUtil for connection & pool management.
class MariaDBExceptionUtil:
@@ -93,57 +95,7 @@ class MariaDBExceptionUtil:
)
-class MariaDBDatabase(Database, MariaDBExceptionUtil):
- REGEX_CHARACTER = "regexp"
-
- # NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
- # it drops the cache and uses the next non cached value in setval query and
- # puts that in the backup file, which will start the counter
- # from that value when inserting any new record in the doctype.
- # By default the cache is 1000 which will mess up the sequence when
- # using the system after a restore.
- # issue link: https://jira.mariadb.org/browse/MDEV-21786
- SEQUENCE_CACHE = 50
-
- def setup_type_map(self):
- self.db_type = "mariadb"
- self.type_map = {
- "Currency": ("decimal", "21,9"),
- "Int": ("int", "11"),
- "Long Int": ("bigint", "20"),
- "Float": ("decimal", "21,9"),
- "Percent": ("decimal", "21,9"),
- "Check": ("int", "1"),
- "Small Text": ("text", ""),
- "Long Text": ("longtext", ""),
- "Code": ("longtext", ""),
- "Text Editor": ("longtext", ""),
- "Markdown Editor": ("longtext", ""),
- "HTML Editor": ("longtext", ""),
- "Date": ("date", ""),
- "Datetime": ("datetime", "6"),
- "Time": ("time", "6"),
- "Text": ("text", ""),
- "Data": ("varchar", self.VARCHAR_LEN),
- "Link": ("varchar", self.VARCHAR_LEN),
- "Dynamic Link": ("varchar", self.VARCHAR_LEN),
- "Password": ("text", ""),
- "Select": ("varchar", self.VARCHAR_LEN),
- "Rating": ("decimal", "3,2"),
- "Read Only": ("varchar", self.VARCHAR_LEN),
- "Attach": ("text", ""),
- "Attach Image": ("text", ""),
- "Signature": ("longtext", ""),
- "Color": ("varchar", self.VARCHAR_LEN),
- "Barcode": ("longtext", ""),
- "Geolocation": ("longtext", ""),
- "Duration": ("decimal", "21,9"),
- "Icon": ("varchar", self.VARCHAR_LEN),
- "Phone": ("varchar", self.VARCHAR_LEN),
- "Autocomplete": ("varchar", self.VARCHAR_LEN),
- "JSON": ("json", ""),
- }
-
+class MariaDBConnectionUtil:
def get_connection(self):
"""Return MariaDB connection object.
@@ -250,6 +202,58 @@ class MariaDBDatabase(Database, MariaDBExceptionUtil):
conn_settings.update(ssl_params)
return conn_settings
+
+class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
+ REGEX_CHARACTER = "regexp"
+
+ # NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
+ # it drops the cache and uses the next non cached value in setval query and
+ # puts that in the backup file, which will start the counter
+ # from that value when inserting any new record in the doctype.
+ # By default the cache is 1000 which will mess up the sequence when
+ # using the system after a restore.
+ # issue link: https://jira.mariadb.org/browse/MDEV-21786
+ SEQUENCE_CACHE = 50
+
+ def setup_type_map(self):
+ self.db_type = "mariadb"
+ self.type_map = {
+ "Currency": ("decimal", "21,9"),
+ "Int": ("int", "11"),
+ "Long Int": ("bigint", "20"),
+ "Float": ("decimal", "21,9"),
+ "Percent": ("decimal", "21,9"),
+ "Check": ("int", "1"),
+ "Small Text": ("text", ""),
+ "Long Text": ("longtext", ""),
+ "Code": ("longtext", ""),
+ "Text Editor": ("longtext", ""),
+ "Markdown Editor": ("longtext", ""),
+ "HTML Editor": ("longtext", ""),
+ "Date": ("date", ""),
+ "Datetime": ("datetime", "6"),
+ "Time": ("time", "6"),
+ "Text": ("text", ""),
+ "Data": ("varchar", self.VARCHAR_LEN),
+ "Link": ("varchar", self.VARCHAR_LEN),
+ "Dynamic Link": ("varchar", self.VARCHAR_LEN),
+ "Password": ("text", ""),
+ "Select": ("varchar", self.VARCHAR_LEN),
+ "Rating": ("decimal", "3,2"),
+ "Read Only": ("varchar", self.VARCHAR_LEN),
+ "Attach": ("text", ""),
+ "Attach Image": ("text", ""),
+ "Signature": ("longtext", ""),
+ "Color": ("varchar", self.VARCHAR_LEN),
+ "Barcode": ("longtext", ""),
+ "Geolocation": ("longtext", ""),
+ "Duration": ("decimal", "21,9"),
+ "Icon": ("varchar", self.VARCHAR_LEN),
+ "Phone": ("varchar", self.VARCHAR_LEN),
+ "Autocomplete": ("varchar", self.VARCHAR_LEN),
+ "JSON": ("json", ""),
+ }
+
def get_database_size(self):
"""'Returns database size in MB"""
db_size = self.sql(
From aaef732581f470c2264a0f07168bfe498d88a2f6 Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 14:09:09 +0530
Subject: [PATCH 0021/2449] fix!: Remove frappe.db.create_help_table
Help table has been deprecated for a while. It's safe to get rid of the
API too.
---
frappe/database/mariadb/database.py | 16 ----------------
frappe/database/postgres/database.py | 11 -----------
2 files changed, 27 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index f9616761b1..fd9ecd1f96 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -354,22 +354,6 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
) ENGINE=InnoDB DEFAULT CHARSET=utf8"""
)
- def create_help_table(self):
- self.sql(
- """create table help(
- path varchar(255),
- content text,
- title text,
- intro text,
- full_path text,
- fulltext(title),
- fulltext(content),
- index (path))
- COLLATE=utf8mb4_unicode_ci
- ENGINE=MyISAM
- CHARACTER SET=utf8mb4"""
- )
-
@staticmethod
def get_on_duplicate_update(key=None):
return "ON DUPLICATE key UPDATE "
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 84a237e702..d60a6d1918 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -267,17 +267,6 @@ class PostgresDatabase(Database):
)"""
)
- def create_help_table(self):
- self.sql(
- """CREATE TABLE "help"(
- "path" varchar(255),
- "content" text,
- "title" text,
- "intro" text,
- "full_path" text)"""
- )
- self.sql("""CREATE INDEX IF NOT EXISTS "help_index" ON "help" ("path")""")
-
def updatedb(self, doctype, meta=None):
"""
Syncs a `DocType` to the table
From 0fe764c9b8564205e40ae91b22aa4e88c1e6bb03 Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 14:24:26 +0530
Subject: [PATCH 0022/2449] fix: Depend on replica details being there for
Replica ConnectionPool
This logic mirror how replica connections are handled
---
frappe/database/mariadb/database.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index fd9ecd1f96..595a02db6f 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -102,19 +102,18 @@ class MariaDBConnectionUtil:
If frappe.conf.disable_database_connection_pooling is set, return a new connection
object and close existing pool if exists. Else, return a connection from the pool.
"""
- # get pooled connection
global _SITE_POOLS
if frappe.conf.disable_database_connection_pooling:
self.close_connection_pools()
return self.create_connection()
- is_read_only_conn = hasattr(frappe.local, "primary_db")
+ read_only = frappe.conf.read_from_replica and frappe.conf.replica_host
if frappe.local.site not in _SITE_POOLS:
- site_pool = self.create_connection_pool(read_only=is_read_only_conn)
+ site_pool = self.create_connection_pool(read_only=read_only)
else:
- site_pool = self.get_connection_pool(read_only=is_read_only_conn)
+ site_pool = self.get_connection_pool(read_only=read_only)
try:
conn = site_pool.get_connection()
From 9100e8f0bb53a9afb19437b456a11d2077dd0054 Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 18:36:59 +0530
Subject: [PATCH 0023/2449] ci: Install system dependency - libmariadb-dev
---
.github/helper/install_dependencies.sh | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/helper/install_dependencies.sh b/.github/helper/install_dependencies.sh
index f0e8016860..666af13882 100644
--- a/.github/helper/install_dependencies.sh
+++ b/.github/helper/install_dependencies.sh
@@ -21,3 +21,5 @@ sudo apt-get install libcups2-dev
# install redis
sudo apt-get install redis-server
+# install redis
+sudo apt-get install libmariadb-dev
From 607a99f50bdee0a906bca8f87b00617d6293a374 Mon Sep 17 00:00:00 2001
From: gavin
Date: Mon, 23 May 2022 18:43:19 +0530
Subject: [PATCH 0024/2449] fix: F821 undefined name 'InternalError'
tx flake8
---
frappe/geo/utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/geo/utils.py b/frappe/geo/utils.py
index 9340e28a36..d4b225b055 100644
--- a/frappe/geo/utils.py
+++ b/frappe/geo/utils.py
@@ -80,7 +80,7 @@ def return_coordinates(doctype, filters_sql):
"""SELECT name, latitude, longitude FROM `tab{}` WHERE {}""".format(doctype, filters_sql),
as_dict=True,
)
- except InternalError:
+ except frappe.db.InternalError:
frappe.msgprint(
frappe._("This Doctype does not contain latitude and longitude fields"), raise_exception=True
)
From 6637a4ebbe58d677095b5c031a57cde5cfdf5d4c Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 12:34:14 +0530
Subject: [PATCH 0025/2449] fix: Allow non-blocking checks via
MariaDBExceptionUtils
---
frappe/database/mariadb/database.py | 20 ++++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 595a02db6f..9bd2d0bcd2 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -36,15 +36,15 @@ class MariaDBExceptionUtil:
@staticmethod
def is_deadlocked(e: mariadb.Error) -> bool:
- return e.errno == ER.LOCK_DEADLOCK
+ return getattr(e, "errno", None) == ER.LOCK_DEADLOCK
@staticmethod
def is_timedout(e: mariadb.Error) -> bool:
- return e.errno == ER.LOCK_WAIT_TIMEOUT
+ return getattr(e, "errno", None) == ER.LOCK_WAIT_TIMEOUT
@staticmethod
def is_table_missing(e: mariadb.Error) -> bool:
- return e.errno == ER.NO_SUCH_TABLE
+ return getattr(e, "errno", None) == ER.NO_SUCH_TABLE
@staticmethod
def is_missing_table(e: mariadb.Error) -> bool:
@@ -52,31 +52,31 @@ class MariaDBExceptionUtil:
@staticmethod
def is_missing_column(e: mariadb.Error) -> bool:
- return e.errno == ER.BAD_FIELD_ERROR
+ return getattr(e, "errno", None) == ER.BAD_FIELD_ERROR
@staticmethod
def is_duplicate_fieldname(e: mariadb.Error) -> bool:
- return e.errno == ER.DUP_FIELDNAME
+ return getattr(e, "errno", None) == ER.DUP_FIELDNAME
@staticmethod
def is_duplicate_entry(e: mariadb.Error) -> bool:
- return e.errno == ER.DUP_ENTRY
+ return getattr(e, "errno", None) == ER.DUP_ENTRY
@staticmethod
def is_access_denied(e: mariadb.Error) -> bool:
- return e.errno == ER.ACCESS_DENIED_ERROR
+ return getattr(e, "errno", None) == ER.ACCESS_DENIED_ERROR
@staticmethod
def cant_drop_field_or_key(e: mariadb.Error) -> bool:
- return e.errno == ER.CANT_DROP_FIELD_OR_KEY
+ return getattr(e, "errno", None) == ER.CANT_DROP_FIELD_OR_KEY
@staticmethod
def is_syntax_error(e: mariadb.Error) -> bool:
- return e.errno == ER.PARSE_ERROR
+ return getattr(e, "errno", None) == ER.PARSE_ERROR
@staticmethod
def is_data_too_long(e: mariadb.Error) -> bool:
- return e.errno == ER.DATA_TOO_LONG
+ return getattr(e, "errno", None) == ER.DATA_TOO_LONG
@staticmethod
def is_primary_key_violation(e: mariadb.Error) -> bool:
From 73c3db12ee2e87518dbb06a52720648956c95781 Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 12:44:21 +0530
Subject: [PATCH 0026/2449] refactor(minor): Move internal util to the module
where it's used
---
frappe/commands/scheduler.py | 16 ----------------
frappe/installer.py | 19 +++++++++++++++----
2 files changed, 15 insertions(+), 20 deletions(-)
diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py
index ed6a0dea57..70af784930 100755
--- a/frappe/commands/scheduler.py
+++ b/frappe/commands/scheduler.py
@@ -5,22 +5,6 @@ import click
import frappe
from frappe.commands import get_site, pass_context
from frappe.exceptions import SiteNotSpecifiedError
-from frappe.utils import cint
-
-
-def _is_scheduler_enabled():
- enable_scheduler = False
- try:
- frappe.connect()
- enable_scheduler = (
- cint(frappe.db.get_single_value("System Settings", "enable_scheduler")) and True or False
- )
- except:
- pass
- finally:
- frappe.db.close()
-
- return enable_scheduler
@click.command("trigger-scheduler-event", help="Trigger a scheduler event")
diff --git a/frappe/installer.py b/frappe/installer.py
index 5cd46e618d..42cc023c6a 100644
--- a/frappe/installer.py
+++ b/frappe/installer.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import json
@@ -11,7 +11,20 @@ import click
import frappe
from frappe.defaults import _clear_cache
-from frappe.utils import is_git_url
+from frappe.utils import cint, is_git_url
+
+
+def _is_scheduler_enabled() -> bool:
+ enable_scheduler = False
+ try:
+ frappe.connect()
+ enable_scheduler = cint(frappe.db.get_single_value("System Settings", "enable_scheduler"))
+ except Exception:
+ pass
+ finally:
+ frappe.db.close()
+
+ return bool(enable_scheduler)
def _new_site(
@@ -30,11 +43,9 @@ def _new_site(
db_type=None,
db_host=None,
db_port=None,
- new_site=False,
):
"""Install a new Frappe site"""
- from frappe.commands.scheduler import _is_scheduler_enabled
from frappe.utils import get_site_path, scheduler, touch_file
if not force and os.path.exists(site):
From 639fa621386b15490c99c22c54516fab9e5a63dd Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 18:41:31 +0530
Subject: [PATCH 0027/2449] fix: frappe.DISABLE_DATABASE_POOLING to override
frappe.conf.disable_database_connection_pooling
Also, don't pool root connections
---
frappe/__init__.py | 1 +
frappe/commands/site.py | 2 ++
frappe/database/mariadb/database.py | 16 +++++++++++++++-
3 files changed, 18 insertions(+), 1 deletion(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index c97dc42a9b..ec016c8b36 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -41,6 +41,7 @@ __title__ = "Frappe Framework"
controllers = {}
local = Local()
STANDARD_USERS = ("Guest", "Administrator")
+DISABLE_DATABASE_POOLING = None
_dev_server = int(sbool(os.environ.get("DEV_SERVER", False)))
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 628a10d67e..c8191daaa1 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -68,6 +68,8 @@ def new_site(
"Create a new site"
from frappe.installer import _new_site
+ frappe.DISABLE_DATABASE_POOLING = True
+
frappe.init(site=site, new_site=True)
_new_site(
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 9bd2d0bcd2..fe43e8ffed 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -26,6 +26,16 @@ _POOL_SIZE = 4
# of those processes/workers. Check MariaDBConnectionUtil for connection & pool management.
+def is_connection_pooling_enabled() -> bool:
+ """Set `frappe.DISABLE_CONNECTION_POOLING` to enable/disable connection pooling for all on current
+ process. This will override config key `disable_database_connection_pooling`. Set key
+ `disable_database_connection_pooling` in site config for persistent settings across workers."""
+
+ if frappe.DISABLE_DATABASE_POOLING is not None:
+ return not frappe.DISABLE_DATABASE_POOLING
+ return frappe.local.conf.disable_database_connection_pooling
+
+
class MariaDBExceptionUtil:
ProgrammingError = mariadb.ProgrammingError
TableMissingError = mariadb.ProgrammingError
@@ -104,7 +114,11 @@ class MariaDBConnectionUtil:
"""
global _SITE_POOLS
- if frappe.conf.disable_database_connection_pooling:
+ # don't pool root connections
+ if self.user == "root":
+ return self.create_connection()
+
+ if is_connection_pooling_enabled():
self.close_connection_pools()
return self.create_connection()
From 7e7695343502c4734dd5ce11c48704e568deea38 Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 18:43:23 +0530
Subject: [PATCH 0028/2449] fix(drop-site): Tell user where archived site data
is stored
---
frappe/commands/site.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index c8191daaa1..0a97a4107f 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -87,7 +87,6 @@ def new_site(
db_type=db_type,
db_host=db_host,
db_port=db_port,
- new_site=True,
)
if set_default:
@@ -849,9 +848,10 @@ def _drop_site(
archived_sites_path = archived_sites_path or os.path.join(
frappe.get_app_path("frappe"), "..", "..", "..", "archived", "sites"
)
+ archived_sites_path = os.path.realpath(archived_sites_path)
+ click.secho(f"Moving site to archive under {archived_sites_path}", fg="green")
os.makedirs(archived_sites_path, exist_ok=True)
-
move(archived_sites_path, site)
From 32a30ce933f5146e2322894849d56957ad28a79f Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 18:44:35 +0530
Subject: [PATCH 0029/2449] refactor: DBManager
* Simplify logic: DRY, lesser indentation & all DAT
* Utilize newer APIs, f-strings & more
* Cleaner namespace
* Conform inconsistent behaviours
---
frappe/database/db_manager.py | 58 +++++++++++++----------------------
1 file changed, 22 insertions(+), 36 deletions(-)
diff --git a/frappe/database/db_manager.py b/frappe/database/db_manager.py
index 8f810fe54b..f90fb59d97 100644
--- a/frappe/database/db_manager.py
+++ b/frappe/database/db_manager.py
@@ -1,5 +1,3 @@
-import os
-
import frappe
@@ -15,63 +13,51 @@ class DbManager:
return self.db.sql("select user()")[0][0].split("@")[1]
def create_user(self, user, password, host=None):
- # Create user if it doesn't exist.
- if not host:
- host = self.get_current_host()
-
- if password:
- self.db.sql("CREATE USER '%s'@'%s' IDENTIFIED BY '%s';" % (user, host, password))
- else:
- self.db.sql("CREATE USER '%s'@'%s';" % (user, host))
+ host = host or self.get_current_host()
+ password_predicate = f" IDENTIFIED BY '{password}'" if password else ""
+ self.db.sql(f"CREATE USER '{user}'@'{host}'{password_predicate}")
def delete_user(self, target, host=None):
- if not host:
- host = self.get_current_host()
- try:
- self.db.sql("DROP USER '%s'@'%s';" % (target, host))
- except Exception as e:
- if e.args[0] == 1396:
- pass
- else:
- raise
+ host = host or self.get_current_host()
+ self.db.sql(f"DROP USER IF EXISTS '{target}'@'{host}'")
def create_database(self, target):
if target in self.get_database_list():
self.drop_database(target)
-
- self.db.sql("CREATE DATABASE `%s` ;" % target)
+ self.db.sql(f"CREATE DATABASE `{target}`")
def drop_database(self, target):
- self.db.sql("DROP DATABASE IF EXISTS `%s`;" % target)
+ self.db.sql(f"DROP DATABASE IF EXISTS `{target}`")
def grant_all_privileges(self, target, user, host=None):
- if not host:
- host = self.get_current_host()
-
- if frappe.conf.get("rds_db", 0) == 1:
- self.db.sql(
- "GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW, CREATE ROUTINE, ALTER ROUTINE, EXECUTE, LOCK TABLES ON `%s`.* TO '%s'@'%s';"
- % (target, user, host)
+ host = host or self.get_current_host()
+ permissions = (
+ (
+ "SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, INDEX, ALTER, "
+ "CREATE TEMPORARY TABLES, CREATE VIEW, EVENT, TRIGGER, SHOW VIEW, "
+ "CREATE ROUTINE, ALTER ROUTINE, EXECUTE, LOCK TABLES"
)
- else:
- self.db.sql("GRANT ALL PRIVILEGES ON `%s`.* TO '%s'@'%s';" % (target, user, host))
+ if frappe.conf.rds_db
+ else "ALL PRIVILEGES"
+ )
+ self.db.sql(f"GRANT {permissions} ON `{target}`.* TO '{user}'@'{host}'")
def flush_privileges(self):
self.db.sql("FLUSH PRIVILEGES")
def get_database_list(self):
- """get list of databases"""
- return [d[0] for d in self.db.sql("SHOW DATABASES")]
+ return self.db.sql("SHOW DATABASES", pluck=True)
@staticmethod
def restore_database(target, source, user, password):
+ import os
+ from distutils.spawn import find_executable
+
from frappe.utils import make_esc
esc = make_esc("$ ")
-
- from distutils.spawn import find_executable
-
pv = find_executable("pv")
+
if pv:
pipe = "{pv} {source} |".format(pv=pv, source=source)
source = ""
From a7838ccca4ea8671174d3d0e74092ac5268a0022 Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 18:49:12 +0530
Subject: [PATCH 0030/2449] fix: Remove half baked support for filters_config
in db.query
---
frappe/database/query.py | 18 ++++++------------
1 file changed, 6 insertions(+), 12 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index f608539854..c2dd076f8f 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -5,7 +5,6 @@ from typing import Any, Callable, Dict, List, Tuple, Union
import frappe
from frappe import _
-from frappe.boot import get_additional_filters_from_hooks
from frappe.model.db_query import get_timespan_date_range
from frappe.query_builder import Criterion, Field, Order, Table
@@ -173,19 +172,14 @@ class Query:
# default operators
all_operators = OPERATOR_MAP.copy()
- # update with site-specific custom operators
- additional_filters_config = get_additional_filters_from_hooks()
-
- if additional_filters_config:
+ # TODO: update with site-specific custom operators / removed previous buggy implementation
+ if frappe.get_hooks("filters_config"):
from frappe.utils.commands import warn
- warn("'filters_config' hook is not completely implemented yet in frappe.db.query engine")
-
- for _operator, function in additional_filters_config.items():
- if callable(function):
- all_operators.update({_operator.casefold(): function})
- elif isinstance(function, dict):
- all_operators[_operator.casefold()] = frappe.get_attr(function.get("get_field"))()["operator"]
+ warn(
+ "The 'filters_config' hook used to add custom operators is not yet implemented"
+ " in frappe.db.query engine. Use db_query (frappe.get_list) instead."
+ )
return all_operators
From 3ed808aec7db82ffec9ed9bbde13f210196fa138 Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 18:52:19 +0530
Subject: [PATCH 0031/2449] fix: Connection Pooling
* Reduce _POOL_SIZE from 4 to 1. New pools will have just one
connection. They can scale up as per requirement there after.
* Set auto_connect flag in MariaDB connection - https://mariadb.com/docs/connect/programming-languages/python/connect/
---
frappe/database/mariadb/database.py | 17 ++++++++++++-----
1 file changed, 12 insertions(+), 5 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index fe43e8ffed..c8b365c61d 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -16,7 +16,7 @@ if TYPE_CHECKING:
_SITE_POOLS = defaultdict(frappe._dict)
_MAX_POOL_SIZE = 64
-_POOL_SIZE = 4
+_POOL_SIZE = 1
# _POOL_SIZE is selected "arbitrarily" to avoid overloading the server and being mindful of multitenancy
# init size of connection pool will be _POOL_SIZE for each site. Replica setups will have separate pool.
@@ -107,6 +107,11 @@ class MariaDBExceptionUtil:
class MariaDBConnectionUtil:
def get_connection(self):
+ conn = self._get_connection()
+ conn.auto_reconnect = True
+ return conn
+
+ def _get_connection(self) -> "mariadb.Connection":
"""Return MariaDB connection object.
If frappe.conf.disable_database_connection_pooling is set, return a new connection
@@ -173,14 +178,14 @@ class MariaDBConnectionUtil:
)
pool.set_config(**self.get_connection_settings())
- for _ in range(_POOL_SIZE):
- pool.add_connection()
-
if read_only:
_SITE_POOLS[frappe.local.site].read_only = pool
else:
_SITE_POOLS[frappe.local.site].default = pool
+ for _ in range(_POOL_SIZE):
+ pool.add_connection()
+
return pool
def create_connection(self):
@@ -191,7 +196,6 @@ class MariaDBConnectionUtil:
"host": self.host,
"user": self.user,
"password": self.password,
- "database": self.user,
"converter": {
FIELD_TYPE.NEWDECIMAL: float,
FIELD_TYPE.DATETIME: get_datetime,
@@ -199,6 +203,9 @@ class MariaDBConnectionUtil:
},
}
+ if self.user != "root":
+ conn_settings["database"] = self.user
+
if self.port:
conn_settings["port"] = int(self.port)
From d87197f04211435bddeae04112ff1a735a8f96c6 Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 19:18:22 +0530
Subject: [PATCH 0032/2449] fix: Correct use of is_connection_pooling_enabled
check
---
frappe/database/mariadb/database.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index c8b365c61d..a85df44dac 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -123,7 +123,7 @@ class MariaDBConnectionUtil:
if self.user == "root":
return self.create_connection()
- if is_connection_pooling_enabled():
+ if not is_connection_pooling_enabled():
self.close_connection_pools()
return self.create_connection()
From f7c5c27d818420392d48d0a61c98dd1c25509cf3 Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 19:35:57 +0530
Subject: [PATCH 0033/2449] fix: Use sql_ddl instead of sql
Previous attempts to run this would lead to stalls...long long stalls.
And nothing blocking the query, it would stay in a "Waiting for table
metadata lock" state.
---
frappe/database/db_manager.py | 2 +-
frappe/database/mariadb/setup_db.py | 33 +++++++++++++----------------
2 files changed, 16 insertions(+), 19 deletions(-)
diff --git a/frappe/database/db_manager.py b/frappe/database/db_manager.py
index f90fb59d97..f69c93db0f 100644
--- a/frappe/database/db_manager.py
+++ b/frappe/database/db_manager.py
@@ -27,7 +27,7 @@ class DbManager:
self.db.sql(f"CREATE DATABASE `{target}`")
def drop_database(self, target):
- self.db.sql(f"DROP DATABASE IF EXISTS `{target}`")
+ self.db.sql_ddl(f"DROP DATABASE IF EXISTS `{target}`")
def grant_all_privileges(self, target, user, host=None):
host = host or self.get_current_host()
diff --git a/frappe/database/mariadb/setup_db.py b/frappe/database/mariadb/setup_db.py
index 4399ccfa6a..c26aaea343 100644
--- a/frappe/database/mariadb/setup_db.py
+++ b/frappe/database/mariadb/setup_db.py
@@ -83,9 +83,9 @@ def setup_help_database(help_db_name):
def drop_user_and_database(db_name, root_login, root_password):
frappe.local.db = get_root_connection(root_login, root_password)
dbman = DbManager(frappe.local.db)
+ dbman.drop_database(db_name)
dbman.delete_user(db_name, host="%")
dbman.delete_user(db_name)
- dbman.drop_database(db_name)
def bootstrap_database(db_name, verbose, source_sql=None):
@@ -131,7 +131,7 @@ def check_database_settings():
else:
expected_variables = expected_settings_10_3_later
- mariadb_variables = frappe._dict(frappe.db.sql("""show variables"""))
+ mariadb_variables = frappe._dict(frappe.db.sql("show variables"))
# Check each expected value vs. actuals:
result = True
for key, expected_value in expected_variables.items():
@@ -142,16 +142,19 @@ def check_database_settings():
)
result = False
if not result:
- site = frappe.local.site
- msg = (
- "Creation of your site - {x} failed because MariaDB is not properly {sep}"
- "configured. If using version 10.2.x or earlier, make sure you use the {sep}"
- "the Barracuda storage engine. {sep}{sep}"
- "Please verify the settings above in MariaDB's my.cnf. Restart MariaDB. And {sep}"
- "then run `bench new-site {x}` again.{sep2}"
- ""
- ).format(x=site, sep2="\n" * 2, sep="\n")
- print_db_config(msg)
+ print(
+ (
+ "=" * 80 + "\n"
+ "Creation of your site - {x} failed because MariaDB is not properly {sep}"
+ "configured. If using version 10.2.x or earlier, make sure you use the {sep}"
+ "the Barracuda storage engine. {sep}{sep}"
+ "Please verify the settings above in MariaDB's my.cnf. Restart MariaDB. And {sep}"
+ "then run `bench new-site {x}` again.{sep2}"
+ ""
+ "=" * 80
+ ).format(x=frappe.local.site, sep2="\n" * 2, sep="\n")
+ )
+
return result
@@ -173,9 +176,3 @@ def get_root_connection(root_login, root_password):
)
return frappe.local.flags.root_connection
-
-
-def print_db_config(explanation):
- print("=" * 80)
- print(explanation)
- print("=" * 80)
From 0a8941c58317956e0661f90fa2357e39674f63c3 Mon Sep 17 00:00:00 2001
From: gavin
Date: Tue, 24 May 2022 19:54:12 +0530
Subject: [PATCH 0034/2449] fix: Disable db pooling on all commands by default
Set envvar DATABASE_POOLING to enable
---
frappe/commands/site.py | 2 --
frappe/utils/bench_helper.py | 6 ++++++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 0a97a4107f..80acf647e0 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -68,8 +68,6 @@ def new_site(
"Create a new site"
from frappe.installer import _new_site
- frappe.DISABLE_DATABASE_POOLING = True
-
frappe.init(site=site, new_site=True)
_new_site(
diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py
index a0b011acc1..8de10b73d4 100644
--- a/frappe/utils/bench_helper.py
+++ b/frappe/utils/bench_helper.py
@@ -1,6 +1,7 @@
import importlib
import json
import os
+import sys
import traceback
import warnings
@@ -106,4 +107,9 @@ if __name__ == "__main__":
if not frappe._dev_server:
warnings.simplefilter("ignore")
+ # disable pooling for commands executed via bench unless explicitly stated otherwise
+ # - except for commands serve & worker
+ if not {"serve", "worker"} & set(sys.argv) and int(os.environ.get("DATABASE_POOLING", 0)):
+ frappe.DISABLE_DATABASE_POOLING = True
+
main()
From 4f72eb9eac45ac4a9917e65df8ac22389e247938 Mon Sep 17 00:00:00 2001
From: gavin
Date: Wed, 25 May 2022 11:44:08 +0530
Subject: [PATCH 0035/2449] refactor: Base Database class
* DRY, explicit > implicit usages
* Don't re-compute and do multiple calls for errprint, log, mogrify, etc
* Use consistent logging methods
* Simplify logic / Use newer APIs where applicable
---
frappe/database/database.py | 59 ++++++++++++++++---------------------
1 file changed, 26 insertions(+), 33 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index c6cb162fa0..2cd950f785 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -1,10 +1,11 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
# Database Module
# --------------------
import datetime
+import json
import random
import re
import string
@@ -159,24 +160,16 @@ class Database(object):
if debug:
time_start = time()
- self.log_query(query, values, debug, explain)
-
if values != ():
-
- # MySQL-python==1.2.5 hack!
if not isinstance(values, (dict, tuple, list)):
values = (values,)
- self._cursor.execute(query, values)
+ self.log_query(query, values, debug, explain)
- if frappe.flags.in_migrate:
- self.log_touched_tables(query, values)
+ self._cursor.execute(query, values)
- else:
- self._cursor.execute(query)
-
- if frappe.flags.in_migrate:
- self.log_touched_tables(query)
+ if frappe.flags.in_migrate:
+ self.log_touched_tables(query, values)
if debug:
time_end = time()
@@ -184,8 +177,7 @@ class Database(object):
except Exception as e:
if self.is_syntax_error(e):
- frappe.errprint("Syntax error in query:")
- frappe.errprint(query)
+ frappe.errprint(f"Syntax error in query:\n{query}")
elif self.is_deadlocked(e):
raise frappe.QueryDeadlockError(e)
@@ -195,7 +187,7 @@ class Database(object):
elif frappe.conf.db_type == "postgres":
# TODO: added temporarily
- print(e)
+ frappe.errprint(f"Error in query:\n{e}")
raise
if ignore_ddl and (
@@ -229,21 +221,23 @@ class Database(object):
return self._cursor.fetchall()
def log_query(self, query, values, debug, explain):
+ mogrified_query = None
+
# for debugging in tests
if frappe.conf.get("allow_tests") and frappe.cache().get_value("flag_print_sql"):
- print(self.mogrify(query, values))
+ mogrified_query = mogrified_query or self.mogrify(query, values)
+ print(mogrified_query)
# debug
if debug:
if explain and query.strip().lower().startswith("select"):
self.explain_query(query, values)
- frappe.errprint(self.mogrify(query, values))
+ mogrified_query = mogrified_query or self.mogrify(query, values)
+ frappe.errprint(mogrified_query)
- # info
- if (frappe.conf.get("logging") or False) == 2:
- frappe.log("<<<< query")
- frappe.log(self.mogrify(query, values))
- frappe.log(">>>>")
+ if frappe.conf.logging == 2:
+ mogrified_query = mogrified_query or self.mogrify(query, values)
+ frappe.log(f"<<<< query\n{mogrified_query}\n>>>>")
def mogrify(self, query, values):
"""build the query string with values"""
@@ -252,23 +246,22 @@ class Database(object):
else:
try:
return self._cursor.mogrify(query, values)
- except: # noqa: E722
+ except BaseException: # noqa: E722
return (query, values)
def explain_query(self, query, values=None):
"""Print `EXPLAIN` in error log."""
try:
frappe.errprint("--- query explain ---")
- if values is None:
- self._cursor.execute("explain " + query)
- else:
- self._cursor.execute("explain " + query, values)
- import json
+
+ explain_query = f"EXPLAIN {query}"
+ values = values or ()
+ self._cursor.execute(explain_query, values)
frappe.errprint(json.dumps(self.fetch_as_dict(), indent=1))
frappe.errprint("--- query explain end ---")
- except Exception:
- frappe.errprint("error in query explain")
+ except Exception as e:
+ frappe.errprint(f"error in query explain: {e}")
def sql_list(self, query, values=(), debug=False, **kwargs):
"""Return data as list of single elements (first column).
@@ -278,7 +271,7 @@ class Database(object):
# doctypes = ["DocType", "DocField", "User", ...]
doctypes = frappe.db.sql_list("select name from DocType")
"""
- return [r[0] for r in self.sql(query, values, **kwargs, debug=debug)]
+ return self.sql(query, values, **kwargs, debug=debug, pluck=True)
def sql_ddl(self, query, values=(), debug=False):
"""Commit and execute a query. DDL (Data Definition Language) queries that alter schema
@@ -1193,7 +1186,7 @@ class Database(object):
def log_touched_tables(self, query, values=None):
if values:
- query = frappe.safe_decode(self._cursor.mogrify(query, values))
+ query = frappe.safe_decode(self.mogrify(query, values))
if query.strip().lower().split()[0] in ("insert", "delete", "update", "alter", "drop", "rename"):
# single_word_regex is designed to match following patterns
# `tabXxx`, tabXxx and "tabXxx"
From 958fc2b0b2b5dd269fdb8f2ba373d3876dd3deb9 Mon Sep 17 00:00:00 2001
From: gavin
Date: Wed, 25 May 2022 12:26:24 +0530
Subject: [PATCH 0036/2449] refactor: Database
* Change query notations - QB > raw
* Update logic of DB APIs - simplify & perf improvements
---
frappe/database/database.py | 76 +++++++++++++----------------
frappe/database/mariadb/database.py | 21 ++++++++
2 files changed, 54 insertions(+), 43 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 2cd950f785..d8f76e76ff 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -22,7 +22,7 @@ from frappe import _
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.functions import Count
from frappe.query_builder.utils import DocType
-from frappe.utils import cast, get_datetime, getdate, now, sbool
+from frappe.utils import cast, get_datetime, get_table_name, getdate, now, sbool
from .query import Query
@@ -842,13 +842,8 @@ class Database(object):
def touch(self, doctype, docname):
"""Update the modified timestamp of this document."""
modified = now()
- self.sql(
- """update `tab{doctype}` set `modified`=%s
- where name=%s""".format(
- doctype=doctype
- ),
- (modified, docname),
- )
+ DocType = frappe.qb.DocType(doctype)
+ frappe.qb.update(DocType).set(DocType.modified, modified).where(DocType.name == docname).run()
return modified
@staticmethod
@@ -960,22 +955,11 @@ class Database(object):
return self.table_exists(doctype)
def get_tables(self, cached=True):
- tables = frappe.cache().get_value("db_tables")
- if not tables or not cached:
- table_rows = self.sql(
- """
- SELECT table_name
- FROM information_schema.tables
- WHERE table_schema NOT IN ('pg_catalog', 'information_schema')
- """
- )
- tables = {d[0] for d in table_rows}
- frappe.cache().set_value("db_tables", tables)
- return tables
+ raise NotImplementedError
def a_row_exists(self, doctype):
"""Returns True if atleast one row exists."""
- return self.sql("select name from `tab{doctype}` limit 1".format(doctype=doctype))
+ return frappe.get_all(doctype, limit=1, order_by=None, as_list=True)
def exists(self, dt, dn=None, cache=False):
"""Return the document name of a matching document, or None.
@@ -1047,28 +1031,27 @@ class Database(object):
from frappe.utils import now_datetime
- return self.sql(
- """select count(name) from `tab{doctype}`
- where creation >= %s""".format(
- doctype=doctype
- ),
- now_datetime() - relativedelta(minutes=minutes),
- )[0][0]
+ Table = frappe.qb.DocType(doctype)
+
+ return (
+ frappe.qb.from_(Table)
+ .select(Count(Table.name))
+ .where(Table.creation >= now_datetime() - relativedelta(minutes=minutes))
+ .run()[0][0]
+ )
def get_db_table_columns(self, table) -> List[str]:
"""Returns list of column names from given table."""
columns = frappe.cache().hget("table_columns", table)
if columns is None:
- columns = [
- r[0]
- for r in self.sql(
- """
- select column_name
- from information_schema.columns
- where table_name = %s """,
- table,
- )
- ]
+ information_schema = frappe.qb.Schema("information_schema")
+
+ columns = (
+ frappe.qb.from_(information_schema.columns)
+ .select(information_schema.columns.column_name)
+ .where(information_schema.columns.table_name == table)
+ .run(pluck=True)
+ )
if columns:
frappe.cache().hset("table_columns", table, columns)
@@ -1087,12 +1070,19 @@ class Database(object):
return column in self.get_table_columns(doctype)
def get_column_type(self, doctype, column):
- return self.sql(
- """SELECT column_type FROM INFORMATION_SCHEMA.COLUMNS
- WHERE table_name = 'tab{0}' AND column_name = '{1}' """.format(
- doctype, column
+ """Returns column type from database."""
+ information_schema = frappe.qb.Schema("information_schema")
+ table = get_table_name(doctype)
+
+ return (
+ frappe.qb.from_(information_schema.columns)
+ .select(information_schema.columns.column_type)
+ .where(
+ (information_schema.columns.table_name == table)
+ & (information_schema.columns.column_name == column)
)
- )[0][0]
+ .run(pluck=True)[0]
+ )
def has_index(self, table_name, index_name):
raise NotImplementedError
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index a85df44dac..b022a5c821 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -461,3 +461,24 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
def get_database_list(self):
return self.sql("SHOW DATABASES", pluck=True)
+
+ def get_tables(self, cached=True):
+ """Returns list of tables"""
+ to_query = not cached
+
+ if cached:
+ tables = frappe.cache().get_value("db_tables")
+ to_query = not tables
+
+ if to_query:
+ information_schema = frappe.qb.Schema("information_schema")
+
+ tables = (
+ frappe.qb.from_(information_schema.tables)
+ .select(information_schema.tables.table_name)
+ .where(information_schema.tables.table_schema != "information_schema")
+ .run(pluck=True)
+ )
+ frappe.cache().set_value("db_tables", tables)
+
+ return tables
From ff9c89450e4f355dd3cbf5f663d3fef57c41db22 Mon Sep 17 00:00:00 2001
From: gavin
Date: Wed, 25 May 2022 13:01:01 +0530
Subject: [PATCH 0037/2449] feat(db): _transform_query to convert args passed
to db.cursor
Transform query & parameters based on client requirements. Eg: MariaDB
client doesn't behave similar to PyMySQL or Psycopg2.
ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-205
---
frappe/database/database.py | 5 +++++
frappe/database/mariadb/database.py | 23 +++++++++++++++++++++++
2 files changed, 28 insertions(+)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index d8f76e76ff..2af890bdb1 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -89,6 +89,9 @@ class Database(object):
def get_database_size(self):
raise NotImplementedError
+ def _transform_query(self, query, values):
+ return query, values
+
def sql(
self,
query,
@@ -164,6 +167,8 @@ class Database(object):
if not isinstance(values, (dict, tuple, list)):
values = (values,)
+ query, values = self._transform_query(query, values)
+
self.log_query(query, values, debug, explain)
self._cursor.execute(query, values)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index b022a5c821..a66a563ed6 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -1,3 +1,4 @@
+import re
from collections import defaultdict
from typing import TYPE_CHECKING, Dict, List, Tuple, Union
@@ -14,6 +15,7 @@ from frappe.utils import UnicodeWithAttrs, get_datetime, get_table_name
if TYPE_CHECKING:
from mariadb import ConnectionPool
+_PARAM_COMP = re.compile(r"%\([\w]*\)s")
_SITE_POOLS = defaultdict(frappe._dict)
_MAX_POOL_SIZE = 64
_POOL_SIZE = 1
@@ -222,6 +224,27 @@ class MariaDBConnectionUtil:
conn_settings.update(ssl_params)
return conn_settings
+ def _transform_query(self, query: str, values: Dict) -> str:
+ """Converts a query with named placeholders to a query with %s and values dict to a tuple.
+
+ This is a workaround since the MariaDB Python client (1.0.11) responds inconsistently
+ depending on the substitions in the query & type of values passed.
+
+ ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-205
+ """
+ pos_values = []
+ named_tokens = _PARAM_COMP.findall(query)
+
+ if len(named_tokens) == len(values):
+ return query, values
+
+ for token in named_tokens:
+ key = token[2:-2]
+ pos_values.append(values[key])
+ query = query.replace(token, "%s", 1)
+
+ return query, pos_values
+
class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
REGEX_CHARACTER = "regexp"
From 8ffdc2d4650c80e85c3835285f1ae10185c370a1 Mon Sep 17 00:00:00 2001
From: gavin
Date: Wed, 25 May 2022 14:01:13 +0530
Subject: [PATCH 0038/2449] refactor(minor): PostgresDatabase's exceptions
Focus on Non-blocking Postgres exception checking by:
* Use safe getattr to fetch pgcode instead
* Use psycopg errorcodes module to use named variables instead of direct
codes...for way superior readability xD
Also, moved exceptions out of the main class - just code separation, no
namespace change.
---
frappe/database/postgres/database.py | 124 +++++++++++++++------------
1 file changed, 68 insertions(+), 56 deletions(-)
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index d60a6d1918..fc2a6b7941 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -3,7 +3,17 @@ from typing import List, Tuple, Union
import psycopg2
import psycopg2.extensions
-from psycopg2.errorcodes import STRING_DATA_RIGHT_TRUNCATION
+from psycopg2.errorcodes import (
+ CLASS_INTEGRITY_CONSTRAINT_VIOLATION,
+ DEADLOCK_DETECTED,
+ DUPLICATE_COLUMN,
+ INSUFFICIENT_PRIVILEGE,
+ STRING_DATA_RIGHT_TRUNCATION,
+ UNDEFINED_COLUMN,
+ UNDEFINED_TABLE,
+ UNIQUE_VIOLATION,
+)
+from psycopg2.errors import SyntaxError
from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
import frappe
@@ -21,7 +31,7 @@ DEC2FLOAT = psycopg2.extensions.new_type(
psycopg2.extensions.register_type(DEC2FLOAT)
-class PostgresDatabase(Database):
+class PostgresExceptionUtil:
ProgrammingError = psycopg2.ProgrammingError
TableMissingError = psycopg2.ProgrammingError
OperationalError = psycopg2.OperationalError
@@ -29,6 +39,62 @@ class PostgresDatabase(Database):
SQLError = psycopg2.ProgrammingError
DataError = psycopg2.DataError
InterfaceError = psycopg2.InterfaceError
+
+ @staticmethod
+ def is_deadlocked(e):
+ return getattr(e, "pgcode", None) == DEADLOCK_DETECTED
+
+ @staticmethod
+ def is_timedout(e):
+ # http://initd.org/psycopg/docs/extensions.html?highlight=datatype#psycopg2.extensions.QueryCanceledError
+ return isinstance(e, psycopg2.extensions.QueryCanceledError)
+
+ @staticmethod
+ def is_syntax_error(e):
+ return isinstance(e, SyntaxError)
+
+ @staticmethod
+ def is_table_missing(e):
+ return getattr(e, "pgcode", None) == UNDEFINED_TABLE
+
+ @staticmethod
+ def is_missing_table(e):
+ return PostgresDatabase.is_table_missing(e)
+
+ @staticmethod
+ def is_missing_column(e):
+ return getattr(e, "pgcode", None) == UNDEFINED_COLUMN
+
+ @staticmethod
+ def is_access_denied(e):
+ return getattr(e, "pgcode", None) == INSUFFICIENT_PRIVILEGE
+
+ @staticmethod
+ def cant_drop_field_or_key(e):
+ return getattr(e, "pgcode", None) == CLASS_INTEGRITY_CONSTRAINT_VIOLATION
+
+ @staticmethod
+ def is_duplicate_entry(e):
+ return getattr(e, "pgcode", None) == UNIQUE_VIOLATION
+
+ @staticmethod
+ def is_primary_key_violation(e):
+ return getattr(e, "pgcode", None) == UNIQUE_VIOLATION and "_pkey" in cstr(e.args[0])
+
+ @staticmethod
+ def is_unique_key_violation(e):
+ return getattr(e, "pgcode", None) == UNIQUE_VIOLATION and "_key" in cstr(e.args[0])
+
+ @staticmethod
+ def is_duplicate_fieldname(e):
+ return getattr(e, "pgcode", None) == DUPLICATE_COLUMN
+
+ @staticmethod
+ def is_data_too_long(e):
+ return getattr(e, "pgcode", None) == STRING_DATA_RIGHT_TRUNCATION
+
+
+class PostgresDatabase(PostgresExceptionUtil, Database):
REGEX_CHARACTER = "~"
# NOTE; The sequence cache for postgres is per connection.
@@ -149,60 +215,6 @@ class PostgresDatabase(Database):
def is_type_datetime(code):
return code == psycopg2.DATETIME
- # exception type
- @staticmethod
- def is_deadlocked(e):
- return e.pgcode == "40P01"
-
- @staticmethod
- def is_timedout(e):
- # http://initd.org/psycopg/docs/extensions.html?highlight=datatype#psycopg2.extensions.QueryCanceledError
- return isinstance(e, psycopg2.extensions.QueryCanceledError)
-
- @staticmethod
- def is_syntax_error(e):
- return isinstance(e, psycopg2.errors.SyntaxError)
-
- @staticmethod
- def is_table_missing(e):
- return getattr(e, "pgcode", None) == "42P01"
-
- @staticmethod
- def is_missing_table(e):
- return PostgresDatabase.is_table_missing(e)
-
- @staticmethod
- def is_missing_column(e):
- return getattr(e, "pgcode", None) == "42703"
-
- @staticmethod
- def is_access_denied(e):
- return e.pgcode == "42501"
-
- @staticmethod
- def cant_drop_field_or_key(e):
- return e.pgcode.startswith("23")
-
- @staticmethod
- def is_duplicate_entry(e):
- return e.pgcode == "23505"
-
- @staticmethod
- def is_primary_key_violation(e):
- return getattr(e, "pgcode", None) == "23505" and "_pkey" in cstr(e.args[0])
-
- @staticmethod
- def is_unique_key_violation(e):
- return getattr(e, "pgcode", None) == "23505" and "_key" in cstr(e.args[0])
-
- @staticmethod
- def is_duplicate_fieldname(e):
- return e.pgcode == "42701"
-
- @staticmethod
- def is_data_too_long(e):
- return e.pgcode == STRING_DATA_RIGHT_TRUNCATION
-
def rename_table(self, old_name: str, new_name: str) -> Union[List, Tuple]:
old_name = get_table_name(old_name)
new_name = get_table_name(new_name)
From 5a29177e6b4c6b145d6e5c81422445c91e4dde4b Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 17:42:52 +0530
Subject: [PATCH 0039/2449] fix(db): Log queried tables through generated query
---
frappe/database/database.py | 10 ++++------
1 file changed, 4 insertions(+), 6 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index cd53c3e1fd..181eda98ab 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -189,9 +189,6 @@ class Database(object):
self._cursor.execute(query, values)
- if frappe.flags.in_migrate:
- self.log_touched_tables(query, values)
-
if debug:
time_end = time()
frappe.errprint(("Execution time: {0} sec").format(round(time_end - time_start, 2)))
@@ -263,6 +260,9 @@ class Database(object):
mogrified_query = mogrified_query or self.mogrify(query, values)
frappe.log(f"<<<< query\n{mogrified_query}\n>>>>")
+ if frappe.flags.in_migrate:
+ self.log_touched_tables(mogrified_query or query)
+
def mogrify(self, query, values):
"""build the query string with values"""
if not values:
@@ -1217,9 +1217,7 @@ class Database(object):
else:
return None
- def log_touched_tables(self, query, values=None):
- if values:
- query = frappe.safe_decode(self._cursor.mogrify(query, values))
+ def log_touched_tables(self, query):
if is_query_type(query, ("insert", "delete", "update", "alter", "drop", "rename")):
# single_word_regex is designed to match following patterns
# `tabXxx`, tabXxx and "tabXxx"
From ded55fd98e6aff5829c049ee3d2852a181eb315e Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 17:43:44 +0530
Subject: [PATCH 0040/2449] fix(db): Skip transformation of query if no named
params found
---
frappe/database/mariadb/database.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index a66a563ed6..79378f0d43 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -235,7 +235,7 @@ class MariaDBConnectionUtil:
pos_values = []
named_tokens = _PARAM_COMP.findall(query)
- if len(named_tokens) == len(values):
+ if not named_tokens or len(named_tokens) == len(values):
return query, values
for token in named_tokens:
From bfd51aa43a9c2d9a13e319133f29eca1d16c0d6d Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 15 Jun 2022 17:44:29 +0530
Subject: [PATCH 0041/2449] fix(qb): Use fallback og table if not found in
schema mapper
---
frappe/query_builder/builder.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/query_builder/builder.py b/frappe/query_builder/builder.py
index d2fdeab324..4d3702e228 100644
--- a/frappe/query_builder/builder.py
+++ b/frappe/query_builder/builder.py
@@ -76,7 +76,7 @@ class Postgres(Base, PostgreSQLQuery):
if isinstance(table, Table):
if table._schema:
if table._schema._name == "information_schema":
- table = cls.schema_translation[table._table_name]
+ table = cls.schema_translation.get(table._table_name) or table
elif isinstance(table, str):
table = cls.DocType(table)
From 0adf5e127a91eade38c88da6119365ce554c667f Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 12:31:14 +0530
Subject: [PATCH 0042/2449] fix(db): Track query engine under db._filter_engine
---
frappe/database/database.py | 13 +++++--------
1 file changed, 5 insertions(+), 8 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 181eda98ab..5a84a350a8 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -1,14 +1,12 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
-# Database Module
-# --------------------
-
import datetime
import json
import random
import re
import string
+import traceback
from contextlib import contextmanager
from time import time
from typing import Dict, List, Optional, Tuple, Union
@@ -75,15 +73,16 @@ class Database(object):
self.password = password or frappe.conf.db_password
self.value_cache = {}
+ # self.last_query last sql query executed
@property
def query(self):
- if not hasattr(self, "_query"):
+ if not hasattr(self, "_filter_engine"):
from .query import Query
- self._query = Query()
+ self._filter_engine = Query()
del Query
- return self._query
+ return self._filter_engine
def setup_type_map(self):
pass
@@ -205,8 +204,6 @@ class Database(object):
elif frappe.conf.db_type == "postgres":
# TODO: added temporarily
- import traceback
-
traceback.print_stack()
frappe.errprint(f"Error in query:\n{e}")
raise
From 889ced30357b2f0cae2fb91b3cdaf9f8f92066e1 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 12:32:26 +0530
Subject: [PATCH 0043/2449] refactor: frappe.db.sql
* Move everything except _cursor.execute outside try-except block - This
caused multiple traceback printing (by recursion of db.sql)
* Include values mogrifying & executing via client alone in execution
time for query
* Reduce indentations lol
---
frappe/database/database.py | 44 +++++++++++++++++--------------------
1 file changed, 20 insertions(+), 24 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 5a84a350a8..554612b083 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -166,32 +166,23 @@ class Database(object):
# in transaction validations
self.check_transaction_status(query)
-
self.clear_db_table_cache(query)
- # autocommit
if auto_commit:
self.commit()
- # execute
+ if debug:
+ time_start = time()
+
+ if values:
+ if not isinstance(values, (tuple, dict, list)):
+ values = (values,)
+ query, values = self._transform_query(query, values)
+ else:
+ values = None
+
try:
- if debug:
- time_start = time()
-
- if values != ():
- if not isinstance(values, (dict, tuple, list)):
- values = (values,)
-
- query, values = self._transform_query(query, values)
-
- self.log_query(query, values, debug, explain)
-
self._cursor.execute(query, values)
-
- if debug:
- time_end = time()
- frappe.errprint(("Execution time: {0} sec").format(round(time_end - time_start, 2)))
-
except Exception as e:
if self.is_syntax_error(e):
frappe.errprint(f"Syntax error in query:\n{query}")
@@ -202,19 +193,24 @@ class Database(object):
elif self.is_timedout(e):
raise frappe.QueryTimeoutError(e)
+ # TODO: added temporarily
elif frappe.conf.db_type == "postgres":
- # TODO: added temporarily
traceback.print_stack()
frappe.errprint(f"Error in query:\n{e}")
raise
- if ignore_ddl and (
- self.is_missing_column(e) or self.is_table_missing(e) or self.cant_drop_field_or_key(e)
+ if not (
+ ignore_ddl
+ and (self.is_missing_column(e) or self.is_table_missing(e) or self.cant_drop_field_or_key(e))
):
- pass
- else:
raise
+ if debug:
+ time_end = time()
+ frappe.errprint(f"Execution time: {time_end - time_start:.2f} sec")
+
+ self.log_query(query, values, debug, explain)
+
if auto_commit:
self.commit()
From effa942f4c35f922fb79525955602746aeae5c5d Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 13:26:04 +0530
Subject: [PATCH 0044/2449] refactor: frappe.db.log_query
* Mogrify queries and set them as frappe.db.last_query instead of
directly interfacing with the clients
* This is required for now as the MariaDB client uses binary protocol to
talk to the server and doesn't build the queries itself
* Add typing hints
* Imported Query object as FilterEngine - Query is too ambiguous lol
ref: https://jira.mariadb.org/browse/CONPY-208?focusedCommentId=226873&page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-226873
---
frappe/database/database.py | 71 +++++++++++++++-------------
frappe/database/mariadb/database.py | 4 ++
frappe/database/postgres/database.py | 4 ++
3 files changed, 47 insertions(+), 32 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 554612b083..7a4953c333 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -9,6 +9,7 @@ import string
import traceback
from contextlib import contextmanager
from time import time
+from types import NoneType
from typing import Dict, List, Optional, Tuple, Union
from pypika.terms import Criterion, NullValue, PseudoColumn
@@ -17,6 +18,7 @@ import frappe
import frappe.defaults
import frappe.model.meta
from frappe import _
+from frappe.database.query import Query as FilterEngine
from frappe.exceptions import DoesNotExistError
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.functions import Count
@@ -29,6 +31,9 @@ INDEX_PATTERN = re.compile(r"\s*\([^)]+\)\s*")
SINGLE_WORD_PATTERN = re.compile(r'([`"]?)(tab([A-Z]\w+))\1')
MULTI_WORD_PATTERN = re.compile(r'([`"])(tab([A-Z]\w+)( [A-Z]\w+)+)\1')
+Query = Union[str, frappe.qb]
+QueryValues = Union[Tuple, List, Dict, NoneType]
+
def is_query_type(query: str, query_type: Union[str, Tuple[str]]) -> bool:
return query.lstrip().split(maxsplit=1)[0].lower().startswith(query_type)
@@ -78,10 +83,7 @@ class Database(object):
@property
def query(self):
if not hasattr(self, "_filter_engine"):
- from .query import Query
-
- self._filter_engine = Query()
- del Query
+ self._filter_engine = FilterEngine()
return self._filter_engine
def setup_type_map(self):
@@ -105,13 +107,13 @@ class Database(object):
def get_database_size(self):
raise NotImplementedError
- def _transform_query(self, query, values):
+ def _transform_query(self, query: Query, values: QueryValues):
return query, values
def sql(
self,
- query,
- values=(),
+ query: Query,
+ values: QueryValues = None,
as_dict=0,
as_list=0,
formatted=0,
@@ -127,7 +129,7 @@ class Database(object):
"""Execute a SQL query and fetch all rows.
:param query: SQL query.
- :param values: List / dict of values to be escaped and substituted in the query.
+ :param values: Tuple / List / Dict of values to be escaped and substituted in the query.
:param as_dict: Return as a dictionary.
:param as_list: Always return as a list.
:param formatted: Format values like date etc.
@@ -234,51 +236,56 @@ class Database(object):
else:
return self._cursor.fetchall()
- def log_query(self, query, values, debug, explain):
- mogrified_query = None
-
- # for debugging in tests
+ def _log_query(self, mogrified_query: str, debug: bool = False, explain: bool = False) -> None:
+ """Takes the query and logs it to various interfaces according to the settings."""
if frappe.conf.get("allow_tests") and frappe.cache().get_value("flag_print_sql"):
- mogrified_query = mogrified_query or self.mogrify(query, values)
print(mogrified_query)
- # debug
if debug:
- if explain and is_query_type(query, "select"):
- self.explain_query(query, values)
- mogrified_query = mogrified_query or self.mogrify(query, values)
+ if explain and is_query_type(mogrified_query, "select"):
+ self.explain_query(mogrified_query)
frappe.errprint(mogrified_query)
if frappe.conf.logging == 2:
- mogrified_query = mogrified_query or self.mogrify(query, values)
frappe.log(f"<<<< query\n{mogrified_query}\n>>>>")
if frappe.flags.in_migrate:
- self.log_touched_tables(mogrified_query or query)
+ self.log_touched_tables(mogrified_query)
+
+ def log_query(
+ self, query: str, values: QueryValues = None, debug: bool = False, explain: bool = False
+ ) -> str:
+ # TODO: Use mogrify until MariaDB Connector/C 1.1 is released and we can fetch something
+ # like cursor._transformed_statement from the cursor object. We can also avoid setting
+ # mogrified_query if we don't need to log it.
+ mogrified_query = self.mogrify(query, values)
+ self._log_query(mogrified_query, debug, explain)
+ return mogrified_query
def mogrify(self, query, values):
"""build the query string with values"""
if not values:
return query
- else:
- try:
- return self._cursor.mogrify(query, values)
- except BaseException: # noqa: E722
- return (query, values)
+
+ try:
+ return self._cursor.mogrify(query, values)
+ except BaseException: # noqa: E722
+ if isinstance(values, dict):
+ return query % {k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items()}
+ elif isinstance(values, (list, tuple)):
+ return query % tuple(frappe.db.escape(v) if isinstance(v, str) else v for v in values)
+ return (query, values)
def explain_query(self, query, values=None):
"""Print `EXPLAIN` in error log."""
+ frappe.errprint("--- query explain ---")
try:
- frappe.errprint("--- query explain ---")
-
- explain_query = f"EXPLAIN {query}"
- values = values or ()
- self._cursor.execute(explain_query, values)
-
- frappe.errprint(json.dumps(self.fetch_as_dict(), indent=1))
- frappe.errprint("--- query explain end ---")
+ self._cursor.execute(f"EXPLAIN {query}", values)
except Exception as e:
frappe.errprint(f"error in query explain: {e}")
+ else:
+ frappe.errprint(json.dumps(self.fetch_as_dict(), indent=1))
+ frappe.errprint("--- query explain end ---")
def sql_list(self, query, values=(), debug=False, **kwargs):
"""Return data as list of single elements (first column).
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 79378f0d43..da33007d5d 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -311,6 +311,10 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
return db_size[0].get("database_size")
+ def log_query(self, query, values, debug, explain):
+ self.last_query = super().log_query(query, values, debug, explain)
+ return self.last_query
+
@staticmethod
def escape(s, percent=True):
"""Excape quotes and percent in given string."""
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 0bcc2adcb2..2444d41279 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -147,6 +147,10 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
"JSON": ("json", ""),
}
+ @property
+ def last_query(self):
+ return self._cursor.query
+
def get_connection(self):
conn = psycopg2.connect(
"host='{}' dbname='{}' user='{}' password='{}' port={}".format(
From 154d794c6c10327085303fd9a39ae8b28129f5a9 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 13:29:35 +0530
Subject: [PATCH 0045/2449] fix: Recorder to use frappe.db.last_query to pick
out last executed stmt
---
frappe/recorder.py | 14 +++++---------
1 file changed, 5 insertions(+), 9 deletions(-)
diff --git a/frappe/recorder.py b/frappe/recorder.py
index 87e001fe31..ff64c082bc 100644
--- a/frappe/recorder.py
+++ b/frappe/recorder.py
@@ -12,6 +12,7 @@ import sqlparse
import frappe
from frappe import _
+from frappe.database.database import is_query_type
RECORDER_INTERCEPT_FLAG = "recorder-intercept"
RECORDER_REQUEST_SPARSE_HASH = "recorder-requests-sparse"
@@ -25,18 +26,13 @@ def sql(*args, **kwargs):
end_time = time.time()
stack = list(get_current_stack_frames())
-
- if frappe.db.db_type == "postgres":
- query = frappe.db._cursor.query
- else:
- query = frappe.db._cursor._executed
-
- query = sqlparse.format(query.strip(), keyword_case="upper", reindent=True)
+ last_query = frappe.db.last_query
+ query = sqlparse.format(last_query.strip(), keyword_case="upper", reindent=True)
# Collect EXPLAIN for executed query
- if query.lower().strip().split()[0] in ("select", "update", "delete"):
+ if is_query_type(query, ("select", "update", "delete")):
# Only SELECT/UPDATE/DELETE queries can be "EXPLAIN"ed
- explain_result = frappe.db._sql("EXPLAIN {}".format(query), as_dict=True)
+ explain_result = frappe.db._sql(f"EXPLAIN {query}", as_dict=True)
else:
explain_result = []
From bc3780560eb8db43ad2afafcea9c52ebf0f8c6f2 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 14:07:10 +0530
Subject: [PATCH 0046/2449] perf: Use lazy mogrified query for logging
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
For parameterized queries, there's an improvement of ~30% in query
execution via frappe.db.sql - from 58.6 µs ± 2.37 µs to 44.6 µs ± 1.56 µs
---
frappe/database/database.py | 38 ++++++++++++++++++---------------
frappe/database/utils.py | 42 +++++++++++++++++++++++++++++++++++++
frappe/recorder.py | 3 +--
3 files changed, 64 insertions(+), 19 deletions(-)
create mode 100644 frappe/database/utils.py
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 7a4953c333..1bba2c871e 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -19,6 +19,7 @@ import frappe.defaults
import frappe.model.meta
from frappe import _
from frappe.database.query import Query as FilterEngine
+from frappe.database.utils import LazyMogrify, Query, QueryValues, is_query_type
from frappe.exceptions import DoesNotExistError
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.functions import Count
@@ -31,13 +32,6 @@ INDEX_PATTERN = re.compile(r"\s*\([^)]+\)\s*")
SINGLE_WORD_PATTERN = re.compile(r'([`"]?)(tab([A-Z]\w+))\1')
MULTI_WORD_PATTERN = re.compile(r'([`"])(tab([A-Z]\w+)( [A-Z]\w+)+)\1')
-Query = Union[str, frappe.qb]
-QueryValues = Union[Tuple, List, Dict, NoneType]
-
-
-def is_query_type(query: str, query_type: Union[str, Tuple[str]]) -> bool:
- return query.lstrip().split(maxsplit=1)[0].lower().startswith(query_type)
-
class Database(object):
"""
@@ -78,7 +72,7 @@ class Database(object):
self.password = password or frappe.conf.db_password
self.value_cache = {}
- # self.last_query last sql query executed
+ # self.last_query lazy attribute of last sql query executed
@property
def query(self):
@@ -238,19 +232,25 @@ class Database(object):
def _log_query(self, mogrified_query: str, debug: bool = False, explain: bool = False) -> None:
"""Takes the query and logs it to various interfaces according to the settings."""
- if frappe.conf.get("allow_tests") and frappe.cache().get_value("flag_print_sql"):
- print(mogrified_query)
+ _query = None
+
+ if frappe.conf.allow_tests and frappe.cache().get_value("flag_print_sql"):
+ _query = _query or str(mogrified_query)
+ print(_query)
if debug:
- if explain and is_query_type(mogrified_query, "select"):
- self.explain_query(mogrified_query)
- frappe.errprint(mogrified_query)
+ _query = _query or str(mogrified_query)
+ if explain and is_query_type(_query, "select"):
+ self.explain_query(_query)
+ frappe.errprint(_query)
if frappe.conf.logging == 2:
- frappe.log(f"<<<< query\n{mogrified_query}\n>>>>")
+ _query = _query or str(mogrified_query)
+ frappe.log(f"<<<< query\n{_query}\n>>>>")
if frappe.flags.in_migrate:
- self.log_touched_tables(mogrified_query)
+ _query = _query or str(mogrified_query)
+ self.log_touched_tables(_query)
def log_query(
self, query: str, values: QueryValues = None, debug: bool = False, explain: bool = False
@@ -258,11 +258,11 @@ class Database(object):
# TODO: Use mogrify until MariaDB Connector/C 1.1 is released and we can fetch something
# like cursor._transformed_statement from the cursor object. We can also avoid setting
# mogrified_query if we don't need to log it.
- mogrified_query = self.mogrify(query, values)
+ mogrified_query = self.lazy_mogrify(query, values)
self._log_query(mogrified_query, debug, explain)
return mogrified_query
- def mogrify(self, query, values):
+ def mogrify(self, query: Query, values: QueryValues):
"""build the query string with values"""
if not values:
return query
@@ -276,6 +276,10 @@ class Database(object):
return query % tuple(frappe.db.escape(v) if isinstance(v, str) else v for v in values)
return (query, values)
+ def lazy_mogrify(self, query: Query, values: QueryValues) -> LazyMogrify:
+ """Wrap the object with str to generate mogrified query."""
+ return LazyMogrify(query, values)
+
def explain_query(self, query, values=None):
"""Print `EXPLAIN` in error log."""
frappe.errprint("--- query explain ---")
diff --git a/frappe/database/utils.py b/frappe/database/utils.py
new file mode 100644
index 0000000000..47a5222ede
--- /dev/null
+++ b/frappe/database/utils.py
@@ -0,0 +1,42 @@
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
+# License: MIT. See LICENSE
+
+from functools import cached_property
+from types import NoneType
+from typing import Dict, List, Tuple, Union
+
+import frappe
+from frappe.query_builder.builder import MariaDB, Postgres
+
+Query = Union[str, MariaDB, Postgres]
+QueryValues = Union[Tuple, List, Dict, NoneType]
+
+
+def is_query_type(query: str, query_type: Union[str, Tuple[str]]) -> bool:
+ return query.lstrip().split(maxsplit=1)[0].lower().startswith(query_type)
+
+
+class LazyString:
+ def _setup(self) -> None:
+ raise NotImplementedError
+
+ @cached_property
+ def value(self) -> str:
+ return self._setup()
+
+ def __str__(self) -> str:
+ return self.value
+
+ def __repr__(self) -> str:
+ return f"'{self.value}'"
+
+
+class LazyMogrify(LazyString):
+ __slots__ = ()
+
+ def __init__(self, query, values) -> None:
+ self.query = query
+ self.values = values
+
+ def _setup(self) -> str:
+ return frappe.db.mogrify(self.query, self.values)
diff --git a/frappe/recorder.py b/frappe/recorder.py
index ff64c082bc..3ecf2b2b96 100644
--- a/frappe/recorder.py
+++ b/frappe/recorder.py
@@ -26,8 +26,7 @@ def sql(*args, **kwargs):
end_time = time.time()
stack = list(get_current_stack_frames())
- last_query = frappe.db.last_query
- query = sqlparse.format(last_query.strip(), keyword_case="upper", reindent=True)
+ query = sqlparse.format(str(frappe.db.last_query).strip(), keyword_case="upper", reindent=True)
# Collect EXPLAIN for executed query
if is_query_type(query, ("select", "update", "delete")):
From 25b87a9d497887dd0a07e0e473679b78b619f6ee Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 15:04:35 +0530
Subject: [PATCH 0047/2449] chore: NoneType alias for < PY310
---
frappe/database/database.py | 1 -
frappe/database/utils.py | 6 +++++-
frappe/model/rename_doc.py | 6 +++++-
3 files changed, 10 insertions(+), 3 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 1bba2c871e..6dd3306b18 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -9,7 +9,6 @@ import string
import traceback
from contextlib import contextmanager
from time import time
-from types import NoneType
from typing import Dict, List, Optional, Tuple, Union
from pypika.terms import Criterion, NullValue, PseudoColumn
diff --git a/frappe/database/utils.py b/frappe/database/utils.py
index 47a5222ede..0edbc07f1d 100644
--- a/frappe/database/utils.py
+++ b/frappe/database/utils.py
@@ -2,7 +2,11 @@
# License: MIT. See LICENSE
from functools import cached_property
-from types import NoneType
+
+try:
+ from types import NoneType
+except ImportError:
+ NoneType = type(None)
from typing import Dict, List, Tuple, Union
import frappe
diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py
index 25e471d4b0..652703aed5 100644
--- a/frappe/model/rename_doc.py
+++ b/frappe/model/rename_doc.py
@@ -1,5 +1,9 @@
# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
+try:
+ from types import NoneType
+except ImportError:
+ NoneType = type(None)
from typing import TYPE_CHECKING, Dict, List, Optional
import frappe
@@ -46,7 +50,7 @@ def update_document_title(
# TODO: omit this after runtime type checking (ref: https://github.com/frappe/frappe/pull/14927)
for obj in [docname, updated_title, updated_name]:
- if not isinstance(obj, (str, type(None))):
+ if not isinstance(obj, (str, NoneType)):
frappe.throw(f"{obj=} must be of type str or None")
# handle bad API usages
From 1a772e304c194fc91d5efebfd0ba5fd9c367a79c Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 16:01:38 +0530
Subject: [PATCH 0048/2449] fix(db): Store result of last executed query under
frappe.db.last_result
---
frappe/database/database.py | 12 +++++++-----
1 file changed, 7 insertions(+), 5 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 6dd3306b18..414e0c9358 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -212,8 +212,10 @@ class Database(object):
if not self._cursor.description:
return ()
+ self.last_result = self._cursor.fetchall()
+
if pluck:
- return [r[0] for r in self._cursor.fetchall()]
+ return [r[0] for r in self.last_result]
# scrub output if required
if as_dict:
@@ -223,11 +225,11 @@ class Database(object):
r.update(update)
return ret
elif as_list:
- return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
+ return self.convert_to_lists(self.last_result, formatted, as_utf8)
elif as_utf8:
- return self.convert_to_lists(self._cursor.fetchall(), formatted, as_utf8)
+ return self.convert_to_lists(self.last_result, formatted, as_utf8)
else:
- return self._cursor.fetchall()
+ return self.last_result
def _log_query(self, mogrified_query: str, debug: bool = False, explain: bool = False) -> None:
"""Takes the query and logs it to various interfaces according to the settings."""
@@ -335,7 +337,7 @@ class Database(object):
def fetch_as_dict(self, formatted=0, as_utf8=0):
"""Internal. Converts results to dict."""
- result = self._cursor.fetchall()
+ result = self.last_result
ret = []
if result:
keys = [column[0] for column in self._cursor.description]
From 355e997045c78932c425aba6b20f595d40638f24 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 16:21:32 +0530
Subject: [PATCH 0049/2449] test: Get rid of magical forgiving behaviour of
pymysql
("test",) would be interpretted as ("test") which would be just "test".
MariaDB client doesn't handle errenous inputs like this that PyMySQL
tolerated
---
frappe/email/doctype/notification/test_notification.py | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py
index 4d8b26c559..039b6db2f0 100644
--- a/frappe/email/doctype/notification/test_notification.py
+++ b/frappe/email/doctype/notification/test_notification.py
@@ -84,7 +84,7 @@ class TestNotification(unittest.TestCase):
def test_condition(self):
"""Check notification is triggered based on a condition."""
event = frappe.new_doc("Event")
- event.subject = ("test",)
+ event.subject = "test"
event.event_type = "Private"
event.starts_on = "2014-06-06 12:00:00"
event.insert()
@@ -137,7 +137,7 @@ class TestNotification(unittest.TestCase):
def test_value_changed(self):
event = frappe.new_doc("Event")
- event.subject = ("test",)
+ event.subject = "test"
event.event_type = "Private"
event.starts_on = "2014-06-06 12:00:00"
event.insert()
@@ -186,7 +186,7 @@ class TestNotification(unittest.TestCase):
frappe.db.commit()
event = frappe.new_doc("Event")
- event.subject = ("test-2",)
+ event.subject = "test-2"
event.event_type = "Private"
event.starts_on = "2014-06-06 12:00:00"
event.insert()
@@ -200,9 +200,8 @@ class TestNotification(unittest.TestCase):
event.delete()
def test_date_changed(self):
-
event = frappe.new_doc("Event")
- event.subject = ("test",)
+ event.subject = "test"
event.event_type = "Private"
event.starts_on = "2014-01-01 12:00:00"
event.insert()
From 71ed8417d3f3683077f633992999e027b53dde98 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 16 Jun 2022 16:23:27 +0530
Subject: [PATCH 0050/2449] test: frappe.db.describe returns List[Tuple]
through mariadb
---
frappe/tests/test_db.py | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py
index 73b5446404..d6cdbdd655 100644
--- a/frappe/tests/test_db.py
+++ b/frappe/tests/test_db.py
@@ -517,10 +517,9 @@ class TestDDLCommandsMaria(unittest.TestCase):
test_table_name = "TestNotes"
def setUp(self) -> None:
- frappe.db.commit()
- frappe.db.sql(
+ frappe.db.sql_ddl(
f"""
- CREATE TABLE `tab{self.test_table_name}` (`id` INT NULL, content TEXT, PRIMARY KEY (`id`));
+ CREATE TABLE IF NOT EXISTS `tab{self.test_table_name}` (`id` INT NULL, content TEXT, PRIMARY KEY (`id`));
"""
)
@@ -545,10 +544,10 @@ class TestDDLCommandsMaria(unittest.TestCase):
def test_describe(self) -> None:
self.assertEqual(
- (
+ [
("id", "int(11)", "NO", "PRI", None, ""),
("content", "text", "YES", "", None, ""),
- ),
+ ],
frappe.db.describe(self.test_table_name),
)
From a03bf6b0bba75b5936627aec9e85b8fadc48f855 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 17 Jun 2022 11:31:55 +0530
Subject: [PATCH 0051/2449] fix(db): Transform values only if not None
---
frappe/database/database.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 414e0c9358..68ab33d4f6 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -169,12 +169,10 @@ class Database(object):
if debug:
time_start = time()
- if values:
+ if values != None:
if not isinstance(values, (tuple, dict, list)):
values = (values,)
query, values = self._transform_query(query, values)
- else:
- values = None
try:
self._cursor.execute(query, values)
From ca0016a996b7ed4dfb83b6f7f324037780fe3bee Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 17 Jun 2022 11:32:40 +0530
Subject: [PATCH 0052/2449] test(sequence): Use mariadb client's exception
handling
---
frappe/tests/test_sequence.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/frappe/tests/test_sequence.py b/frappe/tests/test_sequence.py
index a60e4b1ac9..82bb8ab257 100644
--- a/frappe/tests/test_sequence.py
+++ b/frappe/tests/test_sequence.py
@@ -1,5 +1,4 @@
import psycopg2
-import pymysql
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -35,10 +34,10 @@ class TestSequence(FrappeTestCase):
try:
frappe.db.get_next_sequence_val(seq_name)
- except pymysql.err.OperationalError as e:
- self.assertEqual(e.args[0], 4084)
except psycopg2.errors.SequenceGeneratorLimitExceeded:
pass
+ except frappe.db.ProgrammingError as e:
+ self.assertEqual(getattr(e, "errno", None), 4084)
else:
self.fail("NEXTVAL didn't raise any error upon sequence's end")
From 90c716bce0de7710f7f63d6ea42ff01df10f4061 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 17 Jun 2022 12:10:43 +0530
Subject: [PATCH 0053/2449] fix(sequence): Setup & use
SequenceGeneratorLimitExceeded error
---
frappe/database/mariadb/database.py | 4 ++++
frappe/database/postgres/database.py | 3 ++-
frappe/database/sequence.py | 17 +++++++++++------
frappe/tests/test_sequence.py | 6 +-----
4 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index da33007d5d..327c995d48 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -46,6 +46,10 @@ class MariaDBExceptionUtil:
SQLError = mariadb.ProgrammingError
DataError = mariadb.DataError
+ # match ER_SEQUENCE_RUN_OUT - https://mariadb.com/kb/en/mariadb-error-codes/
+ SequenceGeneratorLimitExceeded = mariadb.ProgrammingError
+ SequenceGeneratorLimitExceeded.errno = 4084
+
@staticmethod
def is_deadlocked(e: mariadb.Error) -> bool:
return getattr(e, "errno", None) == ER.LOCK_DEADLOCK
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 2444d41279..f5ecabd6c4 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -13,7 +13,7 @@ from psycopg2.errorcodes import (
UNDEFINED_TABLE,
UNIQUE_VIOLATION,
)
-from psycopg2.errors import SyntaxError
+from psycopg2.errors import SequenceGeneratorLimitExceeded, SyntaxError
from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
import frappe
@@ -44,6 +44,7 @@ class PostgresExceptionUtil:
SQLError = psycopg2.ProgrammingError
DataError = psycopg2.DataError
InterfaceError = psycopg2.InterfaceError
+ SequenceGeneratorLimitExceeded = SequenceGeneratorLimitExceeded
@staticmethod
def is_deadlocked(e):
diff --git a/frappe/database/sequence.py b/frappe/database/sequence.py
index 6a352d20d1..54362a5895 100644
--- a/frappe/database/sequence.py
+++ b/frappe/database/sequence.py
@@ -57,12 +57,17 @@ def create_sequence(
def get_next_val(doctype_name: str, slug: str = "_id_seq") -> int:
- return db.multisql(
- {
- "postgres": f"select nextval('\"{scrub(doctype_name + slug)}\"')",
- "mariadb": f"select nextval(`{scrub(doctype_name + slug)}`)",
- }
- )[0][0]
+ sequence_name = scrub(f"{doctype_name}{slug}")
+
+ if db.db_type == "postgres":
+ sequence_name = f"'\"{sequence_name}\"'"
+ elif db.db_type == "mariadb":
+ sequence_name = f"`{sequence_name}`"
+
+ try:
+ return db.sql(f"SELECT nextval({sequence_name})")[0][0]
+ except IndexError:
+ raise db.SequenceGeneratorLimitExceeded
def set_next_val(
diff --git a/frappe/tests/test_sequence.py b/frappe/tests/test_sequence.py
index 82bb8ab257..c6ea0bc8c0 100644
--- a/frappe/tests/test_sequence.py
+++ b/frappe/tests/test_sequence.py
@@ -1,5 +1,3 @@
-import psycopg2
-
import frappe
from frappe.tests.utils import FrappeTestCase
@@ -34,10 +32,8 @@ class TestSequence(FrappeTestCase):
try:
frappe.db.get_next_sequence_val(seq_name)
- except psycopg2.errors.SequenceGeneratorLimitExceeded:
+ except frappe.db.SequenceGeneratorLimitExceeded:
pass
- except frappe.db.ProgrammingError as e:
- self.assertEqual(getattr(e, "errno", None), 4084)
else:
self.fail("NEXTVAL didn't raise any error upon sequence's end")
From 3af8d5caea0b667d7028e5cb23b6fd993778eb5a Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 17 Jun 2022 12:47:44 +0530
Subject: [PATCH 0054/2449] fix: Add fallbacks for values
Psycopg seems to like None over () and MariaDB - PyMySQL can't seem to
agree on anything - so this seems to keep everyone happy...a very
delicate balance :crie:
---
frappe/database/database.py | 5 +++--
frappe/database/postgres/database.py | 3 +++
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 68ab33d4f6..b6fc6f0fa8 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -71,7 +71,8 @@ class Database(object):
self.password = password or frappe.conf.db_password
self.value_cache = {}
- # self.last_query lazy attribute of last sql query executed
+ # self.db_type: str
+ # self.last_query (lazy) attribute of last sql query executed
@property
def query(self):
@@ -101,7 +102,7 @@ class Database(object):
raise NotImplementedError
def _transform_query(self, query: Query, values: QueryValues):
- return query, values
+ return query, values or None
def sql(
self,
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index f5ecabd6c4..0d4a177741 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -193,6 +193,9 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
modify_query(query), modify_values(values), *args, **kwargs
)
+ def lazy_mogrify(self, *args, **kwargs) -> str:
+ return self.last_query
+
def get_tables(self, cached=True):
return [
d[0]
From 9b6a048bcd9b17d9ceec9cf0fa3cbf7aae2e1abb Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 17 Jun 2022 12:49:12 +0530
Subject: [PATCH 0055/2449] refactor(minor): Use db.db_type instead of
conf.db_type
---
frappe/database/database.py | 10 +++++-----
1 file changed, 5 insertions(+), 5 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index b6fc6f0fa8..8e5e19bbf0 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -188,7 +188,7 @@ class Database(object):
raise frappe.QueryTimeoutError(e)
# TODO: added temporarily
- elif frappe.conf.db_type == "postgres":
+ elif self.db_type == "postgres":
traceback.print_stack()
frappe.errprint(f"Error in query:\n{e}")
raise
@@ -949,7 +949,7 @@ class Database(object):
frappe.call(method[0], *(method[1] or []), **(method[2] or {}))
self.sql("commit")
- if frappe.conf.db_type == "postgres":
+ if self.db_type == "postgres":
# Postgres requires explicitly starting new transaction
self.begin()
@@ -1187,7 +1187,7 @@ class Database(object):
return self.is_missing_column(e) or self.is_table_missing(e)
def multisql(self, sql_dict, values=(), **kwargs):
- current_dialect = frappe.db.db_type or "mariadb"
+ current_dialect = self.db_type or "mariadb"
query = sql_dict.get(current_dialect)
return self.sql(query, values, **kwargs)
@@ -1260,9 +1260,9 @@ class Database(object):
query = frappe.qb.into(table)
if ignore_duplicates:
# Pypika does not have same api for ignoring duplicates
- if frappe.conf.db_type == "mariadb":
+ if self.db_type == "mariadb":
query = query.ignore()
- elif frappe.conf.db_type == "postgres":
+ elif self.db_type == "postgres":
query = query.on_conflict().do_nothing()
values_to_insert = values[start_index : start_index + chunk_size]
From 1f1d91a0565459c67de6f4116a0d356452fb0415 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Sat, 18 Jun 2022 14:04:38 +0530
Subject: [PATCH 0056/2449] fix: Make postgres' last_query lazy decodable
---
frappe/database/postgres/database.py | 3 ++-
frappe/database/utils.py | 10 ++++++++++
2 files changed, 12 insertions(+), 1 deletion(-)
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 0d4a177741..414cac1a3f 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -19,6 +19,7 @@ from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
import frappe
from frappe.database.database import Database
from frappe.database.postgres.schema import PostgresTable
+from frappe.database.utils import LazyDecode
from frappe.utils import cstr, get_table_name
# cast decimals as floats
@@ -150,7 +151,7 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
@property
def last_query(self):
- return self._cursor.query
+ return LazyDecode(self._cursor.query)
def get_connection(self):
conn = psycopg2.connect(
diff --git a/frappe/database/utils.py b/frappe/database/utils.py
index 0edbc07f1d..6da4aade4a 100644
--- a/frappe/database/utils.py
+++ b/frappe/database/utils.py
@@ -35,6 +35,16 @@ class LazyString:
return f"'{self.value}'"
+class LazyDecode(LazyString):
+ __slots__ = ()
+
+ def __init__(self, value: str) -> None:
+ self._value = value
+
+ def _setup(self) -> None:
+ return self._value.decode()
+
+
class LazyMogrify(LazyString):
__slots__ = ()
From 48243346e3832a664689a131f657b534bddb72ab Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 20 Jun 2022 13:00:24 +0530
Subject: [PATCH 0057/2449] fix(get_contact_list): Don't pass conditions as
query value
* Conditions passed are not valid prepared statement values. They
can be passed as string substitution since they're generated by DBQuery.
* Added typing hints & other improvements
* Removed seemingly pointless try-except block
---
frappe/desk/reportview.py | 3 +--
frappe/email/__init__.py | 37 +++++++++++++++++--------------------
frappe/model/db_query.py | 4 ++--
3 files changed, 20 insertions(+), 24 deletions(-)
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index d6dce68399..c7295730de 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -684,8 +684,7 @@ def build_match_conditions(doctype, user=None, as_condition=True):
)
if as_condition:
return match_conditions.replace("%", "%%")
- else:
- return match_conditions
+ return match_conditions
def get_filters_cond(
diff --git a/frappe/email/__init__.py b/frappe/email/__init__.py
index fae60baebf..51917cc7af 100644
--- a/frappe/email/__init__.py
+++ b/frappe/email/__init__.py
@@ -1,6 +1,8 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
+from typing import Dict, List
+
import frappe
from frappe.desk.reportview import build_match_conditions
@@ -10,31 +12,26 @@ def sendmail_to_system_managers(subject, content):
@frappe.whitelist()
-def get_contact_list(txt, page_length=20):
+def get_contact_list(txt, page_length=20) -> List[Dict]:
"""Returns contacts (from autosuggest)"""
- cached_contacts = get_cached_contacts(txt)
- if cached_contacts:
+ if cached_contacts := get_cached_contacts(txt):
return cached_contacts[:page_length]
- try:
- match_conditions = build_match_conditions("Contact")
- match_conditions = "and {0}".format(match_conditions) if match_conditions else ""
+ reportview_conditions = build_match_conditions("Contact")
+ match_conditions = f"and {reportview_conditions}" if reportview_conditions else ""
- out = frappe.db.sql(
- """select email_id as value,
- concat(first_name, ifnull(concat(' ',last_name), '' )) as description
- from tabContact
- where name like %(txt)s or email_id like %(txt)s
- %(condition)s
- limit %(page_length)s""",
- {"txt": "%" + txt + "%", "condition": match_conditions, "page_length": page_length},
- as_dict=True,
- )
- out = filter(None, out)
-
- except:
- raise
+ out = frappe.db.sql(
+ f"""select email_id as value,
+ concat(first_name, ifnull(concat(' ',last_name), '' )) as description
+ from tabContact
+ where name like %(txt)s or email_id like %(txt)s
+ {match_conditions}
+ limit %(page_length)s""",
+ {"txt": f"%{txt}%", "page_length": page_length},
+ as_dict=True,
+ )
+ out = list(filter(None, out))
update_contact_cache(out)
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 82913db98d..dfe5304a87 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -6,7 +6,7 @@ import copy
import json
import re
from datetime import datetime
-from typing import List
+from typing import List, Union
import frappe
import frappe.defaults
@@ -720,7 +720,7 @@ class DatabaseQuery(object):
return condition
- def build_match_conditions(self, as_condition=True):
+ def build_match_conditions(self, as_condition=True) -> Union[str, List]:
"""add match conditions if applicable"""
self.match_filters = []
self.match_conditions = []
From a58a5bb848ff22959a6253e68a57411206bd22fd Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 20 Jun 2022 13:06:05 +0530
Subject: [PATCH 0058/2449] fix: Improve _transform_query unique key
identification
---
frappe/database/database.py | 4 ++--
frappe/database/mariadb/database.py | 2 +-
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 8e5e19bbf0..73460614cb 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -170,7 +170,7 @@ class Database(object):
if debug:
time_start = time()
- if values != None:
+ if values is not None:
if not isinstance(values, (tuple, dict, list)):
values = (values,)
query, values = self._transform_query(query, values)
@@ -179,7 +179,7 @@ class Database(object):
self._cursor.execute(query, values)
except Exception as e:
if self.is_syntax_error(e):
- frappe.errprint(f"Syntax error in query:\n{query}")
+ frappe.errprint(f"Syntax error in query:\n{query} {values}")
elif self.is_deadlocked(e):
raise frappe.QueryDeadlockError(e)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 327c995d48..1ca8e821fb 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -239,7 +239,7 @@ class MariaDBConnectionUtil:
pos_values = []
named_tokens = _PARAM_COMP.findall(query)
- if not named_tokens or len(named_tokens) == len(values):
+ if not named_tokens or len(set(named_tokens)) == len(values):
return query, values
for token in named_tokens:
From 14003e5ac957dbfd31c3d122ea8b25bd0856e309 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 20 Jun 2022 15:31:15 +0530
Subject: [PATCH 0059/2449] refactor: DISABLE_DATABASE_CONNECTION_POOLING conf
+ var name
---
frappe/__init__.py | 2 +-
frappe/database/mariadb/database.py | 6 +++---
frappe/utils/bench_helper.py | 2 +-
3 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 542c783319..45c5ca500e 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -43,7 +43,7 @@ __title__ = "Frappe Framework"
controllers = {}
local = Local()
STANDARD_USERS = ("Guest", "Administrator")
-DISABLE_DATABASE_POOLING = None
+DISABLE_DATABASE_CONNECTION_POOLING = None
_dev_server = int(sbool(os.environ.get("DEV_SERVER", False)))
_qb_patched = {}
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 1ca8e821fb..c444e492a2 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -33,9 +33,9 @@ def is_connection_pooling_enabled() -> bool:
process. This will override config key `disable_database_connection_pooling`. Set key
`disable_database_connection_pooling` in site config for persistent settings across workers."""
- if frappe.DISABLE_DATABASE_POOLING is not None:
- return not frappe.DISABLE_DATABASE_POOLING
- return frappe.local.conf.disable_database_connection_pooling
+ if frappe.DISABLE_DATABASE_CONNECTION_POOLING is not None:
+ return not frappe.DISABLE_DATABASE_CONNECTION_POOLING
+ return not frappe.local.conf.disable_database_connection_pooling
class MariaDBExceptionUtil:
diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py
index 8de10b73d4..8d109d9737 100644
--- a/frappe/utils/bench_helper.py
+++ b/frappe/utils/bench_helper.py
@@ -110,6 +110,6 @@ if __name__ == "__main__":
# disable pooling for commands executed via bench unless explicitly stated otherwise
# - except for commands serve & worker
if not {"serve", "worker"} & set(sys.argv) and int(os.environ.get("DATABASE_POOLING", 0)):
- frappe.DISABLE_DATABASE_POOLING = True
+ frappe.DISABLE_DATABASE_CONNECTION_POOLING = True
main()
From f08f29b8a347a103e92832785827d9fc389c7209 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 22 Jun 2022 11:44:59 +0530
Subject: [PATCH 0060/2449] Revert "fix: Disable db pooling on all commands by
default"
This reverts commit 0a8941c58317956e0661f90fa2357e39674f63c3 since it
may not be required as we start pooling with single connections only.
---
frappe/commands/site.py | 2 ++
frappe/utils/bench_helper.py | 6 ------
2 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 13d702d6f3..4db143b076 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -69,6 +69,8 @@ def new_site(
"Create a new site"
from frappe.installer import _new_site
+ frappe.DISABLE_DATABASE_CONNECTION_POOLING = True
+
frappe.init(site=site, new_site=True)
_new_site(
diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py
index 8d109d9737..a0b011acc1 100644
--- a/frappe/utils/bench_helper.py
+++ b/frappe/utils/bench_helper.py
@@ -1,7 +1,6 @@
import importlib
import json
import os
-import sys
import traceback
import warnings
@@ -107,9 +106,4 @@ if __name__ == "__main__":
if not frappe._dev_server:
warnings.simplefilter("ignore")
- # disable pooling for commands executed via bench unless explicitly stated otherwise
- # - except for commands serve & worker
- if not {"serve", "worker"} & set(sys.argv) and int(os.environ.get("DATABASE_POOLING", 0)):
- frappe.DISABLE_DATABASE_CONNECTION_POOLING = True
-
main()
From ef078a4ab56379e106433848bd425da9e4e27c21 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 23 Jun 2022 19:40:17 +0530
Subject: [PATCH 0061/2449] refactor(db-read_only): Track conn type in Database
instance
---
frappe/__init__.py | 4 +++-
frappe/database/__init__.py | 10 +++++++---
frappe/database/database.py | 12 +++++++++++-
frappe/database/mariadb/database.py | 20 +++++++++-----------
4 files changed, 30 insertions(+), 16 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index e61b1829fa..09248fa99f 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -279,7 +279,9 @@ def connect_replica():
user = local.conf.replica_db_name
password = local.conf.replica_db_password
- local.replica_db = get_db(host=local.conf.replica_host, user=user, password=password, port=port)
+ local.replica_db = get_db(
+ host=local.conf.replica_host, user=user, password=password, port=port, read_only=True
+ )
# swap db connections
local.primary_db = local.db
diff --git a/frappe/database/__init__.py b/frappe/database/__init__.py
index 7de3fabf01..423442d344 100644
--- a/frappe/database/__init__.py
+++ b/frappe/database/__init__.py
@@ -39,17 +39,21 @@ def drop_user_and_database(db_name, root_login=None, root_password=None):
)
-def get_db(host=None, user=None, password=None, port=None):
+def get_db(host=None, user=None, password=None, port=None, read_only=False):
import frappe
if frappe.conf.db_type == "postgres":
import frappe.database.postgres.database
- return frappe.database.postgres.database.PostgresDatabase(host, user, password, port=port)
+ return frappe.database.postgres.database.PostgresDatabase(
+ host, user, password, port=port, read_only=read_only
+ )
else:
import frappe.database.mariadb.database
- return frappe.database.mariadb.database.MariaDBDatabase(host, user, password, port=port)
+ return frappe.database.mariadb.database.MariaDBDatabase(
+ host, user, password, port=port, read_only=read_only
+ )
def setup_help_database(help_db_name):
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 73460614cb..f3f539b792 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -52,12 +52,22 @@ class Database(object):
class InvalidColumnName(frappe.ValidationError):
pass
- def __init__(self, host=None, user=None, password=None, ac_name=None, use_default=0, port=None):
+ def __init__(
+ self,
+ host=None,
+ user=None,
+ password=None,
+ ac_name=None,
+ use_default=0,
+ port=None,
+ read_only=False,
+ ):
self.setup_type_map()
self.host = host or frappe.conf.db_host or "127.0.0.1"
self.port = port or frappe.conf.db_port or ""
self.user = user or frappe.conf.db_name
self.db_name = frappe.conf.db_name
+ self.read_only = read_only # Uses READ ONLY connection if set
self._conn = None
if ac_name:
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index c444e492a2..c255d40d28 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -133,12 +133,10 @@ class MariaDBConnectionUtil:
self.close_connection_pools()
return self.create_connection()
- read_only = frappe.conf.read_from_replica and frappe.conf.replica_host
-
if frappe.local.site not in _SITE_POOLS:
- site_pool = self.create_connection_pool(read_only=read_only)
+ site_pool = self.create_connection_pool()
else:
- site_pool = self.get_connection_pool(read_only=read_only)
+ site_pool = self.get_connection_pool()
try:
conn = site_pool.get_connection()
@@ -165,26 +163,26 @@ class MariaDBConnectionUtil:
pass
_SITE_POOLS.pop(frappe.local.site, None)
- def get_pool_name(self, read_only=False) -> str:
- pool_type = "read-only" if read_only else "default"
+ def get_pool_name(self) -> str:
+ pool_type = "read-only" if self.read_only else "default"
return f"{frappe.local.site}-{pool_type}"
- def get_connection_pool(self, read_only=False) -> "ConnectionPool":
+ def get_connection_pool(self) -> "ConnectionPool":
"""Return MariaDB connection pool object.
If `read_only` is True, return a read only pool.
"""
- return _SITE_POOLS[frappe.local.site]["read_only" if read_only else "default"]
+ return _SITE_POOLS[frappe.local.site]["read_only" if self.read_only else "default"]
- def create_connection_pool(self, read_only=False):
+ def create_connection_pool(self):
pool = mariadb.ConnectionPool(
- pool_name=self.get_pool_name(read_only=read_only),
+ pool_name=self.get_pool_name(),
pool_size=_MAX_POOL_SIZE,
pool_reset_connection=False,
)
pool.set_config(**self.get_connection_settings())
- if read_only:
+ if self.read_only:
_SITE_POOLS[frappe.local.site].read_only = pool
else:
_SITE_POOLS[frappe.local.site].default = pool
From b8d2c195a6e6c438846913b45037c564a8a7af90 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 23 Jun 2022 19:42:12 +0530
Subject: [PATCH 0062/2449] fix: Disable connection pooling via bench commands
unless specified
---
frappe/database/database.py | 1 -
frappe/utils/bench_helper.py | 2 ++
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index f3f539b792..89852c3e1b 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -1168,7 +1168,6 @@ class Database(object):
def close(self):
"""Close database connection."""
if self._conn:
- # self._cursor.close()
self._conn.close()
self._cursor = None
self._conn = None
diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py
index a0b011acc1..10ace1b1b6 100644
--- a/frappe/utils/bench_helper.py
+++ b/frappe/utils/bench_helper.py
@@ -106,4 +106,6 @@ if __name__ == "__main__":
if not frappe._dev_server:
warnings.simplefilter("ignore")
+ frappe.DISABLE_DATABASE_CONNECTION_POOLING = not int(os.environ.get("DATABASE_POOLING", "0"))
+
main()
From b867dedf1536b9fa6afdfd06d063ef4b48f85186 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 23 Jun 2022 23:40:20 +0530
Subject: [PATCH 0063/2449] refactor(run-ui-tests): Maintain list of cypress
plugins
---
frappe/commands/utils.py | 14 +++++++++++---
1 file changed, 11 insertions(+), 3 deletions(-)
diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py
index 41a4b27bcf..a6bf48fac9 100644
--- a/frappe/commands/utils.py
+++ b/frappe/commands/utils.py
@@ -874,11 +874,19 @@ def run_ui_tests(
and os.path.exists(coverage_plugin_path)
and cint(subprocess.getoutput("npm view cypress version")[:1]) >= 6
):
- # install cypress
+ # install cypress & dependent plugins
click.secho("Installing Cypress...", fg="yellow")
- frappe.commands.popen(
- "yarn add cypress@^6 cypress-file-upload@^5 @4tw/cypress-drag-drop@^2 cypress-real-events @testing-library/cypress@^8 @cypress/code-coverage@^3 --no-lockfile"
+ packages = " ".join(
+ [
+ "cypress@^6",
+ "cypress-file-upload@^5",
+ "@4tw/cypress-drag-drop@^2",
+ "cypress-real-events",
+ "@testing-library/cypress@^8",
+ "@cypress/code-coverage@^3",
+ ]
)
+ frappe.commands.popen(f"yarn add {packages} --no-lockfile")
# run for headless mode
run_or_open = "run --browser chrome --record" if headless else "open"
From b96bd8a45ba648b7302c06561a64812b7baa9322 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sun, 26 Jun 2022 16:21:26 +0530
Subject: [PATCH 0064/2449] feat: encrypt 2FA secrets
---
frappe/patches.txt | 1 +
frappe/patches/v12_0/encrypt_2fa_secrets.py | 70 +++++++++++++++++++++
frappe/twofactor.py | 48 +++++++++-----
3 files changed, 104 insertions(+), 15 deletions(-)
create mode 100644 frappe/patches/v12_0/encrypt_2fa_secrets.py
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 66422c7db0..d3274105fe 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -121,6 +121,7 @@ execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings')
frappe.patches.v12_0.remove_example_email_thread_notify
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
frappe.patches.v12_0.set_correct_url_in_files
+frappe.patches.v12_0.encrypt_2fa_secrets
execute:frappe.reload_doc('core', 'doctype', 'doctype')
execute:frappe.reload_doc('custom', 'doctype', 'property_setter')
frappe.patches.v13_0.remove_invalid_options_for_data_fields
diff --git a/frappe/patches/v12_0/encrypt_2fa_secrets.py b/frappe/patches/v12_0/encrypt_2fa_secrets.py
new file mode 100644
index 0000000000..195a282908
--- /dev/null
+++ b/frappe/patches/v12_0/encrypt_2fa_secrets.py
@@ -0,0 +1,70 @@
+import frappe
+import frappe.defaults
+from frappe.model.naming import make_autoname
+from frappe.twofactor import PARENT_FOR_DEFAULTS
+from frappe.utils import now_datetime
+from frappe.utils.password import encrypt
+
+DOCTYPE = "DefaultValue"
+OLD_PARENT = "__default"
+
+
+def execute():
+ table = frappe.qb.DocType(DOCTYPE)
+
+ # set parent for `*_otplogin`
+ (
+ frappe.qb.update(table)
+ .set(table.parent, PARENT_FOR_DEFAULTS)
+ .where(table.parent == OLD_PARENT)
+ .where(table.defkey.like("%_otplogin"))
+ ).run()
+
+ # create new encrypted records for `*_otpsecret`
+ secrets = {
+ key: value
+ for key, value in frappe.defaults.get_defaults_for(parent=OLD_PARENT).items()
+ if key.endswith("_otpsecret")
+ }
+
+ fields = (
+ "name",
+ "creation",
+ "modified",
+ "modified_by",
+ "owner",
+ "parent",
+ "parenttype",
+ "parentfield",
+ "defkey",
+ "defvalue",
+ )
+
+ user = frappe.session.user
+ now = str(now_datetime())
+
+ values = [
+ (
+ make_autoname("hash", DOCTYPE),
+ now,
+ now,
+ user,
+ user,
+ PARENT_FOR_DEFAULTS,
+ "__default",
+ "system_defaults",
+ key,
+ encrypt(value),
+ )
+ for key, value in secrets.items()
+ ]
+
+ frappe.db.bulk_insert(DOCTYPE, fields, values)
+
+ frappe.db.delete(
+ DOCTYPE,
+ {
+ "defkey": ("in", list(secrets)),
+ "parent": OLD_PARENT,
+ },
+ )
diff --git a/frappe/twofactor.py b/frappe/twofactor.py
index 6d01331d7d..db165371fa 100644
--- a/frappe/twofactor.py
+++ b/frappe/twofactor.py
@@ -8,9 +8,25 @@ import pyotp
from pyqrcode import create as qrcreate
import frappe
+import frappe.defaults
from frappe import _
from frappe.utils import cint, get_datetime, get_url, time_diff_in_seconds
from frappe.utils.background_jobs import enqueue
+from frappe.utils.password import decrypt, encrypt
+
+PARENT_FOR_DEFAULTS = "__2fa"
+
+
+def get_default(key):
+ return frappe.db.get_default(key, parent=PARENT_FOR_DEFAULTS)
+
+
+def set_default(key, value):
+ frappe.db.set_default(key, value, parent=PARENT_FOR_DEFAULTS)
+
+
+def clear_default(key):
+ frappe.defaults.clear_default(key, parent=PARENT_FOR_DEFAULTS)
class ExpiredLoginException(Exception):
@@ -118,11 +134,13 @@ def two_factor_is_enabled_for_(user):
def get_otpsecret_for_(user):
"""Set OTP Secret for user even if not set."""
- otp_secret = frappe.db.get_default(user + "_otpsecret")
- if not otp_secret:
- otp_secret = b32encode(os.urandom(10)).decode("utf-8")
- frappe.db.set_default(user + "_otpsecret", otp_secret)
- frappe.db.commit()
+ if otp_secret := get_default(user + "_otpsecret"):
+ return decrypt(otp_secret)
+
+ otp_secret = b32encode(os.urandom(10)).decode("utf-8")
+ set_default(user + "_otpsecret", encrypt(otp_secret))
+ frappe.db.commit()
+
return otp_secret
@@ -162,8 +180,8 @@ def confirm_otp_token(login_manager, otp=None, tmp_id=None):
totp = pyotp.TOTP(otp_secret)
if totp.verify(otp):
# show qr code only once
- if not frappe.db.get_default(login_manager.user + "_otplogin"):
- frappe.db.set_default(login_manager.user + "_otplogin", 1)
+ if not get_default(login_manager.user + "_otplogin"):
+ set_default(login_manager.user + "_otplogin", 1)
delete_qrimage(login_manager.user)
tracker.add_success_attempt()
return True
@@ -180,7 +198,7 @@ def get_verification_obj(user, token, otp_secret):
verification_obj = process_2fa_for_sms(user, token, otp_secret)
elif verification_method == "OTP App":
# check if this if the first time that the user is trying to login. If so, send an email
- if not frappe.db.get_default(user + "_otplogin"):
+ if not get_default(user + "_otplogin"):
verification_obj = process_2fa_for_email(user, token, otp_secret, otp_issuer, method="OTP App")
else:
verification_obj = process_2fa_for_otp_app(user, otp_secret, otp_issuer)
@@ -207,7 +225,7 @@ def process_2fa_for_sms(user, token, otp_secret):
def process_2fa_for_otp_app(user, otp_secret, otp_issuer):
"""Process OTP App method for 2fa."""
totp_uri = pyotp.TOTP(otp_secret).provisioning_uri(user, issuer_name=otp_issuer)
- if frappe.db.get_default(user + "_otplogin"):
+ if get_default(user + "_otplogin"):
otp_setup_completed = True
else:
otp_setup_completed = False
@@ -222,7 +240,7 @@ def process_2fa_for_email(user, token, otp_secret, otp_issuer, method="Email"):
message = None
status = True
prompt = ""
- if method == "OTP App" and not frappe.db.get_default(user + "_otplogin"):
+ if method == "OTP App" and not get_default(user + "_otplogin"):
"""Sending one-time email for OTP App"""
totp_uri = pyotp.TOTP(otp_secret).provisioning_uri(user, issuer_name=otp_issuer)
qrcode_link = get_link_for_qrcode(user, totp_uri)
@@ -328,7 +346,7 @@ def send_token_via_sms(otpsecret, token=None, phone_no=None):
is_async=True,
job_name=None,
now=False,
- **sms_args
+ **sms_args,
)
return True
@@ -364,7 +382,7 @@ def send_token_via_email(user, token, otp_secret, otp_issuer, subject=None, mess
is_async=True,
job_name=None,
now=False,
- **email_args
+ **email_args,
)
return True
@@ -464,8 +482,8 @@ def reset_otp_secret(user):
otp_issuer = frappe.db.get_value("System Settings", "System Settings", "otp_issuer_name")
user_email = frappe.db.get_value("User", user, "email")
if frappe.session.user in ["Administrator", user]:
- frappe.defaults.clear_default(user + "_otplogin")
- frappe.defaults.clear_default(user + "_otpsecret")
+ clear_default(user + "_otplogin")
+ clear_default(user + "_otpsecret")
email_args = {
"recipients": user_email,
"sender": None,
@@ -484,7 +502,7 @@ def reset_otp_secret(user):
is_async=True,
job_name=None,
now=False,
- **email_args
+ **email_args,
)
return frappe.msgprint(
_("OTP Secret has been reset. Re-registration will be required on next login.")
From 0e4044eebaf27791a499ffb2f4c383391c1baad6 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sun, 26 Jun 2022 16:47:42 +0530
Subject: [PATCH 0065/2449] fix: return if no secrets found
---
frappe/patches/v12_0/encrypt_2fa_secrets.py | 3 +++
1 file changed, 3 insertions(+)
diff --git a/frappe/patches/v12_0/encrypt_2fa_secrets.py b/frappe/patches/v12_0/encrypt_2fa_secrets.py
index 195a282908..002dbe0f03 100644
--- a/frappe/patches/v12_0/encrypt_2fa_secrets.py
+++ b/frappe/patches/v12_0/encrypt_2fa_secrets.py
@@ -27,6 +27,9 @@ def execute():
if key.endswith("_otpsecret")
}
+ if not secrets:
+ return
+
fields = (
"name",
"creation",
From aa8396531328fba1dacbf7e3f5889549bf7d5aa4 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 27 Jun 2022 12:19:54 +0530
Subject: [PATCH 0066/2449] ci(ui-tests): Print 'bench start' log on failure
Co-authored-by: Ankush Menat
---
.github/helper/install.sh | 2 +-
.github/workflows/ui-tests.yml | 4 ++++
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 3ef7db34f6..41fdead675 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -59,7 +59,7 @@ cd ./apps/frappe || exit
yarn add node-sass@4.13.1
cd ../..
-bench start &
+bench start &> bench_start.log &
bench --site test_site reinstall --yes
if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi
if [ "$TYPE" == "server" ]; then CI=Yes bench build --app frappe; fi
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 09b2a3caf8..ecc77f491d 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -175,3 +175,7 @@ jobs:
files: /home/runner/frappe-bench/sites/coverage.xml
verbose: true
flags: server
+
+ - name: Show bench console if tests failed
+ if: ${{ failure() }}
+ run: cat ~/frappe-bench/bench_start.log
\ No newline at end of file
From 786d52f6f97d0db04fd3b4e4692fa1b51ca7b14f Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 27 Jun 2022 13:56:08 +0530
Subject: [PATCH 0067/2449] chore: disable recorder UI test
---
cypress/integration/recorder.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js
index 7d4c83abf5..57d3c01356 100644
--- a/cypress/integration/recorder.js
+++ b/cypress/integration/recorder.js
@@ -1,4 +1,4 @@
-context('Recorder', () => {
+context.skip('Recorder', () => {
before(() => {
cy.login();
});
From daf3f05fea8eb1cb6e95f5cb9484b9d28303f448 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 29 Jun 2022 16:32:00 +0530
Subject: [PATCH 0068/2449] fix(db): Hanlde sequences in db.sql values
---
frappe/database/mariadb/database.py | 21 ++++++++++++++++++++-
1 file changed, 20 insertions(+), 1 deletion(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index c255d40d28..4febc21f8a 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -5,7 +5,7 @@ from typing import TYPE_CHECKING, Dict, List, Tuple, Union
import mariadb
from mariadb.constants import FIELD_TYPE
from pymysql.constants import ER
-from pymysql.converters import escape_string
+from pymysql.converters import escape_sequence, escape_string
import frappe
from frappe.database.database import Database
@@ -15,6 +15,7 @@ from frappe.utils import UnicodeWithAttrs, get_datetime, get_table_name
if TYPE_CHECKING:
from mariadb import ConnectionPool
+_FIND_ITER_PATTERN = re.compile("%s")
_PARAM_COMP = re.compile(r"%\([\w]*\)s")
_SITE_POOLS = defaultdict(frappe._dict)
_MAX_POOL_SIZE = 64
@@ -235,6 +236,24 @@ class MariaDBConnectionUtil:
ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-205
"""
pos_values = []
+
+ # Handle sequences in values - PyMySQL & Psycopg allowed them but MariaDB client doesn't
+ # This leads to a DataError. MariaDB connector expects a flat tuple. Build queries with
+ # the ['%s'] * len(values) pattern to avoid this block.
+ if isinstance(values, (tuple, list)) and any(isinstance(v, (tuple, list)) for v in values):
+ values = list(values)
+ find_iter = _FIND_ITER_PATTERN.finditer(query)
+
+ for i, val in enumerate(values):
+ pos = next(find_iter)
+ if isinstance(val, list):
+ query = (
+ query[: pos.start()]
+ + escape_sequence(val, charset=self._conn.character_set)
+ + query[pos.end() :]
+ )
+ del values[i]
+
named_tokens = _PARAM_COMP.findall(query)
if not named_tokens or len(set(named_tokens)) == len(values):
From 39d30ffeaa1afcce9e63b7ddde5e8e22e34d3127 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 30 Jun 2022 19:12:10 +0530
Subject: [PATCH 0069/2449] fix: Database._transform_query
* Handle dict substitutions in transformations too
* Allow list / tuple values
* Check for values inconsistencies before flattening step
---
frappe/database/mariadb/database.py | 26 ++++++++++++++------------
1 file changed, 14 insertions(+), 12 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 4febc21f8a..b620c235a3 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -236,6 +236,18 @@ class MariaDBConnectionUtil:
ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-205
"""
pos_values = []
+ named_tokens = _PARAM_COMP.findall(query)
+
+ for token in named_tokens:
+ key = token[2:-2]
+ val = values[key]
+ if isinstance(val, (tuple, list)):
+ val = escape_sequence(val)
+ pos_values.append(val)
+ query = query.replace(token, "%s", 1)
+
+ if pos_values:
+ values = pos_values
# Handle sequences in values - PyMySQL & Psycopg allowed them but MariaDB client doesn't
# This leads to a DataError. MariaDB connector expects a flat tuple. Build queries with
@@ -246,7 +258,7 @@ class MariaDBConnectionUtil:
for i, val in enumerate(values):
pos = next(find_iter)
- if isinstance(val, list):
+ if isinstance(val, (list, tuple)):
query = (
query[: pos.start()]
+ escape_sequence(val, charset=self._conn.character_set)
@@ -254,17 +266,7 @@ class MariaDBConnectionUtil:
)
del values[i]
- named_tokens = _PARAM_COMP.findall(query)
-
- if not named_tokens or len(set(named_tokens)) == len(values):
- return query, values
-
- for token in named_tokens:
- key = token[2:-2]
- pos_values.append(values[key])
- query = query.replace(token, "%s", 1)
-
- return query, pos_values
+ return query, values
class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
From cc33a7eae5c73760f24792e27aaddc09b1a8e1a8 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 30 Jun 2022 19:41:47 +0530
Subject: [PATCH 0070/2449] chore(deps): Bump MariaDB client from 1.0.11 to
1.1.2
---
pyproject.toml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pyproject.toml b/pyproject.toml
index cd132b4dec..a103df83d6 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -39,7 +39,7 @@ dependencies = [
"html5lib~=1.1",
"ipython~=8.4.0",
"ldap3~=2.9",
- "mariadb~=1.0.11",
+ "mariadb~=1.1.2",
"markdown2~=2.4.0",
"maxminddb-geolite2==2018.703",
"num2words~=0.5.10",
From 448f9573f51a9b1e6a497c6c4809395f35fa2211 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 4 Jul 2022 11:56:55 +0530
Subject: [PATCH 0071/2449] ci: Separate PY / conflict validation step, merge
install
---
.github/helper/install_dependencies.sh | 7 -------
.github/workflows/patch-mariadb-tests.yml | 21 ++++++++++++---------
.github/workflows/server-mariadb-tests.yml | 18 +++++++++++-------
.github/workflows/server-postgres-tests.yml | 18 +++++++++++-------
.github/workflows/ui-tests.yml | 18 +++++++++++-------
5 files changed, 45 insertions(+), 37 deletions(-)
diff --git a/.github/helper/install_dependencies.sh b/.github/helper/install_dependencies.sh
index 6a837f268a..397203f3cc 100644
--- a/.github/helper/install_dependencies.sh
+++ b/.github/helper/install_dependencies.sh
@@ -2,13 +2,6 @@
set -e
-# Check for merge conflicts before proceeding
-python -m compileall -f "${GITHUB_WORKSPACE}"
-if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
- then echo "Found merge conflicts"
- exit 1
-fi
-
# install wkhtmltopdf
wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml
index 0c9fe2bb8a..5194444d49 100644
--- a/.github/workflows/patch-mariadb-tests.yml
+++ b/.github/workflows/patch-mariadb-tests.yml
@@ -30,6 +30,14 @@ jobs:
- name: Clone
uses: actions/checkout@v3
+ - name: Check for valid Python & Merge Conflicts
+ run: |
+ python -m compileall -f "${GITHUB_WORKSPACE}"
+ if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
+ then echo "Found merge conflicts"
+ exit 1
+ fi
+
- name: Setup Python
uses: "gabrielfalcao/pyenv-action@v9"
with:
@@ -92,22 +100,17 @@ jobs:
${{ runner.os }}-yarn-
- name: Install Dependencies
- if: ${{ steps.check-build.outputs.build == 'strawberry' }}
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
- env:
- BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
- AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
- TYPE: server
-
- - name: Install
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: |
+ bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
pip install frappe-bench
pyenv global $(pyenv versions | grep '3.10')
bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
- DB: mariadb
+ BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
+ AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: server
+ DB: mariadb
- name: Run Patch Tests
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml
index 719972f535..028ef30746 100644
--- a/.github/workflows/server-mariadb-tests.yml
+++ b/.github/workflows/server-mariadb-tests.yml
@@ -44,6 +44,14 @@ jobs:
with:
python-version: '3.10'
+ - name: Check for valid Python & Merge Conflicts
+ run: |
+ python -m compileall -f "${GITHUB_WORKSPACE}"
+ if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
+ then echo "Found merge conflicts"
+ exit 1
+ fi
+
- name: Check if build should be run
id: check-build
run: |
@@ -104,18 +112,14 @@ jobs:
- name: Install Dependencies
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
+ run: |
+ bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
+ bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: server
-
- - name: Install
- if: ${{ steps.check-build.outputs.build == 'strawberry' }}
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- env:
DB: mariadb
- TYPE: server
- name: Run Tests
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml
index 8f015f43e6..af7541dd6e 100644
--- a/.github/workflows/server-postgres-tests.yml
+++ b/.github/workflows/server-postgres-tests.yml
@@ -47,6 +47,14 @@ jobs:
with:
python-version: '3.10'
+ - name: Check for valid Python & Merge Conflicts
+ run: |
+ python -m compileall -f "${GITHUB_WORKSPACE}"
+ if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
+ then echo "Found merge conflicts"
+ exit 1
+ fi
+
- name: Check if build should be run
id: check-build
run: |
@@ -107,18 +115,14 @@ jobs:
- name: Install Dependencies
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
+ run: |
+ bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
+ bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: server
-
- - name: Install
- if: ${{ steps.check-build.outputs.build == 'strawberry' }}
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- env:
DB: postgres
- TYPE: server
- name: Run Tests
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index 5dc5cb9c4c..65debe989c 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -43,6 +43,14 @@ jobs:
with:
python-version: '3.10'
+ - name: Check for valid Python & Merge Conflicts
+ run: |
+ python -m compileall -f "${GITHUB_WORKSPACE}"
+ if grep -lr --exclude-dir=node_modules "^<<<<<<< " "${GITHUB_WORKSPACE}"
+ then echo "Found merge conflicts"
+ exit 1
+ fi
+
- name: Check if build should be run
id: check-build
run: |
@@ -113,18 +121,14 @@ jobs:
- name: Install Dependencies
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
+ run: |
+ bash ${GITHUB_WORKSPACE}/.github/helper/install_dependencies.sh
+ bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
env:
BEFORE: ${{ env.GITHUB_EVENT_PATH.before }}
AFTER: ${{ env.GITHUB_EVENT_PATH.after }}
TYPE: ui
-
- - name: Install
- if: ${{ steps.check-build.outputs.build == 'strawberry' }}
- run: bash ${GITHUB_WORKSPACE}/.github/helper/install.sh
- env:
DB: mariadb
- TYPE: ui
- name: Instrument Source Code
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
From 1723a6dc9dbe15502f9bec5c901d563318413d6e Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 5 Jul 2022 14:59:11 +0530
Subject: [PATCH 0072/2449] ci: Add mariadb system dependency
---
.github/helper/install.sh | 8 ++------
.github/helper/install_dependencies.sh | 17 +++++++++--------
2 files changed, 11 insertions(+), 14 deletions(-)
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index d038be1b23..88ca012c9b 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -1,9 +1,9 @@
#!/bin/bash
-
set -e
-
cd ~ || exit
+echo "Setting Up Bench..."
+
pip install frappe-bench
bench -v init frappe-bench --skip-assets --python "$(which python)" --frappe-path "${GITHUB_WORKSPACE}"
@@ -17,10 +17,6 @@ if [ "$TYPE" == "server" ]; then
fi
if [ "$DB" == "mariadb" ];then
- curl -LsS -O https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
- sudo bash mariadb_repo_setup --mariadb-server-version=10.6
- sudo apt install mariadb-client
-
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL character_set_server = 'utf8mb4'";
mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
diff --git a/.github/helper/install_dependencies.sh b/.github/helper/install_dependencies.sh
index 397203f3cc..f16bd61a53 100644
--- a/.github/helper/install_dependencies.sh
+++ b/.github/helper/install_dependencies.sh
@@ -1,12 +1,13 @@
#!/bin/bash
-
set -e
- # install wkhtmltopdf
-wget -O /tmp/wkhtmltox.tar.xz https://github.com/frappe/wkhtmltopdf/raw/master/wkhtmltox-0.12.3_linux-generic-amd64.tar.xz
-tar -xf /tmp/wkhtmltox.tar.xz -C /tmp
-sudo mv /tmp/wkhtmltox/bin/wkhtmltopdf /usr/local/bin/wkhtmltopdf
-sudo chmod o+x /usr/local/bin/wkhtmltopdf
+echo "Setting Up System Dependencies..."
-# install cups
-sudo apt update && sudo apt install libcups2-dev libmariadb-dev redis-server
+wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
+sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
+
+curl -LsS -O https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
+sudo bash mariadb_repo_setup --mariadb-server-version=10.6
+
+sudo apt update
+sudo apt install libcups2-dev redis-server libmariadb3 libmariadb-dev mariadb-client
From dbb37acedf7918a4a12a8444343b76439ed6d818 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 5 Jul 2022 17:47:14 +0530
Subject: [PATCH 0073/2449] fix: Transform queries with all types of `values`
---
frappe/database/database.py | 9 ++++-----
1 file changed, 4 insertions(+), 5 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 6808f51d31..07ef86072e 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -113,7 +113,7 @@ class Database:
raise NotImplementedError
def _transform_query(self, query: Query, values: QueryValues):
- return query, values or None
+ return query, values or ()
def sql(
self,
@@ -181,10 +181,9 @@ class Database:
if debug:
time_start = time()
- if values is not None:
- if not isinstance(values, (tuple, dict, list)):
- values = (values,)
- query, values = self._transform_query(query, values)
+ if not isinstance(values, (tuple, dict, list)):
+ values = (values,)
+ query, values = self._transform_query(query, values)
try:
self._cursor.execute(query, values)
From 4dc2ecefba95d4a9e886477312c776ed0c529072 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 5 Jul 2022 17:47:54 +0530
Subject: [PATCH 0074/2449] fix: Use mariadb's constants instead of pymysql
---
frappe/database/mariadb/database.py | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 671b28d168..8b69eba7ae 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -3,8 +3,7 @@ from collections import defaultdict
from typing import TYPE_CHECKING
import mariadb
-from mariadb.constants import FIELD_TYPE
-from pymysql.constants import ER
+from mariadb.constants import ERR, FIELD_TYPE
from pymysql.converters import escape_sequence, escape_string
import frappe
@@ -53,15 +52,15 @@ class MariaDBExceptionUtil:
@staticmethod
def is_deadlocked(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.LOCK_DEADLOCK
+ return getattr(e, "errno", None) == ERR.ER_LOCK_DEADLOCK
@staticmethod
def is_timedout(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.LOCK_WAIT_TIMEOUT
+ return getattr(e, "errno", None) == ERR.ER_LOCK_WAIT_TIMEOUT
@staticmethod
def is_table_missing(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.NO_SUCH_TABLE
+ return getattr(e, "errno", None) == ERR.ER_NO_SUCH_TABLE
@staticmethod
def is_missing_table(e: mariadb.Error) -> bool:
@@ -69,31 +68,31 @@ class MariaDBExceptionUtil:
@staticmethod
def is_missing_column(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.BAD_FIELD_ERROR
+ return getattr(e, "errno", None) == ERR.ER_BAD_FIELD_ERROR
@staticmethod
def is_duplicate_fieldname(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.DUP_FIELDNAME
+ return getattr(e, "errno", None) == ERR.ER_DUP_FIELDNAME
@staticmethod
def is_duplicate_entry(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.DUP_ENTRY
+ return getattr(e, "errno", None) == ERR.ER_DUP_ENTRY
@staticmethod
def is_access_denied(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.ACCESS_DENIED_ERROR
+ return getattr(e, "errno", None) == ERR.ER_ACCESS_DENIED_ERROR
@staticmethod
def cant_drop_field_or_key(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.CANT_DROP_FIELD_OR_KEY
+ return getattr(e, "errno", None) == ERR.ER_CANT_DROP_FIELD_OR_KEY
@staticmethod
def is_syntax_error(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.PARSE_ERROR
+ return getattr(e, "errno", None) == ERR.ER_PARSE_ERROR
@staticmethod
def is_data_too_long(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ER.DATA_TOO_LONG
+ return getattr(e, "errno", None) == ERR.ER_DATA_TOO_LONG
@staticmethod
def is_primary_key_violation(e: mariadb.Error) -> bool:
@@ -266,7 +265,7 @@ class MariaDBConnectionUtil:
)
del values[i]
- return query, values
+ return query, values or ()
class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
From 1faa2fd1e2eab6458e197b0d46fe22d28bf803e0 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 6 Jul 2022 10:52:48 +0530
Subject: [PATCH 0075/2449] fix: Format string instead of % to avoid TypeError
---
frappe/database/database.py | 8 +++++---
1 file changed, 5 insertions(+), 3 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 07ef86072e..54a54d01a7 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -279,11 +279,13 @@ class Database:
try:
return self._cursor.mogrify(query, values)
- except BaseException:
+ except AttributeError:
if isinstance(values, dict):
- return query % {k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items()}
+ return query.format(
+ **{k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items()}
+ )
elif isinstance(values, (list, tuple)):
- return query % tuple(frappe.db.escape(v) if isinstance(v, str) else v for v in values)
+ return query.format(*(frappe.db.escape(v) if isinstance(v, str) else v for v in values))
return query, values
def lazy_mogrify(self, query: Query, values: QueryValues) -> LazyMogrify:
From 6a76f8ad5f2d3b6c8052c5db6c3b6b9025230acf Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 6 Jul 2022 11:47:06 +0530
Subject: [PATCH 0076/2449] fix(pg): Transform Falsy values as None
ref: https://github.com/frappe/frappe/runs/7208763442
---
frappe/database/database.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 54a54d01a7..7ee522d99f 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -113,7 +113,7 @@ class Database:
raise NotImplementedError
def _transform_query(self, query: Query, values: QueryValues):
- return query, values or ()
+ return query, values or None
def sql(
self,
From 41b93392b16ade56d829daa66b9805e85aea8533 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 6 Jul 2022 15:58:45 +0530
Subject: [PATCH 0077/2449] fix: Database._transform_result
Transform data not already casted by mariadb client
---
frappe/database/database.py | 7 ++++--
frappe/database/mariadb/database.py | 34 ++++++++++++++++++++++++-----
2 files changed, 33 insertions(+), 8 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 7ee522d99f..1003618a6e 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -112,9 +112,12 @@ class Database:
def get_database_size(self):
raise NotImplementedError
- def _transform_query(self, query: Query, values: QueryValues):
+ def _transform_query(self, query: Query, values: QueryValues) -> tuple:
return query, values or None
+ def _transform_result(self, result: list[tuple]) -> list[tuple]:
+ return result
+
def sql(
self,
query: Query,
@@ -221,7 +224,7 @@ class Database:
if not self._cursor.description:
return ()
- self.last_result = self._cursor.fetchall()
+ self.last_result = self._transform_result(self._cursor.fetchall())
if pluck:
return [r[0] for r in self.last_result]
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 8b69eba7ae..c112fcf62b 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -1,5 +1,6 @@
import re
from collections import defaultdict
+from decimal import Decimal
from typing import TYPE_CHECKING
import mariadb
@@ -200,11 +201,7 @@ class MariaDBConnectionUtil:
"host": self.host,
"user": self.user,
"password": self.password,
- "converter": {
- FIELD_TYPE.NEWDECIMAL: float,
- FIELD_TYPE.DATETIME: get_datetime,
- UnicodeWithAttrs: escape_string,
- },
+ "converter": self.CONVERSION_MAP,
}
if self.user != "root":
@@ -226,6 +223,10 @@ class MariaDBConnectionUtil:
conn_settings.update(ssl_params)
return conn_settings
+
+class MariaDBCursorPatchUtil:
+ """Patch mariadb.cursor.Cursor to handle things not supported by pinned version of MariaDB client."""
+
def _transform_query(self, query: str, values: dict) -> str:
"""Converts a query with named placeholders to a query with %s and values dict to a tuple.
@@ -267,8 +268,24 @@ class MariaDBConnectionUtil:
return query, values or ()
+ def _transform_result(self, result: list[tuple], description=tuple[tuple]) -> list[tuple]:
+ # ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-213
+ _result = []
+ for row in result:
+ _row = []
+ for el in row:
+ if isinstance(el, Decimal):
+ el = float(el)
+ elif isinstance(el, UnicodeWithAttrs):
+ el = escape_string(el)
+ _row.append(el)
+ _result.append(tuple(_row))
+ return _result
-class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
+
+class MariaDBDatabase(
+ MariaDBCursorPatchUtil, MariaDBConnectionUtil, MariaDBExceptionUtil, Database
+):
REGEX_CHARACTER = "regexp"
# NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
@@ -279,6 +296,11 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
# using the system after a restore.
# issue link: https://jira.mariadb.org/browse/MDEV-21786
SEQUENCE_CACHE = 50
+ CONVERSION_MAP = {
+ FIELD_TYPE.NEWDECIMAL: float,
+ FIELD_TYPE.DATETIME: get_datetime,
+ UnicodeWithAttrs: escape_string,
+ }
def setup_type_map(self):
self.db_type = "mariadb"
From 576fa32af4d16c11cbb753ce7947d2274332c15f Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 7 Jul 2022 12:09:36 +0530
Subject: [PATCH 0078/2449] fix(mariadb): SequenceGeneratorLimitExceeded is an
OperationalError now :D
---
frappe/database/mariadb/database.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index c112fcf62b..bed863b3cc 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -48,7 +48,7 @@ class MariaDBExceptionUtil:
DataError = mariadb.DataError
# match ER_SEQUENCE_RUN_OUT - https://mariadb.com/kb/en/mariadb-error-codes/
- SequenceGeneratorLimitExceeded = mariadb.ProgrammingError
+ SequenceGeneratorLimitExceeded = mariadb.OperationalError
SequenceGeneratorLimitExceeded.errno = 4084
@staticmethod
From 8ccc0d039be2c8b9f22e08f646a84e7918055300 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 7 Jul 2022 15:57:57 +0530
Subject: [PATCH 0079/2449] fix: Database._transform_query
* Don't try to convert None to sequence
* Skip transforming values that mariadb~=1.1 can handle - only worry
about sequences for now
---
frappe/database/database.py | 14 ++++---
frappe/database/mariadb/database.py | 62 +++++++++++------------------
2 files changed, 33 insertions(+), 43 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 1003618a6e..9b5f7bbfe5 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -9,6 +9,7 @@ import string
import traceback
from contextlib import contextmanager
from time import time
+from types import NoneType
from pypika.terms import Criterion, NullValue, PseudoColumn
@@ -184,7 +185,7 @@ class Database:
if debug:
time_start = time()
- if not isinstance(values, (tuple, dict, list)):
+ if not isinstance(values, (NoneType, tuple, dict, list)):
values = (values,)
query, values = self._transform_query(query, values)
@@ -206,6 +207,11 @@ class Database:
frappe.errprint(f"Error in query:\n{e}")
raise
+ elif isinstance(e, self.ProgrammingError):
+ traceback.print_stack()
+ frappe.errprint(f"Error in query:\n{query, values}")
+ raise
+
if not (
ignore_ddl
and (self.is_missing_column(e) or self.is_table_missing(e) or self.cant_drop_field_or_key(e))
@@ -284,11 +290,9 @@ class Database:
return self._cursor.mogrify(query, values)
except AttributeError:
if isinstance(values, dict):
- return query.format(
- **{k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items()}
- )
+ return query % {k: frappe.db.escape(v) if isinstance(v, str) else v for k, v in values.items()}
elif isinstance(values, (list, tuple)):
- return query.format(*(frappe.db.escape(v) if isinstance(v, str) else v for v in values))
+ return query % tuple(frappe.db.escape(v) if isinstance(v, str) else v for v in values)
return query, values
def lazy_mogrify(self, query: Query, values: QueryValues) -> LazyMogrify:
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index bed863b3cc..71317b5884 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -8,7 +8,7 @@ from mariadb.constants import ERR, FIELD_TYPE
from pymysql.converters import escape_sequence, escape_string
import frappe
-from frappe.database.database import Database
+from frappe.database.database import Database, QueryValues
from frappe.database.mariadb.schema import MariaDBTable
from frappe.utils import UnicodeWithAttrs, get_datetime, get_table_name
@@ -227,48 +227,34 @@ class MariaDBConnectionUtil:
class MariaDBCursorPatchUtil:
"""Patch mariadb.cursor.Cursor to handle things not supported by pinned version of MariaDB client."""
- def _transform_query(self, query: str, values: dict) -> str:
- """Converts a query with named placeholders to a query with %s and values dict to a tuple.
+ def _transform_query(self, query: str, values: QueryValues) -> tuple:
+ """Transform the query to handle things not supported by pinned version of MariaDB client.
- This is a workaround since the MariaDB Python client (1.0.11) responds inconsistently
- depending on the substitions in the query & type of values passed.
-
- ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-205
+ Transformations:
+ - Escape sequences in values
"""
- pos_values = []
- named_tokens = _PARAM_COMP.findall(query)
+ _values = []
- for token in named_tokens:
- key = token[2:-2]
- val = values[key]
- if isinstance(val, (tuple, list)):
- val = escape_sequence(val)
- pos_values.append(val)
- query = query.replace(token, "%s", 1)
+ if isinstance(values, (tuple, list)):
+ for val in values:
+ if isinstance(val, (tuple, list)):
+ _values.append(escape_sequence(val, charset=self._conn.character_set))
+ else:
+ _values.append(val)
+ values = _values
+ else:
+ for token in _PARAM_COMP.findall(query):
+ key = token[2:-2]
+ try:
+ val = values[key]
+ except KeyError:
+ raise self.ProgrammingError(f"Missing value for key '{key}'")
+ if isinstance(val, (tuple, list)):
+ values[key] = escape_sequence(val, charset=self._conn.character_set)
- if pos_values:
- values = pos_values
+ return query, values or []
- # Handle sequences in values - PyMySQL & Psycopg allowed them but MariaDB client doesn't
- # This leads to a DataError. MariaDB connector expects a flat tuple. Build queries with
- # the ['%s'] * len(values) pattern to avoid this block.
- if isinstance(values, (tuple, list)) and any(isinstance(v, (tuple, list)) for v in values):
- values = list(values)
- find_iter = _FIND_ITER_PATTERN.finditer(query)
-
- for i, val in enumerate(values):
- pos = next(find_iter)
- if isinstance(val, (list, tuple)):
- query = (
- query[: pos.start()]
- + escape_sequence(val, charset=self._conn.character_set)
- + query[pos.end() :]
- )
- del values[i]
-
- return query, values or ()
-
- def _transform_result(self, result: list[tuple], description=tuple[tuple]) -> list[tuple]:
+ def _transform_result(self, result: list[tuple]) -> list[tuple]:
# ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-213
_result = []
for row in result:
From e7023fa74ddf927a6947b2b0b68b1e8b0d00af1b Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 7 Jul 2022 17:07:40 +0530
Subject: [PATCH 0080/2449] feat(ldap): Allow setting "User Type" for new users
Prior to this, every user found in LDAP would mean a System User is
created - now pick the type and role you want to give newly created
users. For the given user types, the role may be picked from:
System User | ldap_settings.default_user_role (Fetched from LDAP settings)
Website User | N/A
{{ Custom Type }} | user_type.role (Fetched from User Type record)
---
.../ldap_group_mapping.json | 7 ++--
.../doctype/ldap_settings/ldap_settings.json | 34 +++++++++++++++----
.../doctype/ldap_settings/ldap_settings.py | 27 ++++++++++-----
3 files changed, 52 insertions(+), 16 deletions(-)
diff --git a/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.json b/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.json
index 92db68e962..9bfe1eac56 100644
--- a/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.json
+++ b/frappe/integrations/doctype/ldap_group_mapping/ldap_group_mapping.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2019-05-29 01:24:29.585060",
"doctype": "DocType",
"editable_grid": 1,
@@ -19,13 +20,14 @@
"fieldname": "erpnext_role",
"fieldtype": "Link",
"in_list_view": 1,
- "label": "ERPNext Role",
+ "label": "User Role",
"options": "Role",
"reqd": 1
}
],
"istable": 1,
- "modified": "2019-07-15 06:46:38.050408",
+ "links": [],
+ "modified": "2022-07-07 16:28:44.828514",
"modified_by": "Administrator",
"module": "Integrations",
"name": "LDAP Group Mapping",
@@ -34,5 +36,6 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "ASC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.json b/frappe/integrations/doctype/ldap_settings/ldap_settings.json
index fd45a71538..f5472a5097 100644
--- a/frappe/integrations/doctype/ldap_settings/ldap_settings.json
+++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.json
@@ -42,7 +42,10 @@
"column_break_33",
"ldap_group_member_attribute",
"ldap_group_mappings_section",
+ "default_user_type",
+ "column_break_38",
"default_role",
+ "section_break_40",
"ldap_groups",
"ldap_group_field"
],
@@ -79,9 +82,11 @@
"reqd": 1
},
{
+ "depends_on": "eval: doc.default_user_type == \"System User\"",
"fieldname": "default_role",
"fieldtype": "Link",
- "label": "Default Role on Creation",
+ "label": "Default User Role",
+ "mandatory_depends_on": "eval: doc.default_user_type == \"System User\"",
"options": "Role",
"reqd": 1
},
@@ -249,10 +254,10 @@
"label": "Group Object Class"
},
{
- "description": "string value, i.e. {0} or uid={0},ou=users,dc=example,dc=com",
- "fieldname": "ldap_custom_group_search",
- "fieldtype": "Data",
- "label": "Custom Group Search"
+ "description": "string value, i.e. {0} or uid={0},ou=users,dc=example,dc=com",
+ "fieldname": "ldap_custom_group_search",
+ "fieldtype": "Data",
+ "label": "Custom Group Search"
},
{
"description": "Requires any valid fdn path. i.e. ou=users,dc=example,dc=com",
@@ -268,12 +273,28 @@
"fieldtype": "Data",
"label": "LDAP search path for Groups",
"reqd": 1
+ },
+ {
+ "fieldname": "default_user_type",
+ "fieldtype": "Link",
+ "label": "Default User Type",
+ "options": "User Type",
+ "reqd": 1
+ },
+ {
+ "fieldname": "column_break_38",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_40",
+ "fieldtype": "Section Break",
+ "hide_border": 1
}
],
"in_create": 1,
"issingle": 1,
"links": [],
- "modified": "2021-07-27 11:51:43.328271",
+ "modified": "2022-07-07 16:51:46.230793",
"modified_by": "Administrator",
"module": "Integrations",
"name": "LDAP Settings",
@@ -294,5 +315,6 @@
"read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
index ef6493717f..249d4e31f3 100644
--- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
@@ -1,11 +1,16 @@
# Copyright (c) 2015, Frappe Technologies and contributors
# License: MIT. See LICENSE
+from typing import TYPE_CHECKING
+
import frappe
from frappe import _, safe_encode
from frappe.model.document import Document
from frappe.twofactor import authenticate_for_2factor, confirm_otp_token, should_run_2fa
+if TYPE_CHECKING:
+ from frappe.core.doctype.user.user import User
+
class LDAPSettings(Document):
def validate(self):
@@ -134,7 +139,7 @@ class LDAPSettings(Document):
setattr(user, key, value)
user.save(ignore_permissions=True)
- def sync_roles(self, user, additional_groups=None):
+ def sync_roles(self, user: "User", additional_groups=None):
current_roles = {d.role for d in user.get("roles")}
@@ -158,7 +163,9 @@ class LDAPSettings(Document):
user.remove_roles(*roles_to_remove)
def create_or_update_user(self, user_data, groups=None):
- user = None
+ user: "User" = None
+ role: str = None
+
if frappe.db.exists("User", user_data["email"]):
user = frappe.get_doc("User", user_data["email"])
LDAPSettings.update_user_fields(user=user, user_data=user_data)
@@ -169,16 +176,20 @@ class LDAPSettings(Document):
"doctype": "User",
"send_welcome_email": 0,
"language": "",
- "user_type": "System User",
- # "roles": [{
- # "role": self.default_role
- # }]
+ "user_type": self.default_user_type,
}
)
user = frappe.get_doc(doc)
user.insert(ignore_permissions=True)
- # always add default role.
- user.add_roles(self.default_role)
+
+ if self.default_user_type == "System User":
+ role = self.default_role
+ else:
+ role = frappe.db.get_value("User Type", user.user_type, "role")
+
+ if role:
+ user.add_roles(role)
+
self.sync_roles(user, groups)
return user
From b20f77b9b965ba5fb03f3f7b88883413ef3e2251 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 8 Jul 2022 11:39:37 +0530
Subject: [PATCH 0081/2449] fix(ldap): Set default user type to System User
---
frappe/integrations/doctype/ldap_settings/ldap_settings.py | 2 ++
1 file changed, 2 insertions(+)
diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
index 249d4e31f3..23f4965438 100644
--- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
@@ -14,6 +14,8 @@ if TYPE_CHECKING:
class LDAPSettings(Document):
def validate(self):
+ self.default_user_type = self.default_user_type or "System User"
+
if not self.enabled:
return
From ee97038c71e5a72dde107105d6868062c57e02fb Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 7 Jul 2022 17:34:08 +0530
Subject: [PATCH 0082/2449] chore: Add typing + reduce import paths
---
.../doctype/ldap_settings/ldap_settings.py | 101 +++++++-----------
1 file changed, 37 insertions(+), 64 deletions(-)
diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
index 23f4965438..f9d083a12f 100644
--- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
@@ -1,8 +1,20 @@
-# Copyright (c) 2015, Frappe Technologies and contributors
+# Copyright (c) 2022, Frappe Technologies and contributors
# License: MIT. See LICENSE
+import ssl
from typing import TYPE_CHECKING
+import ldap3
+from ldap3 import AUTO_BIND_TLS_BEFORE_BIND, HASHED_SALTED_SHA, MODIFY_REPLACE
+from ldap3.abstract.entry import Entry
+from ldap3.core.exceptions import (
+ LDAPAttributeError,
+ LDAPInvalidCredentialsResult,
+ LDAPInvalidFilterError,
+ LDAPNoSuchObjectResult,
+)
+from ldap3.utils.hashed import hashed
+
import frappe
from frappe import _, safe_encode
from frappe.model.document import Document
@@ -20,7 +32,6 @@ class LDAPSettings(Document):
return
if not self.flags.ignore_mandatory:
-
if (
self.ldap_search_string.count("(") == self.ldap_search_string.count(")")
and self.ldap_search_string.startswith("(")
@@ -35,8 +46,6 @@ class LDAPSettings(Document):
try:
if conn.result["type"] == "bindResponse" and self.base_dn:
- import ldap3
-
conn.search(
search_base=self.ldap_search_path_user,
search_filter="(objectClass=*)",
@@ -47,13 +56,13 @@ class LDAPSettings(Document):
search_base=self.ldap_search_path_group, search_filter="(objectClass=*)", attributes=["cn"]
)
- except ldap3.core.exceptions.LDAPAttributeError as ex:
+ except LDAPAttributeError as ex:
frappe.throw(
_("LDAP settings incorrect. validation response was: {0}").format(ex),
title=_("Misconfigured"),
)
- except ldap3.core.exceptions.LDAPNoSuchObjectResult:
+ except LDAPNoSuchObjectResult:
frappe.throw(
_("Ensure the user and group search paths are correct."), title=_("Misconfigured")
)
@@ -82,12 +91,8 @@ class LDAPSettings(Document):
)
)
- def connect_to_ldap(self, base_dn, password, read_only=True):
+ def connect_to_ldap(self, base_dn, password, read_only=True) -> ldap3.Connection:
try:
- import ssl
-
- import ldap3
-
if self.require_trusted_certificate == "Yes":
tls_configuration = ldap3.Tls(validate=ssl.CERT_REQUIRED, version=ssl.PROTOCOL_TLS_CLIENT)
else:
@@ -101,9 +106,9 @@ class LDAPSettings(Document):
tls_configuration.ca_certs_file = self.local_ca_certs_file
server = ldap3.Server(host=self.ldap_server_url, tls=tls_configuration)
- bind_type = ldap3.AUTO_BIND_TLS_BEFORE_BIND if self.ssl_tls_mode == "StartTLS" else True
+ bind_type = AUTO_BIND_TLS_BEFORE_BIND if self.ssl_tls_mode == "StartTLS" else True
- conn = ldap3.Connection(
+ return ldap3.Connection(
server=server,
user=base_dn,
password=password,
@@ -112,18 +117,16 @@ class LDAPSettings(Document):
raise_exceptions=True,
)
- return conn
-
except ImportError:
msg = _("Please Install the ldap3 library via pip to use ldap functionality.")
frappe.throw(msg, title=_("LDAP Not Installed"))
- except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
+ except LDAPInvalidCredentialsResult:
frappe.throw(_("Invalid username or password"))
except Exception as ex:
frappe.throw(_(str(ex)))
@staticmethod
- def get_ldap_client_settings():
+ def get_ldap_client_settings() -> dict:
# return the settings to be used on the client side.
result = {"enabled": False}
ldap = frappe.get_cached_doc("LDAP Settings")
@@ -133,21 +136,16 @@ class LDAPSettings(Document):
return result
@classmethod
- def update_user_fields(cls, user, user_data):
-
+ def update_user_fields(cls, user: "User", user_data: dict):
updatable_data = {key: value for key, value in user_data.items() if key != "email"}
for key, value in updatable_data.items():
setattr(user, key, value)
user.save(ignore_permissions=True)
- def sync_roles(self, user: "User", additional_groups=None):
-
+ def sync_roles(self, user: "User", additional_groups: list = None):
current_roles = {d.role for d in user.get("roles")}
-
- needed_roles = set()
- needed_roles.add(self.default_role)
-
+ needed_roles = {self.default_role}
lower_groups = [g.lower() for g in additional_groups or []]
all_mapped_roles = {r.erpnext_role for r in self.ldap_groups}
@@ -164,7 +162,7 @@ class LDAPSettings(Document):
user.remove_roles(*roles_to_remove)
- def create_or_update_user(self, user_data, groups=None):
+ def create_or_update_user(self, user_data: dict, groups: list = None):
user: "User" = None
role: str = None
@@ -216,38 +214,28 @@ class LDAPSettings(Document):
return ldap_attributes
- def fetch_ldap_groups(self, user, conn):
- import ldap3
+ def fetch_ldap_groups(self, user: Entry, conn: ldap3.Connection) -> list:
+ if not isinstance(user, Entry):
+ raise TypeError("Invalid type, attribute 'user' must be of type 'ldap3.abstract.entry.Entry'")
- if type(user) is not ldap3.abstract.entry.Entry:
- raise TypeError(
- "Invalid type, attribute {} must be of type '{}'".format("user", "ldap3.abstract.entry.Entry")
- )
-
- if type(conn) is not ldap3.core.connection.Connection:
- raise TypeError(
- "Invalid type, attribute {} must be of type '{}'".format("conn", "ldap3.Connection")
- )
+ if not isinstance(conn, ldap3.Connection):
+ raise TypeError("Invalid type, attribute 'conn' must be of type 'ldap3.Connection'")
fetch_ldap_groups = None
-
ldap_object_class = None
ldap_group_members_attribute = None
if self.ldap_directory_server.lower() == "active directory":
-
ldap_object_class = "Group"
ldap_group_members_attribute = "member"
user_search_str = user.entry_dn
elif self.ldap_directory_server.lower() == "openldap":
-
ldap_object_class = "posixgroup"
ldap_group_members_attribute = "memberuid"
user_search_str = getattr(user, self.ldap_username_field).value
elif self.ldap_directory_server.lower() == "custom":
-
ldap_object_class = self.ldap_group_objectclass
ldap_group_members_attribute = self.ldap_group_member_attribute
ldap_custom_group_search = self.ldap_custom_group_search or "{0}"
@@ -258,39 +246,31 @@ class LDAPSettings(Document):
# this path will be hit for everyone with preconfigured ldap settings. this must be taken into account so as not to break ldap for those users.
if self.ldap_group_field:
-
fetch_ldap_groups = getattr(user, self.ldap_group_field).values
if ldap_object_class is not None:
conn.search(
search_base=self.ldap_search_path_group,
- search_filter="(&(objectClass={})({}={}))".format(
- ldap_object_class, ldap_group_members_attribute, user_search_str
- ),
+ search_filter=f"(&(objectClass={ldap_object_class})({ldap_group_members_attribute}={user_search_str}))",
attributes=["cn"],
) # Build search query
if len(conn.entries) >= 1:
-
fetch_ldap_groups = []
for group in conn.entries:
fetch_ldap_groups.append(group["cn"].value)
return fetch_ldap_groups
- def authenticate(self, username, password):
-
+ def authenticate(self, username: str, password: str):
if not self.enabled:
frappe.throw(_("LDAP is not enabled."))
user_filter = self.ldap_search_string.format(username)
ldap_attributes = self.get_ldap_attributes()
-
conn = self.connect_to_ldap(self.base_dn, self.get_password(raise_exception=False))
try:
- import ldap3
-
conn.search(
search_base=self.ldap_search_path_user,
search_filter=f"{user_filter}",
@@ -299,26 +279,21 @@ class LDAPSettings(Document):
if len(conn.entries) == 1 and conn.entries[0]:
user = conn.entries[0]
-
groups = self.fetch_ldap_groups(user, conn)
# only try and connect as the user, once we have their fqdn entry.
if user.entry_dn and password and conn.rebind(user=user.entry_dn, password=password):
-
return self.create_or_update_user(self.convert_ldap_entry_to_dict(user), groups=groups)
- raise ldap3.core.exceptions.LDAPInvalidCredentialsResult # even though nothing foundor failed authentication raise invalid credentials
+ raise LDAPInvalidCredentialsResult # even though nothing foundor failed authentication raise invalid credentials
- except ldap3.core.exceptions.LDAPInvalidFilterError:
+ except LDAPInvalidFilterError:
frappe.throw(_("Please use a valid LDAP search filter"), title=_("Misconfigured"))
- except ldap3.core.exceptions.LDAPInvalidCredentialsResult:
+ except LDAPInvalidCredentialsResult:
frappe.throw(_("Invalid username or password"))
def reset_password(self, user, password, logout_sessions=False):
- from ldap3 import HASHED_SALTED_SHA, MODIFY_REPLACE
- from ldap3.utils.hashed import hashed
-
search_filter = f"({self.ldap_email_field}={user})"
conn = self.connect_to_ldap(
@@ -347,8 +322,7 @@ class LDAPSettings(Document):
else:
frappe.throw(_("No LDAP User found for email: {0}").format(user))
- def convert_ldap_entry_to_dict(self, user_entry):
-
+ def convert_ldap_entry_to_dict(self, user_entry: Entry):
# support multiple email values
email = user_entry[self.ldap_email_field]
@@ -359,7 +333,6 @@ class LDAPSettings(Document):
}
# optional fields
-
if self.ldap_middle_name_field:
data["middle_name"] = user_entry[self.ldap_middle_name_field].value
@@ -379,7 +352,7 @@ class LDAPSettings(Document):
def login():
# LDAP LOGIN LOGIC
args = frappe.form_dict
- ldap = frappe.get_doc("LDAP Settings")
+ ldap: LDAPSettings = frappe.get_doc("LDAP Settings")
user = ldap.authenticate(frappe.as_unicode(args.usr), frappe.as_unicode(args.pwd))
@@ -396,7 +369,7 @@ def login():
@frappe.whitelist()
def reset_password(user, password, logout):
- ldap = frappe.get_doc("LDAP Settings")
+ ldap: LDAPSettings = frappe.get_doc("LDAP Settings")
if not ldap.enabled:
frappe.throw(_("LDAP is not enabled."))
ldap.reset_password(user, password, logout_sessions=int(logout))
From 17b3f171ff5ce465c34f7481cc17ba371e777dc7 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 11 Jul 2022 13:18:19 +0530
Subject: [PATCH 0083/2449] fix: update patch to use case when...then
---
frappe/patches.txt | 2 +-
frappe/patches/v12_0/encrypt_2fa_secrets.py | 73 ---------------------
frappe/patches/v13_0/encrypt_2fa_secrets.py | 45 +++++++++++++
3 files changed, 46 insertions(+), 74 deletions(-)
delete mode 100644 frappe/patches/v12_0/encrypt_2fa_secrets.py
create mode 100644 frappe/patches/v13_0/encrypt_2fa_secrets.py
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 98a2da6970..b432ec7786 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -121,7 +121,6 @@ execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings')
frappe.patches.v12_0.remove_example_email_thread_notify
execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders()
frappe.patches.v12_0.set_correct_url_in_files
-frappe.patches.v12_0.encrypt_2fa_secrets
execute:frappe.reload_doc('core', 'doctype', 'doctype') #2022-06-21
execute:frappe.reload_doc('custom', 'doctype', 'property_setter')
frappe.patches.v13_0.remove_invalid_options_for_data_fields
@@ -184,6 +183,7 @@ frappe.patches.v13_0.queryreport_columns
frappe.patches.v13_0.jinja_hook
frappe.patches.v13_0.update_notification_channel_if_empty
frappe.patches.v13_0.set_first_day_of_the_week
+frappe.patches.v13_0.encrypt_2fa_secrets
execute:frappe.reload_doc('custom', 'doctype', 'custom_field')
frappe.patches.v14_0.update_workspace2 # 20.09.2021
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021
diff --git a/frappe/patches/v12_0/encrypt_2fa_secrets.py b/frappe/patches/v12_0/encrypt_2fa_secrets.py
deleted file mode 100644
index 002dbe0f03..0000000000
--- a/frappe/patches/v12_0/encrypt_2fa_secrets.py
+++ /dev/null
@@ -1,73 +0,0 @@
-import frappe
-import frappe.defaults
-from frappe.model.naming import make_autoname
-from frappe.twofactor import PARENT_FOR_DEFAULTS
-from frappe.utils import now_datetime
-from frappe.utils.password import encrypt
-
-DOCTYPE = "DefaultValue"
-OLD_PARENT = "__default"
-
-
-def execute():
- table = frappe.qb.DocType(DOCTYPE)
-
- # set parent for `*_otplogin`
- (
- frappe.qb.update(table)
- .set(table.parent, PARENT_FOR_DEFAULTS)
- .where(table.parent == OLD_PARENT)
- .where(table.defkey.like("%_otplogin"))
- ).run()
-
- # create new encrypted records for `*_otpsecret`
- secrets = {
- key: value
- for key, value in frappe.defaults.get_defaults_for(parent=OLD_PARENT).items()
- if key.endswith("_otpsecret")
- }
-
- if not secrets:
- return
-
- fields = (
- "name",
- "creation",
- "modified",
- "modified_by",
- "owner",
- "parent",
- "parenttype",
- "parentfield",
- "defkey",
- "defvalue",
- )
-
- user = frappe.session.user
- now = str(now_datetime())
-
- values = [
- (
- make_autoname("hash", DOCTYPE),
- now,
- now,
- user,
- user,
- PARENT_FOR_DEFAULTS,
- "__default",
- "system_defaults",
- key,
- encrypt(value),
- )
- for key, value in secrets.items()
- ]
-
- frappe.db.bulk_insert(DOCTYPE, fields, values)
-
- frappe.db.delete(
- DOCTYPE,
- {
- "defkey": ("in", list(secrets)),
- "parent": OLD_PARENT,
- },
- )
diff --git a/frappe/patches/v13_0/encrypt_2fa_secrets.py b/frappe/patches/v13_0/encrypt_2fa_secrets.py
new file mode 100644
index 0000000000..1814ff50c5
--- /dev/null
+++ b/frappe/patches/v13_0/encrypt_2fa_secrets.py
@@ -0,0 +1,45 @@
+import frappe
+import frappe.defaults
+from frappe.cache_manager import clear_defaults_cache
+from frappe.twofactor import PARENT_FOR_DEFAULTS
+from frappe.utils.password import encrypt
+
+DOCTYPE = "DefaultValue"
+OLD_PARENT = "__default"
+
+
+def execute():
+ table = frappe.qb.DocType(DOCTYPE)
+
+ # set parent for `*_otplogin`
+ (
+ frappe.qb.update(table)
+ .set(table.parent, PARENT_FOR_DEFAULTS)
+ .where(table.parent == OLD_PARENT)
+ .where(table.defkey.like("%_otplogin"))
+ ).run()
+
+ # update records for `*_otpsecret`
+ secrets = {
+ key: value
+ for key, value in frappe.defaults.get_defaults_for(parent=OLD_PARENT).items()
+ if key.endswith("_otpsecret")
+ }
+
+ if not secrets:
+ return
+
+ defvalue_cases = frappe.qb.terms.Case()
+
+ for key, value in secrets.items():
+ defvalue_cases.when(table.defkey == key, encrypt(value))
+
+ (
+ frappe.qb.update(table)
+ .set(table.parent, PARENT_FOR_DEFAULTS)
+ .set(table.defvalue, defvalue_cases)
+ .where(table.parent == OLD_PARENT)
+ .where(table.defkey.like("%_otpsecret"))
+ ).run()
+
+ clear_defaults_cache()
From ea3bfc914c1e3e448ca976bbbb96c01ebbaedf99 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 11 Jul 2022 13:43:38 +0530
Subject: [PATCH 0084/2449] test: use newly created `get_default` helper
---
frappe/tests/test_twofactor.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/tests/test_twofactor.py b/frappe/tests/test_twofactor.py
index d9bf982cd2..b04cb6a91d 100644
--- a/frappe/tests/test_twofactor.py
+++ b/frappe/tests/test_twofactor.py
@@ -12,6 +12,7 @@ from frappe.twofactor import (
authenticate_for_2factor,
confirm_otp_token,
get_cached_user_pass,
+ get_default,
get_otpsecret_for_,
get_verification_obj,
should_run_2fa,
@@ -111,7 +112,7 @@ class TestTwoFactor(unittest.TestCase):
def test_get_otpsecret_for_user(self):
"""OTP secret should be set for user."""
self.assertTrue(get_otpsecret_for_(self.user))
- self.assertTrue(frappe.db.get_default(self.user + "_otpsecret"))
+ self.assertTrue(get_default(self.user + "_otpsecret"))
def test_confirm_otp_token(self):
"""Ensure otp is confirmed"""
From 95f67b8de85267d6620f9ea311bf07bd45a1fc91 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 14 Jul 2022 13:14:58 +0530
Subject: [PATCH 0085/2449] fix: ignore empty part in naming series (#17508)
on v13 doc.get("") returns entire doc dictionary, this gets strigified
and becomes a problem for naming.
---
frappe/model/naming.py | 21 +++++++++++++++++++--
frappe/tests/test_naming.py | 27 +++++++++++++++++++++++++++
2 files changed, 46 insertions(+), 2 deletions(-)
diff --git a/frappe/model/naming.py b/frappe/model/naming.py
index 0ce6704c39..49a58da314 100644
--- a/frappe/model/naming.py
+++ b/frappe/model/naming.py
@@ -1,6 +1,7 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
+import datetime
import re
from typing import TYPE_CHECKING, Callable, Optional
@@ -23,6 +24,17 @@ NAMING_SERIES_PATTERN = re.compile(r"^[\w\- \/.#{}]+$", re.UNICODE)
BRACED_PARAMS_PATTERN = re.compile(r"(\{[\w | #]+\})")
+# Types that can be using in naming series fields
+NAMING_SERIES_PART_TYPES = (
+ int,
+ str,
+ datetime.datetime,
+ datetime.date,
+ datetime.time,
+ datetime.timedelta,
+)
+
+
class InvalidNamingSeriesError(frappe.ValidationError):
pass
@@ -298,6 +310,9 @@ def parse_naming_series(
series_set = False
today = now_datetime()
for e in parts:
+ if not e:
+ continue
+
part = ""
if e.startswith("#"):
if not series_set:
@@ -320,14 +335,16 @@ def parse_naming_series(
part = frappe.defaults.get_user_default("fiscal_year")
elif e.startswith("{") and doc:
e = e.replace("{", "").replace("}", "")
- part = (cstr(doc.get(e)) or "").strip()
+ part = doc.get(e)
elif doc and doc.get(e):
- part = (cstr(doc.get(e)) or "").strip()
+ part = doc.get(e)
else:
part = e
if isinstance(part, str):
name += part
+ elif isinstance(part, NAMING_SERIES_PART_TYPES):
+ name += cstr(part).strip()
return name
diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py
index 5feaad6f9b..9f5b46a4d1 100644
--- a/frappe/tests/test_naming.py
+++ b/frappe/tests/test_naming.py
@@ -9,6 +9,7 @@ from frappe.model.naming import (
append_number_if_name_exists,
determine_consecutive_week_number,
getseries,
+ parse_naming_series,
revert_series_if_last,
)
from frappe.tests.utils import FrappeTestCase
@@ -342,6 +343,32 @@ class TestNaming(FrappeTestCase):
name.startswith("KOOH-on_update"), f"incorrect name generated {name}, missing field value"
)
+ def test_naming_with_empty_part(self):
+ # check naming with empty part (duplicate dots)
+
+ webhook = frappe.new_doc("Webhook")
+ webhook.webhook_docevent = "on_update"
+
+ series = "KOOH-..{webhook_docevent}.-.####"
+
+ name = parse_naming_series(series, doc=webhook)
+ self.assertTrue(
+ name.startswith("KOOH-on_update"), f"incorrect name generated {name}, missing field value"
+ )
+
+ def test_naming_with_unsupported_part(self):
+ # check naming with empty part (duplicate dots)
+
+ webhook = frappe.new_doc("Webhook")
+ webhook.webhook_docevent = {"dict": "not supported"}
+
+ series = "KOOH-..{webhook_docevent}.-.####"
+
+ name = parse_naming_series(series, doc=webhook)
+ self.assertTrue(
+ name.startswith("KOOH-"), f"incorrect name generated {name}, missing field value"
+ )
+
def make_invalid_todo():
frappe.get_doc({"doctype": "ToDo", "description": "Test"}).insert(set_name="ToDo")
From a8f86abbd8fa16c18eb3b621e90c5f77eb69d1d0 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Thu, 14 Jul 2022 14:09:00 +0530
Subject: [PATCH 0086/2449] refactor: Replaced blog's feedback with comment's
comment_type='Like' (#17479)
---
frappe/core/doctype/comment/comment.json | 14 +++-
frappe/core/doctype/feedback/feedback.js | 8 --
frappe/core/doctype/feedback/feedback.json | 73 ------------------
frappe/core/doctype/feedback/feedback.py | 9 ---
frappe/core/doctype/feedback/test_feedback.py | 42 ----------
frappe/patches.txt | 1 +
.../v14_0/setup_likes_from_feedback.py | 30 ++++++++
.../templates/includes/comments/comment.html | 2 +-
.../templates/includes/comments/comments.html | 7 +-
.../templates/includes/comments/comments.py | 8 +-
.../templates/includes/feedback/__init__.py | 0
.../templates/includes/feedback/feedback.py | 55 -------------
.../includes/likes}/__init__.py | 0
.../feedback.html => likes/likes.html} | 24 +++---
frappe/templates/includes/likes/likes.py | 77 +++++++++++++++++++
.../website/doctype/blog_post/blog_post.json | 18 ++---
frappe/website/doctype/blog_post/blog_post.py | 52 ++++++-------
.../blog_post/templates/blog_post.html | 19 ++++-
.../doctype/blog_post/test_blog_post.py | 23 ++++++
.../doctype/blog_settings/blog_settings.json | 18 ++---
.../doctype/blog_settings/blog_settings.py | 4 +-
21 files changed, 221 insertions(+), 263 deletions(-)
delete mode 100644 frappe/core/doctype/feedback/feedback.js
delete mode 100644 frappe/core/doctype/feedback/feedback.json
delete mode 100644 frappe/core/doctype/feedback/feedback.py
delete mode 100644 frappe/core/doctype/feedback/test_feedback.py
create mode 100644 frappe/patches/v14_0/setup_likes_from_feedback.py
delete mode 100644 frappe/templates/includes/feedback/__init__.py
delete mode 100644 frappe/templates/includes/feedback/feedback.py
rename frappe/{core/doctype/feedback => templates/includes/likes}/__init__.py (100%)
rename frappe/templates/includes/{feedback/feedback.html => likes/likes.html} (78%)
create mode 100644 frappe/templates/includes/likes/likes.py
diff --git a/frappe/core/doctype/comment/comment.json b/frappe/core/doctype/comment/comment.json
index fe465f46bd..9f27e7e7be 100644
--- a/frappe/core/doctype/comment/comment.json
+++ b/frappe/core/doctype/comment/comment.json
@@ -1,4 +1,5 @@
{
+ "actions": [],
"creation": "2019-02-07 10:10:46.845678",
"doctype": "DocType",
"editable_grid": 1,
@@ -17,7 +18,8 @@
"link_name",
"reference_owner",
"section_break_10",
- "content"
+ "content",
+ "ip_address"
],
"fields": [
{
@@ -102,9 +104,16 @@
"ignore_xss_filter": 1,
"in_list_view": 1,
"label": "Content"
+ },
+ {
+ "fieldname": "ip_address",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "IP Address"
}
],
- "modified": "2019-09-02 21:00:10.784787",
+ "links": [],
+ "modified": "2022-07-12 17:35:31.774137",
"modified_by": "Administrator",
"module": "Core",
"name": "Comment",
@@ -138,6 +147,7 @@
"quick_entry": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "comment_type",
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/feedback/feedback.js b/frappe/core/doctype/feedback/feedback.js
deleted file mode 100644
index 131f0e19d8..0000000000
--- a/frappe/core/doctype/feedback/feedback.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2021, Frappe Technologies and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Feedback', {
- // refresh: function(frm) {
-
- // }
-});
diff --git a/frappe/core/doctype/feedback/feedback.json b/frappe/core/doctype/feedback/feedback.json
deleted file mode 100644
index f8380cfda6..0000000000
--- a/frappe/core/doctype/feedback/feedback.json
+++ /dev/null
@@ -1,73 +0,0 @@
-{
- "actions": [],
- "creation": "2021-06-03 19:02:55.328423",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "reference_doctype",
- "reference_name",
- "column_break_3",
- "like",
- "ip_address"
- ],
- "fields": [
- {
- "fieldname": "column_break_3",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "reference_doctype",
- "fieldtype": "Select",
- "in_list_view": 1,
- "label": "Reference Document Type",
- "options": "\nBlog Post"
- },
- {
- "fieldname": "reference_name",
- "fieldtype": "Dynamic Link",
- "in_list_view": 1,
- "label": "Reference Name",
- "options": "reference_doctype",
- "reqd": 1
- },
- {
- "fieldname": "ip_address",
- "fieldtype": "Data",
- "hidden": 1,
- "label": "IP Address",
- "read_only": 1
- },
- {
- "default": "0",
- "fieldname": "like",
- "fieldtype": "Check",
- "label": "Like"
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-11-10 20:53:21.255593",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Feedback",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "reference_name",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/frappe/core/doctype/feedback/feedback.py b/frappe/core/doctype/feedback/feedback.py
deleted file mode 100644
index c616787e4b..0000000000
--- a/frappe/core/doctype/feedback/feedback.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies and contributors
-# License: MIT. See LICENSE
-
-# import frappe
-from frappe.model.document import Document
-
-
-class Feedback(Document):
- pass
diff --git a/frappe/core/doctype/feedback/test_feedback.py b/frappe/core/doctype/feedback/test_feedback.py
deleted file mode 100644
index e8e29e75ae..0000000000
--- a/frappe/core/doctype/feedback/test_feedback.py
+++ /dev/null
@@ -1,42 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies and Contributors
-# License: MIT. See LICENSE
-
-import unittest
-
-import frappe
-
-
-class TestFeedback(unittest.TestCase):
- def tearDown(self):
- frappe.form_dict.reference_doctype = None
- frappe.form_dict.reference_name = None
- frappe.form_dict.like = None
- frappe.local.request_ip = None
-
- def test_feedback_creation_updation(self):
- from frappe.website.doctype.blog_post.test_blog_post import make_test_blog
-
- test_blog = make_test_blog()
-
- frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"})
-
- from frappe.templates.includes.feedback.feedback import give_feedback
-
- frappe.form_dict.reference_doctype = "Blog Post"
- frappe.form_dict.reference_name = test_blog.name
- frappe.form_dict.like = True
- frappe.local.request_ip = "127.0.0.1"
-
- feedback = give_feedback()
-
- self.assertEqual(feedback.like, True)
-
- frappe.form_dict.like = False
-
- updated_feedback = give_feedback()
-
- self.assertEqual(updated_feedback.like, False)
-
- frappe.db.delete("Feedback", {"reference_doctype": "Blog Post"})
-
- test_blog.delete()
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 425468f06c..d9b827931c 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -192,6 +192,7 @@ frappe.patches.v14_0.reset_creation_datetime
frappe.patches.v14_0.remove_is_first_startup
frappe.patches.v14_0.clear_long_pending_stale_logs
frappe.patches.v14_0.log_settings_migration
+frappe.patches.v14_0.setup_likes_from_feedback
[post_model_sync]
frappe.patches.v14_0.drop_data_import_legacy
diff --git a/frappe/patches/v14_0/setup_likes_from_feedback.py b/frappe/patches/v14_0/setup_likes_from_feedback.py
new file mode 100644
index 0000000000..d88f69ce4b
--- /dev/null
+++ b/frappe/patches/v14_0/setup_likes_from_feedback.py
@@ -0,0 +1,30 @@
+import frappe
+
+
+def execute():
+ frappe.reload_doctype("Comment")
+
+ if frappe.db.count("Feedback") > 20000:
+ frappe.db.auto_commit_on_many_writes = True
+
+ for feedback in frappe.get_all("Feedback", fields=["*"]):
+ if feedback.like:
+ new_comment = frappe.new_doc("Comment")
+ new_comment.comment_type = "Like"
+ new_comment.comment_email = feedback.owner
+ new_comment.content = "Liked by: " + feedback.owner
+ new_comment.reference_doctype = feedback.reference_doctype
+ new_comment.reference_name = feedback.reference_name
+ new_comment.creation = feedback.creation
+ new_comment.modified = feedback.modified
+ new_comment.owner = feedback.owner
+ new_comment.modified_by = feedback.modified_by
+ new_comment.ip_address = feedback.ip_address
+ new_comment.db_insert()
+
+ if frappe.db.auto_commit_on_many_writes:
+ frappe.db.auto_commit_on_many_writes = False
+
+ # clean up
+ frappe.db.delete("Feedback")
+ frappe.db.commit()
diff --git a/frappe/templates/includes/comments/comment.html b/frappe/templates/includes/comments/comment.html
index 4713ee498d..64de9e5943 100644
--- a/frappe/templates/includes/comments/comment.html
+++ b/frappe/templates/includes/comments/comment.html
@@ -13,6 +13,6 @@
{{ frappe.utils.pretty_date(comment.creation) }}
- {{ frappe.utils.strip_html(comment.content) | markdown }}
+
\ No newline at end of file
diff --git a/frappe/templates/includes/comments/comments.html b/frappe/templates/includes/comments/comments.html
index 0007f56934..63ec6a21bd 100644
--- a/frappe/templates/includes/comments/comments.html
+++ b/frappe/templates/includes/comments/comments.html
@@ -57,7 +57,7 @@
{% endblock %}
diff --git a/frappe/website/doctype/blog_post/test_blog_post.py b/frappe/website/doctype/blog_post/test_blog_post.py
index d202d15642..3ea447d90c 100644
--- a/frappe/website/doctype/blog_post/test_blog_post.py
+++ b/frappe/website/doctype/blog_post/test_blog_post.py
@@ -152,6 +152,29 @@ class TestBlogPost(FrappeTestCase):
frappe.delete_doc("Blog Post", blog.name)
frappe.delete_doc("Blog Category", blog.blog_category)
+ def test_like_dislike(self):
+ test_blog = make_test_blog()
+
+ frappe.db.delete("Comment", {"comment_type": "Like", "reference_doctype": "Blog Post"})
+
+ from frappe.templates.includes.likes.likes import like
+
+ frappe.form_dict.reference_doctype = "Blog Post"
+ frappe.form_dict.reference_name = test_blog.name
+ frappe.form_dict.like = True
+ frappe.local.request_ip = "127.0.0.1"
+
+ liked = like()
+ self.assertEqual(liked, True)
+
+ frappe.form_dict.like = False
+
+ disliked = like()
+ self.assertEqual(disliked, False)
+
+ frappe.db.delete("Comment", {"comment_type": "Like", "reference_doctype": "Blog Post"})
+ test_blog.delete()
+
def scrub(text):
return WebsiteGenerator.scrub(None, text)
diff --git a/frappe/website/doctype/blog_settings/blog_settings.json b/frappe/website/doctype/blog_settings/blog_settings.json
index aed1e77969..4e89af5c8e 100644
--- a/frappe/website/doctype/blog_settings/blog_settings.json
+++ b/frappe/website/doctype/blog_settings/blog_settings.json
@@ -19,7 +19,7 @@
"cta_label",
"cta_url",
"section_break_12",
- "feedback_limit",
+ "like_limit",
"column_break_14",
"comment_limit"
],
@@ -89,13 +89,6 @@
"fieldname": "section_break_12",
"fieldtype": "Section Break"
},
- {
- "default": "5",
- "description": "Feedback limit per hour",
- "fieldname": "feedback_limit",
- "fieldtype": "Int",
- "label": "Feedback limit"
- },
{
"default": "5",
"description": "Comment limit per hour",
@@ -118,13 +111,20 @@
"fieldname": "browse_by_category",
"fieldtype": "Check",
"label": "Browse by category"
+ },
+ {
+ "default": "5",
+ "description": "Like limit per hour",
+ "fieldname": "like_limit",
+ "fieldtype": "Int",
+ "label": "Like limit"
}
],
"icon": "fa fa-cog",
"idx": 1,
"issingle": 1,
"links": [],
- "modified": "2021-12-20 13:40:32.312459",
+ "modified": "2022-07-12 17:45:49.108398",
"modified_by": "Administrator",
"module": "Website",
"name": "Blog Settings",
diff --git a/frappe/website/doctype/blog_settings/blog_settings.py b/frappe/website/doctype/blog_settings/blog_settings.py
index ed22f64fd7..6b1d7b6323 100644
--- a/frappe/website/doctype/blog_settings/blog_settings.py
+++ b/frappe/website/doctype/blog_settings/blog_settings.py
@@ -15,8 +15,8 @@ class BlogSettings(Document):
clear_cache("writers")
-def get_feedback_limit():
- return frappe.db.get_single_value("Blog Settings", "feedback_limit") or 5
+def get_like_limit():
+ return frappe.db.get_single_value("Blog Settings", "like_limit") or 5
def get_comment_limit():
From 0a41c4051c2f8bb63bce2b11f45cf105200f0daf Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 14 Jul 2022 18:01:47 +0530
Subject: [PATCH 0087/2449] fix: Do not relay email to standard users
Co-authored-by: Ritwik Puri
---
frappe/core/doctype/communication/mixins.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py
index 695b8bebae..af3b0a4661 100644
--- a/frappe/core/doctype/communication/mixins.py
+++ b/frappe/core/doctype/communication/mixins.py
@@ -73,7 +73,8 @@ class CommunicationEmailMixin:
if include_sender:
cc.append(self.sender_mailid)
if is_inbound_mail_communcation:
- cc.append(self.get_owner())
+ if (doc_owner := self.get_owner()) not in frappe.STANDARD_USERS:
+ cc.append(doc_owner)
cc = set(cc) - {self.sender_mailid}
cc.update(self.get_assignees())
From 1f0e019e89a887bb14c59d3566b9fdd9d4659b2b Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 14 Jul 2022 19:02:19 +0530
Subject: [PATCH 0088/2449] fix(UX): correct message for empty prepared report
(#17517)
---
frappe/public/js/frappe/views/reports/query_report.js | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index 80b251e5ec..e15cc339ae 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -623,6 +623,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
if (data.prepared_report) {
this.prepared_report = true;
+ this.prepared_report_document = data.doc
// If query_string contains prepared_report_name then set filters
// to match the mentioned prepared report doc and disable editing
if (query_params.prepared_report_name) {
@@ -1800,7 +1801,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
}
toggle_nothing_to_show(flag) {
- let message = this.prepared_report
+ let message = (this.prepared_report && !this.prepared_report_document)
? __('This is a background report. Please set the appropriate filters and then generate a new one.')
: this.get_no_result_message();
From 2e577aa64e22d36bd2c643461eab7a76f77385b2 Mon Sep 17 00:00:00 2001
From: chillaranand
Date: Thu, 14 Jul 2022 16:47:59 +0530
Subject: [PATCH 0089/2449] fix: Check for file url before validating remote
file
---
frappe/core/doctype/file/file.py | 2 +-
frappe/core/doctype/file/test_file.py | 10 ++++++++++
2 files changed, 11 insertions(+), 1 deletion(-)
diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py
index 7a66d45c73..e1faf331d6 100755
--- a/frappe/core/doctype/file/file.py
+++ b/frappe/core/doctype/file/file.py
@@ -262,7 +262,7 @@ class File(Document):
def validate_remote_file(self):
"""Validates if file uploaded using URL already exist"""
site_url = get_url()
- if "/files/" in self.file_url and self.file_url.startswith(site_url):
+ if self.file_url and "/files/" in self.file_url and self.file_url.startswith(site_url):
self.file_url = self.file_url.split(site_url, 1)[1]
def set_folder_name(self):
diff --git a/frappe/core/doctype/file/test_file.py b/frappe/core/doctype/file/test_file.py
index a9d40b35b1..d849a2de4d 100644
--- a/frappe/core/doctype/file/test_file.py
+++ b/frappe/core/doctype/file/test_file.py
@@ -510,6 +510,16 @@ class TestFile(FrappeTestCase):
).insert(ignore_permissions=True)
self.assertRaisesRegex(ValidationError, "not a zip file", test_file.unzip)
+ def test_create_file_without_file_url(self):
+ test_file = frappe.get_doc(
+ {
+ "doctype": "File",
+ "file_name": "logo",
+ "content": "frappe",
+ }
+ ).insert()
+ assert test_file is not None
+
class TestAttachment(unittest.TestCase):
test_doctype = "Test For Attachment"
From c200f5b3ae8431d03ccdf18c0c3f53dd8782f2dd Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 15 Jul 2022 11:59:04 +0530
Subject: [PATCH 0090/2449] ci: check build requirement before setting up
python
[skip ci]
---
.github/workflows/patch-mariadb-tests.yml | 24 ++++++++++++-----------
1 file changed, 13 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml
index 73e0dda5de..1e21ae8549 100644
--- a/.github/workflows/patch-mariadb-tests.yml
+++ b/.github/workflows/patch-mariadb-tests.yml
@@ -30,17 +30,6 @@ jobs:
- name: Clone
uses: actions/checkout@v3
- - name: Setup Python
- uses: "gabrielfalcao/pyenv-action@v10"
- with:
- versions: 3.10:latest, 3.7:latest
-
- - name: Setup Node
- uses: actions/setup-node@v3
- with:
- node-version: 14
- check-latest: true
-
- name: Check if build should be run
id: check-build
run: |
@@ -50,6 +39,19 @@ jobs:
PR_NUMBER: ${{ github.event.number }}
REPO_NAME: ${{ github.repository }}
+ - name: Setup Python
+ if: ${{ steps.check-build.outputs.build == 'strawberry' }}
+ uses: "gabrielfalcao/pyenv-action@v10"
+ with:
+ versions: 3.10:latest, 3.7:latest
+
+ - name: Setup Node
+ if: ${{ steps.check-build.outputs.build == 'strawberry' }}
+ uses: actions/setup-node@v3
+ with:
+ node-version: 14
+ check-latest: true
+
- name: Add to Hosts
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
run: echo "127.0.0.1 test_site" | sudo tee -a /etc/hosts
From ddbbc0ef8df16f57a921f2e0f2c634535448bd80 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Fri, 15 Jul 2022 14:23:05 +0530
Subject: [PATCH 0091/2449] fix: add default system manager role when changing
from child to non-child table doctype
---
frappe/core/doctype/doctype/doctype.js | 12 +++++++++---
frappe/core/doctype/doctype/doctype.py | 7 +++----
2 files changed, 12 insertions(+), 7 deletions(-)
diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js
index 514e3a9455..3a9b1f63dc 100644
--- a/frappe/core/doctype/doctype/doctype.js
+++ b/frappe/core/doctype/doctype/doctype.js
@@ -46,9 +46,7 @@ frappe.ui.form.on('DocType', {
}
if(frm.is_new()) {
- if (!(frm.doc.permissions && frm.doc.permissions.length)) {
- frm.add_child('permissions', {role: 'System Manager'});
- }
+ frm.events.set_default_permission(frm);
} else {
frm.toggle_enable("engine", 0);
}
@@ -65,6 +63,14 @@ frappe.ui.form.on('DocType', {
if (frm.doc.istable && frm.is_new()) {
frm.set_value('autoname', 'autoincrement');
frm.set_value('allow_rename', 0);
+ } else if (!frm.doc.istable && !frm.is_new()) {
+ frm.events.set_default_permission(frm);
+ }
+ },
+
+ set_default_permission: (frm) => {
+ if (!(frm.doc.permissions && frm.doc.permissions.length)) {
+ frm.add_child('permissions', {role: 'System Manager'});
}
},
});
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index dbbbbc521a..2b28373384 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -181,10 +181,6 @@ class DocType(Document):
)
)
- def after_insert(self):
- # clear user cache so that on the next reload this doctype is included in boot
- clear_user_cache(frappe.session.user)
-
def set_defaults_for_single_and_table(self):
if self.issingle:
self.allow_import = 0
@@ -412,6 +408,9 @@ class DocType(Document):
delete_notification_count_for(doctype=self.name)
frappe.clear_cache(doctype=self.name)
+ # clear user cache so that on the next reload this doctype is included in boot
+ clear_user_cache(frappe.session.user)
+
if not frappe.flags.in_install and hasattr(self, "before_update"):
self.sync_global_search()
From 5738676b461128d77c016c7125ff0bceb2d007ca Mon Sep 17 00:00:00 2001
From: phot0n
Date: Fri, 15 Jul 2022 19:23:59 +0530
Subject: [PATCH 0092/2449] fix: use on_update hook instead of after_save and
after_insert
after_save hook doesn;t exist so using that triggers nothing
---
frappe/hooks.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/frappe/hooks.py b/frappe/hooks.py
index a337d8e0d3..66820ecd0f 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -180,12 +180,10 @@ doc_events = {
"on_update": "frappe.integrations.doctype.google_contacts.google_contacts.update_contacts_to_google_contacts",
},
"DocType": {
- "after_insert": "frappe.cache_manager.build_domain_restriced_doctype_cache",
- "after_save": "frappe.cache_manager.build_domain_restriced_doctype_cache",
+ "on_update": "frappe.cache_manager.build_domain_restriced_doctype_cache",
},
"Page": {
- "after_insert": "frappe.cache_manager.build_domain_restriced_page_cache",
- "after_save": "frappe.cache_manager.build_domain_restriced_page_cache",
+ "on_update": "frappe.cache_manager.build_domain_restriced_page_cache",
},
}
From 454766c26379d36ecf3c7cfe7b30fcdc9b6e8a34 Mon Sep 17 00:00:00 2001
From: Amin Ahmed
Date: Fri, 15 Jul 2022 21:17:34 +0300
Subject: [PATCH 0093/2449] Update datepicker_i18n.js
---
frappe/public/js/frappe/form/controls/datepicker_i18n.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/form/controls/datepicker_i18n.js b/frappe/public/js/frappe/form/controls/datepicker_i18n.js
index f010325c2e..a5b825072d 100644
--- a/frappe/public/js/frappe/form/controls/datepicker_i18n.js
+++ b/frappe/public/js/frappe/form/controls/datepicker_i18n.js
@@ -22,10 +22,10 @@ import "air-datepicker/dist/js/i18n/datepicker.zh.js";
months: ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'اكتوبر', 'نوفمبر', 'ديسمبر'],
monthsShort: ['يناير', 'فبراير', 'مارس', 'أبريل', 'مايو', 'يونيو', 'يوليو', 'أغسطس', 'سبتمبر', 'اكتوبر', 'نوفمبر', 'ديسمبر'],
today: 'اليوم',
- clear: 'Clear',
+ clear: 'حذف',
dateFormat: 'dd/mm/yyyy',
timeFormat: 'hh:ii aa',
- firstDay: 0
+ firstDay: 6
};
})(jQuery);
From 55fb8acafa3e63d2982850331f5cd8c6976d937d Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sun, 17 Jul 2022 20:07:05 +0530
Subject: [PATCH 0094/2449] perf(DX): add watchdog as developer dependency
Werkzeug reloader is right now using an inefficient `stat` based reloader which is horrible on large codebases with low-powered devices.
Difference:
- `stat` based reloader basically checks each and every file if they have changed or not.
- watchdog subscribes to platform specific change events on kernel (like kqueue, fsevents or inotify )
---
pyproject.toml | 1 +
1 file changed, 1 insertion(+)
diff --git a/pyproject.toml b/pyproject.toml
index a1706ac33e..5eeb6f46dd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -109,3 +109,4 @@ coverage = "~=6.4.1"
Faker = "~=13.12.1"
pyngrok = "~=5.0.5"
unittest-xml-reporting = "~=3.0.4"
+watchdog = "~=2.1.9"
From 8edae2ce098f6a7fc08034726570aba9d221aea7 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sun, 17 Jul 2022 10:29:57 +0530
Subject: [PATCH 0095/2449] fix: encrypt access_token when setting in db after
refreshing access_token
---
frappe/email/oauth.py | 16 +++++++++++-----
1 file changed, 11 insertions(+), 5 deletions(-)
diff --git a/frappe/email/oauth.py b/frappe/email/oauth.py
index 46d1565275..9adeb2f9de 100644
--- a/frappe/email/oauth.py
+++ b/frappe/email/oauth.py
@@ -102,12 +102,18 @@ class Oauth:
def _refresh_access_token(self) -> str:
"""Refreshes access token via calling `refresh_access_token` method of oauth service object"""
service_obj = self._get_service_object()
- access_token = service_obj.refresh_access_token(self._refresh_token).get("access_token", None)
+ access_token = service_obj.refresh_access_token(self._refresh_token).get("access_token")
+
+ if access_token:
+ # set the new access token in db
+ frappe.db.set_value(
+ "Email Account",
+ self.email_account,
+ "access_token",
+ encrypt(access_token),
+ update_modified=False,
+ )
- # set the new access token in db
- frappe.db.set_value(
- "Email Account", self.email_account, "access_token", access_token, update_modified=False
- )
return access_token
def _get_service_object(self):
From f679dc3fdd64e8515a03f0d2d855a24bf41cd6b1 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sun, 17 Jul 2022 11:14:01 +0530
Subject: [PATCH 0096/2449] fix(security): restrict the god google callback
the common google callback can be used to trigger any method in the whole codebase
restrict it by only allowing domain specific callback method and raise
an error if the domain is not found
---
frappe/email/oauth.py | 1 -
.../google_contacts/google_contacts.py | 1 -
.../doctype/google_drive/google_drive.py | 1 -
frappe/integrations/google_oauth.py | 30 ++++++++++++++-----
.../website_settings/google_indexing.py | 1 -
5 files changed, 23 insertions(+), 11 deletions(-)
diff --git a/frappe/email/oauth.py b/frappe/email/oauth.py
index 9adeb2f9de..89b6df15d8 100644
--- a/frappe/email/oauth.py
+++ b/frappe/email/oauth.py
@@ -150,7 +150,6 @@ def authorize_google_access(email_account, doctype: str = "Email Account", code:
if not code:
return oauth_obj.get_authentication_url(
{
- "method": "frappe.email.oauth.authorize_google_access",
"redirect": f"/app/Form/{quote(doctype)}/{quote(email_account)}",
"success_query_param": "successful_authorization=1",
"email_account": email_account,
diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py
index c1f445b599..9a20d5e905 100644
--- a/frappe/integrations/doctype/google_contacts/google_contacts.py
+++ b/frappe/integrations/doctype/google_contacts/google_contacts.py
@@ -45,7 +45,6 @@ def authorize_access(g_contact, reauthorize=False, code=None):
if not oauth_code or reauthorize:
return oauth_obj.get_authentication_url(
{
- "method": "frappe.integrations.doctype.google_contacts.google_contacts.authorize_access",
"g_contact": g_contact,
"redirect": f"/app/Form/{quote('Google Contacts')}/{quote(g_contact)}",
},
diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py
index 62100ae7c5..6b1e03eccb 100644
--- a/frappe/integrations/doctype/google_drive/google_drive.py
+++ b/frappe/integrations/doctype/google_drive/google_drive.py
@@ -57,7 +57,6 @@ def authorize_access(reauthorize=False, code=None):
frappe.db.set_value("Google Drive", None, "backup_folder_id", "")
return oauth_obj.get_authentication_url(
{
- "method": "frappe.integrations.doctype.google_drive.google_drive.authorize_access",
"redirect": f"/app/Form/{quote('Google Drive')}",
},
)
diff --git a/frappe/integrations/google_oauth.py b/frappe/integrations/google_oauth.py
index edce63493e..1d5ed3723f 100644
--- a/frappe/integrations/google_oauth.py
+++ b/frappe/integrations/google_oauth.py
@@ -19,6 +19,12 @@ _SERVICES = {
"drive": ("drive", "v3"),
"indexing": ("indexing", "v3"),
}
+_DOMAIN_CALLBACK_METHODS = {
+ "mail": "frappe.email.oauth.authorize_google_access",
+ "contacts": "frappe.integrations.doctype.google_contacts.google_contacts.authorize_access",
+ "drive": "frappe.integrations.doctype.google_drive.google_drive.authorize_access",
+ "indexing": "frappe.website.doctype.website_settings.google_indexing.authorize_access",
+}
class GoogleAuthenticationError(Exception):
@@ -100,6 +106,7 @@ class GoogleOAuth:
:param state: [optional] dict of values which you need on callback (for calling methods, redirection back to the form, doc name, etc)
"""
+ state.update({"domain": self.domain})
state = json.dumps(state)
callback_url = get_request_site_address(True) + CALLBACK_METHOD
@@ -175,13 +182,22 @@ def callback(state: str, code: str = None, error: str = None) -> None:
failure_query_param = state.pop("failure_query_param", "")
if not error:
- state.update({"code": code})
- frappe.get_attr(state.pop("method"))(**state)
+ if (domain := state.pop("domain")) in _DOMAIN_CALLBACK_METHODS:
+ state.update({"code": code})
+ frappe.get_attr(_DOMAIN_CALLBACK_METHODS[domain])(**state)
- # GET request, hence using commit to persist changes
- frappe.db.commit() # nosemgrep
-
- redirect = f"{redirect}?{failure_query_param if error else success_query_param}"
+ # GET request, hence using commit to persist changes
+ frappe.db.commit() # nosemgrep
+ else:
+ return frappe.respond_as_web_page(
+ "Invalid Google Callback",
+ "The callback domain provided is not valid for Google Authentication",
+ http_status_code=400,
+ indicator_color="red",
+ width=640,
+ )
frappe.local.response["type"] = "redirect"
- frappe.local.response["location"] = redirect
+ frappe.local.response[
+ "location"
+ ] = f"{redirect}?{failure_query_param if error else success_query_param}"
diff --git a/frappe/website/doctype/website_settings/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py
index 4f67949f86..9dbd415b02 100644
--- a/frappe/website/doctype/website_settings/google_indexing.py
+++ b/frappe/website/doctype/website_settings/google_indexing.py
@@ -26,7 +26,6 @@ def authorize_access(reauthorize=False, code=None):
if not oauth_code or reauthorize:
return oauth_obj.get_authentication_url(
{
- "method": "frappe.website.doctype.website_settings.google_indexing.authorize_access",
"redirect": f"/app/Form/{quote('Website Settings')}",
},
)
From d7bb903212513b6e301e873d4aca196daee674a9 Mon Sep 17 00:00:00 2001
From: chillaranand
Date: Tue, 12 Jul 2022 19:28:11 +0530
Subject: [PATCH 0097/2449] refactor: Removed parse package
---
.../doctype/communication/communication.py | 12 ++++++----
.../communication/test_communication.py | 23 +++++++++++++++++--
pyproject.toml | 1 -
3 files changed, 29 insertions(+), 7 deletions(-)
diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py
index fac8ac74b9..c049ccff45 100644
--- a/frappe/core/doctype/communication/communication.py
+++ b/frappe/core/doctype/communication/communication.py
@@ -5,7 +5,7 @@ from collections import Counter
from email.utils import getaddresses
from urllib.parse import unquote
-from parse import compile
+from bs4 import BeautifulSoup
import frappe
from frappe import _
@@ -144,8 +144,8 @@ class Communication(Document, CommunicationEmailMixin):
if not self.content:
return
- quill_parser = compile('{}
')
- email_body = quill_parser.parse(self.content)
+ soup = BeautifulSoup(self.content, "html.parser")
+ email_body = soup.find("div", {"class": "ql-editor read-mode"})
if not email_body:
return
@@ -169,7 +169,11 @@ class Communication(Document, CommunicationEmailMixin):
if not signature:
return
- _signature = quill_parser.parse(signature)[0] if "ql-editor" in signature else None
+ soup = BeautifulSoup(signature, "html.parser")
+ html_signature = soup.find("div", {"class": "ql-editor read-mode"})
+ _signature = None
+ if html_signature:
+ _signature = html_signature.renderContents()
if (_signature or signature) not in self.content:
self.content = f'{self.content}
{signature}'
diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py
index 77f83b7f91..ba586f3f3a 100644
--- a/frappe/core/doctype/communication/test_communication.py
+++ b/frappe/core/doctype/communication/test_communication.py
@@ -6,11 +6,12 @@ from urllib.parse import quote
import frappe
from frappe.core.doctype.communication.communication import get_emails
from frappe.email.doctype.email_queue.email_queue import EmailQueue
+from frappe.tests.utils import FrappeTestCase
test_records = frappe.get_test_records("Communication")
-class TestCommunication(unittest.TestCase):
+class TestCommunication(FrappeTestCase):
def test_email(self):
valid_email_list = [
"Full Name ",
@@ -259,8 +260,25 @@ class TestCommunication(unittest.TestCase):
self.assertEqual(emails[1], "first.lastname@email.com")
self.assertEqual(emails[2], "test@user.com")
+ def test_signature_in_email_content(self):
+ email_account = create_email_account()
+ signature = email_account.signature
+ comm = frappe.get_doc(
+ {
+ "doctype": "Communication",
+ "communication_medium": "Email",
+ "subject": "Document Link in Email",
+ "sender": "comm_sender@example.com",
+ "content": """
+ Hi,
+ How are you?
+
""",
+ }
+ ).insert(ignore_permissions=True)
+ assert signature in comm.content
-class TestCommunicationEmailMixin(unittest.TestCase):
+
+class TestCommunicationEmailMixin(FrappeTestCase):
def new_communication(self, recipients=None, cc=None, bcc=None):
recipients = ", ".join(recipients or [])
cc = ", ".join(cc or [])
@@ -345,6 +363,7 @@ def create_email_account():
"append_to": "ToDo",
"email_account_name": "_Test Comm Account 1",
"enable_outgoing": 1,
+ "default_outgoing": 1,
"smtp_server": "test.example.com",
"email_id": "test_comm@example.com",
"password": "password",
diff --git a/pyproject.toml b/pyproject.toml
index a1706ac33e..ca69113400 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -43,7 +43,6 @@ dependencies = [
"num2words~=0.5.10",
"oauthlib~=3.1.0",
"openpyxl~=3.0.7",
- "parse~=1.19.0",
"passlib~=1.7.4",
"pdfkit~=1.0.0",
"phonenumbers==8.12.40",
From 404e9ab4e60661367ba17c14dbf9b7431dbc9fc7 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sun, 17 Jul 2022 14:52:36 +0530
Subject: [PATCH 0098/2449] minor: add auto fetch for updating used_oauth field
in User Email doctype (when added a user email manually)
---
frappe/core/doctype/user/user.js | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js
index 41b7e7fb38..05a1102c03 100644
--- a/frappe/core/doctype/user/user.js
+++ b/frappe/core/doctype/user/user.js
@@ -287,6 +287,18 @@ frappe.ui.form.on('User', {
}
});
+
+frappe.ui.form.on('User Email', {
+ email_account(frm, cdt, cdn) {
+ let child_row = locals[cdt][cdn];
+ frappe.model.get_value("Email Account", child_row.email_account, "auth_method", (value) => {
+ child_row.used_oauth = value.auth_method === "OAuth";
+ frm.refresh_field("user_emails", cdn, "used_oauth");
+ });
+ }
+});
+
+
function has_access_to_edit_user() {
return has_common(frappe.user_roles, get_roles_for_editing_user());
}
From 1fd47f1c10b9c9adf6e56d35bc1e8be87dc6baf7 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Wed, 13 Jul 2022 13:07:32 +0530
Subject: [PATCH 0099/2449] chore: remove unused no_remaining field from email
account doctype
---
.../core/doctype/communication/test_communication.py | 1 -
frappe/email/doctype/email_account/email_account.js | 2 --
.../email/doctype/email_account/email_account.json | 12 +-----------
frappe/email/doctype/email_account/test_records.json | 1 -
4 files changed, 1 insertion(+), 15 deletions(-)
diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py
index 77f83b7f91..2621fe3c6b 100644
--- a/frappe/core/doctype/communication/test_communication.py
+++ b/frappe/core/doctype/communication/test_communication.py
@@ -358,7 +358,6 @@ def create_email_account():
"send_notification_to": "test_comm@example.com",
"pop3_server": "pop.test.example.com",
"imap_folder": [{"folder_name": "INBOX", "append_to": "ToDo"}],
- "no_remaining": "0",
"enable_automatic_linking": 1,
}
).insert(ignore_permissions=True)
diff --git a/frappe/email/doctype/email_account/email_account.js b/frappe/email/doctype/email_account/email_account.js
index d22009963d..3faf83800d 100644
--- a/frappe/email/doctype/email_account/email_account.js
+++ b/frappe/email/doctype/email_account/email_account.js
@@ -123,8 +123,6 @@ frappe.ui.form.on("Email Account", {
},
enable_incoming: function(frm) {
- frm.doc.no_remaining = null; //perform full sync
- //frm.set_df_property("append_to", "reqd", frm.doc.enable_incoming);
frm.trigger("warn_autoreply_on_incoming");
},
diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json
index ecb5af7378..9395526fe4 100644
--- a/frappe/email/doctype/email_account/email_account.json
+++ b/frappe/email/doctype/email_account/email_account.json
@@ -70,7 +70,6 @@
"brand_logo",
"uidvalidity",
"uidnext",
- "no_remaining",
"no_failed"
],
"fields": [
@@ -472,15 +471,6 @@
"label": "UIDNEXT",
"no_copy": 1
},
- {
- "fieldname": "no_remaining",
- "fieldtype": "Data",
- "hidden": 1,
- "hide_days": 1,
- "hide_seconds": 1,
- "label": "No of emails remaining to be synced",
- "no_copy": 1
- },
{
"fieldname": "no_failed",
"fieldtype": "Int",
@@ -616,7 +606,7 @@
"icon": "fa fa-inbox",
"index_web_pages_for_search": 1,
"links": [],
- "modified": "2022-07-11 18:34:06.945668",
+ "modified": "2022-07-13 13:05:45.445572",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Account",
diff --git a/frappe/email/doctype/email_account/test_records.json b/frappe/email/doctype/email_account/test_records.json
index 66eb5a9b2e..2e204e5277 100644
--- a/frappe/email/doctype/email_account/test_records.json
+++ b/frappe/email/doctype/email_account/test_records.json
@@ -18,7 +18,6 @@
"unreplied_for_mins": 20,
"send_notification_to": "test_unreplied@example.com",
"pop3_server": "pop.test.example.com",
- "no_remaining":"0",
"append_to": "ToDo",
"imap_folder": [{"folder_name": "INBOX", "append_to": "ToDo"}, {"folder_name": "Test Folder", "append_to": "Communication"}],
"track_email_status": 1
From 29c855b02807107813676bc4c2db49e4944462a0 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Jul 2022 15:10:49 +0530
Subject: [PATCH 0100/2449] fix: db.get_value -> db.get_single_value (#17531)
db.get_value for singles returns string type always, this is confusing
behaviour, db.get_single_value should be used instead.
semgrep rule: https://github.com/frappe/semgrep-rules/pull/16
---
.../core/doctype/sms_settings/sms_settings.py | 2 +-
.../system_settings/system_settings.py | 2 +-
frappe/core/doctype/user/user.py | 6 ++---
.../dropbox_settings/dropbox_settings.py | 6 ++---
.../doctype/google_drive/google_drive.py | 2 +-
.../oauth_provider_settings.py | 4 +++-
.../s3_backup_settings/s3_backup_settings.py | 4 ++--
.../patches/v11_0/set_dropbox_file_backup.py | 2 +-
.../pages/integrations/razorpay_checkout.py | 2 +-
frappe/twofactor.py | 22 ++++++++-----------
frappe/utils/data.py | 2 +-
.../doctype/web_page_view/web_page_view.py | 2 +-
.../website_settings/google_indexing.py | 2 +-
frappe/www/contact.py | 2 +-
14 files changed, 29 insertions(+), 31 deletions(-)
diff --git a/frappe/core/doctype/sms_settings/sms_settings.py b/frappe/core/doctype/sms_settings/sms_settings.py
index 686890514a..0a5536eb9b 100644
--- a/frappe/core/doctype/sms_settings/sms_settings.py
+++ b/frappe/core/doctype/sms_settings/sms_settings.py
@@ -63,7 +63,7 @@ def send_sms(receiver_list, msg, sender_name="", success_msg=True):
"success_msg": success_msg,
}
- if frappe.db.get_value("SMS Settings", None, "sms_gateway_url"):
+ if frappe.db.get_single_value("SMS Settings", "sms_gateway_url"):
send_via_gateway(arg)
else:
msgprint(_("Please Update SMS Settings"))
diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py
index e4d36b7fc7..fbdc188742 100644
--- a/frappe/core/doctype/system_settings/system_settings.py
+++ b/frappe/core/doctype/system_settings/system_settings.py
@@ -28,7 +28,7 @@ class SystemSettings(Document):
if self.enable_two_factor_auth:
if self.two_factor_method == "SMS":
- if not frappe.db.get_value("SMS Settings", None, "sms_gateway_url"):
+ if not frappe.db.get_single_value("SMS Settings", "sms_gateway_url"):
frappe.throw(
_("Please setup SMS before setting it as an authentication method, via SMS Settings")
)
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 12a48afe7e..6d0de186a5 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -611,10 +611,10 @@ class User(Document):
"""
login_with_mobile = cint(
- frappe.db.get_value("System Settings", "System Settings", "allow_login_using_mobile_number")
+ frappe.db.get_single_value("System Settings", "allow_login_using_mobile_number")
)
login_with_username = cint(
- frappe.db.get_value("System Settings", "System Settings", "allow_login_using_user_name")
+ frappe.db.get_single_value("System Settings", "allow_login_using_user_name")
)
or_filters = [{"name": user_name}]
@@ -861,7 +861,7 @@ def sign_up(email, full_name, redirect_to):
user.insert()
# set default signup role as per Portal Settings
- default_role = frappe.db.get_value("Portal Settings", None, "default_role")
+ default_role = frappe.db.get_single_value("Portal Settings", "default_role")
if default_role:
user.add_roles(default_role)
diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
index 50c5fa8fe6..dc9db2ccda 100644
--- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
+++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py
@@ -62,21 +62,21 @@ def take_backups_weekly():
def take_backups_if(freq):
- if frappe.db.get_value("Dropbox Settings", None, "backup_frequency") == freq:
+ if frappe.db.get_single_value("Dropbox Settings", "backup_frequency") == freq:
take_backup_to_dropbox()
def take_backup_to_dropbox(retry_count=0, upload_db_backup=True):
did_not_upload, error_log = [], []
try:
- if cint(frappe.db.get_value("Dropbox Settings", None, "enabled")):
+ if cint(frappe.db.get_single_value("Dropbox Settings", "enabled")):
validate_file_size()
did_not_upload, error_log = backup_to_dropbox(upload_db_backup)
if did_not_upload:
raise Exception
- if cint(frappe.db.get_value("Dropbox Settings", None, "send_email_for_successful_backup")):
+ if cint(frappe.db.get_single_value("Dropbox Settings", "send_email_for_successful_backup")):
send_email(True, "Dropbox", "Dropbox Settings", "send_notifications_to")
except JobTimeoutException:
if retry_count < 2:
diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py
index 62100ae7c5..c24d797086 100644
--- a/frappe/integrations/doctype/google_drive/google_drive.py
+++ b/frappe/integrations/doctype/google_drive/google_drive.py
@@ -48,7 +48,7 @@ def authorize_access(reauthorize=False, code=None):
"""
oauth_code = (
- frappe.db.get_value("Google Drive", "Google Drive", "authorization_code") if not code else code
+ frappe.db.get_single_value("Google Drive", "authorization_code") if not code else code
)
oauth_obj = GoogleOAuth("drive")
diff --git a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py
index 984382df9d..5a918db587 100644
--- a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py
+++ b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.py
@@ -14,7 +14,9 @@ def get_oauth_settings():
"""Returns oauth settings"""
out = frappe._dict(
{
- "skip_authorization": frappe.db.get_value("OAuth Provider Settings", None, "skip_authorization")
+ "skip_authorization": frappe.db.get_single_value(
+ "OAuth Provider Settings", "skip_authorization"
+ )
}
)
diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py
index 1c2d39be10..568ff71b00 100755
--- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py
+++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py
@@ -76,8 +76,8 @@ def take_backups_monthly():
def take_backups_if(freq):
- if cint(frappe.db.get_value("S3 Backup Settings", None, "enabled")):
- if frappe.db.get_value("S3 Backup Settings", None, "frequency") == freq:
+ if cint(frappe.db.get_single_value("S3 Backup Settings", "enabled")):
+ if frappe.db.get_single_value("S3 Backup Settings", "frequency") == freq:
take_backups_s3()
diff --git a/frappe/patches/v11_0/set_dropbox_file_backup.py b/frappe/patches/v11_0/set_dropbox_file_backup.py
index c9dec31414..396491e8b3 100644
--- a/frappe/patches/v11_0/set_dropbox_file_backup.py
+++ b/frappe/patches/v11_0/set_dropbox_file_backup.py
@@ -4,6 +4,6 @@ from frappe.utils import cint
def execute():
frappe.reload_doctype("Dropbox Settings")
- check_dropbox_enabled = cint(frappe.db.get_value("Dropbox Settings", None, "enabled"))
+ check_dropbox_enabled = cint(frappe.db.get_single_value("Dropbox Settings", "enabled"))
if check_dropbox_enabled == 1:
frappe.db.set_value("Dropbox Settings", None, "file_backup", 1)
diff --git a/frappe/templates/pages/integrations/razorpay_checkout.py b/frappe/templates/pages/integrations/razorpay_checkout.py
index b4f9e74a03..d0e77f6d8a 100644
--- a/frappe/templates/pages/integrations/razorpay_checkout.py
+++ b/frappe/templates/pages/integrations/razorpay_checkout.py
@@ -51,7 +51,7 @@ def get_context(context):
def get_api_key():
- api_key = frappe.db.get_value("Razorpay Settings", None, "api_key")
+ api_key = frappe.db.get_single_value("Razorpay Settings", "api_key")
if cint(frappe.form_dict.get("use_sandbox")):
api_key = frappe.conf.sandbox_api_key
diff --git a/frappe/twofactor.py b/frappe/twofactor.py
index 26fc3ad619..528e7f8c8b 100644
--- a/frappe/twofactor.py
+++ b/frappe/twofactor.py
@@ -27,10 +27,10 @@ def toggle_two_factor_auth(state, roles=None):
def two_factor_is_enabled(user=None):
"""Returns True if 2FA is enabled."""
- enabled = int(frappe.db.get_value("System Settings", None, "enable_two_factor_auth") or 0)
+ enabled = int(frappe.db.get_single_value("System Settings", "enable_two_factor_auth") or 0)
if enabled:
bypass_two_factor_auth = int(
- frappe.db.get_value("System Settings", None, "bypass_2fa_for_retricted_ip_users") or 0
+ frappe.db.get_single_value("System Settings", "bypass_2fa_for_retricted_ip_users") or 0
)
if bypass_two_factor_auth and user:
user_doc = frappe.get_doc("User", user)
@@ -127,7 +127,7 @@ def get_otpsecret_for_(user):
def get_verification_method():
- return frappe.db.get_value("System Settings", None, "two_factor_method")
+ return frappe.db.get_single_value("System Settings", "two_factor_method")
def confirm_otp_token(login_manager, otp=None, tmp_id=None):
@@ -173,7 +173,7 @@ def confirm_otp_token(login_manager, otp=None, tmp_id=None):
def get_verification_obj(user, token, otp_secret):
- otp_issuer = frappe.db.get_value("System Settings", "System Settings", "otp_issuer_name")
+ otp_issuer = frappe.db.get_single_value("System Settings", "otp_issuer_name")
verification_method = get_verification_method()
verification_obj = None
if verification_method == "SMS":
@@ -249,7 +249,7 @@ def process_2fa_for_email(user, token, otp_secret, otp_issuer, method="Email"):
def get_email_subject_for_2fa(kwargs_dict):
"""Get email subject for 2fa."""
subject_template = _("Login Verification Code from {}").format(
- frappe.db.get_value("System Settings", "System Settings", "otp_issuer_name")
+ frappe.db.get_single_value("System Settings", "otp_issuer_name")
)
subject = frappe.render_template(subject_template, kwargs_dict)
return subject
@@ -269,7 +269,7 @@ def get_email_body_for_2fa(kwargs_dict):
def get_email_subject_for_qr_code(kwargs_dict):
"""Get QRCode email subject."""
subject_template = _("One Time Password (OTP) Registration Code from {}").format(
- frappe.db.get_value("System Settings", "System Settings", "otp_issuer_name")
+ frappe.db.get_single_value("System Settings", "otp_issuer_name")
)
subject = frappe.render_template(subject_template, kwargs_dict)
return subject
@@ -289,9 +289,7 @@ def get_link_for_qrcode(user, totp_uri):
key = frappe.generate_hash(length=20)
key_user = f"{key}_user"
key_uri = f"{key}_uri"
- lifespan = (
- int(frappe.db.get_value("System Settings", "System Settings", "lifespan_qrcode_image")) or 240
- )
+ lifespan = int(frappe.db.get_single_value("System Settings", "lifespan_qrcode_image")) or 240
frappe.cache().set_value(key_uri, totp_uri, expires_in_sec=lifespan)
frappe.cache().set_value(key_user, user, expires_in_sec=lifespan)
return get_url(f"/qrcode?k={key}")
@@ -447,9 +445,7 @@ def should_remove_barcode_image(barcode):
"""Check if it's time to delete barcode image from server."""
if isinstance(barcode, str):
barcode = frappe.get_doc("File", barcode)
- lifespan = (
- frappe.db.get_value("System Settings", "System Settings", "lifespan_qrcode_image") or 240
- )
+ lifespan = frappe.db.get_single_value("System Settings", "lifespan_qrcode_image") or 240
if time_diff_in_seconds(get_datetime(), barcode.creation) > int(lifespan):
return True
return False
@@ -464,7 +460,7 @@ def reset_otp_secret(user):
if frappe.session.user != user:
frappe.only_for("System Manager", message=True)
- otp_issuer = frappe.db.get_value("System Settings", "System Settings", "otp_issuer_name")
+ otp_issuer = frappe.db.get_single_value("System Settings", "otp_issuer_name")
user_email = frappe.db.get_value("User", user, "email")
frappe.defaults.clear_default(user + "_otplogin")
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 81ca143682..8a970e57cc 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -1545,7 +1545,7 @@ def get_url(uri: str | None = None, full_address: bool = False) -> str:
host_name = protocol + frappe.local.site
else:
- host_name = frappe.db.get_value("Website Settings", "Website Settings", "subdomain")
+ host_name = frappe.db.get_single_value("Website Settings", "subdomain")
if not host_name:
host_name = "http://localhost"
diff --git a/frappe/website/doctype/web_page_view/web_page_view.py b/frappe/website/doctype/web_page_view/web_page_view.py
index 7417f2d290..c3aef62584 100644
--- a/frappe/website/doctype/web_page_view/web_page_view.py
+++ b/frappe/website/doctype/web_page_view/web_page_view.py
@@ -49,4 +49,4 @@ def get_page_view_count(path):
def is_tracking_enabled():
- return frappe.db.get_value("Website Settings", "Website Settings", "enable_view_tracking")
+ return frappe.db.get_single_value("Website Settings", "enable_view_tracking")
diff --git a/frappe/website/doctype/website_settings/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py
index 4f67949f86..fbd5fa7820 100644
--- a/frappe/website/doctype/website_settings/google_indexing.py
+++ b/frappe/website/doctype/website_settings/google_indexing.py
@@ -16,7 +16,7 @@ def authorize_access(reauthorize=False, code=None):
"""If no Authorization code get it from Google and then request for Refresh Token."""
oauth_code = (
- frappe.db.get_value("Website Settings", "Website Settings", "indexing_authorization_code")
+ frappe.db.get_single_value("Website Settings", "indexing_authorization_code")
if not code
else code
)
diff --git a/frappe/www/contact.py b/frappe/www/contact.py
index 11be5e86da..cf26539ff4 100644
--- a/frappe/www/contact.py
+++ b/frappe/www/contact.py
@@ -51,7 +51,7 @@ def send_message(subject="Website Query", message="", sender=""):
return
# send email
- forward_to_email = frappe.db.get_value("Contact Us Settings", None, "forward_to_email")
+ forward_to_email = frappe.db.get_single_value("Contact Us Settings", "forward_to_email")
if forward_to_email:
frappe.sendmail(recipients=forward_to_email, sender=sender, content=message, subject=subject)
From b73899e99dbd7ca224c8ae4212322459abb20787 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 15 Jul 2022 19:56:15 +0530
Subject: [PATCH 0101/2449] feat: frappe.utils.debug.watch_property to debug JS
This util adds a watcher on any JS object for purpose of debugging,
whenever the property changes a console trace is generated. A custom
callback function can also be passed if required.
Usage:
```javascript
filters = {}
frappe.utils.debug.watch_property(filters, "company");
// any changes to company key on filters object from now on will print a
console trace
```
only for debugging, make sure to not commit it in codebase! :)
debug
---
frappe/public/js/frappe/utils/utils.js | 25 +++++++++++++++++++++++++
1 file changed, 25 insertions(+)
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index 34374d7c85..877add95bf 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1537,4 +1537,29 @@ Object.assign(frappe.utils, {
is_current_user(user) {
return user === frappe.session.user;
},
+
+ debug: {
+ watch_property(obj, prop, callback=console.trace) {
+ if (!frappe.boot.developer_mode) {
+ return;
+ }
+ console.warn("Adding property watcher, make sure to remove it after debugging.");
+
+ // Adapted from https://stackoverflow.com/a/11658693
+ // Reused under CC-BY-SA 4.0
+ // changes: variable names are changed for consistency with our codebase
+ const private_prop = "$_" + prop + "_$";
+ obj[private_prop] = obj[prop];
+
+ Object.defineProperty(obj, prop, {
+ get: function() {
+ return obj[private_prop];
+ },
+ set: function(value) {
+ callback();
+ obj[private_prop] = value;
+ },
+ });
+ },
+ }
});
From 1a7a21bbe5a376084633b80aa412cbb3d8b760b4 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 15 Jul 2022 19:40:58 +0530
Subject: [PATCH 0102/2449] fix(UX)!: respect no filters set by user
Currently if user resets a filter and comes back to same view default
filters get populated. This is annoying behaviour especially since this
is already saved in user setting.
Fix: Always respect user's preference, if their last choice was no
filters then load view without filters.
---
frappe/public/js/frappe/list/base_list.js | 15 ---------------
frappe/public/js/frappe/list/list_view.js | 5 +----
2 files changed, 1 insertion(+), 19 deletions(-)
diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js
index bbee90048b..e5272ccd91 100644
--- a/frappe/public/js/frappe/list/base_list.js
+++ b/frappe/public/js/frappe/list/base_list.js
@@ -764,10 +764,6 @@ class FilterArea {
const doctype_fields = this.list_view.meta.fields;
const title_field = this.list_view.meta.title_field;
- const has_existing_filters = (
- this.list_view.filters
- && this.list_view.filters.length > 0
- );
fields = fields.concat(
doctype_fields
@@ -803,23 +799,12 @@ class FilterArea {
}
}
- let default_value;
-
- if (fieldtype === "Link" && !has_existing_filters) {
- default_value = frappe.defaults.get_user_default(options);
- }
-
- if (["__default", "__global"].includes(default_value)) {
- default_value = null;
- }
-
return {
fieldtype: fieldtype,
label: __(df.label),
options: options,
fieldname: df.fieldname,
condition: condition,
- default: default_value,
onchange: () => this.refresh_list_view(),
ignore_link_validation: fieldtype === "Dynamic Link",
is_filter: 1,
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index 94a3c29b27..cbeda50e53 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -87,10 +87,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
this.menu_items = this.menu_items.concat(this.get_menu_items());
// set filters from view_user_settings or list_settings
- if (
- this.view_user_settings.filters &&
- this.view_user_settings.filters.length
- ) {
+ if (Array.isArray(this.view_user_settings.filters)) {
// Priority 1: view_user_settings
const saved_filters = this.view_user_settings.filters;
this.filters = this.validate_filters(saved_filters);
From a98e47150f7f81ce4524e3e9d414d7ec4205f0d4 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Jul 2022 16:26:00 +0530
Subject: [PATCH 0103/2449] feat(tiny): frappe.log -> frappe.log for server
scripts
This it already whitelisted but in global scope.
[skip ci]
---
frappe/utils/safe_exec.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py
index 03f5d041ce..8e983f11b2 100644
--- a/frappe/utils/safe_exec.py
+++ b/frappe/utils/safe_exec.py
@@ -152,6 +152,7 @@ def get_safe_globals():
enqueue=safe_enqueue,
sanitize_html=frappe.utils.sanitize_html,
log_error=frappe.log_error,
+ log=frappe.log,
db=NamespaceDict(
get_list=frappe.get_list,
get_all=frappe.get_all,
From 59fff76cb34820d760d56b6b488e9a802dd2b308 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Jul 2022 16:47:36 +0530
Subject: [PATCH 0104/2449] fix(dx): word wrap in script diffview
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Helpful for devs who write >120 character long code. 💩
[skip ci]
---
frappe/public/js/frappe/utils/diffview.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/utils/diffview.js b/frappe/public/js/frappe/utils/diffview.js
index a898a318a1..a326fd74bc 100644
--- a/frappe/public/js/frappe/utils/diffview.js
+++ b/frappe/public/js/frappe/utils/diffview.js
@@ -89,7 +89,7 @@ frappe.ui.DiffView = class DiffView {
} else if (line.startsWith("-")) {
line_class = "delete";
}
- html += `${line}
`;
+ html += `${line}
`;
});
return `${html}
`;
}
From a588879094ec997cd1f134aac72e64bb409ab05d Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 18 Jul 2022 17:30:04 +0530
Subject: [PATCH 0105/2449] refactor(minor): LDAP Settings Test Suite
* Re-write blocks for better readability
* De-indent everything
* Add typing, etc
---
.../ldap_settings/test_ldap_settings.py | 308 ++++++------------
1 file changed, 101 insertions(+), 207 deletions(-)
diff --git a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
index f53b5291b3..8587c6c656 100644
--- a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
@@ -1,15 +1,16 @@
-# Copyright (c) 2019, Frappe Technologies and Contributors
+# Copyright (c) 2022, Frappe Technologies and Contributors
# License: MIT. See LICENSE
+import contextlib
import functools
import os
import ssl
-import unittest
-from unittest import mock
+from unittest import TestCase, mock
import ldap3
from ldap3 import MOCK_SYNC, OFFLINE_AD_2012_R2, OFFLINE_SLAPD_2_4, Connection, Server
import frappe
+from frappe.exceptions import MandatoryError, ValidationError
from frappe.integrations.doctype.ldap_settings.ldap_settings import LDAPSettings
@@ -27,10 +28,9 @@ class LDAP_TestCase:
def wrapped(self, *args, **kwargs):
with mock.patch(
- "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap"
- ) as mock_connection:
- mock_connection.return_value = self.connection
-
+ "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap",
+ return_value=self.connection,
+ ):
self.test_class = LDAPSettings(self.doc)
# Create a clean doc
@@ -47,80 +47,64 @@ class LDAP_TestCase:
return wrapped
def clean_test_users():
- try: # clean up test user 1
+ with contextlib.suppress(Exception):
frappe.get_doc("User", "posix.user1@unit.testing").delete()
- except Exception:
- pass
-
- try: # clean up test user 2
+ with contextlib.suppress(Exception):
frappe.get_doc("User", "posix.user2@unit.testing").delete()
- except Exception:
- pass
@classmethod
- def setUpClass(self, ldapServer="OpenLDAP"):
-
- self.clean_test_users()
+ def setUpClass(cls):
+ cls.clean_test_users()
# Save user data for restoration in tearDownClass()
- self.user_ldap_settings = frappe.get_doc("LDAP Settings")
+ cls.user_ldap_settings = frappe.get_doc("LDAP Settings")
# Create test user1
- self.user1doc = {
+ cls.user1doc = {
"username": "posix.user",
"email": "posix.user1@unit.testing",
"first_name": "posix",
+ "doctype": "User",
+ "send_welcome_email": 0,
+ "language": "",
+ "user_type": "System User",
}
- self.user1doc.update(
- {
- "doctype": "User",
- "send_welcome_email": 0,
- "language": "",
- "user_type": "System User",
- }
- )
- user = frappe.get_doc(self.user1doc)
+ user = frappe.get_doc(cls.user1doc)
user.insert(ignore_permissions=True)
- # Create test user1
- self.user2doc = {
+ cls.user2doc = {
"username": "posix.user2",
"email": "posix.user2@unit.testing",
"first_name": "posix",
+ "doctype": "User",
+ "send_welcome_email": 0,
+ "language": "",
+ "user_type": "System User",
}
- self.user2doc.update(
- {
- "doctype": "User",
- "send_welcome_email": 0,
- "language": "",
- "user_type": "System User",
- }
- )
-
- user = frappe.get_doc(self.user2doc)
+ user = frappe.get_doc(cls.user2doc)
user.insert(ignore_permissions=True)
# Setup Mock OpenLDAP Directory
- self.ldap_dc_path = "dc=unit,dc=testing"
- self.ldap_user_path = "ou=users," + self.ldap_dc_path
- self.ldap_group_path = "ou=groups," + self.ldap_dc_path
- self.base_dn = "cn=base_dn_user," + self.ldap_dc_path
- self.base_password = "my_password"
- self.ldap_server = "ldap://my_fake_server:389"
+ cls.ldap_dc_path = "dc=unit,dc=testing"
+ cls.ldap_user_path = f"ou=users,{cls.ldap_dc_path}"
+ cls.ldap_group_path = f"ou=groups,{cls.ldap_dc_path}"
+ cls.base_dn = f"cn=base_dn_user,{cls.ldap_dc_path}"
+ cls.base_password = "my_password"
+ cls.ldap_server = "ldap://my_fake_server:389"
- self.doc = {
+ cls.doc = {
"doctype": "LDAP Settings",
"enabled": True,
- "ldap_directory_server": self.TEST_LDAP_SERVER,
- "ldap_server_url": self.ldap_server,
- "base_dn": self.base_dn,
- "password": self.base_password,
- "ldap_search_path_user": self.ldap_user_path,
- "ldap_search_string": self.TEST_LDAP_SEARCH_STRING,
- "ldap_search_path_group": self.ldap_group_path,
+ "ldap_directory_server": cls.TEST_LDAP_SERVER,
+ "ldap_server_url": cls.ldap_server,
+ "base_dn": cls.base_dn,
+ "password": cls.base_password,
+ "ldap_search_path_user": cls.ldap_user_path,
+ "ldap_search_string": cls.TEST_LDAP_SEARCH_STRING,
+ "ldap_search_path_group": cls.ldap_group_path,
"ldap_user_creation_and_mapping_section": "",
"ldap_email_field": "mail",
- "ldap_username_field": self.LDAP_USERNAME_FIELD,
+ "ldap_username_field": cls.LDAP_USERNAME_FIELD,
"ldap_first_name_field": "givenname",
"ldap_middle_name_field": "",
"ldap_last_name_field": "sn",
@@ -135,50 +119,40 @@ class LDAP_TestCase:
"ldap_group_objectclass": "",
"ldap_group_member_attribute": "",
"default_role": "Newsletter Manager",
- "ldap_groups": self.DOCUMENT_GROUP_MAPPINGS,
+ "ldap_groups": cls.DOCUMENT_GROUP_MAPPINGS,
"ldap_group_field": "",
}
- self.server = Server(host=self.ldap_server, port=389, get_info=self.LDAP_SCHEMA)
-
- self.connection = Connection(
- self.server,
- user=self.base_dn,
- password=self.base_password,
+ cls.server = Server(host=cls.ldap_server, port=389, get_info=cls.LDAP_SCHEMA)
+ cls.connection = Connection(
+ cls.server,
+ user=cls.base_dn,
+ password=cls.base_password,
read_only=True,
client_strategy=MOCK_SYNC,
)
-
- self.connection.strategy.entries_from_json(
- os.path.abspath(os.path.dirname(__file__)) + "/" + self.LDAP_LDIF_JSON
+ cls.connection.strategy.entries_from_json(
+ f"{os.path.abspath(os.path.dirname(__file__))}/{cls.LDAP_LDIF_JSON}"
)
-
- self.connection.bind()
+ cls.connection.bind()
@classmethod
- def tearDownClass(self):
- try:
+ def tearDownClass(cls):
+ with contextlib.suppress(Exception):
frappe.get_doc("LDAP Settings").delete()
- except Exception:
- pass
-
- try:
- # return doc back to user data
- self.user_ldap_settings.save()
-
- except Exception:
- pass
+ # return doc back to user data
+ with contextlib.suppress(Exception):
+ cls.user_ldap_settings.save()
# Clean-up test users
- self.clean_test_users()
+ cls.clean_test_users()
# Clear OpenLDAP connection
- self.connection = None
+ cls.connection = None
@mock_ldap_connection
- def test_mandatory_fields(self):
-
+ def test_mandatory_fields(self: TestCase):
mandatory_fields = [
"ldap_server_url",
"ldap_directory_server",
@@ -195,26 +169,14 @@ class LDAP_TestCase:
] # fields that are required to have ldap functioning need to be mandatory
for mandatory_field in mandatory_fields:
-
localdoc = self.doc.copy()
localdoc[mandatory_field] = ""
- try:
-
+ with contextlib.suppress(MandatoryError, ValidationError):
frappe.get_doc(localdoc).save()
-
self.fail(f"Document LDAP Settings field [{mandatory_field}] is not mandatory")
- except frappe.exceptions.MandatoryError:
- pass
-
- except frappe.exceptions.ValidationError:
- if mandatory_field == "ldap_search_string":
- # additional validation is done on this field, pass in this instance
- pass
-
for non_mandatory_field in self.doc: # Ensure remaining fields have not been made mandatory
-
if non_mandatory_field == "doctype" or non_mandatory_field in mandatory_fields:
continue
@@ -222,15 +184,12 @@ class LDAP_TestCase:
localdoc[non_mandatory_field] = ""
try:
-
frappe.get_doc(localdoc).save()
-
- except frappe.exceptions.MandatoryError:
+ except MandatoryError:
self.fail(f"Document LDAP Settings field [{non_mandatory_field}] should not be mandatory")
@mock_ldap_connection
- def test_validation_ldap_search_string(self):
-
+ def test_validation_ldap_search_string(self: TestCase):
invalid_ldap_search_strings = [
"",
"uid={0}",
@@ -242,19 +201,26 @@ class LDAP_TestCase:
] # ldap search string must be enclosed in '()' for ldap search to work for finding user and have the same number of opening and closing brackets.
for invalid_search_string in invalid_ldap_search_strings:
-
localdoc = self.doc.copy()
localdoc["ldap_search_string"] = invalid_search_string
- try:
+ with contextlib.suppress(ValidationError):
frappe.get_doc(localdoc).save()
-
self.fail(f"LDAP search string [{invalid_search_string}] should not validate")
- except frappe.exceptions.ValidationError:
- pass
-
- def test_connect_to_ldap(self):
+ def test_connect_to_ldap(self: TestCase):
+ # prevent these parameters for security or lack of the und user from being able to configure
+ prevent_connection_parameters = {
+ "mode": {
+ "IP_V4_ONLY": "Locks the user to IPv4 without frappe providing a way to configure",
+ "IP_V6_ONLY": "Locks the user to IPv6 without frappe providing a way to configure",
+ },
+ "auto_bind": {
+ "NONE": "ldap3.Connection must autobind with base_dn",
+ "NO_TLS": "ldap3.Connection must have TLS",
+ "TLS_AFTER_BIND": "[Security] ldap3.Connection TLS bind must occur before bind",
+ },
+ }
# setup a clean doc with ldap disabled so no validation occurs (this is tested seperatly)
local_doc = self.doc.copy()
@@ -262,48 +228,25 @@ class LDAP_TestCase:
self.test_class = LDAPSettings(self.doc)
with mock.patch("ldap3.Server") as ldap3_server_method:
-
- with mock.patch("ldap3.Connection") as ldap3_connection_method:
- ldap3_connection_method.return_value = self.connection
-
+ with mock.patch("ldap3.Connection", return_value=self.connection) as ldap3_connection_method:
with mock.patch("ldap3.Tls") as ldap3_Tls_method:
-
function_return = self.test_class.connect_to_ldap(
base_dn=self.base_dn, password=self.base_password
)
-
args, kwargs = ldap3_connection_method.call_args
- prevent_connection_parameters = {
- # prevent these parameters for security or lack of the und user from being able to configure
- "mode": {
- "IP_V4_ONLY": "Locks the user to IPv4 without frappe providing a way to configure",
- "IP_V6_ONLY": "Locks the user to IPv6 without frappe providing a way to configure",
- },
- "auto_bind": {
- "NONE": "ldap3.Connection must autobind with base_dn",
- "NO_TLS": "ldap3.Connection must have TLS",
- "TLS_AFTER_BIND": "[Security] ldap3.Connection TLS bind must occur before bind",
- },
- }
-
for connection_arg in kwargs:
-
if (
connection_arg in prevent_connection_parameters
and kwargs[connection_arg] in prevent_connection_parameters[connection_arg]
):
-
self.fail(
- "ldap3.Connection was called with {}, failed reason: [{}]".format(
- kwargs[connection_arg],
- prevent_connection_parameters[connection_arg][kwargs[connection_arg]],
- )
+ f"ldap3.Connection was called with {kwargs[connection_arg]}, failed reason: [{prevent_connection_parameters[connection_arg][kwargs[connection_arg]]}]"
)
+ tls_version = ssl.PROTOCOL_TLS_CLIENT
if local_doc["require_trusted_certificate"] == "Yes":
tls_validate = ssl.CERT_REQUIRED
- tls_version = ssl.PROTOCOL_TLS_CLIENT
tls_configuration = ldap3.Tls(validate=tls_validate, version=tls_version)
self.assertTrue(
@@ -313,7 +256,6 @@ class LDAP_TestCase:
else:
tls_validate = ssl.CERT_NONE
- tls_version = ssl.PROTOCOL_TLS_CLIENT
tls_configuration = ldap3.Tls(validate=tls_validate, version=tls_version)
self.assertTrue(kwargs["auto_bind"], "ldap3.Connection must autobind")
@@ -347,7 +289,7 @@ class LDAP_TestCase:
)
self.assertTrue(
- type(function_return) is ldap3.core.connection.Connection,
+ type(function_return) is Connection,
"The return type must be of ldap3.Connection",
)
@@ -363,25 +305,21 @@ class LDAP_TestCase:
)
@mock_ldap_connection
- def test_get_ldap_client_settings(self):
-
+ def test_get_ldap_client_settings(self: TestCase):
result = self.test_class.get_ldap_client_settings()
self.assertIsInstance(result, dict)
-
self.assertTrue(result["enabled"] == self.doc["enabled"]) # settings should match doc
localdoc = self.doc.copy()
localdoc["enabled"] = False
frappe.get_doc(localdoc).save()
-
result = self.test_class.get_ldap_client_settings()
self.assertFalse(result["enabled"]) # must match the edited doc
@mock_ldap_connection
- def test_update_user_fields(self):
-
+ def test_update_user_fields(self: TestCase):
test_user_data = {
"username": "posix.user",
"email": "posix.user1@unit.testing",
@@ -391,11 +329,8 @@ class LDAP_TestCase:
"phone": "08 1234 5678",
"mobile_no": "0421 123 456",
}
-
test_user = frappe.get_doc("User", test_user_data["email"])
-
self.test_class.update_user_fields(test_user, test_user_data)
-
updated_user = frappe.get_doc("User", test_user_data["email"])
self.assertTrue(updated_user.middle_name == test_user_data["middle_name"])
@@ -404,8 +339,7 @@ class LDAP_TestCase:
self.assertTrue(updated_user.mobile_no == test_user_data["mobile_no"])
@mock_ldap_connection
- def test_sync_roles(self):
-
+ def test_sync_roles(self: TestCase):
if self.TEST_LDAP_SERVER.lower() == "openldap":
test_user_data = {
"posix.user1": [
@@ -457,9 +391,8 @@ class LDAP_TestCase:
user.insert(ignore_permissions=True)
for test_user in test_user_data:
-
- test_user_doc = frappe.get_doc("User", test_user + "@unit.testing")
- test_user_roles = frappe.get_roles(test_user + "@unit.testing")
+ test_user_doc = frappe.get_doc("User", f"{test_user}@unit.testing")
+ test_user_roles = frappe.get_roles(f"{test_user}@unit.testing")
self.assertTrue(
len(test_user_roles) == 2, "User should only be a part of the All and Guest roles"
@@ -467,28 +400,22 @@ class LDAP_TestCase:
self.test_class.sync_roles(test_user_doc, test_user_data[test_user]) # update user roles
- frappe.get_doc("User", test_user + "@unit.testing")
- updated_user_roles = frappe.get_roles(test_user + "@unit.testing")
+ frappe.get_doc("User", f"{test_user}@unit.testing")
+ updated_user_roles = frappe.get_roles(f"{test_user}@unit.testing")
self.assertTrue(
len(updated_user_roles) == len(test_user_data[test_user]),
- "syncing of the user roles failed. {} != {} for user {}".format(
- len(updated_user_roles), len(test_user_data[test_user]), test_user
- ),
+ f"syncing of the user roles failed. {len(updated_user_roles)} != {len(test_user_data[test_user])} for user {test_user}",
)
for user_role in updated_user_roles: # match each users role mapped to ldap groups
-
self.assertTrue(
role_to_group_map[user_role] in test_user_data[test_user],
- "during sync_roles(), the user was given role {} which should not have occured".format(
- user_role
- ),
+ f"during sync_roles(), the user was given role {user_role} which should not have occured",
)
@mock_ldap_connection
- def test_create_or_update_user(self):
-
+ def test_create_or_update_user(self: TestCase):
test_user_data = {
"posix.user1": [
"Users",
@@ -498,28 +425,21 @@ class LDAP_TestCase:
"frappe_default_guest",
],
}
-
test_user = "posix.user1"
- frappe.get_doc("User", test_user + "@unit.testing").delete() # remove user 1
+ frappe.get_doc("User", f"{test_user}@unit.testing").delete()
with self.assertRaises(
frappe.exceptions.DoesNotExistError
): # ensure user deleted so function can be tested
- frappe.get_doc("User", test_user + "@unit.testing")
+ frappe.get_doc("User", f"{test_user}@unit.testing")
with mock.patch(
"frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.update_user_fields"
) as update_user_fields_method:
-
- update_user_fields_method.return_value = None
-
with mock.patch(
"frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.sync_roles"
) as sync_roles_method:
-
- sync_roles_method.return_value = None
-
# New user
self.test_class.create_or_update_user(self.user1doc, test_user_data[test_user])
@@ -538,15 +458,12 @@ class LDAP_TestCase:
)
@mock_ldap_connection
- def test_get_ldap_attributes(self):
-
+ def test_get_ldap_attributes(self: TestCase):
method_return = self.test_class.get_ldap_attributes()
-
self.assertTrue(type(method_return) is list)
@mock_ldap_connection
- def test_fetch_ldap_groups(self):
-
+ def test_fetch_ldap_groups(self: TestCase):
if self.TEST_LDAP_SERVER.lower() == "openldap":
test_users = {"posix.user": ["Users", "Administrators"], "posix.user2": ["Users", "Group3"]}
elif self.TEST_LDAP_SERVER.lower() == "active directory":
@@ -556,7 +473,6 @@ class LDAP_TestCase:
}
for test_user in test_users:
-
self.connection.search(
search_base=self.ldap_user_path,
search_filter=self.TEST_LDAP_SEARCH_STRING.format(test_user),
@@ -569,18 +485,13 @@ class LDAP_TestCase:
self.assertTrue(len(method_return) == len(test_users[test_user]))
for returned_group in method_return:
-
self.assertTrue(returned_group in test_users[test_user])
@mock_ldap_connection
- def test_authenticate(self):
-
+ def test_authenticate(self: TestCase):
with mock.patch(
"frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.fetch_ldap_groups"
) as fetch_ldap_groups_function:
-
- fetch_ldap_groups_function.return_value = None
-
self.assertTrue(self.test_class.authenticate("posix.user", "posix_user_password"))
self.assertTrue(
@@ -599,25 +510,19 @@ class LDAP_TestCase:
] # All invalid users should return 'invalid username or password'
for username, password in enumerate(invalid_users):
-
with self.assertRaises(frappe.exceptions.ValidationError) as display_massage:
-
self.test_class.authenticate(username, password)
self.assertTrue(
str(display_massage.exception).lower() == "invalid username or password",
- "invalid credentials passed authentication [user: {}, password: {}]".format(
- username, password
- ),
+ f"invalid credentials passed authentication [user: {username}, password: {password}]",
)
@mock_ldap_connection
- def test_complex_ldap_search_filter(self):
-
+ def test_complex_ldap_search_filter(self: TestCase):
ldap_search_filters = self.TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING
for search_filter in ldap_search_filters:
-
self.test_class.ldap_search_string = search_filter
if (
@@ -633,56 +538,45 @@ class LDAP_TestCase:
else:
self.assertTrue(self.test_class.authenticate("posix.user", "posix_user_password"))
- def test_reset_password(self):
-
+ def test_reset_password(self: TestCase):
self.test_class = LDAPSettings(self.doc)
# Create a clean doc
localdoc = self.doc.copy()
-
localdoc["enabled"] = False
frappe.get_doc(localdoc).save()
with mock.patch(
- "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap"
+ "frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.connect_to_ldap",
+ return_value=self.connection,
) as connect_to_ldap:
- connect_to_ldap.return_value = self.connection
-
with self.assertRaises(
frappe.exceptions.ValidationError
) as validation: # Fail if username string used
self.test_class.reset_password("posix.user", "posix_user_password")
-
self.assertTrue(str(validation.exception) == "No LDAP User found for email: posix.user")
- try:
+ with contextlib.suppress(Exception):
self.test_class.reset_password(
"posix.user1@unit.testing", "posix_user_password"
) # Change Password
-
- except Exception: # An exception from the tested class is ok, as long as the connection to LDAP was made writeable
- pass
-
connect_to_ldap.assert_called_with(self.base_dn, self.base_password, read_only=False)
@mock_ldap_connection
- def test_convert_ldap_entry_to_dict(self):
-
+ def test_convert_ldap_entry_to_dict(self: TestCase):
self.connection.search(
search_base=self.ldap_user_path,
search_filter=self.TEST_LDAP_SEARCH_STRING.format("posix.user"),
attributes=self.test_class.get_ldap_attributes(),
)
-
test_ldap_entry = self.connection.entries[0]
-
method_return = self.test_class.convert_ldap_entry_to_dict(test_ldap_entry)
self.assertTrue(type(method_return) is dict) # must be dict
self.assertTrue(len(method_return) == 6) # there are 6 fields in mock_ldap for use
-class Test_OpenLDAP(LDAP_TestCase, unittest.TestCase):
+class Test_OpenLDAP(LDAP_TestCase, TestCase):
TEST_LDAP_SERVER = "OpenLDAP"
TEST_LDAP_SEARCH_STRING = "(uid={0})"
DOCUMENT_GROUP_MAPPINGS = [
@@ -706,7 +600,7 @@ class Test_OpenLDAP(LDAP_TestCase, unittest.TestCase):
]
-class Test_ActiveDirectory(LDAP_TestCase, unittest.TestCase):
+class Test_ActiveDirectory(LDAP_TestCase, TestCase):
TEST_LDAP_SERVER = "Active Directory"
TEST_LDAP_SEARCH_STRING = "(samaccountname={0})"
DOCUMENT_GROUP_MAPPINGS = [
From 943334a90c43f536688cc6ef033e061108ee3019 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Mon, 18 Jul 2022 17:47:57 +0530
Subject: [PATCH 0106/2449] chore: fix docstrings
---
frappe/integrations/google_oauth.py | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/frappe/integrations/google_oauth.py b/frappe/integrations/google_oauth.py
index 1d5ed3723f..8bc54e0b1d 100644
--- a/frappe/integrations/google_oauth.py
+++ b/frappe/integrations/google_oauth.py
@@ -59,7 +59,6 @@ class GoogleOAuth:
"""Returns a dict with access and refresh token.
:param oauth_code: code got back from google upon successful auhtorization
- :param site_address: side address from which the request is being made
"""
data = {
@@ -102,8 +101,7 @@ class GoogleOAuth:
def get_authentication_url(self, state: dict[str, str]) -> dict[str, str]:
"""Returns google authentication url.
- :param site_address: side address from which the request is being made (for redirect back to site)
- :param state: [optional] dict of values which you need on callback (for calling methods, redirection back to the form, doc name, etc)
+ :param state: dict of values which you need on callback (for calling methods, redirection back to the form, doc name, etc)
"""
state.update({"domain": self.domain})
From 26f4654b31a72b3b1233ae0ca83b3aa1b5a7d20b Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 18 Jul 2022 18:22:12 +0530
Subject: [PATCH 0107/2449] test: Check user role & type updated via LDAP
---
.../integrations/doctype/ldap_settings/test_ldap_settings.py | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
index 8587c6c656..a158d42d61 100644
--- a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
@@ -121,6 +121,7 @@ class LDAP_TestCase:
"default_role": "Newsletter Manager",
"ldap_groups": cls.DOCUMENT_GROUP_MAPPINGS,
"ldap_group_field": "",
+ "default_user_type": "System User",
}
cls.server = Server(host=cls.ldap_server, port=389, get_info=cls.LDAP_SCHEMA)
@@ -338,6 +339,9 @@ class LDAP_TestCase:
self.assertTrue(updated_user.phone == test_user_data["phone"])
self.assertTrue(updated_user.mobile_no == test_user_data["mobile_no"])
+ self.assertEqual(updated_user.user_type, self.test_class.default_user_type)
+ self.assertIn(self.test_class.default_role, frappe.get_roles(updated_user.name))
+
@mock_ldap_connection
def test_sync_roles(self: TestCase):
if self.TEST_LDAP_SERVER.lower() == "openldap":
From 66c77f30dd0e03038dca559eeb3dd8fbb58117fb Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 15 Jul 2022 18:15:34 +0530
Subject: [PATCH 0108/2449] fix: chart wrapper padding
This is close to the card boundary and looks ugly when numbers on Y axis
start colliding with it.
---
frappe/public/scss/desk/global.scss | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss
index 7466bdc874..1e68f374c4 100644
--- a/frappe/public/scss/desk/global.scss
+++ b/frappe/public/scss/desk/global.scss
@@ -589,6 +589,10 @@ details > summary:focus {
background-color: var(--diff-removed);
}
+.chart-wrapper {
+ padding: 1em;
+}
+
// REDESIGN TODO: Handling of broken images?
// img.no-image:before {
// .img-background();
From 3304e1c222a4a53d6e9cdcb7f7349221f3436978 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 18 Jul 2022 14:35:48 +0530
Subject: [PATCH 0109/2449] chore: bump frappe-charts to latest
---
package.json | 2 +-
yarn.lock | 8 ++++----
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/package.json b/package.json
index 1685dc9b25..c4ba042a89 100644
--- a/package.json
+++ b/package.json
@@ -32,7 +32,7 @@
"driver.js": "^0.9.8",
"editorjs-undo": "0.1.6",
"fast-deep-equal": "^2.0.1",
- "frappe-charts": "^2.0.0-rc13",
+ "frappe-charts": "2.0.0-rc22",
"frappe-datatable": "^1.16.4",
"frappe-gantt": "^0.6.0",
"highlight.js": "^10.4.1",
diff --git a/yarn.lock b/yarn.lock
index b80d101883..57d5a47131 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1256,10 +1256,10 @@ fraction.js@^4.1.2:
resolved "https://registry.yarnpkg.com/fraction.js/-/fraction.js-4.2.0.tgz#448e5109a313a3527f5a3ab2119ec4cf0e0e2950"
integrity sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==
-frappe-charts@^2.0.0-rc13:
- version "2.0.0-rc13"
- resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc13.tgz#fdb251d7ae311c41e38f90a3ae108070ec6b9072"
- integrity sha512-Bv7IfllIrjRbKWHn5b769dOSenqdBixAr6m5kurf8ZUOJSLOgK4HOXItJ7BA8n9PvviH9/k5DaloisjLM2Bm1w==
+frappe-charts@^2.0.0-rc22:
+ version "2.0.0-rc22"
+ resolved "https://registry.yarnpkg.com/frappe-charts/-/frappe-charts-2.0.0-rc22.tgz#9a5a747febdc381a1d4d7af96e89cf519dfba8c0"
+ integrity sha512-N7f/8979wJCKjusOinaUYfMxB80YnfuVLrSkjpj4LtyqS0BGS6SuJxUnb7Jl4RWUFEIs7zEhideIKnyLeFZF4Q==
frappe-datatable@^1.16.4:
version "1.16.4"
From 44630f5f62b75a58698f59c08686f7a1a2c17666 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 14 Jul 2022 20:11:58 +0530
Subject: [PATCH 0110/2449] feat: country specific number shortening in charts
---
frappe/public/js/frappe/form/dashboard.js | 3 ++-
frappe/public/js/frappe/utils/utils.js | 15 +++++++++++++--
.../js/frappe/views/reports/query_report.js | 4 ++--
.../js/frappe/views/reports/report_utils.js | 3 ++-
.../public/js/frappe/views/reports/report_view.js | 3 ++-
5 files changed, 21 insertions(+), 7 deletions(-)
diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js
index c057903a63..5c1b5d392f 100644
--- a/frappe/public/js/frappe/form/dashboard.js
+++ b/frappe/public/js/frappe/form/dashboard.js
@@ -554,7 +554,8 @@ frappe.ui.form.Dashboard = class FormDashboard {
colors: ['green'],
truncateLegends: 1,
axisOptions: {
- shortenYAxisNumbers: 1
+ shortenYAxisNumbers: 1,
+ numberFormatter: frappe.utils.format_chart_axis_number,
}
});
this.show();
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index 877add95bf..78c9859b35 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1145,7 +1145,12 @@ Object.assign(frappe.utils, {
{
divisor: 1.0e+5,
symbol: 'Lakh'
- }],
+ },
+ {
+ divisor: 1.0e+3,
+ symbol: 'K',
+ }
+ ],
'':
[{
divisor: 1.0e+12,
@@ -1205,7 +1210,8 @@ Object.assign(frappe.utils, {
axisOptions: {
xIsSeries: 1,
shortenYAxisNumbers: 1,
- xAxisMode: 'tick'
+ xAxisMode: 'tick',
+ numberFormatter: frappe.utils.format_chart_axis_number,
}
};
@@ -1220,6 +1226,11 @@ Object.assign(frappe.utils, {
return new frappe.Chart(wrapper, chart_args);
},
+ format_chart_axis_number(label, country) {
+ const default_country = frappe.sys_defaults.country;
+ return frappe.utils.shorten_number(label, country || default_country, 3);
+ },
+
generate_route(item) {
const type = item.type.toLowerCase();
if (type === "doctype") {
diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js
index e15cc339ae..525bc5af4b 100644
--- a/frappe/public/js/frappe/views/reports/query_report.js
+++ b/frappe/public/js/frappe/views/reports/query_report.js
@@ -944,10 +944,10 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList {
};
}
options.axisOptions = {
- shortenYAxisNumbers: 1
+ shortenYAxisNumbers: 1,
+ numberFormatter: frappe.utils.format_chart_axis_number,
};
options.height = 280;
-
return options;
}
diff --git a/frappe/public/js/frappe/views/reports/report_utils.js b/frappe/public/js/frappe/views/reports/report_utils.js
index f458a4daf6..35c8d132c8 100644
--- a/frappe/public/js/frappe/views/reports/report_utils.js
+++ b/frappe/public/js/frappe/views/reports/report_utils.js
@@ -30,7 +30,8 @@ frappe.report_utils = {
colors: colors,
axisOptions: {
shortenYAxisNumbers: 1,
- xAxisMode: 'tick'
+ xAxisMode: 'tick',
+ numberFormatter: frappe.utils.format_chart_axis_number,
}
};
diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js
index 6880d472d3..2ea780c13d 100644
--- a/frappe/public/js/frappe/views/reports/report_view.js
+++ b/frappe/public/js/frappe/views/reports/report_view.js
@@ -529,7 +529,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
truncateLegends: 1,
colors: ['#70E078', 'light-blue', 'orange', 'red'],
axisOptions: {
- shortenYAxisNumbers: 1
+ shortenYAxisNumbers: 1,
+ numberFormatter: frappe.utils.format_chart_axis_number,
},
tooltipOptions: {
formatTooltipY: value => frappe.format(value, get_df(this.chart_args.y_axes[0]), { always_show_decimals: true, inline: true }, get_doc(value.doc))
From dcbf320b8b75496e56e4b72f889ddd169ade8693 Mon Sep 17 00:00:00 2001
From: Vladislav
Date: Mon, 18 Jul 2022 16:14:35 +0300
Subject: [PATCH 0111/2449] fix: update ru translation (#17512)
---
frappe/translations/ru.csv | 291 ++++++++++++++++++-------------------
1 file changed, 138 insertions(+), 153 deletions(-)
diff --git a/frappe/translations/ru.csv b/frappe/translations/ru.csv
index 5e333c8e9d..bcd02baf93 100644
--- a/frappe/translations/ru.csv
+++ b/frappe/translations/ru.csv
@@ -107,7 +107,7 @@ Hourly,Почасовой,
Hub Sync ID,Идентификатор синхронизации концентратора,
IP Address,IP адрес,
Image,Изображение,
-Image View,Просмотр изображения,
+Image View,Просмотр изображений,
Import Data,Импорт данных,
Import Log,Лог импорта,
Inactive,Неактивный,
@@ -246,7 +246,7 @@ Start Import,Начать импорт,
State,Состояние,
Stopped,Приостановлено,
Subject,Тема,
-Submit,Провести,
+Submit,Подписать,
Successful,Успешно,
Summary,Резюме,
Sunday,Воскресенье,
@@ -445,7 +445,7 @@ Append To can be one of {0},Добавить к может быть одним
Append To is mandatory for incoming mails,Добавить к является обязательным для входящих сообщений,
"Append as communication against this DocType (must have fields, ""Status"", ""Subject"")","Добавить как коммуникацию для этого DocType (должен иметь поля, ""Статус"", ""Тема"")",
Applicable Document Types,Применимые типы документов,
-Apply,Подать заявление,
+Apply,Применить,
Apply Strict User Permissions,Применение строгих пользовательских разрешений,
Apply To All Document Types,Применить ко всем типам документов,
Apply this rule if the User is the Owner,"Применить это правило, если пользователь является владелец",
@@ -679,8 +679,8 @@ Client Information,Информация о клиенте,
Client Script,Скрипт клиента,
Client URLs,URL-адреса клиентов,
Client side script extensions in Javascript,Расширения клиентский сценарий в Javascript,
-Collapsible,Складной,
-Collapsible Depends On,Складные Зависит от,
+Collapsible,Сворачиваемый,
+Collapsible Depends On,Сворачиваемый - зависит от,
Column,Колонка,
Column {0} already exist.,Столбец {0} уже существует.,
Column Break,Разрыв столбца,
@@ -706,7 +706,8 @@ Compiled Successfully,Успешно скомпилировано,
Complete By,Завершить до,
Complete Registration,Полная регистрация,
Complete Setup,Завершение установки,
-Completed By,Завершено,
+Completed By,Завершил(а),
+Completed On,Завершено,
Compose Email,Написать письмо,
Condition Detail,Детализация условий,
Conditions,Условия,
@@ -755,8 +756,6 @@ Created Custom Field {0} in {1},Дата создания настраиваем
Created On,Дата создания,
Criticism,Критика,
Criticize,Критиковать,
-Ctrl + Down,Ctrl + Down,
-Ctrl + Up,Ctrl + Up,
Ctrl+Enter to add comment,"Ctrl+Enter, чтобы добавить комментарий",
Currency Name,Название валюты,
Currency Precision,Точность валюты,
@@ -879,7 +878,7 @@ Disable SMTP server authentication,Отключить аутентификаци
Disable Sidebar Stats,Отключить статистику боковой панели,
Disable Signup,Отключение Регистрация,
Disable Standard Email Footer,Отключить стандартный нижний колонтитул электронной почты,
-Discard,Отбросить,
+Discard,Отменить,
Display,Показать,
Display Depends On,Показание зависит от,
Do not allow user to change after set the first time,Не позволяйте пользователю изменять после установить в первый раз,
@@ -1120,11 +1119,11 @@ First Transaction,Первая сделка,
First data column must be blank.,Первая колонка данных должна быть пустой.,
First set the name and save the record.,Сначала задайте имя и сохраните запись.,
Flag,Флаг,
-Float,Сплавы,
-Float Precision,Float Precision,
-Fold,Сложить,
-Fold can not be at the end of the form,Fold не может быть в конце виде,
-Fold must come before a Section Break,Сложите должны прийти до перерыва раздел,
+Float,Дробное,
+Float Precision,Плавающая точность,
+Fold,Сворачиваемое,
+Fold can not be at the end of the form,Сворачиваемое поле не может быть в конце формы,
+Fold must come before a Section Break,Сворачиваемое должно идти до разрыва раздел,
Folder,Папка,
Folder name should not include '/' (slash),Имя папки не должно включать «/» (косая черта),
Folder {0} is not empty,Папка {0} не пуста,
@@ -1240,15 +1239,12 @@ Home Settings,Домашние настройки,
Home/Test Folder 1,Главная/Тестовая Папка 1,
Home/Test Folder 1/Test Folder 3,Главная/Тестовая Папка 1/Тестовая Папка 3,
Home/Test Folder 2,Главная/Тестовая Папка 2,
-Host,Host,
-Hostname,Hostname,
"How should this currency be formatted? If not set, will use system defaults","Как следует отображать числа в этой валюте? Если не указано, то будут использоваться системные значения",
-I found these: ,Я нашел следующее:,
+I found these: ,Я нашел это: ,
ID,ID,
ID (name) of the entity whose property is to be set,"ID (имя) лица, имущество которого должно быть установлено",
Icon will appear on the button,Иконка появится на кнопке,
Identity Details,Сведения о личности,
-Idx,Idx,
"If Apply Strict User Permission is checked and User Permission is defined for a DocType for a User, then all the documents where value of the link is blank, will not be shown to that User","Если флажок Apply Strict User Permission установлен, а для пользователя DocType для пользователя задано разрешение пользователя, тогда все документы, где значение ссылки пустым, не будут показаны этому пользователю",
If Checked workflow status will not override status in list view,"Если установлен флажок, статус процесса не будет отменять статус в журнале",
If Owner,Если владелец,
@@ -1303,7 +1299,7 @@ In Filter,В фильтрe,
In Global Search,В глобальном поиске,
In Grid View,В табличном виде,
In Hours,В час,
-In List View,В виде списка,
+In List View,Отображать в списке,
In Preview,В предварительном просмотре,
In Reply To,В ответ на,
In Standard Filter,В стандартный фильтр,
@@ -1322,7 +1318,7 @@ Index,Индекс,
Indicator,Индикатор,
Info,Информация,
Info:,Информация:,
-Initial Sync Count,Первоначальная синхронизация Count,
+Initial Sync Count,Первоначальная синхронизация,
InnoDB,InnoDB,
Insert Above,Вставить сверху,
Insert After,Вставить после,
@@ -1333,7 +1329,7 @@ Insert Column Before {0},Вставить столбец до {0},
Insert Style,Вставьте стиль,
Insert new records,Вставить новые записи,
Instructions Emailed,Инструкции отправлены по электронной почте,
-Insufficient Permission for {0},Недостаточное разрешение для {0},
+Insufficient Permission for {0},Недостаточно прав для {0},
Int,Интервал,
Integration Request,Интеграция заявки,
Integration Request Service,Интеграция заявки на обслуживание,
@@ -1374,33 +1370,30 @@ Invalid recipient address,Неверный адрес получателя,
Invalid {0} condition,Недопустимое условие {0},
Inverse,Обратный,
Is,Является,
-Is Attachments Folder,Является папкой вложений,
-Is Child Table,Является дочерней таблицей,
+Is Attachments Folder,Это папка для вложений,
+Is Child Table,Это дочерняя таблицей,
Is Custom Field,Это нестандартное поле,
Is First Startup,Первый запуск,
-Is Folder,Папка,
+Is Folder,Это папка,
Is Global,Является глобальным,
Is Globally Pinned,Глобально закреплено,
Is Home Folder,Является корневой папкой,
Is Mandatory Field,Является обязательным полем,
Is Pinned,Прикреплено,
-Is Primary Contact,Основной контакт,
+Is Primary Contact,Это основной контакт,
Is Private,Является приватным,
-Is Published Field,Есть Опубликовано поле,
-Is Published Field must be a valid fieldname,Опубликовано Поле должно быть действительным имя_полем,
+Is Published Field,Это опубликованое поле,
+Is Published Field must be a valid fieldname,Опубликованое роле должно быть допустимым именем поля,
Is Single,Единственный,
-Is Spam,Спам,
-Is Standard,Стандартный отчёт,
+Is Spam,Это спам,
+Is Standard,Это стандартный отчёт,
Is Submittable,Подлежит исполнению,
Is Table,Является таблицей,
Is Your Company Address,Является адресом вашей компании,
It is risky to delete this file: {0}. Please contact your System Manager.,"Рискованно удалять этот файл: {0}. Пожалуйста, обратитесь к менеджеру системы.",
Item cannot be added to its own descendents,Продукт не может быть добавлен к своим подпродуктам,
-JS,JS,
-JSON,JSON,
JavaScript Format: frappe.query_reports['REPORTNAME'] = {},Формат JavaScript: frappe.query_reports ['REPORTNAME'] = {},
Javascript to append to the head section of the page.,Javascript для добавления к головной части страницы.,
-Jinja,Jinja,
John Doe,Джон Доу,
Kanban,Канбан,
Kanban Board Column,Колонка канбан-доски,
@@ -1480,7 +1473,7 @@ List,Список,
List Filter,Фильтр списка,
List View Setting,Настройка просмотра списка,
List a document type,Перечислите тип документа,
-"List as [{""label"": _(""Jobs""), ""route"":""jobs""}]","Список как [{""Ярлык"": _(""Работы""), ""маршруты"":""работы""}]",
+"List as [{""label"": _(""Jobs""), ""route"":""jobs""}]","Список как [{""Метка"": _(""Работы""), ""маршруты"":""работы""}]",
List of backups available for download,"Список резервных копий, доступных для загрузки",
List of patches executed,Список выполненных патчей,
List of themes for Website.,Список тем для сайта.,
@@ -1513,7 +1506,7 @@ Long Text,Длинный текст,
Looks like something is wrong with this site's Paypal configuration.,"Похоже, что что-то не так с конфигурацией Paypal этого сайта.",
Looks like something is wrong with this site's payment gateway configuration. No payment has been made.,"Похоже, что-то не так с конфигурацией платежного шлюза этого сайта. Платеж не был выполнен.",
"Looks like something went wrong during the transaction. Since we haven't confirmed the payment, Paypal will automatically refund you this amount. If it doesn't, please send us an email and mention the Correlation ID: {0}.","Похоже, что-то пошло не так во время транзакции. Поскольку мы не подтвердили платеж, Paypal автоматически вернет вам эту сумму. Если это не так, отправьте нам электронное письмо и укажите идентификатор корреляции: {0}.",
-Madam,Госпожа,
+Madam,Мадам,
Main Section,Основной раздел,
"Make ""name"" searchable in Global Search","Индексировать ""name"" для глобального поиска",
Make use of longer keyboard patterns,Используйте более длинных моделей клавиатуры,
@@ -1540,7 +1533,7 @@ Max Value,Макс. значение,
Max width for type Currency is 100px in row {0},Макс. ширина для типа валюты 100px в строке {0},
Maximum Attachment Limit for this record reached.,Достигнут предел вложений для этой записи.,
Maximum {0} rows allowed,Макс. {0} строк разрешено,
-"Meaning of Submit, Cancel, Amend","Значение Провести, Отменить, Изменить",
+"Meaning of Submit, Cancel, Amend","Значение Подписать, Отменить, Изменить",
Mention transaction completion page URL,URL-ссылка на страницу-упоминание о завершении транзакции,
Mentions,Упоминания,
Menu,Меню,
@@ -1606,14 +1599,14 @@ New Chat,Новый чат,
New Comment on {0}: {1},Новый комментарий к {0}: {1},
New Connection,Новое соединение,
New Custom Print Format,Новый пользовательский печатный бланк,
-New Email,Новая электронная почта,
+New Email,Новое письмо,
New Email Account,Новый аккаунт электронной почты,
New Event,Новое событие,
New Folder,Новая папка,
New Kanban Board,Новая панель канбан,
New Message from Website Contact Page,Новое сообщение с формы обратной связи на сайте,
New Name,Новое имя,
-New Newsletter,Новый бюллетень,
+New Newsletter,Новая новость,
New Password,Новый пароль,
New Password Required.,Требуется новый пароль.,
New Print Format Name,Название нового печатного бланка,
@@ -1672,8 +1665,8 @@ No template found at path: {0},Нет шаблона по адресу: {0},
No {0} found,{0} не найдено,
No {0} mail,Нет {0} почта,
No {0} permission,Нет {0} разрешение,
-None: End of Workflow,Ни один: Конец потока,
-Not Allowed: Disabled User,Не разрешено: отключен пользователь,
+None: End of Workflow,Нет: конец рабочего процесса,
+Not Allowed: Disabled User,Не разрешено: пользователь отключен,
Not Ancestors Of,Не предки,
Not Descendants Of,Не потомки,
Not Equals,Не равно,
@@ -1688,7 +1681,7 @@ Not a valid Comma Separated Value (CSV File),"Не является допуст
Not a valid User Image.,Недействительный изображение пользователя.,
Not a valid Workflow Action,Недоступное действие рабочего-процесса,
Not a valid user,Не является действительным пользователем,
-Not a zip file,Не zip файл,
+Not a zip file,Не является zip файлом,
Not allowed for {0}: {1},Не разрешено для {0}: {1},
Not allowed for {0}: {1} in Row {2}. Restricted field: {3},Недопустимо для {0}: {1} в строке {2}. Запрещенное поле: {3},
Not allowed for {0}: {1}. Restricted field: {2},Не допускается для {0}: {1}. Запрещенное поле: {2},
@@ -1732,11 +1725,11 @@ OTP Secret has been reset. Re-registration will be required on next login.,OTP S
OTP secret can only be reset by the Administrator.,Секрет OTP может быть сброшен администратором.,
Office,Офис,
Office 365,Офис 365,
-Old Password,Старый Пароль,
-Old Password Required.,Требуется старый пароль.,
+Old Password,Старый пароль,
+Old Password Required.,Старый пароль обязателен.,
Older backups will be automatically deleted,Более старые резервные копии будут автоматически удалены,
"On {0}, {1} wrote:","На {0}, {1} писал:",
-"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.",После отправки поданные документы не могут быть изменены. Они могут быть только отменены и исправлены.,
+"Once submitted, submittable documents cannot be changed. They can only be Cancelled and Amended.",После отправки подписанные документы не могут быть изменены. Они могут быть только отменены и исправлены.,
"Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger).","После такой установки, пользователи получат доступ только к документам (например, сообщениям в блоге), связанным с этими разрешениями пользователя (например, блоггера).",
One Last Step,Последний шаг,
One Time Password (OTP) Registration Code from {},Одноразовый пароль (OTP) Регистрационный код от {},
@@ -1760,7 +1753,7 @@ Open a dialog with mandatory fields to create a new record quickly,"Открой
Open a module or tool,Открыть модуль или инструмент,
Open your authentication app on your mobile phone.,Откройте приложение для проверки подлинности на своем мобильном телефоне.,
Open {0},Открыть {0},
-Opened,Открыт,
+Opened,Открыть,
Operator must be one of {0},Оператор должен быть одним из {0},
Option 1,Опция 1,
Option 2,Опция 2,
@@ -1778,9 +1771,7 @@ Org History Heading,Org История Заголовок,
Orientation,Ориентация,
Original Value,Первоначальная стоимость,
Outgoing email account not correct,Исходящая учетная запись электронной почты не верна,
-Outlook.com,Outlook.com,
Output,Вывод,
-PDF,PDF,
PDF Page Size,Размер PDF страницы,
PDF Settings,Настройки PDF,
PDF generation failed,Не удалось сгенерировать PDF-файл,
@@ -1790,17 +1781,17 @@ Page HTML,Страница HTML,
Page Length,Длина страницы,
Page Name,Имя страницы,
Page Settings,Настройки страницы,
-Page has expired!,Страница просрочена!,
+Page has expired!,Срок действия страницы истек!,
Page not found,Страница не найдена,
Page to show on the website\n,Страница для показа на сайте,
Pages in Desk (place holders),Страницы-заглушки,
Parent,Родитель,
Parent Error Snapshot,Родитель снимка ошибки,
Parent Label,Родительская метка,
-Parent Table,Родитель Таблица,
+Parent Table,Родительская таблица,
Parent is required to get child table data,Родитель обязан получать данные дочерней таблицы,
Parent is the name of the document to which the data will get added to.,"Родитель - это имя документа, к которому будут добавлены данные.",
-Partial Success,Частичный успех,
+Partial Success,Выполнено не полностью,
Partially Successful,Частично успешный,
Participants,Участники,
Passive,Пассивный,
@@ -1903,14 +1894,14 @@ Please specify which date field must be checked,"Просьба уточнить
Please specify which value field must be checked,"Просьба уточнить, какие значения поля должны быть проверены",
Please try again,"Пожалуйста, попробуйте еще раз",
Please verify your Email Address,"Пожалуйста, подтвердите свой адрес электронной почты",
-Point Allocation Periodicity,Периодичность распределения точек,
+Point Allocation Periodicity,Периодичность распределения баллов,
Points,Баллы,
Points Given,Баллы засчитаны,
Port,Порт,
Portal Menu,Меню портала,
-Portal Menu Item,Портал Пункт меню,
+Portal Menu Item,Пункт меню портала,
Post,Опубликовать,
-Post Comment,Оставьте комментарий,
+Post Comment,Оставить комментарий,
Postal,Почтовый,
Postal Code,Почтовый индекс,
Postprocess Method,Метод постпроцесса,
@@ -1966,15 +1957,6 @@ Provider Name,Имя поставщика,
Public Key,Открытый ключ,
Publishable Key,Ключ для публикации,
Published On,Опубликовано на,
-Pull,Тянуть,
-Pull Failed,Не удалось выполнить Pull,
-Pull Insert,Вставить вкладыш,
-Pull Update,Pull Update,
-Push,От себя,
-Push Delete,Нажмите Удалить,
-Push Failed,Ошибка отправки,
-Push Insert,Push Insert,
-Push Update,Push Update,
Python Module,Модуль Python,
Pyver,Pyver,
QR Code,QR код,
@@ -1986,7 +1968,7 @@ Query,Запрос,
Query Report,Отчёт-выборка,
Query must be a SELECT,Запрос должен быть ВЫБОР,
Queue should be one of {0},Очередь должна быть одной из {0},
-Queued for backup. It may take a few minutes to an hour.,Queued для резервного копирования. Это может занять несколько минут до часа.,
+Queued for backup. It may take a few minutes to an hour.,В очереди для резервного копирования. Это может занять от несколько минут до часа.,
Queued for backup. You will receive an email with the download link,Очередь для резервного копирования. Вы получите электронное письмо с ссылкой для загрузки,
Quick Help for Setting Permissions,Быстрая помощь при настройки прав доступа,
Rating: ,Рейтинг: ,
@@ -2091,8 +2073,8 @@ Retake,пересдавать,
Retry,Повторить,
Return to the Verification screen and enter the code displayed by your authentication app,"Вернитесь на экран проверки и введите код, отображаемый приложением для аутентификации.",
Reverse Icon Color,Обратный цвет значка,
-Revert,Вернуть,
-Revert Of,Вернуть из,
+Revert,Возврат,
+Revert Of,Возвращенно из,
Reverted,Отменено,
Review Level,Уровень обзора,
Review Levels,Уровни обзора,
@@ -2101,9 +2083,8 @@ Reviews,Отзывы,
Revoke,Аннулировать,
Revoked,Аннулировано,
Rich Text,Форматированный текст,
-Robots.txt,Robots.txt,
-Role Name,Имя роли,
-Role Permission for Page and Report,Роль Разрешение на страницу и отчет,
+Role Name,Название роли,
+Role Permission for Page and Report,Разрешение роли для страницы и отчета,
Role Permissions,Разрешения роли,
Role Profile,Профиль ролей,
Role and Level,Роль и уровень,
@@ -2198,7 +2179,7 @@ Select Print Format,Выберите бланк для печати,
Select Print Format to Edit,Выберите печатный бланк для редактирования,
Select Role,Выберите роль,
Select Table Columns for {0},Выберите столбцы таблицы для {0},
-Select Your Region,Выберите регион,
+Select Your Region,Выберите ваш регион,
Select a Brand Image first.,Выберите бренд изображение в первую очередь.,
Select a DocType to make a new format,"Выберите DOCTYPE, чтобы сделать новый бланк",
Select a chat to start messaging.,"Выберите чат, чтобы начать обмен сообщениями.",
@@ -2234,7 +2215,6 @@ Send only if there is any data,"Отправить только если ест
Send unsubscribe message in email,Отправить сообщение об отказе от подписки на электронную почту,
Sender,Отправитель,
Sender Email,Электронная почта отправителя,
-Sendgrid,Sendgrid,
Sent Read Receipt,Отправлять уведомление о прочтении,
Sent or Received,Отправлено или получено,
Sent/Received Email,Отправлено/Получено письмо,
@@ -2279,8 +2259,8 @@ Setup Reports to be emailed at regular intervals,Настройка регуля
Share,Поделиться,
Share URL,Поделиться URL,
Share With,Поделиться с,
-Share this document with,Поделитесь этот документ с,
-Share {0} with,Поделиться {0},
+Share this document with,Поделиться этим документом с,
+Share {0} with,Поделиться {0} с,
Shared,Общий,
Shared With,Совместно с,
Shared with everyone,Общий для всех,
@@ -2325,8 +2305,6 @@ Single Post (article).,Один пост(статья).,
Single Types have only one record no tables associated. Values are stored in tabSingles,"Холост Типы нет только одна запись не таблицы, связанные. Значения сохраняются в tabSingles",
Skip Authorization,Пропустить авторизацию,
Skip rows with errors,Пропустить строки с ошибками,
-Skype,Skype,
-Slack,Slack,
Slack Channel,Slack канал,
Slack Webhook Error,Slack Webhook ошибка,
Slack Webhook URL,Неверный URL веб-хостинга,
@@ -2350,14 +2328,13 @@ Something went wrong while generating dropbox access token. Please check error l
Sorry! I could not find what you were looking for.,"Извините! Я не мог найти то, что вы ищете.",
Sorry! Sharing with Website User is prohibited.,Извините! Поделиться с сайта Пользователю запрещается.,
Sorry! User should have complete access to their own record.,Извините! Пользователь должен иметь полный доступ к своей записи.,
-Sorry! You are not permitted to view this page.,Извините! Вам не разрешается для просмотра этой страницы.,
+Sorry! You are not permitted to view this page.,Извините! У вас нет разрешений для просмотра этой страницы.,
"Sorry, you're not authorized.","Извините, вы не авторизованы.",
Sort Field,Сортировать поле,
Sort Order,Порядок сортировки,
Sort field {0} must be a valid fieldname,Сортировка поля {0} должен быть действительным имя_поля,
Source Text,Исходный текст,
Spam,Спам,
-SparkPost,SparkPost,
Special Characters are not allowed,Спецсимволы не допустимы,
"Standard DocType cannot have default print format, use Customize Form","Стандартный DocType не может иметь формат печати по умолчанию, используйте Настроить форму",
Standard Print Format cannot be updated,Стандартный печатный бланк не может быть обновлен,
@@ -2371,12 +2348,11 @@ Start Date Field,Поле начальной даты,
Start a conversation.,Начните разговор.,
Start entering data below this line,Начните вводить данные ниже этой линии,
Start new Format,Начать новую Формат,
-StartTLS,StartTLS,
Started,Начал,
Starting Frappe ...,Запуск Frappé ...,
Starts on,Начало,
-States,Статусы,
-"States for workflow (e.g. Draft, Approved, Cancelled).","Статусы бизнес-процесса (например: черновик, утверждён, отменён).",
+States,Состояния,
+"States for workflow (e.g. Draft, Approved, Cancelled).","Состояния рабочего-процесса (например: черновик, утверждён, отменён).",
Static Parameters,Статические параметры,
Stats based on last month's performance (from {0} to {1}),Статистика на основе результатов прошлого месяца (от {0} до {1}),
Stats based on last week's performance (from {0} to {1}),Статистика на основе результатов прошлой недели (от {0} до {1}),
@@ -2396,7 +2372,7 @@ Subdomain,Субдомен,
Subject Field,Поле темы,
Submit after importing,Отправить после импорта,
Submit an Issue,Отправить вопрос,
-Submit this document to confirm,Провести этот документ для подтверждения,
+Submit this document to confirm,Подписать этот документ для подтверждения,
Submit {0} documents?,Отправить {0} документы?,
Submitting {0},Помещение {0},
Submitted Document cannot be converted back to draft. Transition row {0},Проведенный Документ не может быть преобразован обратно в проект. Переходная строка {0},
@@ -2437,8 +2413,6 @@ Team Members,Члены команды,
Team Members Heading,Члены команды Возглавлять,
Temporarily Disabled,Временно отключен,
Test Email Address,Проверить адрес электронной почты,
-Test Runner,Тест Runner,
-Test_Folder,Test_Folder,
Text,Текст,
Text Align,Выравнивание текста,
Text Color,Цвет текста,
@@ -2463,8 +2437,8 @@ The system provides many pre-defined roles. You can add new roles to set finer p
The user from this field will be rewarded points,Пользователь из этого поля будет вознагражден баллами,
Theme,Тема,
Theme URL,URL темы,
-There can be only one Fold in a form,Там может быть только один Fold в виде,
-There is an error in your Address Template {0},Существует ошибка в адресной Шаблон {0},
+There can be only one Fold in a form,В форме может быть только один Fold,
+There is an error in your Address Template {0},Ошибка в вашем шаблоне адреса {0},
There is no data to be exported,Нет данных для экспорта,
There is some problem with the file url: {0},Существует некоторая проблема с файловой URL: {0},
There must be atleast one permission rule.,Там должно быть по крайней мере один правило разрешения.,
@@ -2502,7 +2476,7 @@ This is similar to a commonly used password.,Это похоже на обычн
This is the template file generated with only the rows having some error. You should use this file for correction and import.,"Это файл шаблона, сгенерированный только строками с некоторой ошибкой. Вы должны использовать этот файл для исправления и импорта.",
This link has already been activated for verification.,Эта ссылка уже была активирована для проверки.,
This link is invalid or expired. Please make sure you have pasted correctly.,"Эта ссылка является недействительным или истек. Пожалуйста, убедитесь, что вы вставили правильно.",
-This may get printed on multiple pages,Это может быть напечатано на нескольких страницах,
+This may get printed on multiple pages,Это будет напечатано на нескольких страницах,
This month,Этот месяц,
This query style is discontinued,Этот стиль запроса прекращен,
This report was generated on {0},Этот отчет был создан в {0},
@@ -2511,7 +2485,6 @@ This request has not yet been approved by the user.,Этот запрос еще
This role update User Permissions for a user,Эта роль обновляет разрешения пользователя для пользователя,
This will log out {0} from all other devices,Это выведет {0} из всех других устройств,
This will permanently remove your data.,Это навсегда удалит ваши данные.,
-Throttled,Throttled,
Thumbnail URL,Миниатюра URL,
Time Interval,Интервал времени,
Time Series,Временные ряды,
@@ -2529,7 +2502,7 @@ Timeseries,Временные ряды,
Timestamp,Временная отметка,
Title Case,Название дела,
Title Field,Название поля,
-Title Prefix,Название Приставка,
+Title Prefix,Название приставки,
Title field must be a valid fieldname,Название поля должно быть допустимым имя_поля,
To Date Field,Поле даты,
To Do,Список дел,
@@ -2589,7 +2562,7 @@ Type:,Тип:,
UID,UID,
UIDNEXT,UIDNEXT,
UIDVALIDITY,UIDVALIDITY,
-UNSEEN,НЕПРОЧИТАННО,
+UNSEEN,НЕПРОЧИТАННЫЕ,
UPPER CASE,ВЕРХНИЙ РЕГИСТР,
"URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n e.g. http://hostname//api/method/frappe.www.login.login_via_facebook","Идентификаторы URI для получения кода авторизации, как только пользователь разрешает доступ, а также ответы недостаточность. Как правило, конечная точка REST подвергается Клиентом App. например, HTTP: //hostname//api/method/frappe.www.login.login_via_facebook",
URLs,URL-адрес,
@@ -2691,7 +2664,6 @@ Value for {0} cannot be a list,Значение {0} не может быть с
Value missing for,Нет значения для,
Value too big,Слишком большое значение,
Values Changed,Значения изменено,
-Verdana,Verdana,
Verfication Code,Код проверки,
Verification Link,Ссылка для проверки,
Verification code has been sent to your registered email address.,Код подтверждения отправлен на ваш адрес электронной почты регистрации.,
@@ -2712,7 +2684,7 @@ View document,Просмотр документа,
View report in your browser,Просмотр отчета в вашем браузере,
View this in your browser,Просмотреть это в вашем браузере,
View {0},Просмотреть {0},
-Viewed By,просмотрены,
+Viewed By,Просмотрено,
Visit,Посетите нас по адресу,
Visitor,Посетитель,
We have received a request for deletion of {0} data associated with: {1},"Мы получили запрос на удаление {0} данных, связанных с: {1}",
@@ -2721,7 +2693,7 @@ Web Form,Веб форма,
Web Form Field,Поле веб формы,
Web Form Fields,Поля веб формы,
Web Page,Веб-страница,
-Web Page Link Text,Web Текст Ссылка на страницу,
+Web Page Link Text,Текст ссылки веб-страницы,
Web Site,Веб-сайт,
Web View,Web View,
Webhook,Webhook,
@@ -2765,9 +2737,7 @@ Workflow state represents the current state of a document.,Состояние р
Write,Написать,
Wrong fieldname {0} in add_fetch configuration of custom script,Неверное имя поля {0} в конфигурации add_fetch пользовательского скрипта,
X Axis Field,Поле оси X,
-XLSX,XLSX,
Y Axis Fields,Поля оси Y,
-Yahoo Mail,Yahoo Mail,
Yandex.Mail,Яндекс.Почта,
Yesterday,Вчера,
You are connected to internet.,Вы подключены к Интернету.,
@@ -2783,14 +2753,14 @@ You are not permitted to view the newsletter.,У Вас нет прав для
You are now following this document. You will receive daily updates via email. You can change this in User Settings.,Вы подписаны на обновления данного документа. Вы будете получать ежедневные обновления по электронной почте. Вы можете изменить это в настройках пользователя.,
You can add dynamic properties from the document by using Jinja templating.,Вы можете добавить динамические свойства из документа с помощью шаблонов Jinja.,
You can also copy-paste this ,Вы также можете скопировать и вставить это ,
-"You can change Submitted documents by cancelling them and then, amending them.","Вы можете изменить Проведенные (Submitted) документы, отменив их, а затем изменив их.",
+"You can change Submitted documents by cancelling them and then, amending them.","Вы можете изменить утвержденные документы, отменив их, а затем отредактировав.",
You can find things by asking 'find orange in customers',Можно искать что-либо написав «найти апельсин у клиентов»,
You can only upload upto 5000 records in one go. (may be less in some cases),"Вы можете загружать одновременно до 5000 записей. (Возможно меньше, в некоторых случаях)",
You can use Customize Form to set levels on fields.,Вы можете использовать Настройку формы (Customize Form) для установки уровней для полей.,
You can use wildcard %,Вы можете использовать подстановочные %,
You can't set 'Options' for field {0},Нельзя включить «Опции» для поля {0},
You can't set 'Translatable' for field {0},Вы не можете установить «Переводимый» для поля {0},
-You cannot give review points to yourself,Вы не можете давать оценки себе,
+You cannot give review points to yourself,Вы не можете не можете начислять себе баллы обзора,
You cannot unset 'Read Only' for field {0},Нельзя отменить «только чтение» для поля {0},
You do not have enough permissions to access this resource. Please contact your manager to get access.,"У вас нет достаточных прав для доступа к этому ресурсу. Пожалуйста, обратитесь к своему менеджеру, чтобы получить доступ.",
You do not have enough permissions to complete the action,У Вас нет достаточных прав для завершения действия,
@@ -2811,13 +2781,13 @@ You need to be in developer mode to edit a Standard Web Form,Вы должны
You need to be logged in and have System Manager Role to be able to access backups.,"Вы должны войти в систему (и иметь роль менеджера системы), чтобы иметь доступ к резервным копиям.",
You need to be logged in to access this {0}.,"Вы должны войти, чтобы получить доступ к {0}.",
"You need to have ""Share"" permission","Вы должны иметь разрешение ""Поделиться""",
-You need write permission to rename,"Вам нужно письменное разрешение, чтобы переименовать",
+You need write permission to rename,"Вам нужно разрешение на запись, чтобы переименовать",
You selected Draft or Cancelled documents,Вы выбрали черновик или отмененные документы,
You unfollowed this document,Вы отписались от этого документа,
Your Country,Ваша страна,
Your Language,Ваш язык,
Your Name,Ваше имя,
-Your account has been locked and will resume after {0} seconds,Ваша учетная запись заблокирована и возобновится после {0} секунд,
+Your account has been locked and will resume after {0} seconds,Ваша учетная запись заблокирована и будет доступна через {0} секунд,
Your connection request to Google Calendar was successfully accepted,Ваш запрос на подключение к Календарю Google был успешно принят,
Your information has been submitted,Ваша информация была представлена,
Your login id is,Ваш ID для авторизации,
@@ -2830,28 +2800,26 @@ Your payment was successfully accepted,Ваш платёж был успешно
"Your session has expired, please login again to continue.","Ваша сессия истекла, пожалуйста, войдите снова, чтобы продолжить.",
Zero,Ноль,
Zero means send records updated at anytime,При нуле обновленные записи отправляются в любое время,
-_doctype,_doctype,
-_report,_report,
adjust,настроить,
after_insert,после_вставки,
-align-center,выровнять-центр,
-align-justify,выровнять-оправдать,
-align-left,выровнять левый,
-align-right,выровнять правый,
+align-center,выровнять-по-центру,
+align-justify,выровнять-по-ширине,
+align-left,выровнять-по-левой-стороне,
+align-right,выровнять-по-правой-стороне,
ap-northeast-1,ар-северо-восток-1,
ap-northeast-2,ар-северо-восток-2,
ap-northeast-3,ар-северо-восток-3,
ap-south-1,ар-юго-1,
ap-southeast-1,ар-юго-восток-1,
ap-southeast-2,ар-юго-восток-2,
-arrow-down,Стрелка вниз,
-arrow-left,стрелка налево,
-arrow-right,стрелка направо,
-arrow-up,Стрелка вверх,
+arrow-down,стрелка-вниз,
+arrow-left,стрелка-налево,
+arrow-right,стрелка-направо,
+arrow-up,стрелка-вверх,
asterisk,звёздочка,
backward,назад,
-ban-circle,Запрет круга,
-bell,Накладная,
+ban-circle,бан-кружок,
+bell,колокольчик,
bookmark,закладка,
briefcase,портфель,
bullhorn,рупор,
@@ -2909,11 +2877,10 @@ gave {0} points,дал {0} баллов,
gift,подарок,
glass,стекло,
globe,глобус,
-hand-down,ручной вниз,
-hand-left,ручной левый,
-hand-right,ручной право,
-hand-up,грабитель,
-hdd,жесткий диск,
+hand-down,рука-вниз,
+hand-left,рука-влево,
+hand-right,рука-вправо,
+hand-up,рука-вверх,
headphones,наушники,
heart,сердце,
hub,хаб,
@@ -3011,7 +2978,7 @@ zoom-in,приблизить,
zoom-out,отдалить,
{0} Calendar,{0} Календарь,
{0} Chart,{0} Диаграмма,
-{0} Dashboard,{0} Панель инструментов,
+{0} Dashboard,{0} Показатели,
{0} List,{0} Список,
{0} Modules,{0} Модули,
{0} Report,{0} Отчет,
@@ -3028,7 +2995,7 @@ zoom-out,отдалить,
{0} appreciated {1},{0} признателен {1},
{0} appreciation point for {1} {2},{0} благодарность за {1} {2},
{0} appreciation points for {1} {2},{0} баллы за {1} {2},
-{0} assigned {1}: {2},{0} назначено {1}: {2},
+{0} assigned {1}: {2},{0} назначил(а) {1}: {2},
{0} cannot be set for Single types,{0} не может быть установлена для отдельных видов,
{0} comments,{0} комментариев,
{0} created successfully,{0} создано успешно,
@@ -3053,7 +3020,7 @@ zoom-out,отдалить,
{0} is not a valid Workflow State. Please update your Workflow and try again.,{0} не является допустимым состоянием рабочего процесса. Обновите свой рабочий процесс и повторите попытку.,
{0} is now default print format for {1} doctype,{0} — теперь формат печати по умолчанию для {1} doctype,
{0} is saved,{0} сохранено,
-{0} items selected,{0} продуктов выбрано,
+{0} items selected,{0} элементов выбрано,
{0} logged in,{0} авторизирован,
{0} logged out: {1},{0} вышел: {1},
{0} minutes ago,{0} минут назад,
@@ -3097,7 +3064,7 @@ zoom-out,отдалить,
{0}: Cannot set Assign Submit if not Submittable,"{0}: Не удается установить Назначить проведение, если не подлежит проведению",
{0}: Cannot set Cancel without Submit,{0}: Не удается установить Отмена без отправки,
{0}: Cannot set Import without Create,{0}: Не удается установить Импорт без Создать,
-"{0}: Cannot set Submit, Cancel, Amend without Write","{0}: Не удается выполнить Провести, Отменить, Изменить без Записать",
+"{0}: Cannot set Submit, Cancel, Amend without Write","{0}: Не удается выполнить Подписать, Отменить, Изменить без Записать",
{0}: Cannot set import as {1} is not importable,{0}: Не удается установить импорт как {1} не является ввозу,
{0}: No basic permissions set,{0}: Не установлен базовый набор разрешений,
"{0}: Only one rule allowed with the same Role, Level and {1}","{0}: только одно правило допускается для той же роли, уровня и {1}",
@@ -3146,7 +3113,7 @@ About {0} seconds remaining,Осталось {0} секунд,
Access Log,Журнал доступа,
Access not allowed from this IP Address,Доступ с этого IP-адреса запрещен,
Action Type,Тип действия,
-Activity Log by ,Активность Журнал по,
+Activity Log by ,Журнал активности по ,
Add Fields,Добавить поля,
Administration,Администрирование,
After Cancel,После отмены,
@@ -3162,7 +3129,7 @@ Allow Auto Repeat,Разрешить автоматическое повторе
Allow Google Calendar Access,Разрешить доступ к календарю Google,
Allow Google Contacts Access,Разрешить доступ к контактам Google,
Allow Google Drive Access,Разрешить доступ Google Drive,
-Allow Guest,Разрешить гость,
+Allow Guest,Разрешить гостя,
Allow Guests to Upload Files,Разрешить гостям загружать файлы,
Also adding the status dependency field {0},Также добавляем поле зависимости статуса {0},
An error occurred while setting Session Defaults,Произошла ошибка при настройке параметров сеанса по умолчанию,
@@ -3216,8 +3183,8 @@ Click on the link below to approve the request,"Нажмите на ссылку
Click on the lock icon to toggle public/private,"Нажмите на значок замка, чтобы переключить публичный / приватный",
Click on {0} to generate Refresh Token.,"Нажмите {0}, чтобы сгенерировать токен обновления.",
Close Condition,Закрыть условие,
-Column {0},Столбец {0},
-Columns / Fields,Колонны / Поля,
+Column {0},Колонка {0},
+Columns / Fields,Колонки / Поля,
"Configure notifications for mentions, assignments, energy points and more.","Настройте уведомления для упоминаний, назначений, энергетических очков и многое другое.",
Contact Email,Эл.почта для связи,
Contact Numbers,Контактные номера,
@@ -3234,8 +3201,7 @@ Could not create razorpay order,Не удалось создать заказ н
Create Log,Создать журнал,
Create your first {0},Создайте свой первый {0},
Created {0} records successfully.,Создано {0} записей успешно.,
-Cron,Cron,
-Cron Format,Крон Формат,
+Cron Format,Cron формат,
Daily Events should finish on the Same Day.,Ежедневные события должны заканчиваться в тот же день.,
Daily Long,Ежедневно,
Default Role on Creation,Роль по умолчанию при создании,
@@ -3257,10 +3223,10 @@ Document type is required to create a dashboard chart,Тип документа
Documentation Link,Документация Ссылка,
Don't Import,Не импортировать,
Don't Send Emails,Не отправлять электронные письма,
-"Drag and drop files, ","Перетащите файлы,",
+"Drag and drop files, ","Перетащите файлы, ",
Drop,Бросить,
Drop Here,Бросить тут,
-Drop files here,Перетащите файлы сюда,
+Drop files here,Поместите файлы сюда,
Dynamic Template,Динамический шаблон,
ERPNext Role,ERPNext роль,
Email / Notifications,Уведомления по электронной почте,
@@ -3407,7 +3373,7 @@ Last Update,Последнее обновление,
Last refreshed,Последнее обновление,
Link Document Type,Тип документа ссылки,
Link Fieldname,Имя поля ссылки,
-Loading import file...,Загрузка файла импорта...,
+Loading import file...,Загрузка импортируемого файла...,
Local Document Type,Локальный тип документа,
Log Data,Данные журнала,
Main Section (HTML),Основной раздел (HTML),
@@ -3415,7 +3381,7 @@ Main Section (Markdown),Основной раздел (Markdown),
"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.","Ведение журнала всех вставок, обновлений и удалений на сайте Event Producer для документов, имеющих потребителей.",
Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.,"Ведение журнала всех использованных событий, а также состояния синхронизации и кнопки Resync в случае сбоя синхронизации.",
Make all attachments private,Сделайте все вложения приватными,
-Mandatory Depends On,Обязательно зависит от,
+Mandatory Depends On,Обязателен - зависит от,
Map Columns,Столбцы карты,
Map columns from {0} to fields in {1},Сопоставить столбцы из {0} с полями в {1},
Mapping column {0} to field {1},Отображение столбца {0} в поле {1},
@@ -3426,7 +3392,7 @@ Me,Мне,
Mention,Упоминание,
Modules,Модули,
Monthly Long,Ежемесячно долго,
-Naming Series,Идентификация по Имени,
+Naming Series,Именование серии,
Navigate Home,Навигация Домой,
Navigate list down,Переместиться вниз по списку,
Navigate list up,Навигация по списку вверх,
@@ -3483,7 +3449,7 @@ Press Alt Key to trigger additional shortcuts in Menu and Sidebar,"Нажмит
Print Settings...,Настройки печати...,
Producer Document Name,Название документа производителя,
Producer URL,URL производителя,
-Property Depends On,Недвижимость зависит от,
+Property Depends On,Свойства зависят от,
Pull from Google Calendar,Загрузить из календаря Google,
Pull from Google Contacts,Загрузить из контактов Google,
Pulled from Google Calendar,Загружено из календаря Google,
@@ -3493,7 +3459,7 @@ Push to Google Contacts,Нажмите на контакты Google,
Queue / Worker,Очередь / Рабочий,
RAW Information Log,Необработанный информационный журнал,
Raw Printing Settings...,Настройки необработанной печати...,
-Read Only Depends On,Только чтение зависит от,
+Read Only Depends On,Только для чтения - зависит от,
Recent Activity,Недавняя активность,
Reference document has been cancelled,Справочный документ был отменен,
Reload File,Перезагрузить файл,
@@ -3526,6 +3492,7 @@ Select Date Range,Выберите диапазон дат,
Select Field,Выберите поле,
Select Field...,Выберите поле...,
Select Filters,Выберите фильтры,
+Edit Filters,Изменить фильтры,
Select Google Calendar to which event should be synced.,"Выберите календарь Google, к которому нужно синхронизировать событие.",
Select Google Contacts to which contact should be synced.,"Выберите Google Контакты, с которыми контакт должен быть синхронизирован.",
Select Group By...,Выбрать группу по...,
@@ -3646,7 +3613,7 @@ Workflow Status,Состояние рабочего процесса,
You are not allowed to export {} doctype,Вы не можете экспортировать {} doctype,
You can try changing the filters of your report.,Вы можете попробовать изменить фильтры вашего отчета.,
You do not have permissions to cancel all linked documents.,У вас нет прав для отмены всех связанных документов.,
-You need to create these first: ,Вам нужно сначала создать это: ,
+You need to create these first: ,Вам сначала нужно создать: ,
You need to enable JavaScript for your app to work.,Вам нужно включить JavaScript для вашего приложения для работы.,
You need to install pycups to use this feature!,"Вам нужно установить pycups, чтобы использовать эту функцию!",
Your Target,Ваша цель,
@@ -3708,7 +3675,7 @@ Currency,Валюта,
Customize,Настроить,
Daily,Ежедневно,
Date,Дата,
-Dear,Уважаемый (ая),
+Dear,Уважаемый(ая),
Default,По умолчанию,
Delete,Удалить,
Description,Описание,
@@ -3727,7 +3694,7 @@ Entity Type,Тип объекта,
Error,Ошибка,
Expired,Истек срок действия,
Export,Экспорт,
-Export not allowed. You need {0} role to export.,Экспорт не допускается. Вам нужно {0} роль для экспорта.,
+Export not allowed. You need {0} role to export.,Экспорт не допускается. Вам нужна роль {0} для экспорта.,
Fetching...,Получение...,
Field,Поле,
File Manager,Файловый менеджер,
@@ -3788,7 +3755,6 @@ Set,Задать,
Setup,Настройки,
Setup Wizard,Мастер установки,
Size,Размер,
-Sr,Sr,
Start,Начать,
Start Time,Стартовое время,
Status,Статус,
@@ -3798,7 +3764,7 @@ Template,Шаблон,
Thursday,Четверг,
Title,Заголовок,
Total,Общая сумма,
-Totals,Всего:,
+Totals,Всего,
Tuesday,Вторник,
Type,Тип,
Update,Обновить,
@@ -3809,10 +3775,10 @@ Welcome to {0},Добро пожаловать в {0},
Year,Год,
Yearly,Ежегодно,
You,Вы,
-You can also copy-paste this link in your browser,Ещё можно скопировать эту ссылку в браузер,
+You can also copy-paste this link in your browser,Вы также можете скопировать и вставить эту ссылку в свой браузер,
and,и,
{0} Name,{0} Имя,
-{0} is required,{0} требуется,
+{0} is required,{0} является обязательным,
ALL,ВСЕ,
Attach File,Прикрепить файл,
Barcode,Штрих-код,
@@ -3887,7 +3853,6 @@ Hidden,Скрытый,
Javascript,Javascript,
Ldap settings,Настройки Ldap,
Mobile number,Мобильный номер,
-Mx,Mx,
No,Нет,
Not found,Не обнаружена,
Notes:,Примечания:,
@@ -3994,7 +3959,7 @@ Desk Page,Рабочий стол,
Desk Shortcut,Сочетание клавиш,
Developer Mode Only,Только режим разработчика,
Disable User Customization,Отключить настройку пользователя,
-For example: {} Open,Например: {} Open,
+For example: {} Open,Например: {} Открыто,
Link Cards,Карты ссылок,
Link To,Ссылка к,
Onboarding,Вводный,
@@ -4026,6 +3991,7 @@ Select Language,Выбрать язык,
Confirm Translations,Подтвердить перевод,
Contributed Translations,Добавленные переводы,
Show Tags,Показать теги,
+Hide Tags,Скрыть теги,
Do not have permission to access {0} bucket.,У вас нет разрешения на доступ к сегменту {0}.,
Allow document creation via Email,Разрешить создание документов по электронной почте,
Sender Field,Поле отправителя,
@@ -4218,12 +4184,12 @@ since last year,с прошлого года,
Show,Показать,
New Number Card,Карточка с новым номером,
Your Shortcuts,Ваши ярлыки,
-You haven't added any Dashboard Charts or Number Cards yet.,Вы еще не добавили диаграммы или карточки с цифрами.,
-Click On Customize to add your first widget,"Нажмите "Настроить", чтобы добавить свой первый виджет.",
+You haven't added any Dashboard Charts or Number Cards yet.,Вы еще не добавили диаграммы или карточки с показателями.,
+Click On Customize to add your first widget,"Нажмите Настроить, чтобы добавить свой первый виджет.",
Are you sure you want to reset all customizations?,"Вы уверены, что хотите сбросить все настройки?",
"Couldn't save, please check the data you have entered","Не удалось сохранить, проверьте данные, которые вы ввели",
Validation Error,Ошибка проверки,
-"You can only upload JPG, PNG, PDF, or Microsoft documents.","Вы можете загружать только документы в форматах JPG, PNG, PDF или Microsoft.",
+"You can only upload JPG, PNG, PDF, or Microsoft documents.","Вы можете загружать только документы в форматах JPG, PNG, PDF или документы Microsoft.",
Reverting length to {0} for '{1}' in '{2}'. Setting the length as {3} will cause truncation of data.,Возврат длины к {0} для '{1}' в '{2}'. Установка длины как {3} вызовет усечение данных.,
'{0}' not allowed for type {1} in row {2},'{0}' не разрешено для типа {1} в строке {2},
Option {0} for field {1} is not a child table,Вариант {0} для поля {1} не является дочерней таблицей,
@@ -4437,7 +4403,7 @@ CTA,CTA,
CTA Label,Метка CTA,
CTA URL,CTA URL,
Default Portal Home,Главная страница портала по умолчанию,
-"Example: ""/desk""",Пример: "/ стол",
+"Example: ""/desk""","Пример: ""/desk""",
Social Link Settings,Настройки социальных ссылок,
Social Link Type,Тип социальной ссылки,
facebook,facebook,
@@ -4543,11 +4509,11 @@ Too Many Requests,Слишком много запросов,
{} is not a valid date string.,{} не является допустимой строкой даты.,
Invalid Date,Недействительная дата,
Please select a valid date filter,"Пожалуйста, выберите действующий фильтр даты",
-Value {0} must be in the valid duration format: d h m s,Значение {0} должно иметь допустимый формат продолжительности: dhms.,
+Value {0} must be in the valid duration format: d h m s,Значение {0} должно иметь допустимый формат продолжительности: д ч м с,
Google Sheets URL is invalid or not publicly accessible.,URL-адрес Google Таблиц недействителен или не является общедоступным.,
"Google Sheets URL must end with ""gid={number}"". Copy and paste the URL from the browser address bar and try again.",URL-адрес Google Таблиц должен заканчиваться на "gid = {number}". Скопируйте и вставьте URL-адрес из адресной строки браузера и повторите попытку.,
Incorrect URL,Неверный URL,
-"""{0}"" is not a valid Google Sheets URL","{0}" не является действительным URL-адресом Google Таблиц,
+"""{0}"" is not a valid Google Sheets URL","""{0}"" не является действительным URL-адресом Google Таблиц",
Duplicate Name,Повторяющееся имя,
"Please check the value of ""Fetch From"" set for field {0}","Проверьте значение параметра "Получить из", установленное для поля {0}.",
Wrong Fetch From value,Неверное значение Fetch From,
@@ -4567,7 +4533,7 @@ Hourly comment limit reached for: {0},Достигнут лимит почасо
Please add a valid comment.,"Пожалуйста, добавьте действительный комментарий.",
Document {0} Already Restored,Документ {0} уже восстановлен,
Restoring Deleted Document,Восстановление удаленного документа,
-{function} of {fieldlabel},{функция} из {fieldlabel},
+{function} of {fieldlabel},{function} из {fieldlabel},
Invalid template file for import,Неверный файл шаблона для импорта,
Invalid or corrupted content for import,Недействительный или поврежденный контент для импорта,
Value {0} must in {1} format,Значение {0} должно быть в формате {1},
@@ -4575,7 +4541,7 @@ Value {0} must in {1} format,Значение {0} должно быть в фо
Could not map column {0} to field {1},Не удалось сопоставить столбец {0} с полем {1},
Skipping Duplicate Column {0},Пропуск повторяющегося столбца {0},
The column {0} has {1} different date formats. Automatically setting {2} as the default format as it is the most common. Please change other values in this column to this format.,"Столбец {0} имеет {1} разные форматы даты. Автоматическая установка {2} в качестве формата по умолчанию, поскольку он является наиболее распространенным. Измените другие значения в этом столбце на этот формат.",
-You have reached the hourly limit for generating password reset links. Please try again later.,"Вы достигли почасового лимита для создания ссылок для сброса пароля. Пожалуйста, попробуйте позже.",
+You have reached the hourly limit for generating password reset links. Please try again later.,"Вы достигли лимита на создание ссылок для сброса пароля. Пожалуйста, попробуйте позже.",
Please hide the standard navbar items instead of deleting them,Скройте стандартные элементы навигационной панели вместо их удаления,
DocType's name should not start or end with whitespace,Имя DocType не должно начинаться или заканчиваться пробелом,
File name cannot have {0},Имя файла не может содержать {0},
@@ -4600,14 +4566,14 @@ Delivery Failed,Доставка не удалась,
Twilio WhatsApp Message Error,Ошибка сообщения Twilio WhatsApp,
A featured post must have a cover image,В избранном посте должна быть обложка.,
Load More,Показать больше,
-Published on,Опубликован в,
+Published on,Опубликован,
Enable developer mode to create a standard Web Template,"Включите режим разработчика, чтобы создать стандартный веб-шаблон",
Was this article helpful?,Эта статья была полезной?,
Thank you for your feedback!,Спасибо за ваш отзыв!,
New Mention on {0},Новое упоминание о {0},
Assignment Update on {0},Обновление задания на {0},
New Document Shared {0},Новый документ опубликован {0},
-Energy Point Update on {0},Обновление Energy Point от {0},
+Energy Point Update on {0},Обновление баллов активности от {0},
You cannot create a dashboard chart from single DocTypes,Вы не можете создать диаграмму панели мониторинга из одного типа документов,
Invalid json added in the custom options: {0},В настраиваемые параметры добавлен недопустимый json: {0},
Invalid JSON in card links for {0},Недействительный JSON в ссылках на карточки для {0},
@@ -4629,7 +4595,7 @@ Worflow States Don't Exist,Состояния Worflow не существуют,
Save Anyway,Все равно сохранить,
Energy Points:,Баллы активности:,
Review Points:,Баллы обзора:,
-Rank:,Ранг:,
+Rank:,Рейтинг:,
Monthly Rank:,Месячный рейтинг:,
Invalid expression set in filter {0} ({1}),Недопустимое выражение в фильтре {0} ({1}),
Invalid expression set in filter {0},В фильтре {0} задано недопустимое выражение,
@@ -4688,12 +4654,12 @@ Open URL in a New Tab,Открыть URL в новой вкладке,
Align Right,Выровнять по правому краю,
Loading Filters...,Загрузка фильтров...,
Count Customizations,Подсчет настроек,
-For Example: {} Open,Например: {} Открыть,
+For Example: {} Open,Например: {} Открыто,
Choose Existing Card or create New Card,Выберите существующую карту или создайте новую карту,
Number Cards,Числовые карты,
Function Based On,Функция на основе,
Add Filters,Добавить фильтры,
-Skip,Пропускать,
+Skip,Пропустить,
Dismiss,Отклонить,
Value cannot be negative for,Значение не может быть отрицательным для,
Value cannot be negative for {0}: {1},Значение не может быть отрицательным для {0}: {1},
@@ -4702,9 +4668,28 @@ Authentication failed while receiving emails from Email Account: {0}.,Ошибк
Message from server: {0},Сообщение с сервера: {0},
Documentation,Документация,
User Forum,Форум пользователей,
-Report an issue,Сообщить об ошибке,
+Report an Issue,Сообщить об ошибке,
+About,О системе,
My Profile,Мой профиль,
My Settings,Мои настройки,
Toggle Full Width,Переключить ширину,
Toggle Theme,Переключить тему,
Modules,Модули,
+You created this {0},Вы создали это {0},
+{0} created this {1},{0} создал(а) это {1},
+You edited this {0},Вы отредактировали это {0},
+{0} edited this {1},{0} отредактировал(а) это {1},
+You viewed this {0},Вы просмотрели это {0},
+{0} viewed this {1},{0} просмотрел(а) это {1},
+Apply Filters,Применить фильтры,
++ Add a Filter,+ Добавить фильтр,
+Is Template,Является шаблоном,
+Show Saved,Показать сохраненные,
+Hide Saved,Скрыть сохраненные,
+Add Tags,Добавить теги,
+Page Size,Формат страницы,
+Set all public,Сделать публичными,
+Set all private,Сделать приватными,
+Drag and drop files here or upload from,Перетащите файлы сюда или загрузите из,
+My Device,Моё устройство,
+Library,Библиотека,
From 36194ac5f56185b1bf989477ed62933706a955e8 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Mon, 18 Jul 2022 21:09:42 +0530
Subject: [PATCH 0112/2449] feat: moving all get_all queries to
frappe.qb.engine
---
frappe/model/db_query.py | 14 ++++++++++++++
1 file changed, 14 insertions(+)
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 9cf831a8b9..0cd9430883 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -121,6 +121,9 @@ class DatabaseQuery:
# if `filters` is a list of strings, its probably fields
filters, fields = fields, filters
+ self.fields, self.filters = fields, filters
+ self.ignore_permissions = ignore_permissions
+
if fields:
self.fields = fields
else:
@@ -207,6 +210,17 @@ class DatabaseQuery:
% args
)
+ if self.ignore_permissions:
+ return frappe.qb.engine.get_query(
+ table=self.doctype, fields=self.fields, filters=self.filters
+ ).run(
+ as_dict=not self.as_list,
+ debug=self.debug,
+ update=self.update,
+ ignore_ddl=self.ignore_ddl,
+ run=self.run,
+ )
+
return frappe.db.sql(
query,
as_dict=not self.as_list,
From 2c3ef963a1c0d1a8e6d5285302e32691e9350fe4 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Mon, 18 Jul 2022 21:29:01 +0530
Subject: [PATCH 0113/2449] fix: using new set of fields and filters
---
frappe/model/db_query.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 0cd9430883..1cf142e857 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -121,7 +121,7 @@ class DatabaseQuery:
# if `filters` is a list of strings, its probably fields
filters, fields = fields, filters
- self.fields, self.filters = fields, filters
+ self.qb_fields, self.qb_filters = fields, filters
self.ignore_permissions = ignore_permissions
if fields:
@@ -212,7 +212,7 @@ class DatabaseQuery:
if self.ignore_permissions:
return frappe.qb.engine.get_query(
- table=self.doctype, fields=self.fields, filters=self.filters
+ table=self.doctype, fields=self.qb_fields, filters=self.qb_filters
).run(
as_dict=not self.as_list,
debug=self.debug,
From f3f7baf185ba27790328495e42a334f31d566c5b Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Mon, 18 Jul 2022 21:51:38 +0530
Subject: [PATCH 0114/2449] feat: Added locals object to execute & pluck to qb
engine
---
frappe/model/db_query.py | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 1cf142e857..83ecc6bcf3 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -121,8 +121,8 @@ class DatabaseQuery:
# if `filters` is a list of strings, its probably fields
filters, fields = fields, filters
+ self.locals = locals()
self.qb_fields, self.qb_filters = fields, filters
- self.ignore_permissions = ignore_permissions
if fields:
self.fields = fields
@@ -210,9 +210,12 @@ class DatabaseQuery:
% args
)
- if self.ignore_permissions:
+ if self.locals.get("ignore_permissions"):
return frappe.qb.engine.get_query(
- table=self.doctype, fields=self.qb_fields, filters=self.qb_filters
+ table=self.doctype,
+ fields=self.qb_fields,
+ filters=self.qb_filters,
+ pluck=self.locals.get("pluck"),
).run(
as_dict=not self.as_list,
debug=self.debug,
From 04f4fd8cfc4dc9cf9b4dd4696e23ea2ab0d0b5c3 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Mon, 18 Jul 2022 22:00:54 +0530
Subject: [PATCH 0115/2449] fix: Redirect to comment in the doc comment section
(#17538)
---
frappe/templates/includes/comments/comments.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/frappe/templates/includes/comments/comments.py b/frappe/templates/includes/comments/comments.py
index c316db37fd..44963051ca 100644
--- a/frappe/templates/includes/comments/comments.py
+++ b/frappe/templates/includes/comments/comments.py
@@ -3,7 +3,7 @@
import re
import frappe
-from frappe import _
+from frappe import _, scrub
from frappe.rate_limiter import rate_limit
from frappe.utils.html_utils import clean_html
from frappe.website.doctype.blog_settings.blog_settings import get_comment_limit
@@ -41,8 +41,13 @@ def add_comment(comment, comment_email, comment_by, reference_doctype, reference
if route:
clear_cache(route)
- content = comment.content + "{}
".format(
- frappe.utils.get_request_site_address(), doc.route, comment.name, _("View Comment")
+ if doc.get("route"):
+ url = f"{frappe.utils.get_request_site_address()}/{doc.route}#{comment.name}"
+ else:
+ url = f"{frappe.utils.get_request_site_address()}/app/{scrub(doc.doctype)}/{doc.name}#comment-{comment.name}"
+
+ content = comment.content + "{}
".format(
+ url, _("View Comment")
)
if doc.doctype == "Blog Post" and not doc.enable_email_notification:
From a822092211533ff17ff9b92dd86f6f868ed63e2e Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 18 Jul 2022 22:16:25 +0530
Subject: [PATCH 0116/2449] fix: remove redundant condition
---
frappe/patches/v13_0/encrypt_2fa_secrets.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/frappe/patches/v13_0/encrypt_2fa_secrets.py b/frappe/patches/v13_0/encrypt_2fa_secrets.py
index 1814ff50c5..3b220f485f 100644
--- a/frappe/patches/v13_0/encrypt_2fa_secrets.py
+++ b/frappe/patches/v13_0/encrypt_2fa_secrets.py
@@ -39,7 +39,6 @@ def execute():
.set(table.parent, PARENT_FOR_DEFAULTS)
.set(table.defvalue, defvalue_cases)
.where(table.parent == OLD_PARENT)
- .where(table.defkey.like("%_otpsecret"))
).run()
clear_defaults_cache()
From 4e0ec7919e765681e920a038d5674a686849a3b4 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Mon, 18 Jul 2022 22:19:52 +0530
Subject: [PATCH 0117/2449] fix: removing additional "`" from fields
---
frappe/database/query.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index 9bb5383b24..8421d417f3 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -465,6 +465,7 @@ class Engine:
is_list = False
if is_list:
+ fields = [field.replace("""`""", "") for field in fields]
function_objects += self.function_objects_from_list(fields=fields)
is_str = isinstance(fields, str)
From 9a7f92ca1de2e91222764393a700db1191fb961c Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 11 Jul 2022 22:16:34 +0530
Subject: [PATCH 0118/2449] fix!: allow system managers to toggle email queue
---
frappe/client.py | 7 ---
.../email/doctype/email_queue/email_queue.py | 17 +++++-
.../doctype/email_queue/email_queue_list.js | 52 +++++++++++--------
frappe/email/queue.py | 2 +-
frappe/patches.txt | 1 +
.../patches/v14_0/set_hold_queue_default.py | 20 +++++++
frappe/public/js/frappe/defaults.js | 14 -----
7 files changed, 68 insertions(+), 45 deletions(-)
create mode 100644 frappe/patches/v14_0/set_hold_queue_default.py
diff --git a/frappe/client.py b/frappe/client.py
index 6ed40f8344..0b097909ca 100644
--- a/frappe/client.py
+++ b/frappe/client.py
@@ -274,13 +274,6 @@ def delete(doctype, name):
frappe.delete_doc(doctype, name, ignore_missing=False)
-@frappe.whitelist(methods=["POST", "PUT"])
-def set_default(key, value, parent=None):
- """set a user default value"""
- frappe.db.set_default(key, value, parent or frappe.session.user)
- frappe.clear_cache(user=frappe.session.user)
-
-
@frappe.whitelist(methods=["POST", "PUT"])
def bulk_update(docs):
"""Bulk update documents
diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py
index eb07be0b38..9c783f675d 100644
--- a/frappe/email/doctype/email_queue/email_queue.py
+++ b/frappe/email/doctype/email_queue/email_queue.py
@@ -27,6 +27,7 @@ from frappe.utils import (
get_hook_method,
get_string_between,
nowdate,
+ sbool,
split_emails,
)
@@ -110,8 +111,11 @@ class EmailQueue(Document):
return self.status in ["Not Sent", "Partially Sent"]
def can_send_now(self):
- hold_queue = cint(frappe.defaults.get_defaults().get("hold_queue")) == 1
- if frappe.are_emails_muted() or not self.is_to_be_sent() or hold_queue:
+ if (
+ frappe.are_emails_muted()
+ or not self.is_to_be_sent()
+ or cint(frappe.db.get_default("hold_queue")) == 1
+ ):
return False
return True
@@ -359,6 +363,8 @@ class SendMailContext:
@frappe.whitelist()
def retry_sending(name):
doc = frappe.get_doc("Email Queue", name)
+ doc.check_permission()
+
if doc and (doc.status == "Error" or doc.status == "Partially Errored"):
doc.status = "Not Sent"
for d in doc.recipients:
@@ -371,9 +377,16 @@ def retry_sending(name):
def send_now(name):
record = EmailQueue.find(name)
if record:
+ record.check_permission()
record.send()
+@frappe.whitelist()
+def toggle_sending(enable):
+ frappe.only_for("System Manager")
+ frappe.db.set_default("hold_queue", 0 if sbool(enable) else 1)
+
+
def on_doctype_update():
"""Add index in `tabCommunication` for `(reference_doctype, reference_name)`"""
frappe.db.add_index(
diff --git a/frappe/email/doctype/email_queue/email_queue_list.js b/frappe/email/doctype/email_queue/email_queue_list.js
index edc6250714..3cffc299af 100644
--- a/frappe/email/doctype/email_queue/email_queue_list.js
+++ b/frappe/email/doctype/email_queue/email_queue_list.js
@@ -3,27 +3,37 @@ frappe.listview_settings['Email Queue'] = {
var colour = {'Sent': 'green', 'Sending': 'blue', 'Not Sent': 'grey', 'Error': 'red', 'Expired': 'orange'};
return [__(doc.status), colour[doc.status], "status,=," + doc.status];
},
- refresh: function(doclist){
- if (has_common(frappe.user_roles, ["Administrator", "System Manager"])){
- if (cint(frappe.defaults.get_default("hold_queue"))){
- doclist.page.clear_inner_toolbar()
- doclist.page.add_inner_button(__("Resume Sending"), function() {
- frappe.defaults.set_default("hold_queue", 0);
- cur_list.refresh();
- })
- } else {
- doclist.page.clear_inner_toolbar()
- doclist.page.add_inner_button(__("Suspend Sending"), function() {
- frappe.defaults.set_default("hold_queue", 1)
- cur_list.refresh();
- })
- }
- }
- },
-
- onload: function(listview) {
+ refresh: show_toggle_sending_button,
+ onload: function(list_view) {
frappe.require("logtypes.bundle.js", () => {
- frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
+ frappe.utils.logtypes.show_log_retention_message(list_view.doctype);
})
}
-}
+};
+
+function show_toggle_sending_button(list_view) {
+ if (!has_common(frappe.user_roles, ["Administrator", "System Manager"]))
+ return;
+
+ const sending_disabled = cint(frappe.sys_defaults.hold_queue);
+ const label = sending_disabled ? __("Resume Sending") : __("Suspend Sending");
+
+ list_view.page.add_inner_button(
+ label,
+ async () => {
+ await frappe.xcall(
+ "frappe.email.doctype.email_queue.email_queue.toggle_sending",
+
+ // enable if disabled
+ {enable: sending_disabled}
+ );
+
+ // set new value for hold_queue in sys_defaults
+ frappe.sys_defaults.hold_queue = sending_disabled ? 0 : 1;
+
+ // clear the button and show one with the opposite label
+ list_view.page.remove_inner_button(label);
+ show_toggle_sending_button(list_view);
+ }
+ );
+}
\ No newline at end of file
diff --git a/frappe/email/queue.py b/frappe/email/queue.py
index 2c3e0ee011..9805ff7c3b 100755
--- a/frappe/email/queue.py
+++ b/frappe/email/queue.py
@@ -148,7 +148,7 @@ def flush(from_test=False):
msgprint(_("Emails are muted"))
from_test = True
- if cint(frappe.defaults.get_defaults().get("hold_queue")) == 1:
+ if cint(frappe.db.get_default("hold_queue")) == 1:
return
for row in get_queue():
diff --git a/frappe/patches.txt b/frappe/patches.txt
index d9b827931c..757f0169d2 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -205,3 +205,4 @@ frappe.patches.v14_0.update_auto_account_deletion_duration
frappe.patches.v14_0.update_integration_request
frappe.patches.v14_0.set_document_expiry_default
frappe.patches.v14_0.delete_data_migration_tool
+frappe.patches.v14_0.set_hold_queue_default
\ No newline at end of file
diff --git a/frappe/patches/v14_0/set_hold_queue_default.py b/frappe/patches/v14_0/set_hold_queue_default.py
new file mode 100644
index 0000000000..442d3801bc
--- /dev/null
+++ b/frappe/patches/v14_0/set_hold_queue_default.py
@@ -0,0 +1,20 @@
+import frappe
+from frappe.cache_manager import clear_defaults_cache
+
+
+def execute():
+ frappe.db.set_default(
+ "hold_queue",
+ frappe.db.get_default("hold_queue", "Administrator") or 0,
+ parent="__default",
+ )
+
+ frappe.db.delete(
+ "DefaultValue",
+ {
+ "defkey": "hold_queue",
+ "parent": ("!=", "__default"),
+ },
+ )
+
+ clear_defaults_cache()
diff --git a/frappe/public/js/frappe/defaults.js b/frappe/public/js/frappe/defaults.js
index 6115afb784..858880df01 100644
--- a/frappe/public/js/frappe/defaults.js
+++ b/frappe/public/js/frappe/defaults.js
@@ -47,20 +47,6 @@ frappe.defaults = {
if(!$.isArray(d)) d = [d];
return d;
},
- set_default: function(key, value, callback) {
- if(typeof value!=="string")
- value = JSON.stringify(value);
-
- frappe.boot.user.defaults[key] = value;
- return frappe.call({
- method: "frappe.client.set_default",
- args: {
- key: key,
- value: value
- },
- callback: callback || function(r) {}
- });
- },
set_user_default_local: function(key, value) {
frappe.boot.user.defaults[key] = value;
},
From 4b0a9da400d87578a39a8d2b81f0efe893515dac Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Mon, 18 Jul 2022 22:32:12 +0530
Subject: [PATCH 0119/2449] refactor: rename `hold_queue` to
`suspend_email_queue`
---
frappe/email/doctype/email_queue/email_queue.py | 4 ++--
frappe/email/doctype/email_queue/email_queue_list.js | 6 +++---
frappe/email/queue.py | 2 +-
frappe/patches.txt | 2 +-
..._default.py => set_suspend_email_queue_default.py} | 11 ++---------
5 files changed, 9 insertions(+), 16 deletions(-)
rename frappe/patches/v14_0/{set_hold_queue_default.py => set_suspend_email_queue_default.py} (63%)
diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py
index 9c783f675d..3c020eea39 100644
--- a/frappe/email/doctype/email_queue/email_queue.py
+++ b/frappe/email/doctype/email_queue/email_queue.py
@@ -114,7 +114,7 @@ class EmailQueue(Document):
if (
frappe.are_emails_muted()
or not self.is_to_be_sent()
- or cint(frappe.db.get_default("hold_queue")) == 1
+ or cint(frappe.db.get_default("suspend_email_queue")) == 1
):
return False
@@ -384,7 +384,7 @@ def send_now(name):
@frappe.whitelist()
def toggle_sending(enable):
frappe.only_for("System Manager")
- frappe.db.set_default("hold_queue", 0 if sbool(enable) else 1)
+ frappe.db.set_default("suspend_email_queue", 0 if sbool(enable) else 1)
def on_doctype_update():
diff --git a/frappe/email/doctype/email_queue/email_queue_list.js b/frappe/email/doctype/email_queue/email_queue_list.js
index 3cffc299af..ab2a1b9a45 100644
--- a/frappe/email/doctype/email_queue/email_queue_list.js
+++ b/frappe/email/doctype/email_queue/email_queue_list.js
@@ -15,7 +15,7 @@ function show_toggle_sending_button(list_view) {
if (!has_common(frappe.user_roles, ["Administrator", "System Manager"]))
return;
- const sending_disabled = cint(frappe.sys_defaults.hold_queue);
+ const sending_disabled = cint(frappe.sys_defaults.suspend_email_queue);
const label = sending_disabled ? __("Resume Sending") : __("Suspend Sending");
list_view.page.add_inner_button(
@@ -28,8 +28,8 @@ function show_toggle_sending_button(list_view) {
{enable: sending_disabled}
);
- // set new value for hold_queue in sys_defaults
- frappe.sys_defaults.hold_queue = sending_disabled ? 0 : 1;
+ // set new value for suspend_email_queue in sys_defaults
+ frappe.sys_defaults.suspend_email_queue = sending_disabled ? 0 : 1;
// clear the button and show one with the opposite label
list_view.page.remove_inner_button(label);
diff --git a/frappe/email/queue.py b/frappe/email/queue.py
index 9805ff7c3b..bc02c6be32 100755
--- a/frappe/email/queue.py
+++ b/frappe/email/queue.py
@@ -148,7 +148,7 @@ def flush(from_test=False):
msgprint(_("Emails are muted"))
from_test = True
- if cint(frappe.db.get_default("hold_queue")) == 1:
+ if cint(frappe.db.get_default("suspend_email_queue")) == 1:
return
for row in get_queue():
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 757f0169d2..7abe7893c5 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -205,4 +205,4 @@ frappe.patches.v14_0.update_auto_account_deletion_duration
frappe.patches.v14_0.update_integration_request
frappe.patches.v14_0.set_document_expiry_default
frappe.patches.v14_0.delete_data_migration_tool
-frappe.patches.v14_0.set_hold_queue_default
\ No newline at end of file
+frappe.patches.v14_0.set_suspend_email_queue_default
\ No newline at end of file
diff --git a/frappe/patches/v14_0/set_hold_queue_default.py b/frappe/patches/v14_0/set_suspend_email_queue_default.py
similarity index 63%
rename from frappe/patches/v14_0/set_hold_queue_default.py
rename to frappe/patches/v14_0/set_suspend_email_queue_default.py
index 442d3801bc..8cdb05a177 100644
--- a/frappe/patches/v14_0/set_hold_queue_default.py
+++ b/frappe/patches/v14_0/set_suspend_email_queue_default.py
@@ -4,17 +4,10 @@ from frappe.cache_manager import clear_defaults_cache
def execute():
frappe.db.set_default(
- "hold_queue",
+ "suspend_email_queue",
frappe.db.get_default("hold_queue", "Administrator") or 0,
parent="__default",
)
- frappe.db.delete(
- "DefaultValue",
- {
- "defkey": "hold_queue",
- "parent": ("!=", "__default"),
- },
- )
-
+ frappe.db.delete("DefaultValue", {"defkey": "hold_queue"})
clear_defaults_cache()
From 8ac7e3221497951a1d6cd1666850dd8c115b2627 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 19 Jul 2022 11:50:48 +0530
Subject: [PATCH 0120/2449] ci: check commit messages with commitlint (#17541)
---
.github/workflows/semantic-commits.yml | 30 ++++++++++++++++++++++++++
package.json | 5 +++++
2 files changed, 35 insertions(+)
create mode 100644 .github/workflows/semantic-commits.yml
diff --git a/.github/workflows/semantic-commits.yml b/.github/workflows/semantic-commits.yml
new file mode 100644
index 0000000000..4bfb273ffa
--- /dev/null
+++ b/.github/workflows/semantic-commits.yml
@@ -0,0 +1,30 @@
+name: Semantic Commits
+
+on:
+ pull_request: {}
+
+permissions:
+ contents: read
+
+concurrency:
+ group: commitcheck-frappe-${{ github.event.number }}
+ cancel-in-progress: true
+
+jobs:
+ commitlint:
+ name: Check Commit Titles
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 0
+
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 14
+ check-latest: true
+
+ - name: Check commit titles
+ run: |
+ npm install @commitlint/cli @commitlint/config-conventional
+ npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
diff --git a/package.json b/package.json
index c4ba042a89..49617caf7e 100644
--- a/package.json
+++ b/package.json
@@ -82,5 +82,10 @@
"snyk": true,
"nyc": {
"report-dir": ".cypress-coverage"
+ },
+ "commitlint": {
+ "extends": [
+ "@commitlint/config-conventional"
+ ]
}
}
From 4eb1fe74a5f85b6d3008b7dfd5c543d82502fb7d Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 19 Jul 2022 12:03:18 +0530
Subject: [PATCH 0121/2449] chore: dont fetch full repo
In most cases we need 10-50 previous commits. Checking out full repo is
time consuming and not required.
---
.github/workflows/semantic-commits.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/semantic-commits.yml b/.github/workflows/semantic-commits.yml
index 4bfb273ffa..a3536d5019 100644
--- a/.github/workflows/semantic-commits.yml
+++ b/.github/workflows/semantic-commits.yml
@@ -17,7 +17,7 @@ jobs:
steps:
- uses: actions/checkout@v3
with:
- fetch-depth: 0
+ fetch-depth: 200
- uses: actions/setup-node@v3
with:
From 21a7291d00cc1c1fa809012f0c46d2e1eef189ef Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 19 Jul 2022 14:32:34 +0530
Subject: [PATCH 0122/2449] ci: Stripped down config for Semantic checks
(#17542)
* ci: Stripped down config for Semantic checks
Conventional commit enforces too many pointless rules, all we care about
is type and subject.
* ci: auto merge on commit pass
---
.github/semantic.yml | 30 ------------------------------
.mergify.yml | 1 +
commitlint.config.js | 25 +++++++++++++++++++++++++
package.json | 5 -----
4 files changed, 26 insertions(+), 35 deletions(-)
delete mode 100644 .github/semantic.yml
create mode 100644 commitlint.config.js
diff --git a/.github/semantic.yml b/.github/semantic.yml
deleted file mode 100644
index fa15046b4a..0000000000
--- a/.github/semantic.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-# Always validate the PR title AND all the commits
-titleAndCommits: true
-
-# Allow use of Merge commits (eg on github: "Merge branch 'master' into feature/ride-unicorns")
-# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
-allowMergeCommits: true
-
-# Allow use of Revert commits (eg on github: "Revert "feat: ride unicorns"")
-# this is only relevant when using commitsOnly: true (or titleAndCommits: true)
-allowRevertCommits: true
-
-# For allowed PR types: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json
-# Tool Reference: https://github.com/zeke/semantic-pull-requests
-
-# By default types specified in commitizen/conventional-commit-types is used.
-# See: https://github.com/commitizen/conventional-commit-types/blob/v3.0.0/index.json
-# You can override the valid types
-types:
- - BREAKING CHANGE
- - feat
- - fix
- - docs
- - style
- - refactor
- - perf
- - test
- - build
- - ci
- - chore
- - revert
diff --git a/.mergify.yml b/.mergify.yml
index d9896df921..a863ee67dd 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -21,6 +21,7 @@ pull_request_rules:
- name: Automatic merge on CI success and review
conditions:
- status-success=Sider
+ - status-success=Check Commit Titles
- status-success=Python Unit Tests (MariaDB) (1)
- status-success=Python Unit Tests (MariaDB) (2)
- status-success=Python Unit Tests (Postgres) (1)
diff --git a/commitlint.config.js b/commitlint.config.js
new file mode 100644
index 0000000000..8847564e53
--- /dev/null
+++ b/commitlint.config.js
@@ -0,0 +1,25 @@
+module.exports = {
+ parserPreset: 'conventional-changelog-conventionalcommits',
+ rules: {
+ 'subject-empty': [2, 'never'],
+ 'type-case': [2, 'always', 'lower-case'],
+ 'type-empty': [2, 'never'],
+ 'type-enum': [
+ 2,
+ 'always',
+ [
+ 'build',
+ 'chore',
+ 'ci',
+ 'docs',
+ 'feat',
+ 'fix',
+ 'perf',
+ 'refactor',
+ 'revert',
+ 'style',
+ 'test',
+ ],
+ ],
+ },
+};
diff --git a/package.json b/package.json
index 49617caf7e..c4ba042a89 100644
--- a/package.json
+++ b/package.json
@@ -82,10 +82,5 @@
"snyk": true,
"nyc": {
"report-dir": ".cypress-coverage"
- },
- "commitlint": {
- "extends": [
- "@commitlint/config-conventional"
- ]
}
}
From c7726d6394d26f7e72a5abb3266b576ef9c4c87c Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 19 Jul 2022 15:00:23 +0530
Subject: [PATCH 0123/2449] fix: Pick default_role for Sytem User type only
---
.../doctype/ldap_settings/ldap_settings.py | 20 +++++++++----------
1 file changed, 10 insertions(+), 10 deletions(-)
diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
index f9d083a12f..735b96968c 100644
--- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py
@@ -145,7 +145,10 @@ class LDAPSettings(Document):
def sync_roles(self, user: "User", additional_groups: list = None):
current_roles = {d.role for d in user.get("roles")}
- needed_roles = {self.default_role}
+ if self.default_user_type == "System User":
+ needed_roles = {self.default_role}
+ else:
+ needed_roles = set()
lower_groups = [g.lower() for g in additional_groups or []]
all_mapped_roles = {r.erpnext_role for r in self.ldap_groups}
@@ -170,15 +173,12 @@ class LDAPSettings(Document):
user = frappe.get_doc("User", user_data["email"])
LDAPSettings.update_user_fields(user=user, user_data=user_data)
else:
- doc = user_data
- doc.update(
- {
- "doctype": "User",
- "send_welcome_email": 0,
- "language": "",
- "user_type": self.default_user_type,
- }
- )
+ doc = user_data | {
+ "doctype": "User",
+ "send_welcome_email": 0,
+ "language": "",
+ "user_type": self.default_user_type,
+ }
user = frappe.get_doc(doc)
user.insert(ignore_permissions=True)
From c55bb98482bb32589b46380373de8edd62ab980a Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 19 Jul 2022 15:01:16 +0530
Subject: [PATCH 0124/2449] test: LDAP test for website user creation
---
.../ldap_settings/test_ldap_settings.py | 45 +++++++++++++------
1 file changed, 32 insertions(+), 13 deletions(-)
diff --git a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
index a158d42d61..9080e0c82a 100644
--- a/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
+++ b/frappe/integrations/doctype/ldap_settings/test_ldap_settings.py
@@ -23,6 +23,11 @@ class LDAP_TestCase:
LDAP_LDIF_JSON = None
TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING = None
+ # for adding type hints during development ^_^
+ assertTrue = TestCase.assertTrue
+ assertEqual = TestCase.assertEqual
+ assertIn = TestCase.assertIn
+
def mock_ldap_connection(f):
@functools.wraps(f)
def wrapped(self, *args, **kwargs):
@@ -51,6 +56,8 @@ class LDAP_TestCase:
frappe.get_doc("User", "posix.user1@unit.testing").delete()
with contextlib.suppress(Exception):
frappe.get_doc("User", "posix.user2@unit.testing").delete()
+ with contextlib.suppress(Exception):
+ frappe.get_doc("User", "website_ldap_user@test.com").delete()
@classmethod
def setUpClass(cls):
@@ -153,7 +160,7 @@ class LDAP_TestCase:
cls.connection = None
@mock_ldap_connection
- def test_mandatory_fields(self: TestCase):
+ def test_mandatory_fields(self):
mandatory_fields = [
"ldap_server_url",
"ldap_directory_server",
@@ -190,7 +197,7 @@ class LDAP_TestCase:
self.fail(f"Document LDAP Settings field [{non_mandatory_field}] should not be mandatory")
@mock_ldap_connection
- def test_validation_ldap_search_string(self: TestCase):
+ def test_validation_ldap_search_string(self):
invalid_ldap_search_strings = [
"",
"uid={0}",
@@ -209,7 +216,7 @@ class LDAP_TestCase:
frappe.get_doc(localdoc).save()
self.fail(f"LDAP search string [{invalid_search_string}] should not validate")
- def test_connect_to_ldap(self: TestCase):
+ def test_connect_to_ldap(self):
# prevent these parameters for security or lack of the und user from being able to configure
prevent_connection_parameters = {
"mode": {
@@ -306,7 +313,7 @@ class LDAP_TestCase:
)
@mock_ldap_connection
- def test_get_ldap_client_settings(self: TestCase):
+ def test_get_ldap_client_settings(self):
result = self.test_class.get_ldap_client_settings()
self.assertIsInstance(result, dict)
@@ -320,7 +327,7 @@ class LDAP_TestCase:
self.assertFalse(result["enabled"]) # must match the edited doc
@mock_ldap_connection
- def test_update_user_fields(self: TestCase):
+ def test_update_user_fields(self):
test_user_data = {
"username": "posix.user",
"email": "posix.user1@unit.testing",
@@ -343,7 +350,19 @@ class LDAP_TestCase:
self.assertIn(self.test_class.default_role, frappe.get_roles(updated_user.name))
@mock_ldap_connection
- def test_sync_roles(self: TestCase):
+ def test_create_website_user(self):
+ new_test_user_data = {
+ "username": "website_ldap_user.test",
+ "email": "website_ldap_user@test.com",
+ "first_name": "Website User - LDAP Test",
+ }
+ self.test_class.default_user_type = "Website User"
+ self.test_class.create_or_update_user(user_data=new_test_user_data, groups=[])
+ new_user = frappe.get_doc("User", new_test_user_data["email"])
+ self.assertEqual(new_user.user_type, "Website User")
+
+ @mock_ldap_connection
+ def test_sync_roles(self):
if self.TEST_LDAP_SERVER.lower() == "openldap":
test_user_data = {
"posix.user1": [
@@ -419,7 +438,7 @@ class LDAP_TestCase:
)
@mock_ldap_connection
- def test_create_or_update_user(self: TestCase):
+ def test_create_or_update_user(self):
test_user_data = {
"posix.user1": [
"Users",
@@ -462,12 +481,12 @@ class LDAP_TestCase:
)
@mock_ldap_connection
- def test_get_ldap_attributes(self: TestCase):
+ def test_get_ldap_attributes(self):
method_return = self.test_class.get_ldap_attributes()
self.assertTrue(type(method_return) is list)
@mock_ldap_connection
- def test_fetch_ldap_groups(self: TestCase):
+ def test_fetch_ldap_groups(self):
if self.TEST_LDAP_SERVER.lower() == "openldap":
test_users = {"posix.user": ["Users", "Administrators"], "posix.user2": ["Users", "Group3"]}
elif self.TEST_LDAP_SERVER.lower() == "active directory":
@@ -492,7 +511,7 @@ class LDAP_TestCase:
self.assertTrue(returned_group in test_users[test_user])
@mock_ldap_connection
- def test_authenticate(self: TestCase):
+ def test_authenticate(self):
with mock.patch(
"frappe.integrations.doctype.ldap_settings.ldap_settings.LDAPSettings.fetch_ldap_groups"
) as fetch_ldap_groups_function:
@@ -523,7 +542,7 @@ class LDAP_TestCase:
)
@mock_ldap_connection
- def test_complex_ldap_search_filter(self: TestCase):
+ def test_complex_ldap_search_filter(self):
ldap_search_filters = self.TEST_VALUES_LDAP_COMPLEX_SEARCH_STRING
for search_filter in ldap_search_filters:
@@ -542,7 +561,7 @@ class LDAP_TestCase:
else:
self.assertTrue(self.test_class.authenticate("posix.user", "posix_user_password"))
- def test_reset_password(self: TestCase):
+ def test_reset_password(self):
self.test_class = LDAPSettings(self.doc)
# Create a clean doc
@@ -567,7 +586,7 @@ class LDAP_TestCase:
connect_to_ldap.assert_called_with(self.base_dn, self.base_password, read_only=False)
@mock_ldap_connection
- def test_convert_ldap_entry_to_dict(self: TestCase):
+ def test_convert_ldap_entry_to_dict(self):
self.connection.search(
search_base=self.ldap_user_path,
search_filter=self.TEST_LDAP_SEARCH_STRING.format("posix.user"),
From 31ef1c7355fa393f275c6fd4c00e86e2e8f7a08c Mon Sep 17 00:00:00 2001
From: Nabin Hait
Date: Tue, 19 Jul 2022 15:23:04 +0530
Subject: [PATCH 0125/2449] perf: option to skip realtime notify update after
insert (#17543)
While doing optimization for the period closing voucher, found that `notify_update` takes a significant amount of time to execute (200 seconds in this case), even though it was not required at all in this specific case (insert of GL Entry). That's why made the function optional by using a flag.
Related PR: https://github.com/frappe/erpnext/pull/31626
---
frappe/model/document.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 9b781b1999..864f2d50b4 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -1092,7 +1092,9 @@ class Document(BaseDocument):
self.run_method("on_update_after_submit")
self.clear_cache()
- self.notify_update()
+
+ if not hasattr(self.flags, "notify_update") or self.flags.notify_update:
+ self.notify_update()
update_global_search(self)
From 2bf14bb29f1c748ced9c0592806b40724e2a9ff2 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Tue, 19 Jul 2022 12:08:17 +0200
Subject: [PATCH 0126/2449] fix: translate page numbers (#17492)
---
frappe/templates/print_format/print_format.css | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/templates/print_format/print_format.css b/frappe/templates/print_format/print_format.css
index baaf5b087d..a10b218b94 100644
--- a/frappe/templates/print_format/print_format.css
+++ b/frappe/templates/print_format/print_format.css
@@ -18,7 +18,7 @@
/* page number */
{% set page_number_position = print_format.page_number.lower().replace(' ', '_') %}
{% if page_number_position in ['top_left', 'top_center', 'top_right', 'bottom_left', 'bottom_center', 'bottom_right'] %}
- {{ render_margin_text(page_number_position, 'counter(page) " of " counter(pages)') }}
+ {{ render_margin_text(page_number_position, _('{0} of {1}').format('counter(page) "', '" counter(pages)')) }}
{% endif %}
}
From a50e0ffa085e5164bb9d04f03192f9de94d2ee40 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Tue, 19 Jul 2022 15:52:15 +0530
Subject: [PATCH 0127/2449] refactor: Webform (#17232)
---
cypress/integration/web_form.js | 237 +++++++++++-
cypress/support/commands.js | 11 +-
frappe/__init__.py | 8 +
.../web_form/edit_profile/edit_profile.json | 6 +-
frappe/patches.txt | 1 +
frappe/patches/v14_0/update_webforms.py | 14 +
frappe/public/js/frappe-web.bundle.js | 1 +
.../js/frappe/form/controls/datetime.js | 1 +
frappe/public/js/frappe/form/formatters.js | 2 +-
frappe/public/js/frappe/web_form/web_form.js | 111 +++---
.../js/frappe/web_form/web_form_list.js | 360 ++++++++++--------
.../js/frappe/web_form/webform_script.js | 151 +++-----
frappe/public/scss/desk/global.scss | 4 +
frappe/public/scss/website/web_form.scss | 288 +++++++++-----
frappe/templates/base.html | 7 +-
frappe/tests/test_webform.py | 12 +-
frappe/utils/data.py | 9 +
.../doctype/web_form/templates/web_form.html | 238 ++++++------
.../web_form/templates/web_form_row.html | 4 -
.../doctype/web_form/templates/web_list.html | 45 +++
.../website/doctype/web_form/test_web_form.py | 8 +-
frappe/website/doctype/web_form/web_form.js | 239 ++++++++----
frappe/website/doctype/web_form/web_form.json | 197 ++++++----
frappe/website/doctype/web_form/web_form.py | 219 +++++++----
.../website/doctype/web_form/web_form_list.js | 10 +
.../web_form_field/web_form_field.json | 11 +-
.../doctype/web_form_list_column/__init__.py | 0
.../web_form_list_column.json | 48 +++
.../web_form_list_column.py | 9 +
frappe/website/doctype/web_page/web_page.py | 4 +
.../website_settings/website_settings.py | 3 +
frappe/website/page_renderers/web_form.py | 12 +-
frappe/website/router.py | 26 ++
frappe/website/serve.py | 3 +
frappe/website/utils.py | 18 +-
.../web_form/request_data/request_data.json | 8 +-
.../request_to_delete_data.json | 8 +-
37 files changed, 1527 insertions(+), 806 deletions(-)
create mode 100644 frappe/patches/v14_0/update_webforms.py
delete mode 100644 frappe/website/doctype/web_form/templates/web_form_row.html
create mode 100644 frappe/website/doctype/web_form/templates/web_list.html
create mode 100644 frappe/website/doctype/web_form/web_form_list.js
create mode 100644 frappe/website/doctype/web_form_list_column/__init__.py
create mode 100644 frappe/website/doctype/web_form_list_column/web_form_list_column.json
create mode 100644 frappe/website/doctype/web_form_list_column/web_form_list_column.py
diff --git a/cypress/integration/web_form.js b/cypress/integration/web_form.js
index bd1c7e147e..74edee0eb9 100644
--- a/cypress/integration/web_form.js
+++ b/cypress/integration/web_form.js
@@ -3,24 +3,253 @@ context('Web Form', () => {
cy.login();
});
+ it('Create Web Form', () => {
+ cy.visit('/app/web-form/new');
+
+ cy.intercept('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
+
+ cy.fill_field('title', 'Note');
+ cy.fill_field('doc_type', 'Note', 'Link');
+ cy.fill_field('module', 'Website', 'Link');
+ cy.click_custom_action_button('Get Fields');
+ cy.click_custom_action_button('Publish');
+
+ cy.wait('@save_form');
+
+ cy.get_field('route').should('have.value', 'note');
+ cy.get('.title-area .indicator-pill').contains('Published');
+ });
+
+ it('Open Web Form (Logged in User)', () => {
+ cy.visit('/note');
+
+ cy.fill_field('title', 'Note 1');
+ cy.get('.web-form-actions button').contains('Save').click();
+
+ cy.url().should('include', '/note/Note%201');
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/Note%201');
+ });
+
+ it('Open Web Form (Guest)', () => {
+ cy.request('/api/method/logout');
+ cy.visit('/note');
+
+ cy.url().should('include', '/note/new');
+
+ cy.fill_field('title', 'Guest Note 1');
+ cy.get('.web-form-actions button').contains('Save').click();
+
+ cy.url().should('include', '/note/new');
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/new');
+ });
+
+ it('Login Required', () => {
+ cy.login();
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.get('input[data-fieldname="login_required"]').check({force: true});
+
+ cy.save();
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/Note%201');
+
+ cy.call('logout');
+
+ cy.visit('/note');
+ cy.get_open_dialog()
+ .get('.modal-message')
+ .contains('You are not permitted to access this page without login.');
+ });
+
+ it('Show List', () => {
+ cy.login();
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "List Settings"}).click();
+ cy.get('input[data-fieldname="show_list"]').check();
+
+ cy.save();
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/list');
+ cy.get('.web-list-table').should('be.visible');
+ });
+
+ it('Show Custom List Title', () => {
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "List Settings"}).click();
+ cy.fill_field('list_title', 'Note List');
+
+ cy.save();
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/list');
+ cy.get('.web-list-header h1').should('contain.text', 'Note List');
+ });
+
+ it('Show Custom List Columns', () => {
+ cy.visit('/note');
+ cy.url().should('include', '/note/list');
+
+ cy.get('.web-list-table thead th').contains('Name');
+ cy.get('.web-list-table thead th').contains('Title');
+
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "List Settings"}).click();
+
+ cy.get('[data-fieldname="list_columns"] .grid-footer button').contains('Add Row').as('add-row');
+
+ cy.get('@add-row').click();
+ cy.get('[data-fieldname="list_columns"] .grid-body .rows').as('grid-rows');
+ cy.get('@grid-rows').find('.grid-row:first [data-fieldname="fieldname"]').click();
+ cy.get('@grid-rows').find('.grid-row:first select[data-fieldname="fieldname"]').select('Title (Data)');
+
+ cy.get('@add-row').click();
+ cy.get('@grid-rows').find('.grid-row[data-idx="2"] [data-fieldname="fieldname"]').click();
+ cy.get('@grid-rows').find('.grid-row[data-idx="2"] select[data-fieldname="fieldname"]').select('Public (Check)');
+
+ cy.get('@add-row').click();
+ cy.get('@grid-rows').find('.grid-row:last [data-fieldname="fieldname"]').click();
+ cy.get('@grid-rows').find('.grid-row:last select[data-fieldname="fieldname"]').select('Content (Text Editor)');
+
+ cy.save();
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/list');
+ cy.get('.web-list-table thead th').contains('Title');
+ cy.get('.web-list-table thead th').contains('Public');
+ cy.get('.web-list-table thead th').contains('Content');
+ });
+
+ it('Breadcrumbs', () => {
+ cy.visit('/note/Note 1');
+ cy.get('.breadcrumb-container .breadcrumb .breadcrumb-item:first a')
+ .should('contain.text', 'Note').click();
+ cy.url().should('include', '/note/list');
+ });
+
+ it('Custom Breadcrumbs', () => {
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.get('.form-section .section-head').contains('Customization').click();
+ cy.fill_field('breadcrumbs', '[{"label": _("Notes"), "route":"note"}]', 'Code');
+ cy.get('.form-section .section-head').contains('Customization').click();
+ cy.save();
+
+ cy.visit('/note/Note 1');
+ cy.get('.breadcrumb-container .breadcrumb .breadcrumb-item:first a')
+ .should('contain.text', 'Notes');
+ });
+
+ it('Read Only', () => {
+ cy.login();
+ cy.visit('/note');
+ cy.url().should('include', '/note/list');
+
+ // Read Only Field
+ cy.get('.web-list-table tbody tr[id="Note 1"]').click();
+ cy.get('.frappe-control[data-fieldname="title"] .control-input')
+ .should('have.css', 'display', 'none');
+ });
+
+ it('Edit Mode', () => {
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.get('input[data-fieldname="allow_edit"]').check();
+
+ cy.save();
+
+ cy.visit('/note/Note 1');
+ cy.url().should('include', '/note/Note%201');
+
+ cy.get('.web-form-actions a').contains('Edit').click();
+ cy.url().should('include', '/note/Note%201/edit');
+
+ // Editable Field
+ cy.get_field('title').should('have.value', 'Note 1');
+
+ cy.fill_field('title', ' Edited');
+ cy.get('.web-form-actions button').contains('Save').click();
+ cy.get_field('title').should('have.value', 'Note 1 Edited');
+ });
+
+ it('Allow Multiple Response', () => {
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.get('input[data-fieldname="allow_multiple"]').check();
+
+ cy.save();
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/list');
+
+ cy.get('.web-list-actions a:visible').contains('New').click();
+ cy.url().should('include', '/note/new');
+
+ cy.fill_field('title', 'Note 2');
+ cy.get('.web-form-actions button').contains('Save').click();
+ });
+
+ it('Allow Delete', () => {
+ cy.visit('/app/web-form/note');
+
+ cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.get('input[data-fieldname="allow_delete"]').check();
+
+ cy.save();
+
+ cy.visit('/note');
+ cy.url().should('include', '/note/list');
+
+ cy.get('.web-list-table tbody tr[id="Note 1"] .list-col-checkbox').click();
+ cy.get('.web-list-table tbody tr[id="Note 2"] .list-col-checkbox').click();
+ cy.get('.web-list-actions button:visible').contains('Delete').click({force: true});
+
+ cy.get('.web-list-actions button').contains('Delete').should('not.be.visible');
+
+ cy.visit('/note');
+ cy.get('.web-list-table tbody tr[id="Note 1"]').should('not.exist');
+ cy.get('.web-list-table tbody tr[id="Note 2"]').should('not.exist');
+ cy.get('.web-list-table tbody tr[id="Guest Note 1"]').should('exist');
+ });
+
it('Navigate and Submit a WebForm', () => {
cy.visit('/update-profile');
- cy.get_field('last_name', 'Data').type('_Test User', {force: true}).wait(200);
+
+ cy.get('.web-form-actions a').contains('Edit').click();
+
+ cy.fill_field('last_name', '_Test User');
+
cy.get('.web-form-actions .btn-primary').click();
- cy.wait(5000);
cy.url().should('include', '/me');
});
it('Navigate and Submit a MultiStep WebForm', () => {
cy.call('frappe.tests.ui_test_helpers.update_webform_to_multistep').then(() => {
cy.visit('/update-profile-duplicate');
- cy.get_field('last_name', 'Data').type('_Test User', {force: true}).wait(200);
+
+ cy.get('.web-form-actions a').contains('Edit').click();
+
+ cy.fill_field('last_name', '_Test User');
+
cy.get('.btn-next').should('be.visible');
cy.get('.btn-next').click();
+
cy.get('.btn-previous').should('be.visible');
cy.get('.btn-next').should('not.be.visible');
+
cy.get('.web-form-actions .btn-primary').click();
- cy.wait(5000);
cy.url().should('include', '/me');
});
});
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 5ee26348e2..5424e8c6e4 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -162,7 +162,12 @@ Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
if (fieldtype === 'Select') {
cy.get('@input').select(value);
} else {
- cy.get('@input').type(value, {waitForAnimations: false, force: true, delay: 100});
+ cy.get('@input').type(value, {
+ waitForAnimations: false,
+ parseSpecialCharSequences: false,
+ force: true,
+ delay: 100
+ });
}
return cy.get('@input');
});
@@ -358,6 +363,10 @@ Cypress.Commands.add('open_list_filter', () => {
cy.get('.filter-popover').should('exist');
});
+Cypress.Commands.add('click_custom_action_button', (name) => {
+ cy.get(`.custom-actions [data-label="${encodeURIComponent(name)}"]`).click();
+});
+
Cypress.Commands.add('click_action_button', (name) => {
cy.findByRole('button', {name: 'Actions'}).click();
cy.get(`.actions-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 3057eacd3b..cd1bfc5583 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -1796,6 +1796,14 @@ def respond_as_web_page(
local.response["context"] = context
+def redirect(url):
+ """Raise a 301 redirect to url"""
+ from frappe.exceptions import Redirect
+
+ flags.redirect_location = url
+ raise Redirect
+
+
def redirect_to_message(title, html, http_status_code=None, context=None, indicator_color=None):
"""Redirects to /message?id=random
Similar to respond_as_web_page, but used to 'redirect' and show message pages like success, failure, etc. with a detailed message
diff --git a/frappe/core/web_form/edit_profile/edit_profile.json b/frappe/core/web_form/edit_profile/edit_profile.json
index c04e705820..cedef71c0e 100644
--- a/frappe/core/web_form/edit_profile/edit_profile.json
+++ b/frappe/core/web_form/edit_profile/edit_profile.json
@@ -18,9 +18,10 @@
"introduction_text": "",
"is_multi_step_form": 0,
"is_standard": 1,
+ "list_columns": [],
"login_required": 1,
"max_attachment_size": 0,
- "modified": "2022-03-22 15:00:43.456738",
+ "modified": "2022-07-18 16:51:19.796411",
"modified_by": "Administrator",
"module": "Core",
"name": "edit-profile",
@@ -29,9 +30,8 @@
"route": "update-profile",
"route_to_success_link": 0,
"show_attachments": 0,
- "show_in_grid": 0,
+ "show_list": 0,
"show_sidebar": 0,
- "sidebar_items": [],
"success_message": "Profile updated successfully.",
"success_url": "/me",
"title": "Update Profile",
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 437648bf9e..f79cadae87 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -194,6 +194,7 @@ frappe.patches.v14_0.remove_is_first_startup
frappe.patches.v14_0.clear_long_pending_stale_logs
frappe.patches.v14_0.log_settings_migration
frappe.patches.v14_0.setup_likes_from_feedback
+frappe.patches.v14_0.update_webforms
[post_model_sync]
frappe.patches.v14_0.drop_data_import_legacy
diff --git a/frappe/patches/v14_0/update_webforms.py b/frappe/patches/v14_0/update_webforms.py
new file mode 100644
index 0000000000..46918f216e
--- /dev/null
+++ b/frappe/patches/v14_0/update_webforms.py
@@ -0,0 +1,14 @@
+# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
+# MIT License. See license.txt
+
+
+import frappe
+
+
+def execute():
+ frappe.reload_doc("website", "doctype", "web_form_list_column")
+ frappe.reload_doctype("Web Form")
+
+ for web_form in frappe.db.get_all("Web Form", fields=["*"]):
+ if web_form.allow_multiple and not web_form.show_list:
+ frappe.db.set_value("Web Form", web_form.name, "show_list", True)
diff --git a/frappe/public/js/frappe-web.bundle.js b/frappe/public/js/frappe-web.bundle.js
index a3bac55e23..21703f83b8 100644
--- a/frappe/public/js/frappe-web.bundle.js
+++ b/frappe/public/js/frappe-web.bundle.js
@@ -3,6 +3,7 @@ import "./frappe/class.js";
import "./frappe/polyfill.js";
import "./lib/moment.js";
import "./frappe/provide.js";
+import "./frappe/form/formatters.js";
import "./frappe/format.js";
import "./frappe/utils/number_format.js";
import "./frappe/utils/utils.js";
diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js
index a086b1b879..c266a928e6 100644
--- a/frappe/public/js/frappe/form/controls/datetime.js
+++ b/frappe/public/js/frappe/form/controls/datetime.js
@@ -14,6 +14,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co
}
get_start_date() {
+ this.value = this.value == null ? undefined : this.value;
let value = frappe.datetime.convert_to_user_tz(this.value);
return frappe.datetime.str_to_obj(value);
}
diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js
index 5cf5a2f4f3..5a15b4fd45 100644
--- a/frappe/public/js/frappe/form/formatters.js
+++ b/frappe/public/js/frappe/form/formatters.js
@@ -196,7 +196,7 @@ frappe.form.formatters = {
Datetime: function(value) {
if(value) {
return moment(frappe.datetime.convert_to_user_tz(value))
- .format(frappe.boot.sysdefaults.date_format.toUpperCase() + ' ' + frappe.boot.sysdefaults.time_format || 'HH:mm:ss');
+ .format(frappe.boot.sysdefaults.date_format.toUpperCase() + ' ' + (frappe.boot.sysdefaults.time_format || 'HH:mm:ss'));
} else {
return "";
}
diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js
index 11e0b782ae..21d88eac49 100644
--- a/frappe/public/js/frappe/web_form/web_form.js
+++ b/frappe/public/js/frappe/web_form/web_form.js
@@ -23,13 +23,14 @@ export default class WebForm extends frappe.ui.FieldGroup {
this.set_sections();
this.set_field_values();
this.setup_listeners();
- if (this.introduction_text) this.set_form_description(this.introduction_text);
- if (this.allow_print && !this.is_new) this.setup_print_button();
- if (this.is_new) this.setup_cancel_button();
- this.setup_primary_action();
+
+ if (this.is_new || this.is_form_editable) {
+ this.setup_primary_action();
+ }
+
+ this.setup_footer_actions();
this.setup_previous_next_button();
this.toggle_section();
- $(".link-btn").remove();
// webform client script
frappe.init_client_script && frappe.init_client_script();
@@ -70,6 +71,14 @@ export default class WebForm extends frappe.ui.FieldGroup {
this.sections = $(`.form-section`);
}
+ setup_footer_actions() {
+ if (this.is_multi_step_form) return;
+
+ if ($('.web-form-container').height() > 600) {
+ $(".web-form-footer").removeClass("hide");
+ }
+ }
+
setup_previous_next_button() {
let me = this;
@@ -87,7 +96,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
$('.btn-previous').on('click', function () {
let is_validated = me.validate_section();
- if (!is_validated) return;
+ if (!is_validated) return false;
/**
The eslint utility cannot figure out if this is an infinite loop in backwards and
@@ -107,12 +116,13 @@ export default class WebForm extends frappe.ui.FieldGroup {
}
/* eslint-enable for-direction */
me.toggle_section();
+ return false;
});
$('.btn-next').on('click', function () {
let is_validated = me.validate_section();
- if (!is_validated) return;
+ if (!is_validated) return false;
for (let idx = me.current_section; idx < me.sections.length; idx++) {
let is_empty = me.is_next_section_empty(idx);
@@ -123,6 +133,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
}
}
me.toggle_section();
+ return false;
});
}
@@ -132,56 +143,20 @@ export default class WebForm extends frappe.ui.FieldGroup {
}
set_default_values() {
+ let defaults = {};
+ for (let df of this.fields) {
+ if (df.default) {
+ defaults[df.fieldname] = df.default;
+ }
+ }
let values = frappe.utils.get_query_params();
delete values.new;
+ Object.assign(defaults, values);
this.set_values(values);
}
- set_form_description(intro) {
- let intro_wrapper = document.getElementById('introduction');
- intro_wrapper.innerHTML = intro;
- intro_wrapper.classList.remove('hidden');
- }
-
- add_button(name, type, action, wrapper_class=".web-form-actions") {
- const button = document.createElement("button");
- button.classList.add("btn", "btn-" + type, "btn-sm", "ml-2");
- button.innerHTML = name;
- button.onclick = action;
- document.querySelector(wrapper_class).appendChild(button);
- }
-
- add_button_to_footer(name, type, action) {
- this.add_button(name, type, action, '.web-form-footer');
- }
-
- add_button_to_header(name, type, action) {
- this.add_button(name, type, action, '.web-form-actions');
- }
-
setup_primary_action() {
- this.add_button_to_header(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
- this.save()
- );
-
- if (!this.is_multi_step_form && $('.frappe-card').height() > 600) {
- // add button on footer if page is long
- this.add_button_to_footer(this.button_label || __("Save", null, "Button in web form"), "primary", () =>
- this.save()
- );
- }
- }
-
- setup_cancel_button() {
- this.add_button_to_header(__("Cancel", null, "Button in web form"), "light", () => this.cancel());
- }
-
- setup_print_button() {
- this.add_button_to_header(
- frappe.utils.icon('print'),
- "light",
- () => this.print()
- );
+ $(".web-form-container").on("submit", () => this.save());
}
validate_section() {
@@ -349,18 +324,21 @@ export default class WebForm extends frappe.ui.FieldGroup {
window.saving = false;
}
});
- return true;
+ return false;
}
- print() {
- window.open(`/printview?
- doctype=${this.doc_type}
- &name=${this.doc.name}
- &format=${this.print_format || "Standard"}`, '_blank');
+ edit() {
+ window.location.href = window.location.pathname + "/edit";
}
cancel() {
- window.location.href = window.location.pathname;
+ let path = window.location.pathname;
+ if (this.is_new) {
+ path = path.replace('/new', '');
+ } else {
+ path = path.replace('/edit', '');
+ }
+ window.location.href = path;
}
handle_success(data) {
@@ -375,12 +353,19 @@ export default class WebForm extends frappe.ui.FieldGroup {
// redirect
setTimeout(() => {
+ let path = window.location.pathname;
+
if (this.success_url) {
- window.location.href = this.success_url;
- } else if(this.login_required) {
- window.location.href =
- window.location.pathname + "?name=" + data.name;
+ path = this.success_url;
+ } else if (this.login_required) {
+ if (this.is_new && data.name) {
+ path = path.replace("/new", "");
+ path = path + "/" + data.name;
+ } else if (this.is_form_editable) {
+ path = path.replace("/edit", "");
+ }
}
- }, 2000);
+ window.location.href = path;
+ }, 1000);
}
}
diff --git a/frappe/public/js/frappe/web_form/web_form_list.js b/frappe/public/js/frappe/web_form/web_form_list.js
index 27e1695788..a4e7480f94 100644
--- a/frappe/public/js/frappe/web_form/web_form_list.js
+++ b/frappe/public/js/frappe/web_form/web_form_list.js
@@ -6,63 +6,74 @@ export default class WebFormList {
constructor(opts) {
Object.assign(this, opts);
frappe.web_form_list = this;
- this.wrapper = document.getElementById("list-table");
+ this.wrapper = $(".web-list-table");
this.make_actions();
this.make_filters();
- $('.link-btn').remove();
}
refresh() {
- if (this.table) {
- Array.from(this.table.tBodies).forEach(tbody => tbody.remove());
- let check = document.getElementById('select-all');
- if (check)
- check.checked = false;
- }
this.rows = [];
- this.page_length = 20;
this.web_list_start = 0;
+ this.page_length = 10;
frappe.run_serially([
() => this.get_list_view_fields(),
() => this.get_data(),
+ () => this.remove_more(),
() => this.make_table(),
() => this.create_more()
]);
}
+ remove_more() {
+ $('.more').remove();
+ }
+
make_filters() {
this.filters = {};
this.filter_input = [];
- const filter_area = document.getElementById('list-filters');
+ let filter_area = $('.web-list-filters');
frappe.call('frappe.website.doctype.web_form.web_form.get_web_form_filters', {
web_form_name: this.web_form_name
}).then(response => {
let fields = response.message;
+ fields.length && filter_area.removeClass('hide');
fields.forEach(field => {
- let col = document.createElement('div.col-sm-4');
- col.classList.add('col', 'col-sm-3');
- filter_area.appendChild(col);
- if (field.default) this.add_filter(field.fieldname, field.default, field.fieldtype);
+ if (["Text Editor", "Text", "Small Text"].includes(field.fieldtype)) {
+ field.fieldtype = "Data";
+ }
+
+ if (["Table", "Signature"].includes(field.fieldtype)) {
+ return;
+ }
let input = frappe.ui.form.make_control({
df: {
fieldtype: field.fieldtype,
fieldname: field.fieldname,
options: field.options,
+ input_class: 'input-xs',
only_select: true,
label: __(field.label),
onchange: (event) => {
- $('#more').remove();
this.add_filter(field.fieldname, input.value, field.fieldtype);
this.refresh();
}
},
- parent: col,
- value: field.default,
+ parent: filter_area,
render_input: 1,
+ only_input: field.fieldtype == "Check" ? false : true,
});
+
+ $(input.wrapper)
+ .addClass('col-md-2')
+ .attr("title", __(field.label)).tooltip({
+ delay: { "show": 600, "hide": 100},
+ trigger: "hover"
+ });
+
+ input.$input.attr("placeholder", __(field.label));
this.filter_input.push(input);
});
this.refresh();
@@ -73,37 +84,65 @@ export default class WebFormList {
if (!value) {
delete this.filters[field];
} else {
- if (fieldtype === 'Data') value = ['like', value + '%'];
+ if (["Data", "Currency", "Float", "Int"].includes(fieldtype)) {
+ value = ['like', '%' + value + '%'];
+ }
Object.assign(this.filters, Object.fromEntries([[field, value]]));
}
}
get_list_view_fields() {
- return frappe
- .call({
- method:
- "frappe.website.doctype.web_form.web_form.get_in_list_view_fields",
- args: { doctype: this.doctype }
- })
- .then(response => (this.fields_list = response.message));
+ if (this.columns) return this.columns;
+
+ if (this.list_columns) {
+ this.columns = this.list_columns.map(df => {
+ return {
+ label: df.label,
+ fieldname: df.fieldname,
+ fieldtype: df.fieldtype
+ };
+ });
+ }
}
fetch_data() {
- return frappe.call({
+ let args = {
method: "frappe.www.list.get_list_data",
args: {
doctype: this.doctype,
- fields: this.fields_list.map(df => df.fieldname),
limit_start: this.web_list_start,
+ limit: this.page_length,
web_form_name: this.web_form_name,
...this.filters
}
- });
+ };
+
+ if (this.no_change(args)) {
+ // console.log('throttled');
+ return Promise.resolve();
+ }
+
+ return frappe.call(args);
+ }
+
+ no_change(args) {
+ // returns true if arguments are same for the last 3 seconds
+ // this helps in throttling if called from various sources
+ if (this.last_args && JSON.stringify(args) === this.last_args) {
+ return true;
+ }
+ this.last_args = JSON.stringify(args);
+ setTimeout(() => {
+ this.last_args = null;
+ }, 3000);
+ return false;
}
async get_data() {
let response = await this.fetch_data();
- this.data = await response.message;
+ if (response) {
+ this.data = await response.message;
+ }
}
more() {
@@ -118,159 +157,145 @@ export default class WebFormList {
}
make_table() {
- this.columns = this.fields_list.map(df => {
- return {
- label: df.label,
- fieldname: df.fieldname,
- fieldtype: df.fieldtype
- };
+ this.table = $(``);
+
+ this.make_table_head();
+ this.make_table_body();
+ }
+
+ make_table_head() {
+ let $thead = $(`
+
+
+
+
+
+ ${__("Sr")}.
+
+
+ `);
+
+ this.check_all = $thead.find('input.select-all');
+ this.check_all.on("click", event => {
+ this.toggle_select_all(event.target.checked);
});
- if (!this.table) {
- this.table = document.createElement("table");
- this.table.classList.add("table");
- this.make_table_head();
- }
+ this.columns.forEach(col => {
+ let $tr = $thead.find("tr");
+ let $th = $(`${__(col.label)} `);
+ $th.appendTo($tr);
+ });
+ $thead.appendTo(this.table);
+ }
+
+ make_table_body() {
if (this.data.length) {
+ this.wrapper.empty();
+
+ if (this.table) {
+ this.table.find('tbody').remove();
+
+ if (this.check_all.length) {
+ this.check_all.prop("checked", false);
+ }
+ }
+
this.append_rows(this.data);
- this.wrapper.appendChild(this.table);
+ this.table.appendTo(this.wrapper);
} else {
- let new_button = "";
- let empty_state = document.createElement("div");
- empty_state.classList.add("no-result", "text-muted", "flex", "justify-center", "align-center");
+ if (this.wrapper.find('.no-result').length) return;
+ this.wrapper.empty();
frappe.has_permission(this.doctype, "", "create", () => {
- new_button = `
-
- ${__("Create a new {0}", [__(this.doctype)])}
-
- `;
-
- empty_state.innerHTML = `
-
-
-
-
-
${__("No {0} found", [__(this.doctype)])}
- ${new_button}
-
- `;
-
- this.wrapper.appendChild(empty_state);
+ this.setup_empty_state();
});
}
}
- make_table_head() {
- // Create Heading
- let thead = this.table.createTHead();
- let row = thead.insertRow();
+ setup_empty_state() {
+ let new_button = `
+
+ ${__("Create a new {0}", [__(this.doctype)])}
+
+ `;
- let th = document.createElement("th");
+ let empty_state = $(`
+
+
+
+
+
+
${__("No {0} found", [__(this.doctype)])}
+ ${new_button}
+
+
+ `);
- let checkbox = document.createElement("input");
- checkbox.type = "checkbox";
- checkbox.id = "select-all";
- checkbox.onclick = event =>
- this.toggle_select_all(event.target.checked);
-
- th.appendChild(checkbox);
- row.appendChild(th);
-
- add_heading(row, __("Sr"));
- this.columns.forEach(col => {
- add_heading(row, __(col.label));
- });
-
- function add_heading(row, label) {
- let th = document.createElement("th");
- th.innerText = label;
- row.appendChild(th);
- }
+ empty_state.appendTo(this.wrapper);
}
append_rows(row_data) {
- const tbody = this.table.childNodes[1] || this.table.createTBody();
+ let $tbody = this.table.find('tbody');
+
+ if (!$tbody.length) {
+ $tbody = $(` `);
+ $tbody.appendTo(this.table);
+ }
+
row_data.forEach((data_item) => {
- let row_element = tbody.insertRow();
- row_element.setAttribute("id", data_item.name);
+ let $row_element = $(` `);
let row = new frappe.ui.WebFormListRow({
- row: row_element,
+ row: $row_element,
doc: data_item,
columns: this.columns,
serial_number: this.rows.length + 1,
events: {
- onEdit: () => this.open_form(data_item.name),
- onSelect: () => this.toggle_delete()
+ on_edit: () => this.open_form(data_item.name),
+ on_select: () => {
+ this.toggle_new();
+ this.toggle_delete();
+ }
}
});
this.rows.push(row);
+ $row_element.appendTo($tbody);
});
}
make_actions() {
- const actions = document.querySelector(".list-view-actions");
+ const actions = $(".web-list-actions");
frappe.has_permission(this.doctype, "", "delete", () => {
- this.addButton(actions, "delete-rows", "danger", true, "Delete", () =>
- this.delete_rows()
- );
+ this.add_button(actions, "delete-rows", "danger", true, "Delete", () => this.delete_rows());
});
-
- this.addButton(
- actions,
- "new",
- "primary",
- false,
- "New",
- () => (window.location.href = window.location.pathname + "?new=1")
- );
}
- addButton(wrapper, id, type, hidden, name, action) {
- if (document.getElementById(id)) return;
- const button = document.createElement("button");
- if (type == "secondary") {
- button.classList.add(
- "btn",
- "btn-secondary",
- "btn-sm",
- "ml-2"
- );
- }
- else if (type == "danger") {
- button.classList.add(
- "btn",
- "btn-danger",
- "button-delete",
- "btn-sm",
- "ml-2"
- );
- }
- else {
- button.classList.add("btn", "btn-primary", "btn-sm", "ml-2");
- }
+ add_button(wrapper, name, type, hidden, text, action) {
+ if ($(`.${name}`).length) return;
- button.id = id;
- button.innerText = name;
- button.hidden = hidden;
+ hidden = hidden ? "hide" : "";
+ type = type == "danger" ? "danger button-delete" : type;
- button.onclick = action;
- wrapper.appendChild(button);
+ let button = $(`
+ ${text}
+ `);
+
+ button.on("click", () => action());
+ button.appendTo(wrapper);
}
create_more() {
if (this.rows.length >= this.page_length) {
- const footer = document.querySelector(".list-view-footer");
- this.addButton(footer, "more", "secondary", false, "More", () => this.more());
+ const footer = $(".web-list-footer");
+ this.add_button(footer, "more", "secondary", false, "Load More", () => this.more());
}
}
@@ -279,7 +304,12 @@ export default class WebFormList {
}
open_form(name) {
- window.location.href = window.location.pathname + "?name=" + name;
+ let path = window.location.pathname;
+ if (path.includes('/list')) {
+ path = path.replace('/list', '');
+ }
+
+ window.location.href = path + "/" + name;
}
get_selected() {
@@ -287,9 +317,15 @@ export default class WebFormList {
}
toggle_delete() {
- if (!this.settings.allow_delete) return
- let btn = document.getElementById("delete-rows");
- btn.hidden = !this.get_selected().length;
+ if (!this.settings.allow_delete) return;
+ let btn = $(".delete-rows");
+ !this.get_selected().length ? btn.addClass('hide') : btn.removeClass('hide');
+ }
+
+ toggle_new() {
+ if (!this.settings.allow_delete) return;
+ let btn = $(".button-new");
+ this.get_selected().length ? btn.addClass('hide') : btn.removeClass('hide');
}
delete_rows() {
@@ -305,8 +341,9 @@ export default class WebFormList {
}
})
.then(() => {
- this.refresh()
- this.toggle_delete()
+ this.refresh();
+ this.toggle_delete();
+ this.toggle_new();
});
}
};
@@ -319,40 +356,37 @@ frappe.ui.WebFormListRow = class WebFormListRow {
make_row() {
// Add Checkboxes
- let cell = this.row.insertCell();
- cell.classList.add('list-col-checkbox');
+ let $cell = $(` `);
- this.checkbox = document.createElement("input");
- this.checkbox.type = "checkbox";
- this.checkbox.onclick = event => {
+ this.checkbox = $(` `);
+ this.checkbox.on("click", event => {
this.toggle_select(event.target.checked);
event.stopImmediatePropagation();
- }
-
- cell.appendChild(this.checkbox);
+ });
+ this.checkbox.appendTo($cell);
+ $cell.appendTo(this.row);
// Add Serial Number
- let serialNo = this.row.insertCell();
- serialNo.classList.add('list-col-serial');
- serialNo.innerText = this.serial_number;
+ let serialNo = $(`${__(this.serial_number)} `);
+ serialNo.appendTo(this.row);
this.columns.forEach(field => {
- let cell = this.row.insertCell();
let formatter = frappe.form.get_formatter(field.fieldtype);
- cell.innerHTML = this.doc[field.fieldname] &&
+ let value = this.doc[field.fieldname] &&
__(formatter(this.doc[field.fieldname], field, {only_value: 1}, this.doc)) || "";
+ let cell = $(`${value} `);
+ cell.appendTo(this.row);
});
- this.row.onclick = () => this.events.onEdit();
- this.row.style.cursor = "pointer";
+ this.row.on("click", () => this.events.on_edit());
}
toggle_select(checked) {
- this.checkbox.checked = checked;
- this.events.onSelect(checked);
+ this.checkbox.prop("checked", checked);
+ this.events.on_select(checked);
}
is_selected() {
- return this.checkbox.checked;
+ return this.checkbox.prop("checked");
}
};
diff --git a/frappe/public/js/frappe/web_form/webform_script.js b/frappe/public/js/frappe/web_form/webform_script.js
index 30ff03cb5d..31fecc778c 100644
--- a/frappe/public/js/frappe/web_form/webform_script.js
+++ b/frappe/public/js/frappe/web_form/webform_script.js
@@ -2,23 +2,15 @@ import WebFormList from './web_form_list'
import WebForm from './web_form'
frappe.ready(function() {
- let query_params = frappe.utils.get_query_params();
- let wrapper = $(".web-form-wrapper");
- let is_list = parseInt(wrapper.data('is-list')) || query_params.is_list;
- let webform_doctype = wrapper.data('web-form-doctype');
- let webform_name = wrapper.data('web-form');
- let login_required = parseInt(wrapper.data('login-required'));
- let allow_delete = parseInt(wrapper.data('allow-delete'));
- let doc_name = query_params.name || '';
- let is_new = query_params.new;
+ let web_form_doc = frappe.web_form_doc;
+ let reference_doc = frappe.reference_doc;
- if (login_required) show_login_prompt();
- else if (is_list) show_grid();
- else show_form(webform_doctype, webform_name, is_new);
+ show_login_prompt();
- document.querySelector("body").style.display = "block";
+ web_form_doc.is_list ? show_list() : show_form();
function show_login_prompt() {
+ if (frappe.session.user != "Guest" || !web_form_doc.login_required) return;
const login_required = new frappe.ui.Dialog({
title: __("Not Permitted"),
primary_action_label: __("Login"),
@@ -30,102 +22,79 @@ frappe.ready(function() {
login_required.set_message(__("You are not permitted to access this page without login."));
}
- function show_grid() {
+ function show_list() {
new WebFormList({
- parent: wrapper,
- doctype: webform_doctype,
- web_form_name: webform_name,
+ doctype: web_form_doc.doc_type,
+ web_form_name: web_form_doc.name,
+ list_columns: web_form_doc.list_columns,
settings: {
- allow_delete
+ allow_delete: web_form_doc.allow_delete
}
});
}
function show_form() {
let web_form = new WebForm({
- parent: wrapper,
- is_new,
- web_form_name: webform_name,
+ parent: $(".web-form-wrapper"),
+ is_new: web_form_doc.is_new,
+ is_form_editable: web_form_doc.is_form_editable,
+ web_form_name: web_form_doc.name,
});
+ let doc = reference_doc || {};
+ setup_fields(web_form_doc, doc);
- get_data().then(r => {
- const data = setup_fields(r.message);
- let web_form_doc = data.web_form;
+ web_form.prepare(web_form_doc, doc);
+ web_form.make();
- // if (web_form_doc.name && web_form_doc.allow_edit === 0) {
- // if (!window.location.href.includes("?new=1")) {
- // window.location.replace(window.location.pathname + "?new=1");
- // }
- // }
- let doc = r.message.doc || build_doc(r.message);
- web_form.prepare(web_form_doc, r.message.doc && web_form_doc.allow_edit === 1 ? r.message.doc : {});
- web_form.make();
+ if (web_form_doc.is_new) {
web_form.set_default_values();
- })
-
- function build_doc(form_data) {
- let doc = {};
- form_data.web_form.web_form_fields.forEach(df => {
- if (df.default) return doc[df.fieldname] = df.default;
- });
- return doc;
}
- function get_data() {
- return frappe.call({
- method: "frappe.website.doctype.web_form.web_form.get_form_data",
- args: {
- doctype: webform_doctype,
- docname: doc_name,
- web_form_name: webform_name
- },
- freeze: true
- });
- }
+ $(".file-size").each(function () {
+ $(this).text(frappe.form.formatters.FileSize($(this).text()));
+ });
+ }
- function setup_fields(form_data) {
- form_data.web_form.web_form_fields.map(df => {
- df.is_web_form = true;
- if (df.fieldtype === "Table") {
- df.get_data = () => {
- let data = [];
- if (form_data.doc) {
- data = form_data.doc[df.fieldname];
- }
- return data;
- };
-
- df.fields = form_data[df.fieldname];
- $.each(df.fields || [], function(_i, field) {
- if (field.fieldtype === "Link") {
- field.only_select = true;
- }
- field.is_web_form = true;
- });
-
- if (df.fieldtype === "Attach") {
- df.is_private = true;
+ function setup_fields(web_form_doc, doc_data) {
+ web_form_doc.web_form_fields.forEach(df => {
+ df.is_web_form = true;
+ df.read_only = !web_form_doc.is_new && !web_form_doc.is_form_editable;
+ if (df.fieldtype === "Table") {
+ df.get_data = () => {
+ let data = [];
+ if (doc_data && doc_data[df.fieldname]) {
+ return doc_data[df.fieldname];
}
+ return data;
+ };
- delete df.parent;
- delete df.parentfield;
- delete df.parenttype;
- delete df.doctype;
-
- return df;
- }
- if (df.fieldtype === "Link") {
- df.only_select = true;
- }
- if (["Attach", "Attach Image"].includes(df.fieldtype)) {
- if (typeof df.options !== "object") {
- df.options = {};
+ $.each(df.fields || [], function(_i, field) {
+ if (field.fieldtype === "Link") {
+ field.only_select = true;
}
- df.options.disable_file_browser = true;
- }
- });
+ field.is_web_form = true;
+ });
- return form_data;
- }
+ if (df.fieldtype === "Attach") {
+ df.is_private = true;
+ }
+
+ delete df.parent;
+ delete df.parentfield;
+ delete df.parenttype;
+ delete df.doctype;
+
+ return df;
+ }
+ if (df.fieldtype === "Link") {
+ df.only_select = true;
+ }
+ if (["Attach", "Attach Image"].includes(df.fieldtype)) {
+ if (typeof df.options !== "object") {
+ df.options = {};
+ }
+ df.options.disable_file_browser = true;
+ }
+ });
}
});
diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss
index 1e68f374c4..0d7ca9ac06 100644
--- a/frappe/public/scss/desk/global.scss
+++ b/frappe/public/scss/desk/global.scss
@@ -76,6 +76,10 @@ a.badge-hover {
text-decoration: underline;
}
+.pointer {
+ cursor: pointer;
+}
+
.inline-block {
display: inline-block;
}
diff --git a/frappe/public/scss/website/web_form.scss b/frappe/public/scss/website/web_form.scss
index 010182e1e5..77c3d21880 100644
--- a/frappe/public/scss/website/web_form.scss
+++ b/frappe/public/scss/website/web_form.scss
@@ -5,37 +5,212 @@
max-width: 800px;
margin: auto;
- .frappe-card {
- padding: 1rem;
+ h1 {
+ font-size: 1.9rem;
+ margin-top: 0;
+ margin-bottom: 0;
+ }
- h1 {
- font-size: 1.9rem;
- margin-top: 0;
- margin-bottom: 0;
- }
+ .web-form-container {
+ border: 1px solid var(--dark-border-color);
+ border-radius: var(--border-radius-md);
+ padding: 2rem;
- .web-form-head {
- margin: 0 -1rem;
- padding: 0 1rem 1rem 1rem;
- margin-bottom: 1rem;
+ .web-form-header {
+ display: flex;
+ justify-content: space-between;
+ margin: 0 -2rem 1rem;
+ padding: 0 2rem 1rem;
border-bottom: 1px solid var(--border-color);
+
+ .web-form-actions {
+ align-self: center;
+ }
}
- #introduction {
- margin-bottom: 2rem;
- }
-
- #introduction p {
+ .web-form-introduction {
color: var(--text-muted);
+ margin-bottom: 2rem;
+
+ p {
+ color: var(--text-muted);
+ }
}
- .web-form-actions button {
- margin-top: 0.1rem;
+ .web-form-wrapper {
+ .form-control {
+ color: var(--text-color);
+ background-color: var(--control-bg);
+ }
+
+ .form-section {
+ .section-head {
+ font-weight: bold;
+ font-size: var(--text-xl);
+ padding: var(--padding-md) 0;
+ }
+ }
+
+ .form-column {
+ padding: 0 var(--padding-md);
+
+ &:first-child {
+ padding-left: 0;
+ }
+
+ &:last-child {
+ padding-right: 0;
+ }
+
+ @include media-breakpoint-down(sm) {
+ padding: 0;
+ }
+ }
+ }
+
+ .web-form-footer {
+ text-align: right;
+ }
+
+ .attachments {
+ margin: 1rem -2rem 0;
+ padding: 1rem 2rem 0;
+ border-top: 1px solid var(--border-color);
+
+ .attachment {
+ display: flex;
+ justify-content: space-between;
+ gap: 6px;
+ max-width: 300px;
+ color: var(--text-muted);
+ font-size: var(--text-md);
+
+ &:hover {
+ text-decoration: none;
+ .file-name span {
+ text-decoration: underline;
+ }
+ }
+ }
}
}
- .frappe-card.list-card {
- min-height: 400px;
+ .web-list-container {
+ min-height: 470px;
+ border: 1px solid var(--dark-border-color);
+ border-radius: var(--border-radius-md);
+ padding: 2rem;
+
+ .web-list-header {
+ display: flex;
+ justify-content: space-between;
+
+ .web-list-actions {
+ align-self: center;
+ }
+ }
+
+ .web-list-filters {
+ display: flex;
+ flex-wrap: wrap;
+ margin: 1rem -2rem 0;
+ padding: 1rem 2rem 0;
+ border-top: 1px solid var(--border-color);
+ gap: 10px;
+
+ .form-group.frappe-control {
+ min-width: 145px;
+ padding: 0px;
+ margin: 0px;
+ align-self: center;
+
+ .checkbox {
+ .input-xs {
+ height: var(--checkbox-size);
+ }
+
+ .help-box {
+ display: none;
+ }
+ }
+
+ .input-xs {
+ height: 28px;
+ line-height: 1.2;
+ }
+ }
+ }
+
+ .web-list-table {
+ overflow: auto;
+ margin: 1rem -2rem 0;
+
+ .table {
+ border-bottom: 1px solid var(--border-color);
+ border-top: 1px solid var(--border-color);
+
+ thead tr {
+ th {
+ border: 0;
+ font-size: 13px;
+ font-weight: normal;
+ color: var(--text-muted);
+
+ &:first-child {
+ padding-left: 1.5rem;
+ }
+
+ &:last-child {
+ padding-right: 1.5rem;
+ }
+
+ input[type="checkbox"] {
+ margin-bottom: -2px;
+ }
+ }
+ }
+
+ tbody tr {
+ color: var(--text-color);
+ cursor: pointer;
+
+ td {
+ font-size: 13px;
+ border-top: 1px solid var(--border-color);
+
+ &:first-child {
+ padding-left: 1.5rem;
+ }
+
+ &:last-child {
+ padding-right: 1.5rem;
+ }
+ }
+ }
+
+ input[type="checkbox"] {
+ margin-left: 0.5rem;
+ margin-top: 2px;
+ }
+
+ .list-col-checkbox {
+ width: 1rem;
+ }
+
+ .list-col-serial {
+ width: 1.5rem;
+ }
+ }
+
+ .no-result {
+ min-height: 330px;
+ border-top: 1px solid var(--border-color);
+ }
+ }
+
+ .web-list-footer {
+ text-align: right;
+ }
}
.breadcrumb-container.container {
@@ -45,76 +220,3 @@
}
}
}
-
-.web-form-wrapper {
- .form-control {
- color: var(--text-color);
- background-color: var(--control-bg);
- }
-
- .form-section {
- .section-head {
- font-weight: bold;
- font-size: var(--text-xl);
- padding: var(--padding-md) 0;
- }
- }
-
- .form-column {
- padding: 0 var(--padding-md);
-
- &:first-child {
- padding-left: 0;
- }
-
- &:last-child {
- padding-right: 0;
- }
-
- @include media-breakpoint-down(sm) {
- padding: 0;
- }
- }
-}
-
-.list-table {
- margin-left: -1rem;
- margin-right: -1rem;
-
- .table {
- thead {
- th {
- border: 0;
- font-size: 13px;
- font-weight: normal;
- color: var(--text-muted);
-
- input[type="checkbox"] {
- margin-bottom: -2px;
- }
- }
- }
-
- tr {
- color: var(--text-color);
-
- td {
- font-size: 13px;
- border-top: 1px solid var(--border-color);
- }
- }
-
- input[type="checkbox"] {
- margin-left: 0.5rem;
- margin-top: 2px;
- }
-
- .list-col-checkbox {
- width: 1rem;
- }
-
- .list-col-serial {
- width: 1.5rem;
- }
- }
-}
diff --git a/frappe/templates/base.html b/frappe/templates/base.html
index b11b775179..e3bfea559e 100644
--- a/frappe/templates/base.html
+++ b/frappe/templates/base.html
@@ -96,12 +96,7 @@
{% block base_scripts %}
diff --git a/frappe/tests/test_webform.py b/frappe/tests/test_webform.py
index d95e4a7498..51868dfdb1 100644
--- a/frappe/tests/test_webform.py
+++ b/frappe/tests/test_webform.py
@@ -8,17 +8,17 @@ from frappe.www.list import get_list_context
class TestWebform(unittest.TestCase):
def test_webform_publish_functionality(self):
- edit_profile = frappe.get_doc("Web Form", "edit-profile")
+ request_data = frappe.get_doc("Web Form", "request-data")
# publish webform
- edit_profile.published = True
- edit_profile.save()
- set_request(method="GET", path="update-profile")
+ request_data.published = True
+ request_data.save()
+ set_request(method="GET", path="request-data/new")
response = get_response()
self.assertEqual(response.status_code, 200)
# un-publish webform
- edit_profile.published = False
- edit_profile.save()
+ request_data.published = False
+ request_data.save()
response = get_response()
self.assertEqual(response.status_code, 404)
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 8a970e57cc..262cc3fc7d 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -1951,6 +1951,15 @@ def generate_hash(*args, **kwargs) -> str:
return frappe.generate_hash(*args, **kwargs)
+def dict_with_keys(dict, keys):
+ """Returns a new dict with a subset of keys"""
+ out = {}
+ for key in dict:
+ if key in keys:
+ out[key] = dict[key]
+ return out
+
+
def guess_date_format(date_string: str) -> str:
DATE_FORMATS = [
r"%d/%b/%y",
diff --git a/frappe/website/doctype/web_form/templates/web_form.html b/frappe/website/doctype/web_form/templates/web_form.html
index 96072a19ea..5b35e6b5b4 100644
--- a/frappe/website/doctype/web_form/templates/web_form.html
+++ b/frappe/website/doctype/web_form/templates/web_form.html
@@ -1,149 +1,129 @@
{% extends "templates/web.html" %}
-{% block title %}{{ _(title) }}{% endblock %}
-
{% block breadcrumbs %}{% endblock %}
-{% macro container_attributes() %}
-data-web-form="{{ name }}" data-web-form-doctype="{{ doc_type }}" data-login-required="{{ frappe.utils.cint(login_required and frappe.session.user=='Guest') }}" data-is-list="{{ frappe.utils.cint(is_list) }}" data-allow-delete="{{ allow_delete }}"
+{% macro action_buttons() %}
+ {% if allow_print and not is_new %}
+ {% set print_format_url = "/printview?doctype=" + doc_type + "&name=" + doc_name + "&format=" + print_format %}
+
+
+
+
+ {% endif %}
+
+ {% if allow_edit and doc_name and not is_form_editable %}
+
+ {{ _("Edit", null, "Button in web form") }}
+ {% endif %}
+
+ {% if is_new or is_form_editable %}
+
+ {{ _("Cancel", null, "Button in web form") }}
+
+ {{ button_label or _("Save", null, "Button in web form") }}
+ {% endif %}
{% endmacro %}
{% block page_content %}
-{% if has_header and login_required and allow_multiple %}
-
-{% include "templates/includes/breadcrumbs.html" %}
-{% else %}
-
-{% endif %}
-
-
-
- {% if is_list %}
-
-
-
-
-
-
-
- {% else %}
-
-
-
-
- {% if show_attachments and not frappe.form_dict.new and attachments %}
-
-
{{ _("Attachments") }}
- {% for attachment in attachments %}
-
- {% endfor %}
-
- {% endif %} {# attachments #}
-
+
+ {% if has_header and login_required and show_list %}
+ {% include "templates/includes/breadcrumbs.html" %}
+ {% else %}
+
{% endif %}
-
-{% if allow_comments and not frappe.form_dict.new and not is_list -%}
-
-
-{%- else -%}
-
-{%- endif %} {# comments #}
+
+
+
+
+ {% if allow_comments and not is_new and not is_list -%}
+
+ {%- else -%}
+
+ {%- endif %} {# comments #}
{% endblock page_content %}
{% block script %}
-
-{{ include_script("controls.bundle.js") }}
-{% if is_list %}
-{{ include_script("dialog.bundle.js") }}
-{{ include_script("web_form.bundle.js") }}
-{{ include_script("bootstrap-4-web.bundle.js") }}
-{% else %}
-{{ include_script("dialog.bundle.js") }}
-
-
-{{ include_script("web_form.bundle.js") }}
-{{ include_script("bootstrap-4-web.bundle.js") }}
-
-{% if client_script %}
-frappe.init_client_script = () => {
- try {
- {{ client_script }}
- } catch(e) {
- console.error('Error in web form client script');
- console.error(e);
- }
-}
-{% endif %}
+
+
-{% if script is defined %}
- {{ script }}
-{% endif %}
-
-{% endif %}
+ {{ include_script("controls.bundle.js") }}
+ {{ include_script("dialog.bundle.js") }}
+ {{ include_script("web_form.bundle.js") }}
+ {{ include_script("bootstrap-4-web.bundle.js") }}
+
+
{% endblock script %}
{% block style %}
-{% if not is_list %}
-{{ include_style('web_form.bundle.css') }}
-{% endif %}
-
-
+
{% endblock %}
diff --git a/frappe/website/doctype/web_form/templates/web_form_row.html b/frappe/website/doctype/web_form/templates/web_form_row.html
deleted file mode 100644
index 2b999819cb..0000000000
--- a/frappe/website/doctype/web_form/templates/web_form_row.html
+++ /dev/null
@@ -1,4 +0,0 @@
-
-
\ No newline at end of file
diff --git a/frappe/website/doctype/web_form/templates/web_list.html b/frappe/website/doctype/web_form/templates/web_list.html
new file mode 100644
index 0000000000..2ec6edaf1c
--- /dev/null
+++ b/frappe/website/doctype/web_form/templates/web_list.html
@@ -0,0 +1,45 @@
+{% extends "templates/web.html" %}
+
+{% block breadcrumbs %}{% endblock %}
+
+{% block page_content %}
+
+
+{% endblock page_content %}
+
+{% block script %}
+
+
+ {{ include_script("controls.bundle.js") }}
+ {{ include_script("dialog.bundle.js") }}
+ {{ include_script("web_form.bundle.js") }}
+ {{ include_script("bootstrap-4-web.bundle.js") }}
+{% endblock script %}
+
+{% block style %}
+
+{% endblock %}
\ No newline at end of file
diff --git a/frappe/website/doctype/web_form/test_web_form.py b/frappe/website/doctype/web_form/test_web_form.py
index 5689bdbeef..13c73d1f14 100644
--- a/frappe/website/doctype/web_form/test_web_form.py
+++ b/frappe/website/doctype/web_form/test_web_form.py
@@ -4,6 +4,7 @@ import json
import unittest
import frappe
+from frappe.utils import set_request
from frappe.website.doctype.web_form.web_form import accept
from frappe.website.serve import get_response_content
@@ -68,8 +69,9 @@ class TestWebForm(unittest.TestCase):
)
def test_webform_render(self):
- content = get_response_content("request-data")
- self.assertIn("Request Data ", content)
+ set_request(method="GET", path="manage-events/new")
+ content = get_response_content("manage-events/new")
+ self.assertIn("New Manage Events ", content)
self.assertIn('data-doctype="Web Form"', content)
- self.assertIn('data-path="request-data"', content)
+ self.assertIn('data-path="manage-events/new"', content)
self.assertIn('source-type="Generator"', content)
diff --git a/frappe/website/doctype/web_form/web_form.js b/frappe/website/doctype/web_form/web_form.js
index 1f27b350be..63b71d35b4 100644
--- a/frappe/website/doctype/web_form/web_form.js
+++ b/frappe/website/doctype/web_form/web_form.js
@@ -1,89 +1,149 @@
-frappe.web_form = {
- set_fieldname_select: function(frm) {
- return new Promise(resolve => {
- var me = this,
- doc = frm.doc;
- if (doc.doc_type) {
- frappe.model.with_doctype(doc.doc_type, function() {
- var fields = $.map(frappe.get_doc("DocType", frm.doc.doc_type).fields, function(d) {
- if (frappe.model.no_value_type.indexOf(d.fieldtype) === -1 ||
- d.fieldtype === 'Table') {
- return { label: d.label + ' (' + d.fieldtype + ')', value: d.fieldname };
- } else {
- return null;
- }
- });
- var currency_fields = $.map(frappe.get_doc("DocType", frm.doc.doc_type).fields, function(d) {
- if (d.fieldtype === 'Currency' || d.fieldtype === 'Float') {
- return { label: d.label, value: d.fieldname };
- } else {
- return null;
- }
- });
-
- frm.fields_dict.web_form_fields.grid.update_docfield_property(
- 'fieldname', 'options', fields
- );
- frappe.meta.get_docfield("Web Form", "amount_field", frm.doc.name).options = [""].concat(currency_fields);
- frm.refresh_field("amount_field");
- resolve();
- });
- }
- });
- }
-};
-
frappe.ui.form.on("Web Form", {
refresh: function(frm) {
// show is-standard only if developer mode
frm.get_field("is_standard").toggle(frappe.boot.developer_mode);
- frappe.web_form.set_fieldname_select(frm);
-
if (frm.doc.is_standard && !frappe.boot.developer_mode) {
frm.set_read_only();
frm.disable_save();
}
+ render_list_settings_message(frm);
- frm.add_custom_button(__('Get Fields'), () => {
- let webform_fieldtypes = frappe.meta.get_field('Web Form Field', 'fieldtype').options.split('\n');
- let fieldnames = (frm.doc.web_form_fields || []).map(d => d.fieldname);
- frappe.model.with_doctype(frm.doc.doc_type, () => {
- let meta = frappe.get_meta(frm.doc.doc_type);
- for (let field of meta.fields) {
- if (webform_fieldtypes.includes(field.fieldtype)
- && !fieldnames.includes(field.fieldname)) {
- frm.add_child('web_form_fields', {
- fieldname: field.fieldname,
- label: field.label,
- fieldtype: field.fieldtype,
- options: field.options,
- reqd: field.reqd,
- default: field.default,
- read_only: field.read_only || field.is_virtual,
- depends_on: field.depends_on,
- mandatory_depends_on: field.mandatory_depends_on,
- read_only_depends_on: field.read_only_depends_on,
- hidden: field.hidden,
- description: field.description
+ frm.trigger('set_fields');
+ frm.trigger('add_get_fields_button');
+ frm.trigger('add_publish_button');
+ },
+
+ login_required: function(frm) {
+ render_list_settings_message(frm);
+ },
+
+ validate: function(frm) {
+ if (!frm.doc.login_required) {
+ frm.set_value("allow_multiple", 0);
+ frm.set_value("allow_edit", 0);
+ frm.set_value("show_list", 0);
+ }
+
+ !frm.doc.allow_multiple && frm.set_value("allow_delete", 0);
+ frm.doc.allow_multiple && frm.set_value("show_list", 1);
+
+ if (!frm.doc.web_form_fields) {
+ frm.scroll_to_field('web_form_fields');
+ frappe.throw(__("Atleast one field is required in Web Form Fields Table"));
+ }
+ },
+
+ add_publish_button(frm) {
+ frm.add_custom_button(frm.doc.published ? __("Unpublish") : __("Publish"), () => {
+ frm.set_value("published", !frm.doc.published);
+ frm.save();
+ });
+ },
+
+ add_get_fields_button(frm) {
+ frm.add_custom_button(__("Get Fields"), () => {
+ let webform_fieldtypes = frappe.meta
+ .get_field("Web Form Field", "fieldtype")
+ .options.split("\n");
+
+ let added_fields = (frm.doc.fields || []).map(d => d.fieldname);
+
+ get_fields_for_doctype(frm.doc.doc_type).then(fields => {
+ for (let df of fields) {
+ if (
+ webform_fieldtypes.includes(df.fieldtype) &&
+ !added_fields.includes(df.fieldname) &&
+ !df.hidden
+ ) {
+ frm.add_child("web_form_fields", {
+ fieldname: df.fieldname,
+ label: df.label,
+ fieldtype: df.fieldtype,
+ options: df.options,
+ reqd: df.reqd,
+ default: df.default,
+ read_only: df.read_only,
+ depends_on: df.depends_on,
+ mandatory_depends_on: df.mandatory_depends_on,
+ read_only_depends_on: df.read_only_depends_on,
});
}
}
- frm.refresh();
+ frm.refresh_field('web_form_fields');
+ frm.scroll_to_field('web_form_fields');
});
});
},
+ set_fields(frm) {
+ let doc = frm.doc;
+
+ let update_options = options => {
+ [
+ frm.fields_dict.web_form_fields.grid,
+ frm.fields_dict.list_columns.grid
+ ].forEach(obj => {
+ obj.update_docfield_property("fieldname", "options", options);
+ });
+ };
+
+ if (!doc.doc_type) {
+ update_options([]);
+ frm.set_df_property("amount_field", "options", []);
+ return;
+ }
+
+ update_options([`Fetching fields from ${doc.doc_type}...`]);
+
+ get_fields_for_doctype(doc.doc_type).then(fields => {
+ let as_select_option = df => ({
+ label: df.label + " (" + df.fieldtype + ")",
+ value: df.fieldname
+ });
+ update_options(fields.map(as_select_option));
+
+ let currency_fields = fields
+ .filter(df => ["Currency", "Float"].includes(df.fieldtype))
+ .map(as_select_option);
+ if (!currency_fields.length) {
+ currency_fields = [
+ {
+ label: `No currency fields in ${doc.doc_type}`,
+ value: "",
+ disabled: true
+ }
+ ];
+ }
+ frm.set_df_property("amount_field", "options", currency_fields);
+ });
+ },
+
title: function(frm) {
if (frm.doc.__islocal) {
var page_name = frm.doc.title.toLowerCase().replace(/ /g, "-");
frm.set_value("route", page_name);
- frm.set_value("success_url", "/" + page_name);
}
},
doc_type: function(frm) {
- frappe.web_form.set_fieldname_select(frm);
+ frm.trigger('set_fields');
+ },
+
+ allow_multiple: function(frm) {
+ frm.doc.allow_multiple && frm.set_value("show_list", 1);
+ }
+});
+
+
+frappe.ui.form.on("Web Form List Column", {
+ fieldname: function(frm, doctype, name) {
+ let doc = frappe.get_doc(doctype, name);
+ let df = frappe.meta.get_docfield(frm.doc.doc_type, doc.fieldname);
+ if (!df) return;
+ doc.fieldtype = df.fieldtype;
+ doc.label = df.label;
+ frm.refresh_field("list_columns");
}
});
@@ -93,22 +153,61 @@ frappe.ui.form.on("Web Form Field", {
var doc = frappe.get_doc(doctype, name);
if (['Section Break', 'Column Break', 'Page Break'].includes(doc.fieldtype)) {
doc.fieldname = '';
+ doc.options = "";
frm.refresh_field("web_form_fields");
}
},
fieldname: function(frm, doctype, name) {
- var doc = frappe.get_doc(doctype, name);
- var df = $.map(frappe.get_doc("DocType", frm.doc.doc_type).fields, function(d) {
- return doc.fieldname == d.fieldname ? d : null;
- })[0];
+ let doc = frappe.get_doc(doctype, name);
+ let df = frappe.meta.get_docfield(frm.doc.doc_type, doc.fieldname);
+ if (!df) return;
doc.label = df.label;
- doc.reqd = df.reqd;
+ doc.fieldtype = df.fieldtype;
doc.options = df.options;
- doc.fieldtype = frappe.meta.get_docfield("Web Form Field", "fieldtype")
- .options.split("\n").indexOf(df.fieldtype) === -1 ? "Data" : df.fieldtype;
- doc.description = df.description;
- doc["default"] = df["default"];
+ doc.reqd = df.reqd;
+ doc.default = df.default;
+ doc.read_only = df.read_only;
+ doc.depends_on = df.depends_on;
+ doc.mandatory_depends_on = df.mandatory_depends_on;
+ doc.read_only_depends_on = df.read_only_depends_on;
+ frm.refresh_field("web_form_fields");
}
});
+
+
+function get_fields_for_doctype(doctype) {
+ return new Promise(resolve =>
+ frappe.model.with_doctype(doctype, resolve)
+ ).then(() => {
+ return frappe.meta.get_docfields(doctype).filter(df => {
+ return (
+ (frappe.model.is_value_type(df.fieldtype) &&
+ !["lft", "rgt"].includes(df.fieldname)) ||
+ ["Table", "Table Multiselect"].includes(df.fieldtype)
+ );
+ });
+ });
+}
+
+function render_list_settings_message(frm) {
+ // render list setting message
+ if (frm.fields_dict['list_setting_message'] && !frm.doc.login_required) {
+ const switch_to_form_settings_tab = `
+
+ ${__("Form Settings Tab")}
+
+ `;
+ $(frm.fields_dict['list_setting_message'].wrapper)
+ .html($(
+ `
+ ${__("Login is required to see web form list view. Enable login_required from {0} to see list settings", [switch_to_form_settings_tab])}
+
`
+ ))
+ .find('span')
+ .click(() => frm.scroll_to_field('login_required'));
+ } else {
+ $(frm.fields_dict['list_setting_message'].wrapper).empty();
+ }
+}
diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json
index 08b2854059..0872c1d654 100644
--- a/frappe/website/doctype/web_form/web_form.json
+++ b/frappe/website/doctype/web_form/web_form.json
@@ -5,43 +5,51 @@
"document_type": "Document",
"engine": "InnoDB",
"field_order": [
+ "title_and_route_tab",
"title",
"route",
+ "published",
+ "column_break_4",
"doc_type",
"module",
- "column_break_4",
"is_standard",
- "is_multi_step_form",
- "published",
+ "introduction",
+ "introduction_text",
+ "form_settings_tab",
"login_required",
- "route_to_success_link",
- "allow_edit",
+ "is_multi_step_form",
"allow_multiple",
- "apply_document_permissions",
- "show_in_grid",
+ "allow_edit",
"allow_delete",
+ "column_break_18",
+ "apply_document_permissions",
"allow_print",
"print_format",
"allow_comments",
"show_attachments",
"allow_incomplete",
- "introduction",
- "introduction_text",
- "fields",
+ "form_fields",
"web_form_fields",
"max_attachment_size",
- "client_script_section",
- "client_script",
- "custom_css_section",
- "custom_css",
"actions",
+ "breadcrumbs",
"button_label",
+ "column_break_29",
"success_message",
+ "route_to_success_link",
"success_url",
- "sidebar_settings",
+ "list_settings_tab",
+ "list_setting_message",
+ "show_list",
+ "list_title",
+ "list_columns",
+ "sidebar_settings_tab",
"show_sidebar",
- "sidebar_items",
- "payments",
+ "website_sidebar",
+ "scripting_style_tab",
+ "client_script",
+ "custom_css",
+ "payments_tab",
"accept_payment",
"payment_gateway",
"payment_button_label",
@@ -50,10 +58,7 @@
"amount_based_on_field",
"amount_field",
"amount",
- "currency",
- "advanced",
- "web_page_link_text",
- "breadcrumbs"
+ "currency"
],
"fields": [
{
@@ -118,25 +123,18 @@
"depends_on": "login_required",
"fieldname": "allow_edit",
"fieldtype": "Check",
- "label": "Allow Edit"
+ "label": "Allow Editing After Submit"
},
{
"default": "0",
"depends_on": "login_required",
"fieldname": "allow_multiple",
"fieldtype": "Check",
- "label": "Allow Multiple"
+ "label": "Allow Multiple Responses"
},
{
"default": "0",
- "depends_on": "allow_multiple",
- "fieldname": "show_in_grid",
- "fieldtype": "Check",
- "label": "Show as Grid"
- },
- {
- "default": "0",
- "depends_on": "allow_multiple",
+ "depends_on": "eval: doc.allow_multiple && doc.login_required",
"fieldname": "allow_delete",
"fieldtype": "Check",
"label": "Allow Delete"
@@ -187,11 +185,6 @@
"ignore_xss_filter": 1,
"label": "Introduction"
},
- {
- "fieldname": "fields",
- "fieldtype": "Section Break",
- "label": "Fields"
- },
{
"fieldname": "web_form_fields",
"fieldtype": "Table",
@@ -203,13 +196,6 @@
"fieldtype": "Int",
"label": "Max Attachment Size (in MB)"
},
- {
- "collapsible": 1,
- "collapsible_depends_on": "client_script",
- "fieldname": "client_script_section",
- "fieldtype": "Section Break",
- "label": "Client Script"
- },
{
"description": "For help see Client Script API and Examples ",
"fieldname": "client_script",
@@ -220,13 +206,13 @@
"collapsible": 1,
"fieldname": "actions",
"fieldtype": "Section Break",
- "label": "Actions"
+ "label": "Customization"
},
{
"default": "Save",
"fieldname": "button_label",
"fieldtype": "Data",
- "label": "Button Label"
+ "label": "Submit Button Label"
},
{
"description": "Message to be displayed on successful completion (only for Guest users)",
@@ -235,36 +221,18 @@
"label": "Success Message"
},
{
+ "depends_on": "route_to_success_link",
"description": "Go to this URL after completing the form",
"fieldname": "success_url",
"fieldtype": "Data",
"label": "Success URL"
},
- {
- "collapsible": 1,
- "fieldname": "sidebar_settings",
- "fieldtype": "Section Break",
- "label": "Sidebar Settings"
- },
{
"default": "0",
"fieldname": "show_sidebar",
"fieldtype": "Check",
"label": "Show Sidebar"
},
- {
- "fieldname": "sidebar_items",
- "fieldtype": "Table",
- "label": "Sidebar Items",
- "options": "Portal Menu Item"
- },
- {
- "collapsible": 1,
- "collapsible_depends_on": "accept_payment",
- "fieldname": "payments",
- "fieldtype": "Section Break",
- "label": "Payments"
- },
{
"default": "0",
"fieldname": "accept_payment",
@@ -321,18 +289,6 @@
"label": "Currency",
"options": "Currency"
},
- {
- "collapsible": 1,
- "fieldname": "advanced",
- "fieldtype": "Section Break",
- "label": "Advanced"
- },
- {
- "description": "Text to be displayed for Link to Web Page if this form has a web page. Link route will be automatically generated based on `page_name` and `parent_website_route`",
- "fieldname": "web_page_link_text",
- "fieldtype": "Data",
- "label": "Web Page Link Text"
- },
{
"description": "List as [{\"label\": _(\"Jobs\"), \"route\":\"jobs\"}]",
"fieldname": "breadcrumbs",
@@ -345,13 +301,6 @@
"label": "Custom CSS",
"options": "CSS"
},
- {
- "collapsible": 1,
- "collapsible_depends_on": "custom_css",
- "fieldname": "custom_css_section",
- "fieldtype": "Section Break",
- "label": "Custom CSS"
- },
{
"default": "0",
"fieldname": "apply_document_permissions",
@@ -363,13 +312,93 @@
"fieldname": "is_multi_step_form",
"fieldtype": "Check",
"label": "Is Multi Step Form"
+ },
+ {
+ "default": "0",
+ "depends_on": "login_required",
+ "fieldname": "show_list",
+ "fieldtype": "Check",
+ "label": "Show List"
+ },
+ {
+ "depends_on": "eval: doc.login_required && doc.show_list",
+ "fieldname": "list_title",
+ "fieldtype": "Data",
+ "label": "Title"
+ },
+ {
+ "depends_on": "eval: doc.login_required && doc.show_list",
+ "fieldname": "list_columns",
+ "fieldtype": "Table",
+ "label": "List Columns",
+ "options": "Web Form List Column"
+ },
+ {
+ "fieldname": "title_and_route_tab",
+ "fieldtype": "Tab Break",
+ "label": "Title & Route"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "form_fields",
+ "fieldtype": "Section Break",
+ "label": "Form Fields"
+ },
+ {
+ "fieldname": "column_break_18",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "website_sidebar",
+ "fieldtype": "Link",
+ "label": "Website Sidebar",
+ "options": "Website Sidebar"
+ },
+ {
+ "fieldname": "column_break_29",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "list_setting_message",
+ "fieldtype": "HTML",
+ "label": "List Setting Message"
+ },
+ {
+ "fieldname": "form_settings_tab",
+ "fieldtype": "Tab Break",
+ "label": "Form Settings"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "show_list",
+ "fieldname": "list_settings_tab",
+ "fieldtype": "Tab Break",
+ "label": "List Settings"
+ },
+ {
+ "collapsible": 1,
+ "fieldname": "sidebar_settings_tab",
+ "fieldtype": "Tab Break",
+ "label": "Sidebar Settings"
+ },
+ {
+ "fieldname": "scripting_style_tab",
+ "fieldtype": "Tab Break",
+ "label": "Scripting / Style"
+ },
+ {
+ "collapsible": 1,
+ "collapsible_depends_on": "accept_payment",
+ "fieldname": "payments_tab",
+ "fieldtype": "Tab Break",
+ "label": "Payments"
}
],
"has_web_view": 1,
"icon": "icon-edit",
"is_published_field": "published",
"links": [],
- "modified": "2022-03-23 15:44:41.385001",
+ "modified": "2022-07-18 15:51:15.288860",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form",
diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py
index ee8861a7aa..e1c9e798e5 100644
--- a/frappe/website/doctype/web_form/web_form.py
+++ b/frappe/website/doctype/web_form/web_form.py
@@ -13,8 +13,8 @@ from frappe.desk.form.meta import get_code_files_via_hooks
from frappe.integrations.utils import get_payment_gateway_controller
from frappe.modules.utils import export_module_json, get_doc_module
from frappe.rate_limiter import rate_limit
-from frappe.utils import cstr
-from frappe.website.utils import get_comment_list
+from frappe.utils import cstr, dict_with_keys, strip_html
+from frappe.website.utils import get_boot_data, get_comment_list, get_sidebar_items
from frappe.website.website_generator import WebsiteGenerator
@@ -32,17 +32,20 @@ class WebForm(WebsiteGenerator):
if not self.module:
self.module = frappe.db.get_value("DocType", self.doc_type, "module")
- if (
- not (
- frappe.flags.in_install
- or frappe.flags.in_patch
- or frappe.flags.in_test
- or frappe.flags.in_fixtures
- )
- and self.is_standard
- and not frappe.conf.developer_mode
- ):
- frappe.throw(_("You need to be in developer mode to edit a Standard Web Form"))
+ in_user_env = not (
+ frappe.flags.in_install
+ or frappe.flags.in_patch
+ or frappe.flags.in_test
+ or frappe.flags.in_fixtures
+ )
+ if in_user_env and self.is_standard and not frappe.conf.developer_mode:
+ # only published can be changed for standard web forms
+ if self.has_value_changed("published"):
+ published_value = self.published
+ self.reload()
+ self.published = published_value
+ else:
+ frappe.throw(_("You need to be in developer mode to edit a Standard Web Form"))
if not frappe.flags.in_import:
self.validate_fields()
@@ -131,60 +134,131 @@ def get_context(context):
def get_context(self, context):
"""Build context to render the `web_form.html` template"""
+ context.is_form_editable = False
self.set_web_form_module()
- doc, delimeter = make_route_string(frappe.form_dict)
- context.doc = doc
- context.delimeter = delimeter
+ if frappe.form_dict.is_list:
+ context.template = "website/doctype/web_form/templates/web_list.html"
+ else:
+ context.template = "website/doctype/web_form/templates/web_form.html"
# check permissions
- if frappe.session.user == "Guest" and frappe.form_dict.name:
- frappe.throw(
- _("You need to be logged in to access this {0}.").format(self.doc_type), frappe.PermissionError
- )
+ if frappe.form_dict.name:
+ if frappe.session.user == "Guest":
+ frappe.throw(
+ _("You need to be logged in to access this {0}.").format(self.doc_type),
+ frappe.PermissionError,
+ )
- if frappe.form_dict.name and not self.has_web_form_permission(
- self.doc_type, frappe.form_dict.name
+ if not frappe.db.exists(self.doc_type, frappe.form_dict.name):
+ raise frappe.PageDoesNotExistError()
+
+ if not self.has_web_form_permission(self.doc_type, frappe.form_dict.name):
+ frappe.throw(
+ _("You don't have the permissions to access this document"), frappe.PermissionError
+ )
+
+ if frappe.local.path == self.route:
+ path = f"/{self.route}/list" if self.show_list else f"/{self.route}/new"
+ frappe.redirect(path)
+
+ if frappe.form_dict.is_list and not self.show_list:
+ frappe.redirect(f"/{self.route}/new")
+
+ if frappe.form_dict.is_edit and not self.allow_edit:
+ frappe.redirect(f"/{self.route}/{frappe.form_dict.name}")
+
+ if frappe.form_dict.is_edit:
+ context.is_form_editable = True
+
+ if (
+ not frappe.form_dict.is_edit
+ and not frappe.form_dict.is_read
+ and self.allow_edit
+ and frappe.form_dict.name
):
- frappe.throw(
- _("You don't have the permissions to access this document"), frappe.PermissionError
- )
+ context.is_form_editable = True
+ frappe.redirect(f"/{frappe.local.path}/edit")
+
+ if (
+ frappe.session.user != "Guest"
+ and not self.allow_multiple
+ and not frappe.form_dict.name
+ and not frappe.form_dict.is_list
+ ):
+ name = frappe.db.get_value(self.doc_type, {"owner": frappe.session.user}, "name")
+ if name:
+ frappe.redirect(f"/{self.route}/{name}")
+
+ # Show new form when
+ # - User is Guest
+ # - Login not required
+ route_to_new = frappe.session.user == "Guest" and not self.login_required
+ if not frappe.form_dict.is_new and route_to_new:
+ frappe.redirect(f"/{self.route}/new")
self.reset_field_parent()
if self.is_standard:
self.use_meta_fields()
- if not frappe.session.user == "Guest":
- if self.allow_edit:
- if self.allow_multiple:
- if not frappe.form_dict.name and not frappe.form_dict.new:
- # list data is queried via JS
- context.is_list = True
- else:
- if frappe.session.user != "Guest" and not frappe.form_dict.name:
- frappe.form_dict.name = frappe.db.get_value(
- self.doc_type, {"owner": frappe.session.user}, "name"
- )
+ # add keys from form_dict to context
+ context.update(dict_with_keys(frappe.form_dict, ["is_list", "is_new", "is_edit", "is_read"]))
- if not frappe.form_dict.name:
- # only a single doc allowed and no existing doc, hence new
- frappe.form_dict.new = 1
+ for df in self.web_form_fields:
+ if df.fieldtype == "Column Break":
+ context.has_column_break = True
+ break
+
+ # load web form doc
+ context.web_form_doc = self.as_dict(no_nulls=True)
+ context.web_form_doc.update(dict_with_keys(context, ["is_list", "is_new", "is_form_editable"]))
+
+ if self.show_sidebar and self.website_sidebar:
+ context.sidebar_items = get_sidebar_items(self.website_sidebar)
if frappe.form_dict.is_list:
- context.is_list = True
+ self.load_list_data(context)
+ else:
+ self.load_form_data(context)
- # always render new form if login is not required or doesn't allow editing existing ones
- if not self.login_required or not self.allow_edit:
- frappe.form_dict.new = 1
+ self.add_custom_context_and_script(context)
+ self.load_translations(context)
+
+ context.boot = get_boot_data()
+ context.boot["link_title_doctypes"] = frappe.boot.get_link_title_doctypes()
+
+ def load_translations(self, context):
+ translated_messages = frappe.translate.get_dict("doctype", self.doc_type)
+ # Sr is not added by default, had to be added manually
+ translated_messages["Sr"] = _("Sr")
+ context.translated_messages = frappe.as_json(translated_messages)
+
+ def load_list_data(self, context):
+ if not self.list_columns:
+ self.list_columns = get_in_list_view_fields(self.doc_type)
+ context.web_form_doc.list_columns = self.list_columns
+
+ def load_form_data(self, context):
+ """Load document `doc` and `layout` properties for template"""
+ context.parents = []
+ if self.show_list:
+ context.parents.append(
+ {
+ "label": _(self.title),
+ "route": f"{self.route}/list",
+ }
+ )
- self.load_document(context)
context.parents = self.get_parents(context)
if self.breadcrumbs:
context.parents = frappe.safe_eval(self.breadcrumbs, {"_": _})
- context.has_header = (frappe.form_dict.name or frappe.form_dict.new) and (
+ if frappe.form_dict.is_new:
+ context.title = _("New {0}").format(context.title)
+
+ context.has_header = (frappe.form_dict.name or frappe.form_dict.is_new) and (
frappe.session.user != "Guest" or not self.login_required
)
@@ -193,33 +267,40 @@ def get_context(context):
"'"
)
- self.add_custom_context_and_script(context)
if not context.max_attachment_size:
context.max_attachment_size = get_max_file_size() / 1024 / 1024
- context.show_in_grid = self.show_in_grid
- self.load_translations(context)
- context.link_title_doctypes = frappe.boot.get_link_title_doctypes()
+ # For Table fields, server-side processing for meta
+ for field in context.web_form_doc.web_form_fields:
+ if field.fieldtype == "Table":
+ field.fields = get_in_list_view_fields(field.options)
- def load_translations(self, context):
- translated_messages = frappe.translate.get_dict("doctype", self.doc_type)
- # Sr is not added by default, had to be added manually
- translated_messages["Sr"] = _("Sr")
- context.translated_messages = frappe.as_json(translated_messages)
+ if field.fieldtype == "Link":
+ field.fieldtype = "Autocomplete"
+ field.options = get_link_options(
+ self.name, field.options, field.allow_read_on_all_link_options
+ )
- def load_document(self, context):
- """Load document `doc` and `layout` properties for template"""
- if frappe.form_dict.name or frappe.form_dict.new:
- context.layout = self.get_layout()
- context.parents = [{"route": self.route, "label": _(self.title)}]
+ context.reference_doc = {}
+ # load reference doc
if frappe.form_dict.name:
- context.doc = frappe.get_doc(self.doc_type, frappe.form_dict.name)
- context.title = context.doc.get(context.doc.meta.get_title_field())
- context.doc.add_seen()
-
- context.reference_doctype = context.doc.doctype
- context.reference_name = context.doc.name
+ context.doc_name = frappe.form_dict.name
+ context.reference_doc = frappe.get_doc(self.doc_type, context.doc_name)
+ context.title = strip_html(
+ context.reference_doc.get(context.reference_doc.meta.get_title_field())
+ )
+ if context.is_form_editable:
+ context.parents.append(
+ {
+ "label": _(context.title),
+ "route": f"{self.route}/{context.doc_name}",
+ }
+ )
+ context.title = _("Edit")
+ context.reference_doc.add_seen()
+ context.reference_doctype = context.reference_doc.doctype
+ context.reference_name = context.reference_doc.name
if self.show_attachments:
context.attachments = frappe.get_all(
@@ -233,7 +314,11 @@ def get_context(context):
)
if self.allow_comments:
- context.comment_list = get_comment_list(context.doc.doctype, context.doc.name)
+ context.comment_list = get_comment_list(
+ context.reference_doc.doctype, context.reference_doc.name
+ )
+
+ context.reference_doc = json.loads(context.reference_doc.as_json())
def get_payment_gateway_url(self, doc):
if self.accept_payment:
@@ -594,7 +679,7 @@ def get_form_data(doctype, docname=None, web_form_name=None):
# For Table fields, server-side processing for meta
for field in out.web_form.web_form_fields:
if field.fieldtype == "Table":
- field.fields = frappe.get_meta(field.options).fields
+ field.fields = get_in_list_view_fields(field.options)
out.update({field.fieldname: field.fields})
if field.fieldtype == "Link":
diff --git a/frappe/website/doctype/web_form/web_form_list.js b/frappe/website/doctype/web_form/web_form_list.js
new file mode 100644
index 0000000000..f426fd9899
--- /dev/null
+++ b/frappe/website/doctype/web_form/web_form_list.js
@@ -0,0 +1,10 @@
+frappe.listview_settings['Web Form'] = {
+ add_fields: ["title", "published"],
+ get_indicator: function(doc) {
+ if (doc.published) {
+ return [__("Published"), "green", "published,=,1"];
+ } else {
+ return [__("Not Published"), "gray", "published,=,0"];
+ }
+ }
+};
\ No newline at end of file
diff --git a/frappe/website/doctype/web_form_field/web_form_field.json b/frappe/website/doctype/web_form_field/web_form_field.json
index 36b1ca2c15..4e0d58d42d 100644
--- a/frappe/website/doctype/web_form_field/web_form_field.json
+++ b/frappe/website/doctype/web_form_field/web_form_field.json
@@ -10,7 +10,6 @@
"label",
"allow_read_on_all_link_options",
"reqd",
- "depends_on",
"read_only",
"show_in_filter",
"hidden",
@@ -19,6 +18,7 @@
"max_length",
"max_value",
"property_depends_on_section",
+ "depends_on",
"mandatory_depends_on",
"column_break_16",
"read_only_depends_on",
@@ -63,7 +63,7 @@
{
"fieldname": "depends_on",
"fieldtype": "Code",
- "label": "Depends On"
+ "label": "Display Depends On"
},
{
"default": "0",
@@ -146,12 +146,13 @@
],
"istable": 1,
"links": [],
- "modified": "2022-01-28 10:41:25.422345",
+ "modified": "2022-06-06 16:00:55.627950",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form Field",
"owner": "Administrator",
"permissions": [],
"sort_field": "modified",
- "sort_order": "DESC"
-}
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/frappe/website/doctype/web_form_list_column/__init__.py b/frappe/website/doctype/web_form_list_column/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/website/doctype/web_form_list_column/web_form_list_column.json b/frappe/website/doctype/web_form_list_column/web_form_list_column.json
new file mode 100644
index 0000000000..e55aeadca6
--- /dev/null
+++ b/frappe/website/doctype/web_form_list_column/web_form_list_column.json
@@ -0,0 +1,48 @@
+{
+ "actions": [],
+ "autoname": "autoincrement",
+ "creation": "2022-06-20 20:02:12.132569",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "fieldname",
+ "fieldtype",
+ "label"
+ ],
+ "fields": [
+ {
+ "fieldname": "fieldname",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Fieldname",
+ "reqd": 1
+ },
+ {
+ "fieldname": "label",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Label"
+ },
+ {
+ "fieldname": "fieldtype",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Fieldtype",
+ "read_only": 1
+ }
+ ],
+ "index_web_pages_for_search": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-06-21 17:22:14.978947",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Web Form List Column",
+ "naming_rule": "Autoincrement",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
+}
\ No newline at end of file
diff --git a/frappe/website/doctype/web_form_list_column/web_form_list_column.py b/frappe/website/doctype/web_form_list_column/web_form_list_column.py
new file mode 100644
index 0000000000..9aff5f1ecc
--- /dev/null
+++ b/frappe/website/doctype/web_form_list_column/web_form_list_column.py
@@ -0,0 +1,9 @@
+# Copyright (c) 2022, Frappe Technologies and contributors
+# For license information, please see license.txt
+
+# import frappe
+from frappe.model.document import Document
+
+
+class WebFormListColumn(Document):
+ pass
diff --git a/frappe/website/doctype/web_page/web_page.py b/frappe/website/doctype/web_page/web_page.py
index a94838baed..b523eb2e83 100644
--- a/frappe/website/doctype/web_page/web_page.py
+++ b/frappe/website/doctype/web_page/web_page.py
@@ -16,6 +16,7 @@ from frappe.website.utils import (
find_first_image,
get_comment_list,
get_html_content_based_on_type,
+ get_sidebar_items,
)
from frappe.website.website_generator import WebsiteGenerator
@@ -70,6 +71,9 @@ class WebPage(WebsiteGenerator):
if not self.show_title:
context["no_header"] = 1
+ if self.show_sidebar:
+ context.sidebar_items = get_sidebar_items(self.website_sidebar)
+
self.set_metatags(context)
self.set_breadcrumbs(context)
self.set_title_and_header(context)
diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py
index bd634b4f32..fffbd94684 100644
--- a/frappe/website/doctype/website_settings/website_settings.py
+++ b/frappe/website/doctype/website_settings/website_settings.py
@@ -7,6 +7,7 @@ from frappe import _
from frappe.integrations.google_oauth import GoogleOAuth
from frappe.model.document import Document
from frappe.utils import encode, get_request_site_address
+from frappe.website.utils import get_boot_data
class WebsiteSettings(Document):
@@ -190,6 +191,8 @@ def get_website_settings(context=None):
if settings.splash_image:
context["splash_image"] = settings.splash_image
+ context.boot = get_boot_data()
+
return context
diff --git a/frappe/website/page_renderers/web_form.py b/frappe/website/page_renderers/web_form.py
index 1953118790..74996e4a78 100644
--- a/frappe/website/page_renderers/web_form.py
+++ b/frappe/website/page_renderers/web_form.py
@@ -1,11 +1,13 @@
-import frappe
from frappe.website.page_renderers.document_page import DocumentPage
+from frappe.website.router import get_page_info_from_web_form
class WebFormPage(DocumentPage):
def can_render(self):
- webform_name = frappe.db.exists("Web Form", {"route": self.path, "published": 1}, cache=True)
- if webform_name:
+ web_form = get_page_info_from_web_form(self.path)
+ if web_form:
self.doctype = "Web Form"
- self.docname = webform_name
- return bool(webform_name)
+ self.docname = web_form.name
+ return True
+ else:
+ return False
diff --git a/frappe/website/router.py b/frappe/website/router.py
index 24a085224b..aa1e15d4c9 100644
--- a/frappe/website/router.py
+++ b/frappe/website/router.py
@@ -30,6 +30,32 @@ def get_page_info_from_web_page_with_dynamic_routes(path):
return page_info[end_point]
+def get_page_info_from_web_form(path):
+ """Query published web forms and evaluate if the route matches"""
+ rules, page_info = [], {}
+ web_forms = frappe.db.get_all("Web Form", ["name", "route", "modified"], {"published": 1})
+ for d in web_forms:
+ rules.append(Rule(f"/{d.route}", endpoint=d.name))
+ rules.append(Rule(f"/{d.route}/list", endpoint=d.name))
+ rules.append(Rule(f"/{d.route}/new", endpoint=d.name))
+ rules.append(Rule(f"/{d.route}/", endpoint=d.name))
+ rules.append(Rule(f"/{d.route}//edit", endpoint=d.name))
+ d.doctype = "Web Form"
+ page_info[d.name] = d
+
+ end_point = evaluate_dynamic_routes(rules, path)
+ if end_point:
+ if path.endswith("/list"):
+ frappe.form_dict.is_list = True
+ elif path.endswith("/new"):
+ frappe.form_dict.is_new = True
+ elif path.endswith("/edit"):
+ frappe.form_dict.is_edit = True
+ else:
+ frappe.form_dict.is_read = True
+ return page_info[end_point]
+
+
def evaluate_dynamic_routes(rules, path):
"""
Use Werkzeug routing to evaluate dynamic routes like /project/
diff --git a/frappe/website/serve.py b/frappe/website/serve.py
index 2c33b5df51..7eb8b017f1 100644
--- a/frappe/website/serve.py
+++ b/frappe/website/serve.py
@@ -1,5 +1,6 @@
import frappe
from frappe.website.page_renderers.error_page import ErrorPage
+from frappe.website.page_renderers.not_found_page import NotFoundPage
from frappe.website.page_renderers.not_permitted_page import NotPermittedPage
from frappe.website.page_renderers.redirect_page import RedirectPage
from frappe.website.path_resolver import PathResolver
@@ -19,6 +20,8 @@ def get_response(path=None, http_status_code=200):
return RedirectPage(endpoint or path, http_status_code).render()
except frappe.PermissionError as e:
response = NotPermittedPage(endpoint, http_status_code, exception=e).render()
+ except frappe.PageDoesNotExistError:
+ response = NotFoundPage(endpoint, http_status_code).render()
except Exception as e:
frappe.log_error(f"{path} failed")
response = ErrorPage(exception=e).render()
diff --git a/frappe/website/utils.py b/frappe/website/utils.py
index 8a0cfbab7c..508026f064 100644
--- a/frappe/website/utils.py
+++ b/frappe/website/utils.py
@@ -12,7 +12,7 @@ from werkzeug.wrappers import Response
import frappe
from frappe import _
from frappe.model.document import Document
-from frappe.utils import md_to_html
+from frappe.utils import cint, get_time_zone, md_to_html
FRONTMATTER_PATTERN = re.compile(r"^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$", re.S | re.M)
H1_TAG_PATTERN = re.compile("([^<]*)")
@@ -158,6 +158,20 @@ def get_home_page_via_hooks():
return home_page
+def get_boot_data():
+ return {
+ "sysdefaults": {
+ "float_precision": cint(frappe.get_system_settings("float_precision")) or 3,
+ "date_format": frappe.get_system_settings("date_format") or "yyyy-mm-dd",
+ "time_format": frappe.get_system_settings("time_format") or "HH:mm:ss",
+ },
+ "time_zone": {
+ "system": get_time_zone(),
+ "user": frappe.db.get_value("User", frappe.session.user, "time_zone") or get_time_zone(),
+ },
+ }
+
+
def is_signup_disabled():
return frappe.db.get_single_value("Website Settings", "disable_signup", True)
@@ -393,7 +407,7 @@ def get_frontmatter(string):
}
-def get_sidebar_items(parent_sidebar, basepath):
+def get_sidebar_items(parent_sidebar, basepath=None):
import frappe.www.list
sidebar_items = []
diff --git a/frappe/website/web_form/request_data/request_data.json b/frappe/website/web_form/request_data/request_data.json
index 591ef4a031..c52a2f6203 100644
--- a/frappe/website/web_form/request_data/request_data.json
+++ b/frappe/website/web_form/request_data/request_data.json
@@ -11,6 +11,7 @@
"apply_document_permissions": 0,
"breadcrumbs": "",
"button_label": "Request Data",
+ "client_script": "",
"creation": "2019-01-24 16:19:26.886096",
"currency": "INR",
"doc_type": "Personal Data Download Request",
@@ -18,10 +19,12 @@
"doctype": "Web Form",
"idx": 0,
"introduction_text": "Request a file containing your personally identifiable information (PII) that is saved on our system. The file will be in JSON format and is sent to you by email. If you would like to have your PII deleted from our system, please make a request to delete data .
",
+ "is_multi_step_form": 0,
"is_standard": 1,
+ "list_columns": [],
"login_required": 0,
"max_attachment_size": 0,
- "modified": "2021-03-25 10:52:13.149538",
+ "modified": "2022-07-18 16:51:07.281527",
"modified_by": "Administrator",
"module": "Website",
"name": "request-data",
@@ -31,9 +34,8 @@
"route": "request-data",
"route_to_success_link": 1,
"show_attachments": 0,
- "show_in_grid": 0,
+ "show_list": 0,
"show_sidebar": 0,
- "sidebar_items": [],
"success_message": "A download link with your data will be sent to the email address associated with your account.",
"success_url": "/desk",
"title": "Request Data",
diff --git a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json
index 1113297df6..ce11666a34 100644
--- a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json
+++ b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json
@@ -9,6 +9,7 @@
"amount": 0.0,
"amount_based_on_field": 0,
"apply_document_permissions": 0,
+ "breadcrumbs": "",
"button_label": "Submit",
"client_script": "",
"creation": "2019-01-25 14:24:12.588810",
@@ -19,10 +20,12 @@
"doctype": "Web Form",
"idx": 0,
"introduction_text": "Send a request to delete your account and personally identifiable information (PII) that is stored on our system. You will receive an email to verify your request. Once the request is verified we will take care of deleting your PII. If you just want to check what PII we have stored, you can request your data .
",
+ "is_multi_step_form": 0,
"is_standard": 1,
+ "list_columns": [],
"login_required": 0,
"max_attachment_size": 0,
- "modified": "2021-11-30 17:56:03.099870",
+ "modified": "2022-07-18 16:51:30.949738",
"modified_by": "Administrator",
"module": "Website",
"name": "request-to-delete-data",
@@ -32,9 +35,8 @@
"route": "request-for-account-deletion",
"route_to_success_link": 0,
"show_attachments": 0,
- "show_in_grid": 0,
+ "show_list": 0,
"show_sidebar": 0,
- "sidebar_items": [],
"success_message": "An email to verify your request has been sent to your email address. Please verify your request to complete the process.",
"success_url": "/",
"title": "Request for Account Deletion",
From eea2616aac12cdc3cefa15987d9958088cc7011e Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 19 Jul 2022 22:28:14 +0530
Subject: [PATCH 0128/2449] style: use `middleware` decorator to keep function
definition intact
---
frappe/app.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/frappe/app.py b/frappe/app.py
index b9db59cdb1..298d94b06c 100644
--- a/frappe/app.py
+++ b/frappe/app.py
@@ -25,7 +25,7 @@ from frappe.utils import get_site_name, sanitize_html
from frappe.utils.error import make_error_snapshot
from frappe.website.serve import get_response
-local_manager = LocalManager([frappe.local])
+local_manager = LocalManager(frappe.local)
_site = None
_sites_path = os.environ.get("SITES_PATH", ".")
@@ -44,6 +44,7 @@ class RequestContext:
frappe.destroy()
+@local_manager.middleware
@Request.application
def application(request):
response = None
@@ -313,9 +314,6 @@ def after_request(rollback):
return rollback
-application = local_manager.make_middleware(application)
-
-
def serve(
port=8000, profile=False, no_reload=False, no_threading=False, site=None, sites_path="."
):
From 8f48c4d9432aeee6a403c8cad336f1e8f21cf227 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Wed, 20 Jul 2022 08:24:49 +0530
Subject: [PATCH 0129/2449] fix: No need to check for permssion again while
attaching a file
---
frappe/core/doctype/file/utils.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/core/doctype/file/utils.py b/frappe/core/doctype/file/utils.py
index e59ec2aede..d99e5cff48 100644
--- a/frappe/core/doctype/file/utils.py
+++ b/frappe/core/doctype/file/utils.py
@@ -327,7 +327,7 @@ def attach_files_to_document(doc: "File", event) -> None:
folder="Home/Attachments",
)
try:
- file.insert()
+ file.insert(ignore_permissions=True)
except Exception:
doc.log_error("Error Attaching File")
From d5bf9b60e098bb42fa4aa00d1c26d11c913702df Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Wed, 20 Jul 2022 10:36:40 +0530
Subject: [PATCH 0130/2449] fix: Query for unseen notification
---
frappe/boot.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frappe/boot.py b/frappe/boot.py
index ad729746fe..c8c98e506f 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -14,7 +14,7 @@ from frappe.email.inbox import get_email_accounts
from frappe.model.base_document import get_controller
from frappe.query_builder import DocType
from frappe.query_builder.functions import Count
-from frappe.query_builder.terms import SubQuery
+from frappe.query_builder.terms import ParameterizedValueWrapper, SubQuery
from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points
from frappe.social.doctype.energy_point_settings.energy_point_settings import (
is_energy_point_enabled,
@@ -331,8 +331,8 @@ def get_unseen_notes():
(note.notify_on_every_login == 1)
& (note.expire_notification_on > frappe.utils.now())
& (
- SubQuery(frappe.qb.from_(nsb).select(nsb.user).where(nsb.parent == note.name)).notin(
- [frappe.session.user]
+ ParameterizedValueWrapper(frappe.session.user).notin(
+ SubQuery(frappe.qb.from_(nsb).select(nsb.user).where(nsb.parent == note.name))
)
)
)
From 99c69907611eddf7ee35ea12469e7462f68d15db Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Wed, 20 Jul 2022 11:30:57 +0530
Subject: [PATCH 0131/2449] fix: Allow `fields` to have "*" without array
---
frappe/desk/reportview.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index caba0212b9..ac0d04c737 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -225,7 +225,7 @@ def parse_json(data):
if isinstance(data.get("or_filters"), str):
data["or_filters"] = json.loads(data["or_filters"])
if isinstance(data.get("fields"), str):
- data["fields"] = json.loads(data["fields"])
+ data["fields"] = ["*"] if data["fields"] == "*" else json.loads(data["fields"])
if isinstance(data.get("docstatus"), str):
data["docstatus"] = json.loads(data["docstatus"])
if isinstance(data.get("save_user_settings"), str):
From 6bbd8fcb32e8ede5b65d69f28b0944ab2daff366 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Wed, 20 Jul 2022 13:16:51 +0530
Subject: [PATCH 0132/2449] fix: Filter notes with `notify_on_login` and not
`notify_on_every_login`
---
frappe/boot.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/boot.py b/frappe/boot.py
index c8c98e506f..e43446f352 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -328,7 +328,7 @@ def get_unseen_notes():
frappe.qb.from_(note)
.select(note.name, note.title, note.content, note.notify_on_every_login)
.where(
- (note.notify_on_every_login == 1)
+ (note.notify_on_login == 1)
& (note.expire_notification_on > frappe.utils.now())
& (
ParameterizedValueWrapper(frappe.session.user).notin(
From 67350e9ac3f2dc0fceb1899c8692adcd9cdd4213 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Wed, 20 Jul 2022 13:23:49 +0530
Subject: [PATCH 0133/2449] test: Add a test case to validate
`get_unseen_notes`
---
frappe/tests/test_boot.py | 29 +++++++++++++++++++++++++++++
1 file changed, 29 insertions(+)
create mode 100644 frappe/tests/test_boot.py
diff --git a/frappe/tests/test_boot.py b/frappe/tests/test_boot.py
new file mode 100644
index 0000000000..8f26a078b0
--- /dev/null
+++ b/frappe/tests/test_boot.py
@@ -0,0 +1,29 @@
+import unittest
+
+import frappe
+from frappe.boot import get_unseen_notes
+from frappe.desk.doctype.note.note import mark_as_seen
+
+
+class TestBootData(unittest.TestCase):
+ def test_get_unseen_notes(self):
+ frappe.db.delete("Note")
+ frappe.db.delete("Note Seen By")
+ note = frappe.get_doc(
+ {
+ "doctype": "Note",
+ "title": "Test Note",
+ "notify_on_login": 1,
+ "content": "Test Note 1",
+ "public": 1,
+ }
+ )
+ note.insert()
+
+ frappe.set_user("test@example.com")
+ unseen_notes = [d.title for d in get_unseen_notes()]
+ self.assertListEqual(unseen_notes, ["Test Note"])
+
+ mark_as_seen(note.name)
+ unseen_notes = [d.title for d in get_unseen_notes()]
+ self.assertListEqual(unseen_notes, [])
From 8a8f0a1c798363ff57d16bfcb67ff55b2f0fd311 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 20 Jul 2022 17:51:45 +0530
Subject: [PATCH 0134/2449] fix: replace incorrect validation on doctype links
(#17561)
---
frappe/core/doctype/doctype/doctype.py | 27 ++++++++++++++++-----
frappe/core/doctype/doctype/test_doctype.py | 2 +-
2 files changed, 22 insertions(+), 7 deletions(-)
diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py
index 2b28373384..9a8a976b9f 100644
--- a/frappe/core/doctype/doctype/doctype.py
+++ b/frappe/core/doctype/doctype/doctype.py
@@ -17,7 +17,7 @@ from frappe.cache_manager import clear_controller_cache, clear_user_cache
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.custom.doctype.property_setter.property_setter import make_property_setter
from frappe.database.schema import validate_column_length, validate_column_name
-from frappe.desk.notifications import delete_notification_count_for
+from frappe.desk.notifications import delete_notification_count_for, get_filters_for
from frappe.desk.utils import validate_route_conflict
from frappe.model import (
child_table_fields,
@@ -982,11 +982,7 @@ def validate_links_table_fieldnames(meta):
fieldnames = tuple(field.fieldname for field in meta.fields)
for index, link in enumerate(meta.links, 1):
- if not frappe.get_meta(link.link_doctype).has_field(link.link_fieldname):
- message = _("Document Links Row #{0}: Could not find field {1} in {2} DocType").format(
- index, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype)
- )
- frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname"))
+ _test_connection_query(doctype=link.link_doctype, field=link.link_fieldname, idx=index)
if not link.is_child_table:
continue
@@ -1015,6 +1011,25 @@ def validate_links_table_fieldnames(meta):
frappe.throw(message, frappe.ValidationError, _("Invalid Table Fieldname"))
+def _test_connection_query(doctype, field, idx):
+ """Make sure that connection can be queried.
+
+ This function executes query similar to one that would be executed for
+ finding count on dashboard and hence validates if fieldname/doctype are
+ correct.
+ """
+ filters = get_filters_for(doctype) or {}
+ filters[field] = ""
+
+ try:
+ frappe.get_all(doctype, filters=filters, limit=1, distinct=True, ignore_ifnull=True)
+ except Exception as e:
+ frappe.clear_last_message()
+ msg = _("Document Links Row #{0}: Invalid doctype or fieldname.").format(idx)
+ msg += " " + str(e)
+ frappe.throw(msg, InvalidFieldNameError)
+
+
def validate_fields_for_doctype(doctype):
meta = frappe.get_meta(doctype, cached=False)
validate_links_table_fieldnames(meta)
diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py
index 3a5ca4329f..a083939c94 100644
--- a/frappe/core/doctype/doctype/test_doctype.py
+++ b/frappe/core/doctype/doctype/test_doctype.py
@@ -543,7 +543,7 @@ class TestDocType(unittest.TestCase):
# check invalid doctype
doc.append("links", {"link_doctype": "User2", "link_fieldname": "first_name"})
- self.assertRaises(frappe.DoesNotExistError, validate_links_table_fieldnames, doc)
+ self.assertRaises(InvalidFieldNameError, validate_links_table_fieldnames, doc)
doc.links = [] # reset links table
# check invalid fieldname
From e7082d611f6b60e55281aab767d60146611d4a6b Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 20 Jul 2022 21:05:23 +0530
Subject: [PATCH 0135/2449] fix: broken realtime doc change updates (#17567)
---
frappe/model/document.py | 4 ++--
frappe/tests/test_document.py | 15 ++++++++++++++-
2 files changed, 16 insertions(+), 3 deletions(-)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 864f2d50b4..3bddaa9aae 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -1093,7 +1093,7 @@ class Document(BaseDocument):
self.clear_cache()
- if not hasattr(self.flags, "notify_update") or self.flags.notify_update:
+ if self.flags.get("notify_update", True):
self.notify_update()
update_global_search(self)
@@ -1147,7 +1147,7 @@ class Document(BaseDocument):
:param fieldname: fieldname of the property to be updated, or a {"field":"value"} dictionary
:param value: value of the property to be updated
:param update_modified: default True. updates the `modified` and `modified_by` properties
- :param notify: default False. run doc.notify_updated() to send updates via socketio
+ :param notify: default False. run doc.notify_update() to send updates via socketio
:param commit: default False. run frappe.db.commit()
"""
if isinstance(fieldname, dict):
diff --git a/frappe/tests/test_document.py b/frappe/tests/test_document.py
index 454ca76983..2cd37366fd 100644
--- a/frappe/tests/test_document.py
+++ b/frappe/tests/test_document.py
@@ -3,7 +3,7 @@
import unittest
from contextlib import contextmanager
from datetime import timedelta
-from unittest.mock import patch
+from unittest.mock import Mock, patch
import frappe
from frappe.app import make_form_dict
@@ -373,6 +373,19 @@ class TestDocument(unittest.TestCase):
except Exception as e:
self.fail(f"Invalid doc hook: {doctype}:{hook}\n{e}")
+ def test_realtime_notify(self):
+ todo = frappe.new_doc("ToDo")
+ todo.description = "this will trigger realtime update"
+ todo.notify_update = Mock()
+ todo.insert()
+ self.assertEqual(todo.notify_update.call_count, 1)
+
+ todo.reload()
+ todo.flags.notify_update = False
+ todo.description = "this won't trigger realtime update"
+ todo.save()
+ self.assertEqual(todo.notify_update.call_count, 1)
+
class TestDocumentWebView(unittest.TestCase):
def get(self, path, user="Guest"):
From 836ce67d8580edff5d98d123dc2495c2923d951a Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 22 Jun 2022 13:27:00 +0530
Subject: [PATCH 0136/2449] feat: undo/redo on form view
This commit implements basic undo/redo on form view fields
---
.../js/frappe/form/controls/base_control.js | 2 ++
frappe/public/js/frappe/form/form.js | 22 ++++++++++++
frappe/public/js/frappe/form/undo_manager.js | 36 +++++++++++++++++++
3 files changed, 60 insertions(+)
create mode 100644 frappe/public/js/frappe/form/undo_manager.js
diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js
index 73831d493b..478ecc0174 100644
--- a/frappe/public/js/frappe/form/controls/base_control.js
+++ b/frappe/public/js/frappe/form/controls/base_control.js
@@ -187,6 +187,8 @@ frappe.ui.form.Control = class BaseControl {
return Promise.resolve();
}
+ const old_value = this.get_model_value();
+ this.frm?.undo_manager.record_change({fieldname: me.df.fieldname, old_value, new_value: value});
this.inside_change_event = true;
function set(value) {
me.inside_change_event = false;
diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js
index 148ec7ca86..c2363757c7 100644
--- a/frappe/public/js/frappe/form/form.js
+++ b/frappe/public/js/frappe/form/form.js
@@ -13,6 +13,7 @@ import './script_helpers';
import './sidebar/form_sidebar';
import './footer/footer';
import './form_tour';
+import {UndoManager } from './undo_manager';
frappe.ui.form.Controller = class FormController {
constructor(opts) {
@@ -38,6 +39,7 @@ frappe.ui.form.Form = class FrappeForm {
this.fetch_dict = {};
this.parent = parent;
this.doctype_layout = frappe.get_doc('DocType Layout', doctype_layout_name);
+ this.undo_manager = new UndoManager({frm: this});
this.setup_meta(doctype);
this.beforeUnloadListener = (event) => {
@@ -143,6 +145,26 @@ frappe.ui.form.Form = class FrappeForm {
condition: () => !this.is_new()
});
+ // Undo and redo
+ frappe.ui.keys.add_shortcut({
+ shortcut: 'ctrl+z',
+ action: () => this.undo_manager.undo(),
+ page: this.page,
+ description: __('Undo last action'),
+ });
+ frappe.ui.keys.add_shortcut({
+ shortcut: 'shift+ctrl+z',
+ action: () => this.undo_manager.redo(),
+ page: this.page,
+ description: __('Redo last action'),
+ });
+ frappe.ui.keys.add_shortcut({
+ shortcut: 'ctrl+y',
+ action: () => this.undo_manager.redo(),
+ page: this.page,
+ description: __('Redo last action'),
+ });
+
let grid_shortcut_keys = [
{
'shortcut': 'Up Arrow',
diff --git a/frappe/public/js/frappe/form/undo_manager.js b/frappe/public/js/frappe/form/undo_manager.js
new file mode 100644
index 0000000000..6046ce15eb
--- /dev/null
+++ b/frappe/public/js/frappe/form/undo_manager.js
@@ -0,0 +1,36 @@
+export class UndoManager {
+ constructor({ frm }) {
+ this.frm = frm;
+ this.undo_stack = [];
+ this.redo_stack = [];
+ }
+ record_change({ fieldname, old_value, new_value }) {
+ this.undo_stack.push({ fieldname, old_value, new_value });
+ }
+
+ undo() {
+ const change = this.undo_stack.pop();
+ if (change) {
+ this.frm.set_value(change.fieldname, change.old_value);
+ this.redo_stack.push(change);
+ } else {
+ this.show_alert(__("Nothing left to undo"));
+ }
+ }
+
+ redo() {
+ const change = this.redo_stack.pop();
+ if (change) {
+ this.frm.set_value(change.fieldname, change.new_value);
+ this.undo_stack.push(change);
+ } else {
+ this.show_alert(__("Nothing left to redo"));
+ }
+ }
+
+ show_alert(msg) {
+ // reduce duration
+ // keyboard interactions shouldn't have long running
+ frappe.show_alert(msg, 3);
+ }
+}
From 715299fc083991b3e086b3b8ecdf51a47741be5f Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 12 Jul 2022 21:07:44 +0530
Subject: [PATCH 0137/2449] fix: erase undo/redo history on doc change/refresh
---
frappe/public/js/frappe/form/form.js | 2 ++
frappe/public/js/frappe/form/undo_manager.js | 5 +++++
2 files changed, 7 insertions(+)
diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js
index c2363757c7..e52b213ba6 100644
--- a/frappe/public/js/frappe/form/form.js
+++ b/frappe/public/js/frappe/form/form.js
@@ -379,6 +379,8 @@ frappe.ui.form.Form = class FrappeForm {
cur_frm = this;
+ this.undo_manager.erase_history();
+
if(this.docname) { // document to show
this.save_disabled = false;
// set the doc
diff --git a/frappe/public/js/frappe/form/undo_manager.js b/frappe/public/js/frappe/form/undo_manager.js
index 6046ce15eb..2f1ab54a07 100644
--- a/frappe/public/js/frappe/form/undo_manager.js
+++ b/frappe/public/js/frappe/form/undo_manager.js
@@ -33,4 +33,9 @@ export class UndoManager {
// keyboard interactions shouldn't have long running
frappe.show_alert(msg, 3);
}
+
+ erase_history() {
+ this.undo_stack = [];
+ this.redo_stack = [];
+ }
}
From a1ca1e2cc6228853e8a0ba27e6f323303b3ec59b Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 12 Jul 2022 21:50:34 +0530
Subject: [PATCH 0138/2449] feat: undo/redo on child table fields
---
.../js/frappe/form/controls/base_control.js | 9 +++-
frappe/public/js/frappe/form/undo_manager.js | 50 ++++++++++++++++---
2 files changed, 51 insertions(+), 8 deletions(-)
diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js
index 478ecc0174..f8ceec4c12 100644
--- a/frappe/public/js/frappe/form/controls/base_control.js
+++ b/frappe/public/js/frappe/form/controls/base_control.js
@@ -188,7 +188,14 @@ frappe.ui.form.Control = class BaseControl {
}
const old_value = this.get_model_value();
- this.frm?.undo_manager.record_change({fieldname: me.df.fieldname, old_value, new_value: value});
+ this.frm?.undo_manager.record_change({
+ fieldname: me.df.fieldname,
+ old_value,
+ new_value: value,
+ doctype: this.doctype,
+ docname: this.docname,
+ is_child: Boolean(this.doc?.parenttype)
+ });
this.inside_change_event = true;
function set(value) {
me.inside_change_event = false;
diff --git a/frappe/public/js/frappe/form/undo_manager.js b/frappe/public/js/frappe/form/undo_manager.js
index 2f1ab54a07..5dd94b82d2 100644
--- a/frappe/public/js/frappe/form/undo_manager.js
+++ b/frappe/public/js/frappe/form/undo_manager.js
@@ -4,33 +4,69 @@ export class UndoManager {
this.undo_stack = [];
this.redo_stack = [];
}
- record_change({ fieldname, old_value, new_value }) {
- this.undo_stack.push({ fieldname, old_value, new_value });
+ record_change({
+ fieldname,
+ old_value,
+ new_value,
+ doctype,
+ docname,
+ is_child,
+ }) {
+ this.undo_stack.push({
+ fieldname,
+ old_value,
+ new_value,
+ doctype,
+ docname,
+ is_child,
+ });
+ console.log(this.undo_stack[this.undo_stack.length - 1]);
}
undo() {
const change = this.undo_stack.pop();
if (change) {
- this.frm.set_value(change.fieldname, change.old_value);
- this.redo_stack.push(change);
+ this.apply_change(change);
+ this.push_reverse_entry(change, this.redo_stack);
} else {
this.show_alert(__("Nothing left to undo"));
}
}
+ push_reverse_entry(change, stack) {
+ stack.push({
+ ...change,
+ new_value: change.old_value,
+ old_value: change.new_value,
+ });
+ }
+
redo() {
const change = this.redo_stack.pop();
if (change) {
- this.frm.set_value(change.fieldname, change.new_value);
- this.undo_stack.push(change);
+ this.apply_change(change);
+ this.push_reverse_entry(change, this.undo_stack);
} else {
this.show_alert(__("Nothing left to redo"));
}
}
+ apply_change(change) {
+ if (change.is_child) {
+ frappe.model.set_value(
+ change.doctype,
+ change.docname,
+ change.fieldname,
+ change.old_value
+ );
+ } else {
+ this.frm.set_value(change.fieldname, change.old_value);
+ }
+ }
+
show_alert(msg) {
// reduce duration
- // keyboard interactions shouldn't have long running
+ // keyboard interactions shouldn't have long running annoying toasts
frappe.show_alert(msg, 3);
}
From 129152c1c599f53e696de0011ec82e29302df2db Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 12 Jul 2022 22:25:55 +0530
Subject: [PATCH 0139/2449] feat: highlight changed fields when doing undo/redo
---
.../js/frappe/form/controls/base_control.js | 2 +-
frappe/public/js/frappe/form/form.js | 8 ++--
frappe/public/js/frappe/form/undo_manager.js | 44 +++++++++----------
3 files changed, 28 insertions(+), 26 deletions(-)
diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js
index f8ceec4c12..b066e5141a 100644
--- a/frappe/public/js/frappe/form/controls/base_control.js
+++ b/frappe/public/js/frappe/form/controls/base_control.js
@@ -188,7 +188,7 @@ frappe.ui.form.Control = class BaseControl {
}
const old_value = this.get_model_value();
- this.frm?.undo_manager.record_change({
+ this.frm?.undo_manager?.record_change({
fieldname: me.df.fieldname,
old_value,
new_value: value,
diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js
index e52b213ba6..4e38a5ee7e 100644
--- a/frappe/public/js/frappe/form/form.js
+++ b/frappe/public/js/frappe/form/form.js
@@ -13,7 +13,7 @@ import './script_helpers';
import './sidebar/form_sidebar';
import './footer/footer';
import './form_tour';
-import {UndoManager } from './undo_manager';
+import { UndoManager } from './undo_manager';
frappe.ui.form.Controller = class FormController {
constructor(opts) {
@@ -1785,7 +1785,7 @@ frappe.ui.form.Form = class FrappeForm {
return sum;
}
- scroll_to_field(fieldname) {
+ scroll_to_field(fieldname, focus=true) {
let field = this.get_field(fieldname);
if (!field) return;
@@ -1805,7 +1805,9 @@ frappe.ui.form.Form = class FrappeForm {
frappe.utils.scroll_to($el, true, 15);
// focus if text field
- $el.find('input, select, textarea').focus();
+ if (focus) {
+ $el.find('input, select, textarea').focus();
+ }
// highlight control inside field
let control_element = $el.find('.form-control')
diff --git a/frappe/public/js/frappe/form/undo_manager.js b/frappe/public/js/frappe/form/undo_manager.js
index 5dd94b82d2..cfffe0611b 100644
--- a/frappe/public/js/frappe/form/undo_manager.js
+++ b/frappe/public/js/frappe/form/undo_manager.js
@@ -20,20 +20,34 @@ export class UndoManager {
docname,
is_child,
});
- console.log(this.undo_stack[this.undo_stack.length - 1]);
+ }
+
+ erase_history() {
+ this.undo_stack = [];
+ this.redo_stack = [];
}
undo() {
const change = this.undo_stack.pop();
if (change) {
- this.apply_change(change);
- this.push_reverse_entry(change, this.redo_stack);
+ this.#apply_change(change);
+ this.#push_reverse_entry(change, this.redo_stack);
} else {
- this.show_alert(__("Nothing left to undo"));
+ this.#show_alert(__("Nothing left to undo"));
}
}
- push_reverse_entry(change, stack) {
+ redo() {
+ const change = this.redo_stack.pop();
+ if (change) {
+ this.#apply_change(change);
+ this.#push_reverse_entry(change, this.undo_stack);
+ } else {
+ this.#show_alert(__("Nothing left to redo"));
+ }
+ }
+
+ #push_reverse_entry(change, stack) {
stack.push({
...change,
new_value: change.old_value,
@@ -41,17 +55,7 @@ export class UndoManager {
});
}
- redo() {
- const change = this.redo_stack.pop();
- if (change) {
- this.apply_change(change);
- this.push_reverse_entry(change, this.undo_stack);
- } else {
- this.show_alert(__("Nothing left to redo"));
- }
- }
-
- apply_change(change) {
+ #apply_change(change) {
if (change.is_child) {
frappe.model.set_value(
change.doctype,
@@ -61,17 +65,13 @@ export class UndoManager {
);
} else {
this.frm.set_value(change.fieldname, change.old_value);
+ this.frm.scroll_to_field(change.fieldname, false);
}
}
- show_alert(msg) {
+ #show_alert(msg) {
// reduce duration
// keyboard interactions shouldn't have long running annoying toasts
frappe.show_alert(msg, 3);
}
-
- erase_history() {
- this.undo_stack = [];
- this.redo_stack = [];
- }
}
From e73f4aa8ab79ddfc2034bb0a7d45788b32a6878f Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 12 Jul 2022 23:22:53 +0530
Subject: [PATCH 0140/2449] fix: avoid nonsensical change triggers
if both values are same then logging it doesn't make much sense.
---
frappe/public/js/frappe/form/undo_manager.js | 22 ++++++++++++--------
1 file changed, 13 insertions(+), 9 deletions(-)
diff --git a/frappe/public/js/frappe/form/undo_manager.js b/frappe/public/js/frappe/form/undo_manager.js
index cfffe0611b..c09c3902b7 100644
--- a/frappe/public/js/frappe/form/undo_manager.js
+++ b/frappe/public/js/frappe/form/undo_manager.js
@@ -12,6 +12,10 @@ export class UndoManager {
docname,
is_child,
}) {
+ if (old_value == new_value) {
+ return;
+ }
+
this.undo_stack.push({
fieldname,
old_value,
@@ -30,24 +34,24 @@ export class UndoManager {
undo() {
const change = this.undo_stack.pop();
if (change) {
- this.#apply_change(change);
- this.#push_reverse_entry(change, this.redo_stack);
+ this._apply_change(change);
+ this._push_reverse_entry(change, this.redo_stack);
} else {
- this.#show_alert(__("Nothing left to undo"));
+ this._show_alert(__("Nothing left to undo"));
}
}
redo() {
const change = this.redo_stack.pop();
if (change) {
- this.#apply_change(change);
- this.#push_reverse_entry(change, this.undo_stack);
+ this._apply_change(change);
+ this._push_reverse_entry(change, this.undo_stack);
} else {
- this.#show_alert(__("Nothing left to redo"));
+ this._show_alert(__("Nothing left to redo"));
}
}
- #push_reverse_entry(change, stack) {
+ _push_reverse_entry(change, stack) {
stack.push({
...change,
new_value: change.old_value,
@@ -55,7 +59,7 @@ export class UndoManager {
});
}
- #apply_change(change) {
+ _apply_change(change) {
if (change.is_child) {
frappe.model.set_value(
change.doctype,
@@ -69,7 +73,7 @@ export class UndoManager {
}
}
- #show_alert(msg) {
+ _show_alert(msg) {
// reduce duration
// keyboard interactions shouldn't have long running annoying toasts
frappe.show_alert(msg, 3);
From 5ea96ced3ac369bd39a29faf9c50e29c66022265 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 19 Jul 2022 20:29:56 +0530
Subject: [PATCH 0141/2449] test: undo/redo, jump to field UI tests
---
cypress/integration/form.js | 60 +++++++++++++++++++++++++++++++++++++
1 file changed, 60 insertions(+)
diff --git a/cypress/integration/form.js b/cypress/integration/form.js
index b395ff77b2..53b87994d7 100644
--- a/cypress/integration/form.js
+++ b/cypress/integration/form.js
@@ -6,6 +6,7 @@ context('Form', () => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
});
});
+
it('create a new form', () => {
cy.visit('/app/todo/new');
cy.get_field('description', 'Text Editor').type('this is a test todo', {force: true}).wait(200);
@@ -95,4 +96,63 @@ context('Form', () => {
})
})
});
+
+ it('let user undo/redo field value changes', { scrollBehavior: false }, () => {
+ const jump_to_field = (field_label) => {
+ cy.get("body")
+ .type("{esc}") // lose focus if any
+ .type("{ctrl+j}") // jump to field
+ .type(field_label)
+ .wait(500)
+ .type("{enter}")
+ .wait(200)
+ .type("{enter}")
+ .wait(500);
+ };
+
+ const type_value = (value) => {
+ cy.focused()
+ .clear()
+ .type(value)
+ .type("{esc}");
+ };
+
+ const undo = () => cy.get("body").type("{esc}").type("{ctrl+z}").wait(500);
+ const redo = () => cy.get("body").type("{esc}").type("{ctrl+y}").wait(500);
+
+ cy.new_form('User');
+
+ jump_to_field("Email");
+ type_value("admin@example.com");
+
+ jump_to_field("Username");
+ type_value("admin42");
+
+ jump_to_field("Birth Date");
+ type_value("12-31-01");
+
+ jump_to_field("Send Welcome Email");
+ cy.focused().uncheck()
+
+ // make a mistake
+ jump_to_field("Username");
+ type_value("admin24");
+
+ // undo behaviour
+ undo();
+ cy.get_field("username").should('have.value', 'admin42');
+
+ // redo behaviour
+ redo();
+ cy.get_field("username").should('have.value', 'admin24');
+
+ // undo everything & redo everything, ensure same values at the end
+ undo(); undo(); undo(); undo(); undo();
+ redo(); redo(); redo(); redo(); redo();
+
+ cy.get_field("username").should('have.value', 'admin24');
+ cy.get_field("email").should('have.value', 'admin@example.com');
+ cy.get_field("birth_date").should('have.value', '12-31-2001'); // parsed value
+ cy.get_field("send_welcome_email").should('not.be.checked');
+ });
});
From 85a3837b14c78816c9647f180d761e09d18bce2d Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Thu, 21 Jul 2022 07:34:47 +0000
Subject: [PATCH 0142/2449] fix: ensure 2FA patch sets parent only for 2FA keys
(#17575)
* fix: ensure 2FA patch sets parent only for 2FA keys
* fix: try to rebuild defaults
* fix: set other app defaults as well
* fix: set POS profile defaults
* fix: exists params
* chore: remove unnecessary change
* fix: handle case where POS Profile doesnt exist
* chore: move erpnext code to erpnext
---
.../system_settings/system_settings.py | 15 +++++----
frappe/patches.txt | 1 +
frappe/patches/v13_0/encrypt_2fa_secrets.py | 1 +
.../patches/v13_0/reset_corrupt_defaults.py | 33 +++++++++++++++++++
4 files changed, 44 insertions(+), 6 deletions(-)
create mode 100644 frappe/patches/v13_0/reset_corrupt_defaults.py
diff --git a/frappe/core/doctype/system_settings/system_settings.py b/frappe/core/doctype/system_settings/system_settings.py
index fbdc188742..4bd41be974 100644
--- a/frappe/core/doctype/system_settings/system_settings.py
+++ b/frappe/core/doctype/system_settings/system_settings.py
@@ -44,12 +44,7 @@ class SystemSettings(Document):
frappe.flags.update_last_reset_password_date = True
def on_update(self):
- for df in self.meta.get("fields"):
- if df.fieldtype not in no_value_fields and self.has_value_changed(df.fieldname):
- frappe.db.set_default(df.fieldname, self.get(df.fieldname))
-
- if self.language:
- set_default_language(self.language)
+ self.set_defaults()
frappe.cache().delete_value("system_settings")
frappe.cache().delete_value("time_zone")
@@ -57,6 +52,14 @@ class SystemSettings(Document):
if frappe.flags.update_last_reset_password_date:
update_last_reset_password_date()
+ def set_defaults(self):
+ for df in self.meta.get("fields"):
+ if df.fieldtype not in no_value_fields and self.has_value_changed(df.fieldname):
+ frappe.db.set_default(df.fieldname, self.get(df.fieldname))
+
+ if self.language:
+ set_default_language(self.language)
+
def update_last_reset_password_date():
frappe.db.sql(
diff --git a/frappe/patches.txt b/frappe/patches.txt
index f79cadae87..ee2eb0d2a1 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -184,6 +184,7 @@ frappe.patches.v13_0.jinja_hook
frappe.patches.v13_0.update_notification_channel_if_empty
frappe.patches.v13_0.set_first_day_of_the_week
frappe.patches.v13_0.encrypt_2fa_secrets
+frappe.patches.v13_0.reset_corrupt_defaults
execute:frappe.reload_doc('custom', 'doctype', 'custom_field')
frappe.patches.v14_0.update_workspace2 # 20.09.2021
frappe.patches.v14_0.save_ratings_in_fraction #23-12-2021
diff --git a/frappe/patches/v13_0/encrypt_2fa_secrets.py b/frappe/patches/v13_0/encrypt_2fa_secrets.py
index 3b220f485f..1814ff50c5 100644
--- a/frappe/patches/v13_0/encrypt_2fa_secrets.py
+++ b/frappe/patches/v13_0/encrypt_2fa_secrets.py
@@ -39,6 +39,7 @@ def execute():
.set(table.parent, PARENT_FOR_DEFAULTS)
.set(table.defvalue, defvalue_cases)
.where(table.parent == OLD_PARENT)
+ .where(table.defkey.like("%_otpsecret"))
).run()
clear_defaults_cache()
diff --git a/frappe/patches/v13_0/reset_corrupt_defaults.py b/frappe/patches/v13_0/reset_corrupt_defaults.py
new file mode 100644
index 0000000000..10e81c7ff1
--- /dev/null
+++ b/frappe/patches/v13_0/reset_corrupt_defaults.py
@@ -0,0 +1,33 @@
+import frappe
+from frappe.patches.v13_0.encrypt_2fa_secrets import DOCTYPE
+from frappe.patches.v13_0.encrypt_2fa_secrets import PARENT_FOR_DEFAULTS as TWOFACTOR_PARENT
+from frappe.utils import cint
+
+
+def execute():
+ """
+ This patch is needed to fix parent incorrectly set as `__2fa` because of
+ https://github.com/frappe/frappe/commit/a822092211533ff17ff9b92dd86f6f868ed63e2e
+ """
+
+ if not frappe.db.get_value(
+ DOCTYPE, {"parent": TWOFACTOR_PARENT, "defkey": ("not like", "%_otp%")}, "defkey"
+ ):
+ return
+
+ # system settings
+ system_settings = frappe.get_single("System Settings")
+ system_settings.set_defaults()
+
+ # home page
+ frappe.db.set_default(
+ "desktop:home_page", "workspace" if cint(system_settings.setup_complete) else "setup-wizard"
+ )
+
+ # letter head
+ try:
+ letter_head = frappe.get_doc("Letter Head", {"is_default": 1})
+ letter_head.set_as_default()
+
+ except frappe.DoesNotExistError:
+ pass
From d2177d16a1d21e794c21373dccbf3638543280a5 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 21 Jul 2022 16:54:41 +0530
Subject: [PATCH 0143/2449] ci: bump node version to node16 (#17564)
* ci: bump node version to node16
* chore: remove `node-sass` - no longer used
Co-authored-by: Sagar Vora
---
.github/helper/install.sh | 5 -----
.github/workflows/patch-mariadb-tests.yml | 2 +-
.github/workflows/publish-assets-develop.yml | 2 +-
.github/workflows/publish-assets-releases.yml | 4 +++-
.github/workflows/release.yml | 6 +++---
.github/workflows/semantic-commits.yml | 2 +-
.github/workflows/server-mariadb-tests.yml | 2 +-
.github/workflows/server-postgres-tests.yml | 2 +-
.github/workflows/ui-tests.yml | 2 +-
9 files changed, 12 insertions(+), 15 deletions(-)
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index b36c1e4b12..e59fa36627 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -56,11 +56,6 @@ bench -v setup requirements --dev
if [ "$TYPE" == "ui" ]; then sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile; fi
-# install node-sass which is required for website theme test
-cd ./apps/frappe || exit
-yarn add node-sass@4.13.1
-cd ../..
-
bench start &
bench --site test_site reinstall --yes
if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi
diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml
index 1e21ae8549..655f99e9dd 100644
--- a/.github/workflows/patch-mariadb-tests.yml
+++ b/.github/workflows/patch-mariadb-tests.yml
@@ -49,7 +49,7 @@ jobs:
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
check-latest: true
- name: Add to Hosts
diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml
index b216718b99..467922e766 100644
--- a/.github/workflows/publish-assets-develop.yml
+++ b/.github/workflows/publish-assets-develop.yml
@@ -15,7 +15,7 @@ jobs:
path: 'frappe'
- uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
- uses: actions/setup-python@v4
with:
python-version: '3.10'
diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/publish-assets-releases.yml
index 2612c45bea..ff1656e55d 100644
--- a/.github/workflows/publish-assets-releases.yml
+++ b/.github/workflows/publish-assets-releases.yml
@@ -16,9 +16,11 @@ jobs:
- uses: actions/checkout@v3
with:
path: 'frappe'
+
- uses: actions/setup-node@v3
with:
- python-version: '12.x'
+ node-version: 16
+
- uses: actions/setup-python@v4
with:
python-version: '3.10'
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml
index f73bed09c7..010022b7f6 100644
--- a/.github/workflows/release.yml
+++ b/.github/workflows/release.yml
@@ -16,10 +16,10 @@ jobs:
with:
fetch-depth: 0
persist-credentials: false
- - name: Setup Node.js v14
+ - name: Setup Node.js
uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
- name: Setup dependencies
run: |
npm install @semantic-release/git @semantic-release/exec --no-save
@@ -31,4 +31,4 @@ jobs:
GIT_AUTHOR_EMAIL: "developers@frappe.io"
GIT_COMMITTER_NAME: "Frappe PR Bot"
GIT_COMMITTER_EMAIL: "developers@frappe.io"
- run: npx semantic-release
\ No newline at end of file
+ run: npx semantic-release
diff --git a/.github/workflows/semantic-commits.yml b/.github/workflows/semantic-commits.yml
index a3536d5019..7afa02d1b9 100644
--- a/.github/workflows/semantic-commits.yml
+++ b/.github/workflows/semantic-commits.yml
@@ -21,7 +21,7 @@ jobs:
- uses: actions/setup-node@v3
with:
- node-version: 14
+ node-version: 16
check-latest: true
- name: Check commit titles
diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml
index 29d88fd9a5..84bb4ba8dc 100644
--- a/.github/workflows/server-mariadb-tests.yml
+++ b/.github/workflows/server-mariadb-tests.yml
@@ -56,7 +56,7 @@ jobs:
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
- node-version: 14
+ node-version: 16
check-latest: true
- name: Add to Hosts
diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml
index 8f015f43e6..09bfad4304 100644
--- a/.github/workflows/server-postgres-tests.yml
+++ b/.github/workflows/server-postgres-tests.yml
@@ -59,7 +59,7 @@ jobs:
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
- node-version: '14'
+ node-version: '16'
check-latest: true
- name: Add to Hosts
diff --git a/.github/workflows/ui-tests.yml b/.github/workflows/ui-tests.yml
index da6b095451..1d0d28223e 100644
--- a/.github/workflows/ui-tests.yml
+++ b/.github/workflows/ui-tests.yml
@@ -55,7 +55,7 @@ jobs:
- uses: actions/setup-node@v3
if: ${{ steps.check-build.outputs.build == 'strawberry' }}
with:
- node-version: 14
+ node-version: 16
check-latest: true
- name: Add to Hosts
From 9e87598ddaa19b565099685c2bdad636b29b3d4f Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 21 Jul 2022 17:03:52 +0530
Subject: [PATCH 0144/2449] refactor: Use FrappeTestCase as it rolls back test
data
---
frappe/tests/test_boot.py | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
diff --git a/frappe/tests/test_boot.py b/frappe/tests/test_boot.py
index 8f26a078b0..11cbeb7621 100644
--- a/frappe/tests/test_boot.py
+++ b/frappe/tests/test_boot.py
@@ -3,9 +3,10 @@ import unittest
import frappe
from frappe.boot import get_unseen_notes
from frappe.desk.doctype.note.note import mark_as_seen
+from frappe.tests.utils import FrappeTestCase
-class TestBootData(unittest.TestCase):
+class TestBootData(FrappeTestCase):
def test_get_unseen_notes(self):
frappe.db.delete("Note")
frappe.db.delete("Note Seen By")
From b1e9bc8d12d35d20e9b9bee0f29922753902d0ca Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Thu, 21 Jul 2022 20:46:03 +0530
Subject: [PATCH 0145/2449] fix: error while genarating date for blog post
google search preview (#17581)
---
frappe/website/doctype/blog_post/blog_post.js | 16 ++++++++--------
1 file changed, 8 insertions(+), 8 deletions(-)
diff --git a/frappe/website/doctype/blog_post/blog_post.js b/frappe/website/doctype/blog_post/blog_post.js
index 97916b6fc6..08bc55f07c 100644
--- a/frappe/website/doctype/blog_post/blog_post.js
+++ b/frappe/website/doctype/blog_post/blog_post.js
@@ -2,21 +2,21 @@
// For license information, please see license.txt
frappe.ui.form.on('Blog Post', {
- refresh: function(frm) {
+ refresh: function (frm) {
frappe.db.get_single_value('Blog Settings', 'show_cta_in_blog').then(value => {
frm.set_df_property("hide_cta", "hidden", !value);
});
generate_google_search_preview(frm);
},
- title: function(frm) {
+ title: function (frm) {
generate_google_search_preview(frm);
frm.trigger('set_route');
},
- meta_description: function(frm) {
+ meta_description: function (frm) {
generate_google_search_preview(frm);
},
- blog_intro: function(frm) {
+ blog_intro: function (frm) {
generate_google_search_preview(frm);
},
blog_category(frm) {
@@ -36,8 +36,8 @@ function generate_google_search_preview(frm) {
if (!(frm.doc.meta_title || frm.doc.title)) return;
let google_preview = frm.get_field("google_preview");
let seo_title = (frm.doc.meta_title || frm.doc.title).slice(0, 60);
- let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160);
- let date = frm.doc.published_on ? new frappe.datetime.datetime(frm.doc.published_on).moment.format('ll') + ' - ' : '';
+ let seo_description = (frm.doc.meta_description || frm.doc.blog_intro || "").slice(0, 160);
+ let date = frm.doc.published_on ? moment(frm.doc.published_on).format('ll') + '-' : '';
let route_array = frm.doc.route ? frm.doc.route.split('/') : [];
route_array.pop();
@@ -49,10 +49,10 @@ function generate_google_search_preview(frm) {
› ${route_array.join(' › ')}
- ${ seo_title }
+ ${seo_title}
- ${ date } ${ seo_description }
+ ${date} ${seo_description}
`);
From 006ebcbedeb41fb21babf0a6c11a709a0a3525eb Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 21 Jul 2022 11:17:02 +0530
Subject: [PATCH 0146/2449] refactor: Use pymysql over mariadb client
This is supposed to be a temporary switch to make the parent PR easier
to digest. MariaDB client has some issues with release, and system
dependencies.
This commit may be reverted to enable mariadb client again.
---
frappe/__init__.py | 1 -
frappe/commands/site.py | 2 -
frappe/database/mariadb/database.py | 253 ++++++----------------------
frappe/utils/bench_helper.py | 2 -
pyproject.toml | 1 -
5 files changed, 55 insertions(+), 204 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 3d3288c16d..20fddb0267 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -48,7 +48,6 @@ __title__ = "Frappe Framework"
controllers = {}
local = Local()
STANDARD_USERS = ("Guest", "Administrator")
-DISABLE_DATABASE_CONNECTION_POOLING = None
_dev_server = int(sbool(os.environ.get("DEV_SERVER", False)))
_qb_patched = {}
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index 62451b6013..e3c7de32a3 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -69,8 +69,6 @@ def new_site(
"Create a new site"
from frappe.installer import _new_site
- frappe.DISABLE_DATABASE_CONNECTION_POOLING = True
-
frappe.init(site=site, new_site=True)
_new_site(
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 71317b5884..e89168194e 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -1,114 +1,87 @@
import re
-from collections import defaultdict
-from decimal import Decimal
-from typing import TYPE_CHECKING
-import mariadb
-from mariadb.constants import ERR, FIELD_TYPE
-from pymysql.converters import escape_sequence, escape_string
+import pymysql
+from pymysql.constants import ER, FIELD_TYPE
+from pymysql.converters import conversions, escape_string
import frappe
-from frappe.database.database import Database, QueryValues
+from frappe.database.database import Database
from frappe.database.mariadb.schema import MariaDBTable
-from frappe.utils import UnicodeWithAttrs, get_datetime, get_table_name
+from frappe.utils import UnicodeWithAttrs, cstr, get_datetime, get_table_name
-if TYPE_CHECKING:
- from mariadb import ConnectionPool
-
-_FIND_ITER_PATTERN = re.compile("%s")
_PARAM_COMP = re.compile(r"%\([\w]*\)s")
-_SITE_POOLS = defaultdict(frappe._dict)
-_MAX_POOL_SIZE = 64
-_POOL_SIZE = 1
-
-# _POOL_SIZE is selected "arbitrarily" to avoid overloading the server and being mindful of multitenancy
-# init size of connection pool will be _POOL_SIZE for each site. Replica setups will have separate pool.
-# This means each site with a replica setup can have 2 active pools of size _POOL_SIZE each. Each pool may
-# expand up to _MAX_POOL_SIZE as per requirement. This cannot be a function of @@global.max_connections,
-# no. of sites since there may be multiple processes holding connections; and this defines the size for each
-# of those processes/workers. Check MariaDBConnectionUtil for connection & pool management.
-
-
-def is_connection_pooling_enabled() -> bool:
- """Set `frappe.DISABLE_CONNECTION_POOLING` to enable/disable connection pooling for all on current
- process. This will override config key `disable_database_connection_pooling`. Set key
- `disable_database_connection_pooling` in site config for persistent settings across workers."""
-
- if frappe.DISABLE_DATABASE_CONNECTION_POOLING is not None:
- return not frappe.DISABLE_DATABASE_CONNECTION_POOLING
- return not frappe.local.conf.disable_database_connection_pooling
class MariaDBExceptionUtil:
- ProgrammingError = mariadb.ProgrammingError
- TableMissingError = mariadb.ProgrammingError
- OperationalError = mariadb.OperationalError
- InternalError = mariadb.InternalError
- SQLError = mariadb.ProgrammingError
- DataError = mariadb.DataError
+ ProgrammingError = pymysql.ProgrammingError
+ TableMissingError = pymysql.ProgrammingError
+ OperationalError = pymysql.OperationalError
+ InternalError = pymysql.InternalError
+ SQLError = pymysql.ProgrammingError
+ DataError = pymysql.DataError
# match ER_SEQUENCE_RUN_OUT - https://mariadb.com/kb/en/mariadb-error-codes/
- SequenceGeneratorLimitExceeded = mariadb.OperationalError
+ SequenceGeneratorLimitExceeded = pymysql.OperationalError
SequenceGeneratorLimitExceeded.errno = 4084
@staticmethod
- def is_deadlocked(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_LOCK_DEADLOCK
+ def is_deadlocked(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.LOCK_DEADLOCK
@staticmethod
- def is_timedout(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_LOCK_WAIT_TIMEOUT
+ def is_timedout(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.LOCK_WAIT_TIMEOUT
@staticmethod
- def is_table_missing(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_NO_SUCH_TABLE
+ def is_table_missing(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.NO_SUCH_TABLE
@staticmethod
- def is_missing_table(e: mariadb.Error) -> bool:
+ def is_missing_table(e: pymysql.Error) -> bool:
return MariaDBDatabase.is_table_missing(e)
@staticmethod
- def is_missing_column(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_BAD_FIELD_ERROR
+ def is_missing_column(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.BAD_FIELD_ERROR
@staticmethod
- def is_duplicate_fieldname(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_DUP_FIELDNAME
+ def is_duplicate_fieldname(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.DUP_FIELDNAME
@staticmethod
- def is_duplicate_entry(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_DUP_ENTRY
+ def is_duplicate_entry(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.DUP_ENTRY
@staticmethod
- def is_access_denied(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_ACCESS_DENIED_ERROR
+ def is_access_denied(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.ACCESS_DENIED_ERROR
@staticmethod
- def cant_drop_field_or_key(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_CANT_DROP_FIELD_OR_KEY
+ def cant_drop_field_or_key(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.CANT_DROP_FIELD_OR_KEY
@staticmethod
- def is_syntax_error(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_PARSE_ERROR
+ def is_syntax_error(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.PARSE_ERROR
@staticmethod
- def is_data_too_long(e: mariadb.Error) -> bool:
- return getattr(e, "errno", None) == ERR.ER_DATA_TOO_LONG
+ def is_data_too_long(e: pymysql.Error) -> bool:
+ return e.args[0] == ER.DATA_TOO_LONG
@staticmethod
- def is_primary_key_violation(e: mariadb.Error) -> bool:
+ def is_primary_key_violation(e: pymysql.Error) -> bool:
return (
MariaDBDatabase.is_duplicate_entry(e)
- and "PRIMARY" in e.errmsg
- and isinstance(e, mariadb.IntegrityError)
+ and "PRIMARY" in cstr(e.args[1])
+ and isinstance(e, pymysql.IntegrityError)
)
@staticmethod
- def is_unique_key_violation(e: mariadb.Error) -> bool:
+ def is_unique_key_violation(e: pymysql.Error) -> bool:
return (
MariaDBDatabase.is_duplicate_entry(e)
- and "Duplicate" in e.errmsg
- and isinstance(e, mariadb.IntegrityError)
+ and "Duplicate" in cstr(e.args[1])
+ and isinstance(e, pymysql.IntegrityError)
)
@@ -118,90 +91,21 @@ class MariaDBConnectionUtil:
conn.auto_reconnect = True
return conn
- def _get_connection(self) -> "mariadb.Connection":
- """Return MariaDB connection object.
-
- If frappe.conf.disable_database_connection_pooling is set, return a new connection
- object and close existing pool if exists. Else, return a connection from the pool.
- """
- global _SITE_POOLS
-
- # don't pool root connections
- if self.user == "root":
- return self.create_connection()
-
- if not is_connection_pooling_enabled():
- self.close_connection_pools()
- return self.create_connection()
-
- if frappe.local.site not in _SITE_POOLS:
- site_pool = self.create_connection_pool()
- else:
- site_pool = self.get_connection_pool()
-
- try:
- conn = site_pool.get_connection()
- except mariadb.PoolError:
- # PoolError is raised when the pool is exhausted
- conn = self.create_connection()
- try:
- site_pool.add_connection(conn)
- # log this via frappe.logger & continue - site needs bigger pool...over _POOL_SIZE
- except mariadb.PoolError:
- # PoolError is raised when size limit is reached
- # log this via frappe.logger & continue - site needs a much bigger pool...over _MAX_POOL_SIZE
- pass
-
- return conn
-
- def close_connection_pools(self):
- if frappe.local.site in _SITE_POOLS:
- pools = _SITE_POOLS[frappe.local.site]
- for pool in pools.values():
- try:
- pool.close()
- except Exception:
- pass
- _SITE_POOLS.pop(frappe.local.site, None)
-
- def get_pool_name(self) -> str:
- pool_type = "read-only" if self.read_only else "default"
- return f"{frappe.local.site}-{pool_type}"
-
- def get_connection_pool(self) -> "ConnectionPool":
- """Return MariaDB connection pool object.
-
- If `read_only` is True, return a read only pool.
- """
- return _SITE_POOLS[frappe.local.site]["read_only" if self.read_only else "default"]
-
- def create_connection_pool(self):
- pool = mariadb.ConnectionPool(
- pool_name=self.get_pool_name(),
- pool_size=_MAX_POOL_SIZE,
- pool_reset_connection=False,
- )
- pool.set_config(**self.get_connection_settings())
-
- if self.read_only:
- _SITE_POOLS[frappe.local.site].read_only = pool
- else:
- _SITE_POOLS[frappe.local.site].default = pool
-
- for _ in range(_POOL_SIZE):
- pool.add_connection()
-
- return pool
+ def _get_connection(self):
+ """Return MariaDB connection object."""
+ return self.create_connection()
def create_connection(self):
- return mariadb.connect(**self.get_connection_settings())
+ return pymysql.connect(**self.get_connection_settings())
def get_connection_settings(self) -> dict:
conn_settings = {
"host": self.host,
"user": self.user,
"password": self.password,
- "converter": self.CONVERSION_MAP,
+ "conv": self.CONVERSION_MAP,
+ "charset": "utf8mb4",
+ "use_unicode": True,
}
if self.user != "root":
@@ -215,63 +119,15 @@ class MariaDBConnectionUtil:
if frappe.conf.db_ssl_ca and frappe.conf.db_ssl_cert and frappe.conf.db_ssl_key:
ssl_params = {
- "ssl": True,
- "ssl_ca": frappe.conf.db_ssl_ca,
- "ssl_cert": frappe.conf.db_ssl_cert,
- "ssl_key": frappe.conf.db_ssl_key,
+ "ca": frappe.conf.db_ssl_ca,
+ "cert": frappe.conf.db_ssl_cert,
+ "key": frappe.conf.db_ssl_key,
}
conn_settings.update(ssl_params)
return conn_settings
-class MariaDBCursorPatchUtil:
- """Patch mariadb.cursor.Cursor to handle things not supported by pinned version of MariaDB client."""
-
- def _transform_query(self, query: str, values: QueryValues) -> tuple:
- """Transform the query to handle things not supported by pinned version of MariaDB client.
-
- Transformations:
- - Escape sequences in values
- """
- _values = []
-
- if isinstance(values, (tuple, list)):
- for val in values:
- if isinstance(val, (tuple, list)):
- _values.append(escape_sequence(val, charset=self._conn.character_set))
- else:
- _values.append(val)
- values = _values
- else:
- for token in _PARAM_COMP.findall(query):
- key = token[2:-2]
- try:
- val = values[key]
- except KeyError:
- raise self.ProgrammingError(f"Missing value for key '{key}'")
- if isinstance(val, (tuple, list)):
- values[key] = escape_sequence(val, charset=self._conn.character_set)
-
- return query, values or []
-
- def _transform_result(self, result: list[tuple]) -> list[tuple]:
- # ref: https://jira.mariadb.org/projects/CONPY/issues/CONPY-213
- _result = []
- for row in result:
- _row = []
- for el in row:
- if isinstance(el, Decimal):
- el = float(el)
- elif isinstance(el, UnicodeWithAttrs):
- el = escape_string(el)
- _row.append(el)
- _result.append(tuple(_row))
- return _result
-
-
-class MariaDBDatabase(
- MariaDBCursorPatchUtil, MariaDBConnectionUtil, MariaDBExceptionUtil, Database
-):
+class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
REGEX_CHARACTER = "regexp"
# NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
@@ -282,7 +138,7 @@ class MariaDBDatabase(
# using the system after a restore.
# issue link: https://jira.mariadb.org/browse/MDEV-21786
SEQUENCE_CACHE = 50
- CONVERSION_MAP = {
+ CONVERSION_MAP = conversions | {
FIELD_TYPE.NEWDECIMAL: float,
FIELD_TYPE.DATETIME: get_datetime,
UnicodeWithAttrs: escape_string,
@@ -342,7 +198,8 @@ class MariaDBDatabase(
return db_size[0].get("database_size")
def log_query(self, query, values, debug, explain):
- self.last_query = super().log_query(query, values, debug, explain)
+ self.last_query = self._cursor._last_executed
+ self._log_query(query, debug, explain)
return self.last_query
@staticmethod
@@ -368,11 +225,11 @@ class MariaDBDatabase(
# column type
@staticmethod
def is_type_number(code):
- return code == mariadb.NUMBER
+ return code == pymysql.NUMBER
@staticmethod
def is_type_datetime(code):
- return code == mariadb.DATETIME
+ return code == pymysql.DATETIME
def rename_table(self, old_name: str, new_name: str) -> list | tuple:
old_name = get_table_name(old_name)
diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py
index 10ace1b1b6..a0b011acc1 100644
--- a/frappe/utils/bench_helper.py
+++ b/frappe/utils/bench_helper.py
@@ -106,6 +106,4 @@ if __name__ == "__main__":
if not frappe._dev_server:
warnings.simplefilter("ignore")
- frappe.DISABLE_DATABASE_CONNECTION_POOLING = not int(os.environ.get("DATABASE_POOLING", "0"))
-
main()
diff --git a/pyproject.toml b/pyproject.toml
index c3ef944b85..5eeb6f46dd 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -38,7 +38,6 @@ dependencies = [
"html5lib~=1.1",
"ipython~=8.4.0",
"ldap3~=2.9",
- "mariadb~=1.1.2",
"markdown2~=2.4.0",
"maxminddb-geolite2==2018.703",
"num2words~=0.5.10",
From 63e618c7097a0049a0d0a25f46e7198d9268b8a9 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 21 Jul 2022 12:31:55 +0530
Subject: [PATCH 0147/2449] test: More resilient tests for sequences
---
frappe/tests/test_db.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py
index 0e172c8f5b..bb64d0278d 100644
--- a/frappe/tests/test_db.py
+++ b/frappe/tests/test_db.py
@@ -574,7 +574,7 @@ class TestDDLCommandsMaria(unittest.TestCase):
self.test_table_name = new_table_name
def test_describe(self) -> None:
- self.assertEqual(
+ self.assertSequenceEqual(
[
("id", "int(11)", "NO", "PRI", None, ""),
("content", "text", "YES", "", None, ""),
@@ -798,7 +798,7 @@ class TestDDLCommandsPost(unittest.TestCase):
self.test_table_name = new_table_name
def test_describe(self) -> None:
- self.assertEqual([("id",), ("content",)], frappe.db.describe(self.test_table_name))
+ self.assertSequenceEqual([("id",), ("content",)], frappe.db.describe(self.test_table_name))
def test_change_type(self) -> None:
from psycopg2.errors import DatatypeMismatch
From e411132c6e8a6bebb719c37303e350958894026f Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 21 Jul 2022 16:27:54 +0530
Subject: [PATCH 0148/2449] fix(db): Revert breaking change of wrapping
NoneType in sequence
---
frappe/database/database.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index f205041acf..40529d0ec9 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -9,7 +9,6 @@ import string
import traceback
from contextlib import contextmanager
from time import time
-from types import NoneType
from pypika.terms import Criterion, NullValue
@@ -105,7 +104,7 @@ class Database:
raise NotImplementedError
def _transform_query(self, query: Query, values: QueryValues) -> tuple:
- return query, values or None
+ return query, values
def _transform_result(self, result: list[tuple]) -> list[tuple]:
return result
@@ -113,7 +112,7 @@ class Database:
def sql(
self,
query: Query,
- values: QueryValues = None,
+ values: QueryValues = (),
as_dict=0,
as_list=0,
formatted=0,
@@ -176,7 +175,7 @@ class Database:
if debug:
time_start = time()
- if not isinstance(values, (NoneType, tuple, dict, list)):
+ if not isinstance(values, (tuple, dict, list)):
values = (values,)
query, values = self._transform_query(query, values)
From 1a610e135df4cc330fc6ab592839282c7efc36a7 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Thu, 21 Jul 2022 17:38:40 +0530
Subject: [PATCH 0149/2449] fix(db): Use sentinel object for default values
paramters
---
frappe/database/database.py | 8 +++++---
frappe/database/postgres/database.py | 6 +++---
frappe/database/utils.py | 2 ++
3 files changed, 10 insertions(+), 6 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 40529d0ec9..e55af037f2 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -16,7 +16,7 @@ import frappe
import frappe.defaults
import frappe.model.meta
from frappe import _
-from frappe.database.utils import LazyMogrify, Query, QueryValues, is_query_type
+from frappe.database.utils import EmptyQueryValues, LazyMogrify, Query, QueryValues, is_query_type
from frappe.exceptions import DoesNotExistError
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.functions import Count
@@ -112,7 +112,7 @@ class Database:
def sql(
self,
query: Query,
- values: QueryValues = (),
+ values: QueryValues = EmptyQueryValues,
as_dict=0,
as_list=0,
formatted=0,
@@ -175,7 +175,9 @@ class Database:
if debug:
time_start = time()
- if not isinstance(values, (tuple, dict, list)):
+ if values == EmptyQueryValues:
+ values = None
+ elif not isinstance(values, (tuple, dict, list)):
values = (values,)
query, values = self._transform_query(query, values)
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index ec3e0084f9..85e4f2f0f7 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -18,7 +18,7 @@ from psycopg2.extensions import ISOLATION_LEVEL_REPEATABLE_READ
import frappe
from frappe.database.database import Database
from frappe.database.postgres.schema import PostgresTable
-from frappe.database.utils import LazyDecode
+from frappe.database.utils import EmptyQueryValues, LazyDecode
from frappe.utils import cstr, get_table_name
# cast decimals as floats
@@ -188,7 +188,7 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
return db_size[0].get("database_size")
# pylint: disable=W0221
- def sql(self, query, values=(), *args, **kwargs):
+ def sql(self, query, values=EmptyQueryValues, *args, **kwargs):
return super().sql(modify_query(query), modify_values(values), *args, **kwargs)
def lazy_mogrify(self, *args, **kwargs) -> str:
@@ -419,7 +419,7 @@ def modify_values(values):
return value
- if not values:
+ if not values or values == EmptyQueryValues:
return values
if isinstance(values, dict):
diff --git a/frappe/database/utils.py b/frappe/database/utils.py
index e71ffbf0d6..4bff2f78e6 100644
--- a/frappe/database/utils.py
+++ b/frappe/database/utils.py
@@ -10,6 +10,8 @@ from frappe.query_builder.builder import MariaDB, Postgres
Query = str | MariaDB | Postgres
QueryValues = tuple | list | dict | NoneType
+EmptyQueryValues = object()
+
def is_query_type(query: str, query_type: str | tuple[str]) -> bool:
return query.lstrip().split(maxsplit=1)[0].lower().startswith(query_type)
From bcfa8c276e46d00e6927924ce190d6ea2b663c95 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 22 Jul 2022 13:16:26 +0530
Subject: [PATCH 0150/2449] fix: Set default port attribute for Database
classes
db.default_port wil be available as a class attribute to hold defaults
for DB types.
Usage: frappe.conf.db_port or frappe.db.default_port
Why: I couldn't run the mariadb command because the defaults aren't set
for my system. server is remote / containerized. Setting port in
equivalent mysql command fixes this.
---
frappe/commands/utils.py | 30 +++++++++++++++-------------
frappe/database/mariadb/database.py | 1 +
frappe/database/postgres/database.py | 1 +
frappe/tests/test_db.py | 1 -
frappe/utils/backups.py | 6 +-----
5 files changed, 19 insertions(+), 20 deletions(-)
diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py
index d0024aed73..3658a35992 100644
--- a/frappe/commands/utils.py
+++ b/frappe/commands/utils.py
@@ -523,22 +523,24 @@ def postgres(context):
def _mariadb():
+ from frappe.database.mariadb.database import MariaDBDatabase
+
mysql = find_executable("mysql")
- os.execv(
+ command = [
mysql,
- [
- mysql,
- "-u",
- frappe.conf.db_name,
- "-p" + frappe.conf.db_password,
- frappe.conf.db_name,
- "-h",
- frappe.conf.db_host or "localhost",
- "--pager=less -SFX",
- "--safe-updates",
- "-A",
- ],
- )
+ "--port",
+ frappe.conf.db_port or MariaDBDatabase.default_port,
+ "-u",
+ frappe.conf.db_name,
+ f"-p{frappe.conf.db_password}",
+ frappe.conf.db_name,
+ "-h",
+ frappe.conf.db_host or "localhost",
+ "--pager=less -SFX",
+ "--safe-updates",
+ "-A",
+ ]
+ os.execv(mysql, command)
def _psql():
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index e89168194e..4bdc1688e9 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -143,6 +143,7 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
FIELD_TYPE.DATETIME: get_datetime,
UnicodeWithAttrs: escape_string,
}
+ default_port = "3306"
def setup_type_map(self):
self.db_type = "mariadb"
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 85e4f2f0f7..58b63e6547 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -108,6 +108,7 @@ class PostgresDatabase(PostgresExceptionUtil, Database):
# to the next non-cached value hence not using cache in postgres.
# ref: https://stackoverflow.com/questions/21356375/postgres-9-0-4-sequence-skipping-numbers
SEQUENCE_CACHE = 0
+ default_port = "5432"
def setup_type_map(self):
self.db_type = "postgres"
diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py
index bb64d0278d..b658919ef5 100644
--- a/frappe/tests/test_db.py
+++ b/frappe/tests/test_db.py
@@ -97,7 +97,6 @@ class TestDB(unittest.TestCase):
)
def test_get_value_limits(self):
-
# check both dict and list style filters
filters = [{"enabled": 1}, [["enabled", "=", 1]]]
for filter in filters:
diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py
index e6068fd299..f7b38b0055 100644
--- a/frappe/utils/backups.py
+++ b/frappe/utils/backups.py
@@ -73,11 +73,7 @@ class BackupGenerator:
if not self.db_type:
self.db_type = "mariadb"
- if not self.db_port:
- if self.db_type == "mariadb":
- self.db_port = 3306
- if self.db_type == "postgres":
- self.db_port = 5432
+ self.db_port = self.db_port or frappe.db.default_port
site = frappe.local.site or frappe.generate_hash(length=8)
self.site_slug = site.replace(".", "_")
From 1dbc0b4d3ca15a53b78349a21fcd5a8f72c503cd Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 22 Jul 2022 13:19:25 +0530
Subject: [PATCH 0151/2449] fix(db*): Use common fallback Datetime str
There existed inconsistencies between db_query & db's fallback for min
datetime in str format - missing decimal seconds places. Now, we're
storing the default string once and re-using it to reduce
inconsistencies or room for human errors.
---
frappe/database/database.py | 11 +++++++++--
frappe/database/utils.py | 1 +
frappe/model/db_query.py | 9 +++++----
3 files changed, 15 insertions(+), 6 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index e55af037f2..68286507ba 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -16,7 +16,14 @@ import frappe
import frappe.defaults
import frappe.model.meta
from frappe import _
-from frappe.database.utils import EmptyQueryValues, LazyMogrify, Query, QueryValues, is_query_type
+from frappe.database.utils import (
+ EmptyQueryValues,
+ FallBackDateTimeStr,
+ LazyMogrify,
+ Query,
+ QueryValues,
+ is_query_type,
+)
from frappe.exceptions import DoesNotExistError
from frappe.model.utils.link_count import flush_local_link_count
from frappe.query_builder.functions import Count
@@ -1071,7 +1078,7 @@ class Database:
@staticmethod
def format_datetime(datetime):
if not datetime:
- return "0001-01-01 00:00:00.000000"
+ return FallBackDateTimeStr
if isinstance(datetime, str):
if ":" not in datetime:
diff --git a/frappe/database/utils.py b/frappe/database/utils.py
index 4bff2f78e6..c4d8cb4953 100644
--- a/frappe/database/utils.py
+++ b/frappe/database/utils.py
@@ -11,6 +11,7 @@ Query = str | MariaDB | Postgres
QueryValues = tuple | list | dict | NoneType
EmptyQueryValues = object()
+FallBackDateTimeStr = "0001-01-01 00:00:00.000000"
def is_query_type(query: str, query_type: str | tuple[str]) -> bool:
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 4c62992e21..a29ede37bf 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -13,6 +13,7 @@ import frappe.permissions
import frappe.share
from frappe import _
from frappe.core.doctype.server_script.server_script_utils import get_server_script_map
+from frappe.database.utils import FallBackDateTimeStr
from frappe.model import optional_fields
from frappe.model.meta import get_table_columns
from frappe.model.utils.user_settings import get_user_settings, update_user_settings
@@ -632,11 +633,11 @@ class DatabaseQuery:
date_range = get_date_range(f.operator.lower(), f.value)
f.operator = "Between"
f.value = date_range
- fallback = "'0001-01-01 00:00:00'"
+ fallback = f"'{FallBackDateTimeStr}'"
if f.operator in (">", "<") and (f.fieldname in ("creation", "modified")):
value = cstr(f.value)
- fallback = "'0001-01-01 00:00:00'"
+ fallback = f"'{FallBackDateTimeStr}'"
elif f.operator.lower() in ("between") and (
f.fieldname in ("creation", "modified")
@@ -644,7 +645,7 @@ class DatabaseQuery:
):
value = get_between_date_filter(f.value, df)
- fallback = "'0001-01-01 00:00:00'"
+ fallback = f"'{FallBackDateTimeStr}'"
elif f.operator.lower() == "is":
if f.value == "set":
@@ -665,7 +666,7 @@ class DatabaseQuery:
elif (df and df.fieldtype == "Datetime") or isinstance(f.value, datetime):
value = frappe.db.format_datetime(f.value)
- fallback = "'0001-01-01 00:00:00'"
+ fallback = f"'{FallBackDateTimeStr}'"
elif df and df.fieldtype == "Time":
value = get_time(f.value).strftime("%H:%M:%S.%f")
From 2bef29bb466c4ad2b23888421e0ac4a9f0c03690 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 22 Jul 2022 13:21:29 +0530
Subject: [PATCH 0152/2449] chore: Deprecate backup script + minor refactors
---
frappe/database/mariadb/database.py | 2 +-
frappe/tests/test_db.py | 2 +-
frappe/tests/test_fmt_datetime.py | 2 +-
frappe/utils/backups.py | 8 ++++++++
4 files changed, 11 insertions(+), 3 deletions(-)
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 4bdc1688e9..5e6d62f842 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -123,7 +123,7 @@ class MariaDBConnectionUtil:
"cert": frappe.conf.db_ssl_cert,
"key": frappe.conf.db_ssl_key,
}
- conn_settings.update(ssl_params)
+ conn_settings |= ssl_params
return conn_settings
diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py
index b658919ef5..b1ff0f81df 100644
--- a/frappe/tests/test_db.py
+++ b/frappe/tests/test_db.py
@@ -135,7 +135,7 @@ class TestDB(unittest.TestCase):
test_inputs = [
{"fieldtype": fieldtype, "value": value} for fieldtype, value in values_dict.items()
]
- for fieldtype in values_dict.keys():
+ for fieldtype in values_dict:
create_custom_field(
"Print Settings",
{
diff --git a/frappe/tests/test_fmt_datetime.py b/frappe/tests/test_fmt_datetime.py
index 031b8c323c..706aca6d7c 100644
--- a/frappe/tests/test_fmt_datetime.py
+++ b/frappe/tests/test_fmt_datetime.py
@@ -125,5 +125,5 @@ class TestFmtDatetime(unittest.TestCase):
for time_fmt, valid_time in test_time_formats.items():
frappe.db.set_default("time_format", time_fmt)
frappe.local.user_time_format = None
- valid_fmt = valid_date + " " + valid_time
+ valid_fmt = f"{valid_date} {valid_time}"
self.assertEqual(format_datetime(test_datetime), valid_fmt)
diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py
index f7b38b0055..f5e13c0873 100644
--- a/frappe/utils/backups.py
+++ b/frappe/utils/backups.py
@@ -697,6 +697,14 @@ def backup(
if __name__ == "__main__":
import sys
+ from frappe.utils.commands import warn
+
+ warn(
+ "Calling the backup script directly is deprecated. "
+ "Use the backup command instead. This script will be removed in Frappe v15.",
+ category=DeprecationWarning,
+ )
+
cmd = sys.argv[1]
db_type = "mariadb"
From d1fbab1c45c0f04f126bfc2ccf2a1c14a1bccd10 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 22 Jul 2022 13:30:47 +0530
Subject: [PATCH 0153/2449] test(db): Add tests for untested db methods
---
frappe/tests/test_db.py | 16 ++++++++++++++++
1 file changed, 16 insertions(+)
diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py
index b1ff0f81df..bb6db76bf5 100644
--- a/frappe/tests/test_db.py
+++ b/frappe/tests/test_db.py
@@ -9,9 +9,11 @@ from random import choice
from unittest.mock import patch
import frappe
+from frappe.core.utils import find
from frappe.custom.doctype.custom_field.custom_field import create_custom_field
from frappe.database import savepoint
from frappe.database.database import Database
+from frappe.database.utils import FallBackDateTimeStr
from frappe.query_builder import Field
from frappe.query_builder.functions import Concat_ws
from frappe.tests.test_query_builder import db_type_is, run_only_if
@@ -20,6 +22,20 @@ from frappe.utils.testutils import clear_custom_fields
class TestDB(unittest.TestCase):
+ def test_datetime_format(self):
+ now_str = now()
+ self.assertEqual(frappe.db.format_datetime(None), FallBackDateTimeStr)
+ self.assertEqual(frappe.db.format_datetime(now_str), now_str)
+
+ @run_only_if(db_type_is.MARIADB)
+ def test_get_column_type(self):
+ desc_data = frappe.db.sql("desc `tabUser`", as_dict=1)
+ user_name_type = find(desc_data, lambda x: x["Field"] == "name")["Type"]
+ self.assertEqual(frappe.db.get_column_type("User", "name"), user_name_type)
+
+ def test_get_database_size(self):
+ self.assertIsInstance(frappe.db.get_database_size(), (float, int))
+
def test_get_value(self):
self.assertEqual(frappe.db.get_value("User", {"name": ["=", "Administrator"]}), "Administrator")
self.assertEqual(frappe.db.get_value("User", {"name": ["like", "Admin%"]}), "Administrator")
From f40214bca8dee3564ec7a0c073c0e1ab3cd67502 Mon Sep 17 00:00:00 2001
From: gavin
Date: Fri, 22 Jul 2022 13:34:04 +0530
Subject: [PATCH 0154/2449] fix(get_last_doc): Allow for_update as kwarg only
Co-authored-by: Ankush Menat
---
frappe/__init__.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index 20fddb0267..e1fa902eba 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -1182,7 +1182,7 @@ def get_doc(*args, **kwargs) -> "Document":
return doc
-def get_last_doc(doctype, filters=None, order_by="creation desc", for_update=False):
+def get_last_doc(doctype, filters=None, order_by="creation desc", *, for_update=False):
"""Get last created document of this type."""
d = get_all(doctype, filters=filters, limit_page_length=1, order_by=order_by, pluck="name")
if d:
From 10da1622f460354e0eb90aac5bfe7fd419389399 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 19 Jul 2022 21:12:21 +0530
Subject: [PATCH 0155/2449] fix: form view broken for virtual doctype
---
frappe/core/doctype/test/test.py | 4 ++--
frappe/desk/form/load.py | 3 ++-
2 files changed, 4 insertions(+), 3 deletions(-)
diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py
index 664d06ac84..c3b8f6accf 100644
--- a/frappe/core/doctype/test/test.py
+++ b/frappe/core/doctype/test/test.py
@@ -3,7 +3,7 @@
import json
-# import frappe
+import frappe
from frappe.model.document import Document
@@ -25,7 +25,7 @@ class test(Document):
def get_list(self, args):
with open("data_file.json") as read_file:
- return [json.load(read_file)]
+ return [frappe._dict(json.load(read_file))]
def get_value(self, fields, filters, **kwargs):
# return []
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index 898c6461e9..f63796d061 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -31,7 +31,8 @@ def getdoc(doctype, name, user=None):
name = doctype
if not frappe.db.exists(doctype, name):
- return []
+ if not frappe.get_meta(doctype).is_virtual:
+ return []
doc = frappe.get_doc(doctype, name)
run_onload(doc)
From b2203893a1e47566cf26618d24c445e0d2528751 Mon Sep 17 00:00:00 2001
From: Shridhar
Date: Fri, 22 Jul 2022 10:59:59 +0530
Subject: [PATCH 0156/2449] fix: do not fetch comments from parent for virtual
doctype
---
frappe/core/doctype/comment/comment.py | 6 +++++-
frappe/desk/reportview.py | 2 +-
2 files changed, 6 insertions(+), 2 deletions(-)
diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py
index dab9cfbfe4..cf00651b15 100644
--- a/frappe/core/doctype/comment/comment.py
+++ b/frappe/core/doctype/comment/comment.py
@@ -11,6 +11,7 @@ from frappe.desk.doctype.notification_log.notification_log import (
get_title,
get_title_html,
)
+from frappe.desk.reportview import is_virtual_doctype
from frappe.exceptions import ImplicitCommitError
from frappe.model.document import Document
from frappe.utils import get_fullname
@@ -152,7 +153,10 @@ def get_comments_from_parent(doc):
`_comments`
"""
try:
- _comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]"
+ if is_virtual_doctype(doc.reference_doctype):
+ _comments = "[]"
+ else:
+ _comments = frappe.db.get_value(doc.reference_doctype, doc.reference_name, "_comments") or "[]"
except Exception as e:
if frappe.db.is_missing_table_or_column(e):
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index c163c4fab5..68dc2933f0 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -272,7 +272,7 @@ def compress(data, args=None):
values.append(new_row)
# add user info for assignments (avatar)
- if row._assign:
+ if row.get("_assign", ""):
for user in json.loads(row._assign):
add_user_info(user, user_info)
From e88bc35cae766527825a608468b87928798383c5 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 12:19:27 +0530
Subject: [PATCH 0157/2449] refactor: move is_virtual_doctype to relevant file
---
frappe/core/doctype/comment/comment.py | 2 +-
frappe/desk/form/load.py | 6 +++---
frappe/desk/reportview.py | 7 +------
frappe/model/utils/__init__.py | 6 ++++++
4 files changed, 11 insertions(+), 10 deletions(-)
diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py
index cf00651b15..4cd15dc815 100644
--- a/frappe/core/doctype/comment/comment.py
+++ b/frappe/core/doctype/comment/comment.py
@@ -179,7 +179,7 @@ def update_comments_in_parent(reference_doctype, reference_name, _comments):
not reference_doctype
or not reference_name
or frappe.db.get_value("DocType", reference_doctype, "issingle")
- or frappe.db.get_value("DocType", reference_doctype, "is_virtual")
+ or is_virtual_doctype(reference_doctype)
):
return
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index f63796d061..167fab4a07 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -11,6 +11,7 @@ import frappe.share
import frappe.utils
from frappe import _, _dict
from frappe.desk.form.document_follow import is_document_followed
+from frappe.model.utils import is_virtual_doctype
from frappe.model.utils.user_settings import get_user_settings
from frappe.permissions import get_doc_permissions
from frappe.utils.data import cstr
@@ -30,9 +31,8 @@ def getdoc(doctype, name, user=None):
if not name:
name = doctype
- if not frappe.db.exists(doctype, name):
- if not frappe.get_meta(doctype).is_virtual:
- return []
+ if not frappe.db.exists(doctype, name) and not is_virtual_doctype(doctype):
+ return []
doc = frappe.get_doc(doctype, name)
run_onload(doc)
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index 68dc2933f0..9500023ffc 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -13,8 +13,8 @@ from frappe.core.doctype.access_log.access_log import make_access_log
from frappe.model import child_table_fields, default_fields, optional_fields
from frappe.model.base_document import get_controller
from frappe.model.db_query import DatabaseQuery
+from frappe.model.utils import is_virtual_doctype
from frappe.utils import add_user_info, cstr, format_duration
-from frappe.utils.caching import site_cache
@frappe.whitelist()
@@ -731,8 +731,3 @@ def get_filters_cond(
else:
cond = ""
return cond
-
-
-@site_cache(maxsize=128)
-def is_virtual_doctype(doctype):
- return frappe.db.get_value("DocType", doctype, "is_virtual")
diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py
index 351b19c8eb..2e940decb6 100644
--- a/frappe/model/utils/__init__.py
+++ b/frappe/model/utils/__init__.py
@@ -7,6 +7,7 @@ import frappe
from frappe import _
from frappe.build import html_to_js_template
from frappe.utils import cstr
+from frappe.utils.caching import site_cache
STANDARD_FIELD_CONVERSION_MAP = {
"name": "Link",
@@ -99,3 +100,8 @@ def get_fetch_values(doctype, fieldname, value):
out[df.fieldname] = frappe.db.get_value(link_df.options, value, source_fieldname)
return out
+
+
+@site_cache(maxsize=128)
+def is_virtual_doctype(doctype):
+ return frappe.db.get_value("DocType", doctype, "is_virtual")
From b8d56eaefbc8539129748489142d9009100aeae0 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 12:31:10 +0530
Subject: [PATCH 0158/2449] chore: remove test doctypes from prod
---
frappe/core/doctype/test/test.json | 4 +-
.../doctype/test_rename_new/__init__.py | 0
.../test_rename_new/test_rename_new.json | 42 -------------------
.../test_rename_new/test_rename_new.py | 9 ----
.../test_rename_new/test_test_rename_new.py | 8 ----
5 files changed, 3 insertions(+), 60 deletions(-)
delete mode 100644 frappe/custom/doctype/test_rename_new/__init__.py
delete mode 100644 frappe/custom/doctype/test_rename_new/test_rename_new.json
delete mode 100644 frappe/custom/doctype/test_rename_new/test_rename_new.py
delete mode 100644 frappe/custom/doctype/test_rename_new/test_test_rename_new.py
diff --git a/frappe/core/doctype/test/test.json b/frappe/core/doctype/test/test.json
index 31a57c9964..4187984d2b 100644
--- a/frappe/core/doctype/test/test.json
+++ b/frappe/core/doctype/test/test.json
@@ -17,7 +17,7 @@
"index_web_pages_for_search": 1,
"is_virtual": 1,
"links": [],
- "modified": "2021-03-31 10:06:57.919697",
+ "modified": "2022-07-22 03:00:59.560061",
"modified_by": "Administrator",
"module": "Core",
"name": "test",
@@ -36,7 +36,9 @@
"write": 1
}
],
+ "read_only": 1,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/custom/doctype/test_rename_new/__init__.py b/frappe/custom/doctype/test_rename_new/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.json b/frappe/custom/doctype/test_rename_new/test_rename_new.json
deleted file mode 100644
index 0b089091a1..0000000000
--- a/frappe/custom/doctype/test_rename_new/test_rename_new.json
+++ /dev/null
@@ -1,42 +0,0 @@
-{
- "actions": [],
- "creation": "2021-01-13 12:47:03.572640",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "random"
- ],
- "fields": [
- {
- "fieldname": "random",
- "fieldtype": "Data",
- "label": "random"
- }
- ],
- "index_web_pages_for_search": 1,
- "links": [],
- "modified": "2021-01-13 12:47:03.572640",
- "modified_by": "Administrator",
- "module": "Custom",
- "name": "Test rename new",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- }
- ],
- "route": "test-rename",
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_rename_new.py
deleted file mode 100644
index ed89d1fad1..0000000000
--- a/frappe/custom/doctype/test_rename_new/test_rename_new.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies and contributors
-# License: MIT. See LICENSE
-
-# import frappe
-from frappe.model.document import Document
-
-
-class Testrenamenew(Document):
- pass
diff --git a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py
deleted file mode 100644
index f1ccf42ede..0000000000
--- a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies and Contributors
-# License: MIT. See LICENSE
-# import frappe
-import unittest
-
-
-class Testrenamenew(unittest.TestCase):
- pass
From e8efd64dbc0cca53b11042ab39ca771f8c2ecc26 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 13:03:29 +0530
Subject: [PATCH 0159/2449] refactor!: better API contracts for virtual doctype
Current APIs implement class methods as instance method, which is
problamamtic while implementing methods. E.g. If load_from_db doesn't
like empty docname then all class method will stop working.
This change while breaking is essential for usability of virtual
doctype.
---
frappe/core/doctype/test/test.py | 22 +++++++--------
frappe/desk/reportview.py | 8 +++---
frappe/model/virtual_doctype.py | 46 ++++++++++++++++++++++++++++++++
frappe/modules/utils.py | 13 +++++----
4 files changed, 67 insertions(+), 22 deletions(-)
create mode 100644 frappe/model/virtual_doctype.py
diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py
index c3b8f6accf..e17b3a0a4a 100644
--- a/frappe/core/doctype/test/test.py
+++ b/frappe/core/doctype/test/test.py
@@ -8,7 +8,7 @@ from frappe.model.document import Document
class test(Document):
- def db_insert(self):
+ def db_insert(self, *args, **kwargs):
d = self.get_valid_dict(convert_dates_to_str=True)
with open("data_file.json", "w+") as read_file:
json.dump(d, read_file)
@@ -18,26 +18,22 @@ class test(Document):
d = json.load(read_file)
super(Document, self).__init__(d)
- def db_update(self):
+ def db_update(self, *args, **kwargs):
d = self.get_valid_dict(convert_dates_to_str=True)
with open("data_file.json", "w+") as read_file:
json.dump(d, read_file)
- def get_list(self, args):
+ @staticmethod
+ def get_list(args):
with open("data_file.json") as read_file:
return [frappe._dict(json.load(read_file))]
- def get_value(self, fields, filters, **kwargs):
- # return []
- with open("data_file.json") as read_file:
- return [json.load(read_file)]
+ @staticmethod
+ def get_count(args):
+ return 5
- def get_count(self, args):
- # return []
- with open("data_file.json") as read_file:
- return [json.load(read_file)]
-
- def get_stats(self, args):
+ @staticmethod
+ def get_stats(args):
# return []
with open("data_file.json") as read_file:
return [json.load(read_file)]
diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py
index 9500023ffc..679b052baf 100644
--- a/frappe/desk/reportview.py
+++ b/frappe/desk/reportview.py
@@ -24,7 +24,7 @@ def get():
# If virtual doctype get data from controller het_list method
if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
- data = compress(controller(args.doctype).get_list(args))
+ data = compress(controller.get_list(args))
else:
data = compress(execute(**args), args=args)
return data
@@ -37,7 +37,7 @@ def get_list():
if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
- data = controller(args.doctype).get_list(args)
+ data = controller.get_list(args)
else:
# uncompressed (refactored from frappe.model.db_query.get_list)
data = execute(**args)
@@ -52,7 +52,7 @@ def get_count():
if is_virtual_doctype(args.doctype):
controller = get_controller(args.doctype)
- data = controller(args.doctype).get_count(args)
+ data = controller.get_count(args)
else:
distinct = "distinct " if args.distinct == "true" else ""
args.fields = [f"count({distinct}`tab{args.doctype}`.name) as total_count"]
@@ -528,7 +528,7 @@ def get_sidebar_stats(stats, doctype, filters=None):
if is_virtual_doctype(doctype):
controller = get_controller(doctype)
args = {"stats": stats, "filters": filters}
- data = controller(doctype).get_stats(args)
+ data = controller.get_stats(args)
else:
data = get_stats(stats, doctype, filters)
diff --git a/frappe/model/virtual_doctype.py b/frappe/model/virtual_doctype.py
new file mode 100644
index 0000000000..a21d88e605
--- /dev/null
+++ b/frappe/model/virtual_doctype.py
@@ -0,0 +1,46 @@
+from typing import Protocol
+
+
+class VirtualDoctype(Protocol):
+ """This class documents requirements that must be met by a doctype controller to function as virtual doctype
+
+
+ Additional requirements:
+ - DocType controller has to inherit from `frappe.model.document.Document` class
+
+ Note:
+ - "Backend" here means any storage service, it can be a database, flat file or network call to API.
+ """
+
+ # ============ class/static methods ============
+
+ @staticmethod
+ def get_list(args):
+ """Similar to reportview.get_list"""
+ ...
+
+ @staticmethod
+ def get_count(args) -> int:
+ """Similar to reportview.get_count, return total count of documents on listview."""
+ ...
+
+ @staticmethod
+ def get_stats(args):
+ """Similar to reportview.get_stats, return sidebar stats."""
+ ...
+
+ # ============ instance methods ============
+
+ def db_insert(self, *args, **kwargs) -> None:
+ """Serialize the `Document` object and insert it in backend."""
+ ...
+
+ def load_from_db(self) -> None:
+ """Using self.name initialize current document from backend data.
+
+ This is responsible for updatinng __dict__ of class with all the fields on doctype."""
+ ...
+
+ def db_update(self, *args, **kwargs):
+ """Serialize the `Document` object and update existing document in backend."""
+ ...
diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py
index f4a386cfc9..a87ac1b3db 100644
--- a/frappe/modules/utils.py
+++ b/frappe/modules/utils.py
@@ -296,22 +296,25 @@ def make_boilerplate(template, doc, opts=None):
custom_controller = "pass"
if doc.get("is_virtual"):
custom_controller = """
- def db_insert(self):
+ def db_insert(self, *args, **kwargs):
pass
def load_from_db(self):
pass
- def db_update(self):
+ def db_update(self, *args, **kwargs):
pass
- def get_list(self, args):
+ @staticmethod
+ def get_list(args):
pass
- def get_count(self, args):
+ @staticmethod
+ def get_count(args):
pass
- def get_stats(self, args):
+ @staticmethod
+ def get_stats(args):
pass"""
with open(target_file_path, "w") as target:
From f1d638473f4461bfbfda559e753aa1424e310d7a Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 14:50:13 +0530
Subject: [PATCH 0160/2449] refactor: add reload function back
Assigning a function to a different name breaks inheritance model.
E.g. doc.reload() won't call virtual doctype's load_from_db but call
original load_from_db
---
frappe/model/document.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index 3bddaa9aae..cadfa573d0 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -179,7 +179,9 @@ class Document(BaseDocument):
if hasattr(self, "__setup__"):
self.__setup__()
- reload = load_from_db
+ def reload(self):
+ """Reload document from database"""
+ self.load_from_db()
def get_latest(self):
if not getattr(self, "latest", None):
From ad9092380160e5d69fe07150c506a4df2396b83e Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 22 Jul 2022 14:59:47 +0530
Subject: [PATCH 0161/2449] test(communication): Add test for handling
duplicate email signatures
---
.../communication/test_communication.py | 38 +++++++++++++------
1 file changed, 26 insertions(+), 12 deletions(-)
diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py
index ba586f3f3a..079eca0d01 100644
--- a/frappe/core/doctype/communication/test_communication.py
+++ b/frappe/core/doctype/communication/test_communication.py
@@ -4,7 +4,7 @@ import unittest
from urllib.parse import quote
import frappe
-from frappe.core.doctype.communication.communication import get_emails
+from frappe.core.doctype.communication.communication import Communication, get_emails
from frappe.email.doctype.email_queue.email_queue import EmailQueue
from frappe.tests.utils import FrappeTestCase
@@ -263,28 +263,43 @@ class TestCommunication(FrappeTestCase):
def test_signature_in_email_content(self):
email_account = create_email_account()
signature = email_account.signature
- comm = frappe.get_doc(
- {
- "doctype": "Communication",
- "communication_medium": "Email",
- "subject": "Document Link in Email",
- "sender": "comm_sender@example.com",
+ base_communication = {
+ "doctype": "Communication",
+ "communication_medium": "Email",
+ "subject": "Document Link in Email",
+ "sender": "comm_sender@example.com",
+ }
+ comm_with_signature = frappe.get_doc(
+ base_communication
+ | {
+ "content": f"""
+ Hi,
+ How are you?
+
{signature}
""",
+ }
+ ).insert(ignore_permissions=True)
+ comm_without_signature = frappe.get_doc(
+ base_communication
+ | {
"content": """
Hi,
How are you?
-
""",
+ """
}
).insert(ignore_permissions=True)
- assert signature in comm.content
+
+ self.assertEqual(comm_with_signature.content, comm_without_signature.content)
+ self.assertEqual(comm_with_signature.content.count(signature), 1)
+ self.assertEqual(comm_without_signature.content.count(signature), 1)
class TestCommunicationEmailMixin(FrappeTestCase):
- def new_communication(self, recipients=None, cc=None, bcc=None):
+ def new_communication(self, recipients=None, cc=None, bcc=None) -> Communication:
recipients = ", ".join(recipients or [])
cc = ", ".join(cc or [])
bcc = ", ".join(bcc or [])
- comm = frappe.get_doc(
+ return frappe.get_doc(
{
"doctype": "Communication",
"communication_type": "Communication",
@@ -295,7 +310,6 @@ class TestCommunicationEmailMixin(FrappeTestCase):
"bcc": bcc,
}
).insert(ignore_permissions=True)
- return comm
def new_user(self, email, **user_data):
user_data.setdefault("first_name", "first_name")
From 9dba593a6e1db38ea6f47b13ef4235624e924c05 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Fri, 22 Jul 2022 15:09:39 +0530
Subject: [PATCH 0162/2449] refactor: Add typing + comprehensions for
readability
---
frappe/core/doctype/communication/mixins.py | 8 ++-
.../communication/test_communication.py | 50 +++++++++----------
2 files changed, 27 insertions(+), 31 deletions(-)
diff --git a/frappe/core/doctype/communication/mixins.py b/frappe/core/doctype/communication/mixins.py
index 695b8bebae..57aea58d56 100644
--- a/frappe/core/doctype/communication/mixins.py
+++ b/frappe/core/doctype/communication/mixins.py
@@ -247,7 +247,7 @@ class CommunicationEmailMixin:
send_me_a_copy=None,
print_letterhead=None,
is_inbound_mail_communcation=None,
- ):
+ ) -> dict:
outgoing_email_account = self.get_outgoing_email_account()
if not outgoing_email_account:
@@ -297,13 +297,11 @@ class CommunicationEmailMixin:
print_letterhead=None,
is_inbound_mail_communcation=None,
):
- input_dict = self.sendmail_input_dict(
+ if input_dict := self.sendmail_input_dict(
print_html=print_html,
print_format=print_format,
send_me_a_copy=send_me_a_copy,
print_letterhead=print_letterhead,
is_inbound_mail_communcation=is_inbound_mail_communcation,
- )
-
- if input_dict:
+ ):
frappe.sendmail(**input_dict)
diff --git a/frappe/core/doctype/communication/test_communication.py b/frappe/core/doctype/communication/test_communication.py
index 079eca0d01..ab3a0ce5a1 100644
--- a/frappe/core/doctype/communication/test_communication.py
+++ b/frappe/core/doctype/communication/test_communication.py
@@ -1,6 +1,7 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import unittest
+from typing import TYPE_CHECKING
from urllib.parse import quote
import frappe
@@ -8,6 +9,10 @@ from frappe.core.doctype.communication.communication import Communication, get_e
from frappe.email.doctype.email_queue.email_queue import EmailQueue
from frappe.tests.utils import FrappeTestCase
+if TYPE_CHECKING:
+ from frappe.contacts.doctype.contact.contact import Contact
+ from frappe.email.doctype.email_account.email_account import EmailAccount
+
test_records = frappe.get_test_records("Communication")
@@ -33,11 +38,13 @@ class TestCommunication(FrappeTestCase):
"[invalid!email].com",
]
- for x in valid_email_list:
- self.assertTrue(frappe.utils.parse_addr(x)[1])
+ for i, x in enumerate(valid_email_list):
+ with self.subTest(i=i, x=x):
+ self.assertTrue(frappe.utils.parse_addr(x)[1])
- for x in invalid_email_list:
- self.assertFalse(frappe.utils.parse_addr(x)[0])
+ for i, x in enumerate(invalid_email_list):
+ with self.subTest(i=i, x=x):
+ self.assertFalse(frappe.utils.parse_addr(x)[0])
def test_name(self):
valid_email_list = [
@@ -130,7 +137,7 @@ class TestCommunication(FrappeTestCase):
self.assertNotEqual(2, len(comm.timeline_links))
def test_contacts_attached(self):
- contact_sender = frappe.get_doc(
+ contact_sender: "Contact" = frappe.get_doc(
{
"doctype": "Contact",
"first_name": "contact_sender",
@@ -139,7 +146,7 @@ class TestCommunication(FrappeTestCase):
contact_sender.add_email("comm_sender@example.com")
contact_sender.insert(ignore_permissions=True)
- contact_recipient = frappe.get_doc(
+ contact_recipient: "Contact" = frappe.get_doc(
{
"doctype": "Contact",
"first_name": "contact_recipient",
@@ -148,7 +155,7 @@ class TestCommunication(FrappeTestCase):
contact_recipient.add_email("comm_recipient@example.com")
contact_recipient.insert(ignore_permissions=True)
- contact_cc = frappe.get_doc(
+ contact_cc: "Contact" = frappe.get_doc(
{
"doctype": "Contact",
"first_name": "contact_cc",
@@ -157,7 +164,7 @@ class TestCommunication(FrappeTestCase):
contact_cc.add_email("comm_cc@example.com")
contact_cc.insert(ignore_permissions=True)
- comm = frappe.get_doc(
+ comm: Communication = frappe.get_doc(
{
"doctype": "Communication",
"communication_medium": "Email",
@@ -169,10 +176,7 @@ class TestCommunication(FrappeTestCase):
).insert(ignore_permissions=True)
comm = frappe.get_doc("Communication", comm.name)
-
- contact_links = []
- for timeline_link in comm.timeline_links:
- contact_links.append(timeline_link.link_name)
+ contact_links = [x.link_name for x in comm.timeline_links]
self.assertIn(contact_sender.name, contact_links)
self.assertIn(contact_recipient.name, contact_links)
@@ -211,10 +215,7 @@ class TestCommunication(FrappeTestCase):
comms = get_communication_data("Note", note.name, as_dict=True)
- data = []
- for comm in comms:
- data.append(comm.name)
-
+ data = [comm.name for comm in comms]
self.assertIn(comm_note_1.name, data)
self.assertIn(comm_note_2.name, data)
@@ -237,14 +238,13 @@ class TestCommunication(FrappeTestCase):
"communication_medium": "Email",
"subject": "Document Link in Email",
"sender": "comm_sender@example.com",
- "recipients": "comm_recipient+{}+{}@example.com".format(quote("Note"), quote(note.name)),
+ "recipients": f'comm_recipient+{quote("Note")}+{quote(note.name)}@example.com',
}
).insert(ignore_permissions=True)
- doc_links = []
- for timeline_link in comm.timeline_links:
- doc_links.append((timeline_link.link_doctype, timeline_link.link_name))
-
+ doc_links = [
+ (timeline_link.link_doctype, timeline_link.link_name) for timeline_link in comm.timeline_links
+ ]
self.assertIn(("Note", note.name), doc_links)
def test_parse_emails(self):
@@ -362,13 +362,13 @@ class TestCommunicationEmailMixin(FrappeTestCase):
comm.delete()
-def create_email_account():
+def create_email_account() -> "EmailAccount":
frappe.delete_doc_if_exists("Email Account", "_Test Comm Account 1")
frappe.flags.mute_emails = False
frappe.flags.sent_mail = None
- email_account = frappe.get_doc(
+ return frappe.get_doc(
{
"is_default": 1,
"is_global": 1,
@@ -395,5 +395,3 @@ def create_email_account():
"enable_automatic_linking": 1,
}
).insert(ignore_permissions=True)
-
- return email_account
From 53c22b04938c0c128c76802b6994f7fe7cfb95fe Mon Sep 17 00:00:00 2001
From: Nabin Hait
Date: Fri, 22 Jul 2022 15:26:30 +0530
Subject: [PATCH 0163/2449] feat: Enable mentions and notify users from any
text field
---
frappe/core/doctype/comment/comment.py | 45 +-------------
frappe/core/doctype/user/test_user.py | 2 +-
frappe/core/doctype/user/user.py | 20 ------
frappe/desk/desktop.py | 1 -
frappe/desk/notifications.py | 62 +++++++++++++++++++
.../public/js/frappe/form/controls/comment.js | 28 +--------
.../js/frappe/form/controls/text_editor.js | 28 ++++++++-
7 files changed, 92 insertions(+), 94 deletions(-)
diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py
index dab9cfbfe4..796af748ca 100644
--- a/frappe/core/doctype/comment/comment.py
+++ b/frappe/core/doctype/comment/comment.py
@@ -3,23 +3,16 @@
import json
import frappe
-from frappe import _
-from frappe.core.doctype.user.user import extract_mentions
from frappe.database.schema import add_column
-from frappe.desk.doctype.notification_log.notification_log import (
- enqueue_create_notification,
- get_title,
- get_title_html,
-)
+from frappe.desk.notifications import notify_mentions
from frappe.exceptions import ImplicitCommitError
from frappe.model.document import Document
-from frappe.utils import get_fullname
from frappe.website.utils import clear_cache
class Comment(Document):
def after_insert(self):
- self.notify_mentions()
+ notify_mentions(self.reference_doctype, self.reference_name, self.content)
self.notify_change("add")
def validate(self):
@@ -63,40 +56,6 @@ class Comment(Document):
update_comments_in_parent(self.reference_doctype, self.reference_name, _comments)
- def notify_mentions(self):
- if self.reference_doctype and self.reference_name and self.content:
- mentions = extract_mentions(self.content)
-
- if not mentions:
- return
-
- sender_fullname = get_fullname(frappe.session.user)
- title = get_title(self.reference_doctype, self.reference_name)
-
- recipients = [
- frappe.db.get_value(
- "User",
- {"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1},
- "email",
- )
- for name in mentions
- ]
-
- notification_message = _("""{0} mentioned you in a comment in {1} {2}""").format(
- frappe.bold(sender_fullname), frappe.bold(self.reference_doctype), get_title_html(title)
- )
-
- notification_doc = {
- "type": "Mention",
- "document_type": self.reference_doctype,
- "document_name": self.reference_name,
- "subject": notification_message,
- "from_user": frappe.session.user,
- "email_content": self.content,
- }
-
- enqueue_create_notification(recipients, notification_doc)
-
def on_doctype_update():
frappe.db.add_index("Comment", ["reference_doctype", "reference_name"])
diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py
index d5e0a108b5..7582954175 100644
--- a/frappe/core/doctype/user/test_user.py
+++ b/frappe/core/doctype/user/test_user.py
@@ -8,13 +8,13 @@ from unittest.mock import patch
import frappe
import frappe.exceptions
from frappe.core.doctype.user.user import (
- extract_mentions,
reset_password,
sign_up,
test_password_strength,
update_password,
verify_password,
)
+from frappe.desk.notifications import extract_mentions
from frappe.frappeclient import FrappeClient
from frappe.model.delete_doc import delete_doc
from frappe.utils import get_url
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 6d0de186a5..232e915435 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -2,8 +2,6 @@
# License: MIT. See LICENSE
from datetime import timedelta
-from bs4 import BeautifulSoup
-
import frappe
import frappe.defaults
import frappe.permissions
@@ -1044,24 +1042,6 @@ def notify_admin_access_to_system_manager(login_manager=None):
)
-def extract_mentions(txt):
- """Find all instances of @mentions in the html."""
- soup = BeautifulSoup(txt, "html.parser")
- emails = []
- for mention in soup.find_all(class_="mention"):
- if mention.get("data-is-group") == "true":
- try:
- user_group = frappe.get_cached_doc("User Group", mention["data-id"])
- emails += [d.user for d in user_group.user_group_members]
- except frappe.DoesNotExistError:
- pass
- continue
- email = mention["data-id"]
- emails.append(email)
-
- return emails
-
-
def handle_password_test_fail(result):
suggestions = result["feedback"]["suggestions"][0] if result["feedback"]["suggestions"] else ""
warning = result["feedback"]["warning"] if "warning" in result["feedback"] else ""
diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py
index e2be2656a9..a90950f411 100644
--- a/frappe/desk/desktop.py
+++ b/frappe/desk/desktop.py
@@ -40,7 +40,6 @@ class Workspace:
self.allowed_modules = self.get_cached("user_allowed_modules", self.get_allowed_modules)
self.doc = frappe.get_cached_doc("Workspace", self.page_name)
-
if (
self.doc
and self.doc.module
diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py
index 2a987f5539..77d40f44d2 100644
--- a/frappe/desk/notifications.py
+++ b/frappe/desk/notifications.py
@@ -3,10 +3,19 @@
import json
+from bs4 import BeautifulSoup
+
import frappe
+from frappe import _
+from frappe.desk.doctype.notification_log.notification_log import (
+ enqueue_create_notification,
+ get_title,
+ get_title_html,
+)
from frappe.desk.doctype.notification_settings.notification_settings import (
get_subscribed_documents,
)
+from frappe.utils import get_fullname
@frappe.whitelist()
@@ -298,3 +307,56 @@ def get_open_count(doctype, name, items=None):
out["timeline_data"] = module.get_timeline_data(doctype, name)
return out
+
+
+def notify_mentions(ref_doctype, ref_name, content):
+ if ref_doctype and ref_name and content:
+ mentions = extract_mentions(content)
+
+ if not mentions:
+ return
+
+ sender_fullname = get_fullname(frappe.session.user)
+ title = get_title(ref_doctype, ref_name)
+
+ recipients = [
+ frappe.db.get_value(
+ "User",
+ {"enabled": 1, "name": name, "user_type": "System User", "allowed_in_mentions": 1},
+ "email",
+ )
+ for name in mentions
+ ]
+
+ notification_message = _("""{0} mentioned you in a comment in {1} {2}""").format(
+ frappe.bold(sender_fullname), frappe.bold(ref_doctype), get_title_html(title)
+ )
+
+ notification_doc = {
+ "type": "Mention",
+ "document_type": ref_doctype,
+ "document_name": ref_name,
+ "subject": notification_message,
+ "from_user": frappe.session.user,
+ "email_content": content,
+ }
+
+ enqueue_create_notification(recipients, notification_doc)
+
+
+def extract_mentions(txt):
+ """Find all instances of @mentions in the html."""
+ soup = BeautifulSoup(txt, "html.parser")
+ emails = []
+ for mention in soup.find_all(class_="mention"):
+ if mention.get("data-is-group") == "true":
+ try:
+ user_group = frappe.get_cached_doc("User Group", mention["data-id"])
+ emails += [d.user for d in user_group.user_group_members]
+ except frappe.DoesNotExistError:
+ pass
+ continue
+ email = mention["data-id"]
+ emails.append(email)
+
+ return emails
diff --git a/frappe/public/js/frappe/form/controls/comment.js b/frappe/public/js/frappe/form/controls/comment.js
index 4550d7045f..d4927dc0cd 100644
--- a/frappe/public/js/frappe/form/controls/comment.js
+++ b/frappe/public/js/frappe/form/controls/comment.js
@@ -71,36 +71,10 @@ frappe.ui.form.ControlComment = class ControlComment extends frappe.ui.form.Cont
const options = super.get_quill_options();
return Object.assign(options, {
theme: 'bubble',
- bounds: this.quill_container[0],
- modules: Object.assign(options.modules, {
- mention: this.get_mention_options()
- })
+ bounds: this.quill_container[0]
});
}
- get_mention_options() {
- if (!this.enable_mentions) {
- return null;
- }
- let me = this;
- return {
- allowedChars: /^[A-Za-z0-9_]*$/,
- mentionDenotationChars: ["@"],
- isolateCharacter: true,
- source: frappe.utils.debounce(async function(search_term, renderList) {
- let method = me.mention_search_method || 'frappe.desk.search.get_names_for_mentions';
- let values = await frappe.xcall(method, {
- search_term
- });
- renderList(values, search_term);
- }, 300),
- renderItem(item) {
- let value = item.value;
- return `${value} ${item.is_group ? frappe.utils.icon('users') : ''}`;
- }
- };
- }
-
get_toolbar_options() {
return [
['bold', 'italic', 'underline', 'strike'],
diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js
index e4e1fff18a..50b625f248 100644
--- a/frappe/public/js/frappe/form/controls/text_editor.js
+++ b/frappe/public/js/frappe/form/controls/text_editor.js
@@ -174,9 +174,33 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
toolbar: this.get_toolbar_options(),
table: true,
imageResize: {},
- magicUrl: true
+ magicUrl: true,
+ mention: this.get_mention_options()
},
- theme: 'snow'
+ theme: 'snow',
+ };
+ }
+
+ get_mention_options() {
+ if (!this.enable_mentions && !this.df.enable_mentions) {
+ return null;
+ }
+ let me = this;
+ return {
+ allowedChars: /^[A-Za-z0-9_]*$/,
+ mentionDenotationChars: ["@"],
+ isolateCharacter: true,
+ source: frappe.utils.debounce(async function(search_term, renderList) {
+ let method = me.mention_search_method || 'frappe.desk.search.get_names_for_mentions';
+ let values = await frappe.xcall(method, {
+ search_term
+ });
+ renderList(values, search_term);
+ }, 300),
+ renderItem(item) {
+ let value = item.value;
+ return `${value} ${item.is_group ? frappe.utils.icon('users') : ''}`;
+ }
};
}
From 4c877258f0cf512f530de140a0d9481b1870821f Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 14:57:32 +0530
Subject: [PATCH 0164/2449] test: add tests for virtual doctype desk
interactions
---
frappe/core/doctype/test/test.py | 63 ++++++++++++++++++------
frappe/core/doctype/test/test_test.py | 70 ++++++++++++++++++++++++---
frappe/model/virtual_doctype.py | 10 +++-
3 files changed, 121 insertions(+), 22 deletions(-)
diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py
index e17b3a0a4a..ffb4becb10 100644
--- a/frappe/core/doctype/test/test.py
+++ b/frappe/core/doctype/test/test.py
@@ -1,39 +1,74 @@
# Copyright (c) 2021, Frappe Technologies and contributors
# License: MIT. See LICENSE
+""" This is a virtual doctype controller for test/demo purposes.
+
+- It uses a JSON file on disk as "backend".
+- All docs are stored in "docs" key of JSON file.
+"""
import json
+import os
+from typing import Any, TypedDict
import frappe
from frappe.model.document import Document
+DATA_FILE = "data_file.json"
+
+
+class Data(TypedDict):
+ docs: dict[str, dict[str, Any]]
+
+
+def get_current_data() -> Data:
+ """Read data from disk"""
+ if not os.path.exists(DATA_FILE):
+ return {"docs": {}}
+
+ with open(DATA_FILE) as f:
+ return json.load(f)
+
+
+def update_data(data: Data) -> None:
+ """Flush updated data to disk"""
+ with open(DATA_FILE, "w+") as data_file:
+ json.dump(data, data_file)
+
class test(Document):
def db_insert(self, *args, **kwargs):
d = self.get_valid_dict(convert_dates_to_str=True)
- with open("data_file.json", "w+") as read_file:
- json.dump(d, read_file)
+
+ data = get_current_data()
+ data["docs"][d.name] = d
+
+ update_data(data)
def load_from_db(self):
- with open("data_file.json") as read_file:
- d = json.load(read_file)
- super(Document, self).__init__(d)
+ data = get_current_data()
+ d = data["docs"].get(self.name)
+ super(Document, self).__init__(d)
def db_update(self, *args, **kwargs):
- d = self.get_valid_dict(convert_dates_to_str=True)
- with open("data_file.json", "w+") as read_file:
- json.dump(d, read_file)
+ # For this example insert and update are same operation,
+ # it might be different for you
+ self.db_insert(*args, **kwargs)
+
+ def delete(self):
+ data = get_current_data()
+ data["docs"].pop(self.name, None)
+ update_data(data)
@staticmethod
def get_list(args):
- with open("data_file.json") as read_file:
- return [frappe._dict(json.load(read_file))]
+ data = get_current_data()
+ return [frappe._dict(doc) for name, doc in data["docs"].items()]
@staticmethod
def get_count(args):
- return 5
+ data = get_current_data()
+ return len(data["docs"])
@staticmethod
def get_stats(args):
- # return []
- with open("data_file.json") as read_file:
- return [json.load(read_file)]
+ return {}
diff --git a/frappe/core/doctype/test/test_test.py b/frappe/core/doctype/test/test_test.py
index 6080c200c1..08bb8b2b84 100644
--- a/frappe/core/doctype/test/test_test.py
+++ b/frappe/core/doctype/test/test_test.py
@@ -1,8 +1,66 @@
-# Copyright (c) 2021, Frappe Technologies and Contributors
-# License: MIT. See LICENSE
-# import frappe
-import unittest
+import json
+import os
+
+import frappe
+from frappe.core.doctype.test.test import DATA_FILE
+from frappe.core.doctype.test.test import test as VirtDocType
+from frappe.desk.form.save import savedocs
+from frappe.tests.utils import FrappeTestCase
-class Testtest(unittest.TestCase):
- pass
+class Testtest(FrappeTestCase):
+ def tearDown(self):
+ if os.path.exists(DATA_FILE):
+ os.remove(DATA_FILE)
+
+ def test_insert_update_and_load_from_desk(self):
+ """Insert, update, reload and assert changes"""
+
+ frappe.response.docs = []
+ doc = json.dumps(
+ {
+ "docstatus": 0,
+ "doctype": "test",
+ "name": "new-test-1",
+ "__islocal": 1,
+ "__unsaved": 1,
+ "owner": "Administrator",
+ "test": "Original Data",
+ }
+ )
+ savedocs(doc, "Save")
+
+ docname = frappe.response.docs[0]["name"]
+
+ doc = frappe.get_doc("test", docname)
+ doc.test = "New Data"
+
+ savedocs(doc.as_json(), "Save")
+
+ doc.reload()
+ self.assertEqual(doc.test, "New Data")
+
+ def test_multiple_doc_insert_and_get_list(self):
+ doc1 = frappe.get_doc(doctype="test", test="first").insert()
+ doc2 = frappe.get_doc(doctype="test", test="second").insert()
+
+ docs = {doc1.name, doc2.name}
+
+ doc2.reload()
+ doc1.reload()
+ updated_docs = {doc1.name, doc2.name}
+ self.assertEqual(docs, updated_docs)
+
+ listed_docs = {d.name for d in VirtDocType.get_list({})}
+ self.assertEqual(docs, listed_docs)
+
+ def test_get_count(self):
+ args = {"doctype": "test", "filters": [], "fields": []}
+ self.assertIsInstance(VirtDocType.get_count(args), int)
+
+ def test_delete_doc(self):
+ doc = frappe.get_doc(doctype="test", test="data").insert()
+ doc.delete()
+
+ listed_docs = {d.name for d in VirtDocType.get_list({})}
+ self.assertNotIn(doc.name, listed_docs)
diff --git a/frappe/model/virtual_doctype.py b/frappe/model/virtual_doctype.py
index a21d88e605..dc228f9577 100644
--- a/frappe/model/virtual_doctype.py
+++ b/frappe/model/virtual_doctype.py
@@ -1,5 +1,7 @@
from typing import Protocol
+import frappe
+
class VirtualDoctype(Protocol):
"""This class documents requirements that must be met by a doctype controller to function as virtual doctype
@@ -15,7 +17,7 @@ class VirtualDoctype(Protocol):
# ============ class/static methods ============
@staticmethod
- def get_list(args):
+ def get_list(args) -> list[frappe._dict]:
"""Similar to reportview.get_list"""
...
@@ -41,6 +43,10 @@ class VirtualDoctype(Protocol):
This is responsible for updatinng __dict__ of class with all the fields on doctype."""
...
- def db_update(self, *args, **kwargs):
+ def db_update(self, *args, **kwargs) -> None:
"""Serialize the `Document` object and update existing document in backend."""
...
+
+ def delete(self, *args, **kwargs) -> None:
+ """Delete the current document from backend"""
+ ...
From d83712d553e313682bcd1043dc320a85418fc9cd Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 15:42:04 +0530
Subject: [PATCH 0165/2449] feat: delete support for virtual doctypes from desk
---
frappe/core/doctype/test/test_test.py | 3 ++-
frappe/model/delete_doc.py | 6 ++++++
2 files changed, 8 insertions(+), 1 deletion(-)
diff --git a/frappe/core/doctype/test/test_test.py b/frappe/core/doctype/test/test_test.py
index 08bb8b2b84..70ba1fd1e1 100644
--- a/frappe/core/doctype/test/test_test.py
+++ b/frappe/core/doctype/test/test_test.py
@@ -60,7 +60,8 @@ class Testtest(FrappeTestCase):
def test_delete_doc(self):
doc = frappe.get_doc(doctype="test", test="data").insert()
- doc.delete()
+
+ frappe.delete_doc(doc.doctype, doc.name)
listed_docs = {d.name for d in VirtDocType.get_list({})}
self.assertNotIn(doc.name, listed_docs)
diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py
index b555dfc5dc..332a4337e2 100644
--- a/frappe/model/delete_doc.py
+++ b/frappe/model/delete_doc.py
@@ -11,6 +11,7 @@ from frappe import _, get_module_path
from frappe.desk.doctype.tag.tag import delete_tags_for_document
from frappe.model.dynamic_links import get_dynamic_link_map
from frappe.model.naming import revert_series_if_last
+from frappe.model.utils import is_virtual_doctype
from frappe.utils.file_manager import remove_all
from frappe.utils.global_search import delete_for_document
from frappe.utils.password import delete_all_passwords_for
@@ -57,11 +58,16 @@ def delete_doc(
doctype = frappe.form_dict.get("dt")
name = frappe.form_dict.get("dn")
+ is_virtual = is_virtual_doctype(doctype)
+
names = name
if isinstance(name, str) or isinstance(name, int):
names = [name]
for name in names or []:
+ if is_virtual:
+ frappe.get_doc(doctype, name).delete()
+ continue
# already deleted..?
if not frappe.db.exists(doctype, name):
From e35671203c4cb35813bb3511b22cea89c3044a14 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 16:15:16 +0530
Subject: [PATCH 0166/2449] refactor: simplify virtual doctype example
---
frappe/core/doctype/test/test.py | 29 +++++++++++++++--------------
1 file changed, 15 insertions(+), 14 deletions(-)
diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py
index ffb4becb10..0219fd10ab 100644
--- a/frappe/core/doctype/test/test.py
+++ b/frappe/core/doctype/test/test.py
@@ -4,11 +4,16 @@
""" This is a virtual doctype controller for test/demo purposes.
- It uses a JSON file on disk as "backend".
-- All docs are stored in "docs" key of JSON file.
+- Key is docname and value is the document itself.
+
+Example:
+{
+ "doc1": {"name": "doc1", ...}
+ "doc2": {"name": "doc2", ...}
+}
"""
import json
import os
-from typing import Any, TypedDict
import frappe
from frappe.model.document import Document
@@ -16,20 +21,16 @@ from frappe.model.document import Document
DATA_FILE = "data_file.json"
-class Data(TypedDict):
- docs: dict[str, dict[str, Any]]
-
-
-def get_current_data() -> Data:
+def get_current_data() -> dict[str, dict]:
"""Read data from disk"""
if not os.path.exists(DATA_FILE):
- return {"docs": {}}
+ return {}
with open(DATA_FILE) as f:
return json.load(f)
-def update_data(data: Data) -> None:
+def update_data(data: dict[str, dict]) -> None:
"""Flush updated data to disk"""
with open(DATA_FILE, "w+") as data_file:
json.dump(data, data_file)
@@ -40,13 +41,13 @@ class test(Document):
d = self.get_valid_dict(convert_dates_to_str=True)
data = get_current_data()
- data["docs"][d.name] = d
+ data[d.name] = d
update_data(data)
def load_from_db(self):
data = get_current_data()
- d = data["docs"].get(self.name)
+ d = data.get(self.name)
super(Document, self).__init__(d)
def db_update(self, *args, **kwargs):
@@ -56,18 +57,18 @@ class test(Document):
def delete(self):
data = get_current_data()
- data["docs"].pop(self.name, None)
+ data.pop(self.name, None)
update_data(data)
@staticmethod
def get_list(args):
data = get_current_data()
- return [frappe._dict(doc) for name, doc in data["docs"].items()]
+ return [frappe._dict(doc) for name, doc in data.items()]
@staticmethod
def get_count(args):
data = get_current_data()
- return len(data["docs"])
+ return len(data)
@staticmethod
def get_stats(args):
From 0f83e9e944686353a1ff6f34c5914581de007e8d Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 16:24:02 +0530
Subject: [PATCH 0167/2449] fix: correct import path
Co-authored-by: gavin
---
frappe/core/doctype/comment/comment.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py
index 4cd15dc815..625933791d 100644
--- a/frappe/core/doctype/comment/comment.py
+++ b/frappe/core/doctype/comment/comment.py
@@ -11,7 +11,7 @@ from frappe.desk.doctype.notification_log.notification_log import (
get_title,
get_title_html,
)
-from frappe.desk.reportview import is_virtual_doctype
+from frappe.model.utils import is_virtual_doctype
from frappe.exceptions import ImplicitCommitError
from frappe.model.document import Document
from frappe.utils import get_fullname
From 0285d4f686bf5233b0222a70ca9d446925021758 Mon Sep 17 00:00:00 2001
From: Shridhar Patil
Date: Fri, 22 Jul 2022 18:49:38 +0530
Subject: [PATCH 0168/2449] fix: frappe.db.exists will always return error for
virtual doctype (#17595)
So first check whether the doctype is virutal
---
frappe/desk/form/load.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index 167fab4a07..d68aab927a 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -31,7 +31,7 @@ def getdoc(doctype, name, user=None):
if not name:
name = doctype
- if not frappe.db.exists(doctype, name) and not is_virtual_doctype(doctype):
+ if not is_virtual_doctype(doctype) and not frappe.db.exists(doctype, name):
return []
doc = frappe.get_doc(doctype, name)
From d11fdc3ca4ccde16137281878ccc416be76d9f2b Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 16:32:45 +0530
Subject: [PATCH 0169/2449] refactor: desk missing doc handling
- reduce 1 query
- make normal and virtual doc behave in same manner
---
frappe/desk/form/load.py | 5 +++--
1 file changed, 3 insertions(+), 2 deletions(-)
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index d68aab927a..5c1e2003ec 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -31,10 +31,11 @@ def getdoc(doctype, name, user=None):
if not name:
name = doctype
- if not is_virtual_doctype(doctype) and not frappe.db.exists(doctype, name):
+ try:
+ doc = frappe.get_doc(doctype, name)
+ except frappe.DoesNotExistError:
return []
- doc = frappe.get_doc(doctype, name)
run_onload(doc)
if not doc.has_permission("read"):
From 615f0dbcb814781f3e2ef13327f20afa8f83b839 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 22 Jul 2022 19:24:34 +0530
Subject: [PATCH 0170/2449] Revert "refactor: desk missing doc handling"
This reverts commit d11fdc3ca4ccde16137281878ccc416be76d9f2b.
---
frappe/desk/form/load.py | 5 ++---
1 file changed, 2 insertions(+), 3 deletions(-)
diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py
index 5c1e2003ec..d68aab927a 100644
--- a/frappe/desk/form/load.py
+++ b/frappe/desk/form/load.py
@@ -31,11 +31,10 @@ def getdoc(doctype, name, user=None):
if not name:
name = doctype
- try:
- doc = frappe.get_doc(doctype, name)
- except frappe.DoesNotExistError:
+ if not is_virtual_doctype(doctype) and not frappe.db.exists(doctype, name):
return []
+ doc = frappe.get_doc(doctype, name)
run_onload(doc)
if not doc.has_permission("read"):
From f2ada4630c36b18f8d25762551e2d5bb7a64330a Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sat, 23 Jul 2022 17:18:43 +0530
Subject: [PATCH 0171/2449] fix: removed exessive quotes from query
---
frappe/database/query.py | 8 +++++---
frappe/model/db_query.py | 2 +-
2 files changed, 6 insertions(+), 4 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index 8421d417f3..29838d9486 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -3,7 +3,7 @@ import re
from ast import literal_eval
from functools import cached_property
from types import BuiltinFunctionType
-from typing import TYPE_CHECKING, Any, Callable
+from typing import Any, Callable
import frappe
from frappe import _
@@ -465,7 +465,6 @@ class Engine:
is_list = False
if is_list:
- fields = [field.replace("""`""", "") for field in fields]
function_objects += self.function_objects_from_list(fields=fields)
is_str = isinstance(fields, str)
@@ -514,6 +513,7 @@ class Engine:
table: str,
fields: list | tuple,
filters: dict[str, str | int] | str | int | list[list | str | int] = None,
+ run: bool = False,
**kwargs,
):
# Clean up state before each query
@@ -539,7 +539,9 @@ class Engine:
else:
query = criterion.select(fields)
-
+ query = str(query).replace("``", "`")
+ if run:
+ return frappe.db.sql(query, **kwargs)
return query
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 83ecc6bcf3..2fff70cd6e 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -210,7 +210,7 @@ class DatabaseQuery:
% args
)
- if self.locals.get("ignore_permissions"):
+ if self.locals.get("ignore_permissions") and ("." not in self.qb_fields):
return frappe.qb.engine.get_query(
table=self.doctype,
fields=self.qb_fields,
From c38f79edd60d04fb21f26b341d70cf13e41d8aae Mon Sep 17 00:00:00 2001
From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com>
Date: Sat, 23 Jul 2022 19:23:05 +0530
Subject: [PATCH 0172/2449] style: Fix import order
---
frappe/core/doctype/comment/comment.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py
index 36fcbe9bdd..02a5d2d58e 100644
--- a/frappe/core/doctype/comment/comment.py
+++ b/frappe/core/doctype/comment/comment.py
@@ -5,9 +5,9 @@ import json
import frappe
from frappe.database.schema import add_column
from frappe.desk.notifications import notify_mentions
-from frappe.model.utils import is_virtual_doctype
from frappe.exceptions import ImplicitCommitError
from frappe.model.document import Document
+from frappe.model.utils import is_virtual_doctype
from frappe.website.utils import clear_cache
From a3ae6794eced49d8837bd636fe4174b9f2f754f6 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 01:25:38 +0530
Subject: [PATCH 0173/2449] fix: fixed PseudoColumn fields
---
frappe/database/query.py | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index 29838d9486..ba0789f545 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -10,6 +10,7 @@ from frappe import _
from frappe.model.db_query import get_timespan_date_range
from frappe.query_builder import Criterion, Field, Order, Table, functions
from frappe.query_builder.functions import Function, SqlFunctions
+from frappe.query_builder.utils import PseudoColumn
TAB_PATTERN = re.compile("^tab")
WORDS_PATTERN = re.compile(r"\w+")
@@ -458,7 +459,6 @@ class Engine:
return None
function_objects = []
-
is_list = isinstance(fields, (list, tuple, set))
if is_list and len(fields) == 1:
fields = fields[0]
@@ -496,6 +496,8 @@ class Engine:
if " as " in field:
field, reference = field.split(" as ")
updated_fields.append(Field(field.strip()).as_(reference))
+ elif "`.`" in str(field):
+ updated_fields.append(PseudoColumn(field.strip()))
else:
updated_fields.append(Field(field))
@@ -539,9 +541,7 @@ class Engine:
else:
query = criterion.select(fields)
- query = str(query).replace("``", "`")
- if run:
- return frappe.db.sql(query, **kwargs)
+
return query
From 9348e1cd980f4eda9009369fac04da5291679a70 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 01:30:44 +0530
Subject: [PATCH 0174/2449] fix: removed eccess "`"
---
frappe/integrations/doctype/webhook/__init__.py | 2 +-
frappe/model/db_query.py | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/integrations/doctype/webhook/__init__.py b/frappe/integrations/doctype/webhook/__init__.py
index 192cd2fa12..b9c96190ca 100644
--- a/frappe/integrations/doctype/webhook/__init__.py
+++ b/frappe/integrations/doctype/webhook/__init__.py
@@ -25,7 +25,7 @@ def run_webhooks(doc, method):
# query webhooks
webhooks_list = frappe.get_all(
"Webhook",
- fields=["name", "`condition`", "webhook_docevent", "webhook_doctype"],
+ fields=["name", "condition", "webhook_docevent", "webhook_doctype"],
filters={"enabled": True},
)
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index 2fff70cd6e..83ecc6bcf3 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -210,7 +210,7 @@ class DatabaseQuery:
% args
)
- if self.locals.get("ignore_permissions") and ("." not in self.qb_fields):
+ if self.locals.get("ignore_permissions"):
return frappe.qb.engine.get_query(
table=self.doctype,
fields=self.qb_fields,
From 3802d83900cb803ca0e53dbfa75f765f3fe00090 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 01:39:53 +0530
Subject: [PATCH 0175/2449] test: Added test for "``" query
---
frappe/tests/test_query.py | 11 +++++++++++
1 file changed, 11 insertions(+)
diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py
index 8afcaf07d0..5fae35623d 100644
--- a/frappe/tests/test_query.py
+++ b/frappe/tests/test_query.py
@@ -4,6 +4,7 @@ import frappe
from frappe.query_builder import Field
from frappe.query_builder.functions import Abs, Count, Max, Timestamp
from frappe.tests.test_query_builder import db_type_is, run_only_if
+from frappe.query_builder.utils import PseudoColumn
class TestQuery(unittest.TestCase):
@@ -42,6 +43,16 @@ class TestQuery(unittest.TestCase):
.get_sql(),
)
+ self.assertEqual(
+ frappe.qb.engine.get_query(
+ "User", fields=["`tabUser`.`name`", "`tabUser`.`email`"], filters={"name": "Administrator"}
+ ).run(),
+ frappe.qb.from_("User")
+ .select(Field("name"), Field("email"))
+ .where(Field("name") == "Administrator")
+ .run()
+ )
+
def test_functions_fields(self):
self.assertEqual(
frappe.qb.engine.get_query("User", fields="Count(name)", filters={}).get_sql(),
From 3d6247efbd0ddba8170386a59fe121086890cdb5 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 14:22:57 +0530
Subject: [PATCH 0176/2449] refactor: removed unnecesary pseudocolumns
---
.../permitted_documents_for_user.py | 2 +-
frappe/share.py | 12 ++++++------
frappe/tests/test_query.py | 3 +--
3 files changed, 8 insertions(+), 9 deletions(-)
diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py
index 362cc6b105..b8d7ca2a97 100644
--- a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py
+++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py
@@ -38,7 +38,7 @@ def validate(user, doctype):
def get_columns_and_fields(doctype):
columns = [f"Name:Link/{doctype}:200"]
- fields = ["`name`"]
+ fields = ["name"]
for df in frappe.get_meta(doctype).fields:
if df.in_list_view and df.fieldtype in data_fieldtypes:
fields.append(f"`{df.fieldname}`")
diff --git a/frappe/share.py b/frappe/share.py
index dfb4836995..7f7ca2033f 100644
--- a/frappe/share.py
+++ b/frappe/share.py
@@ -104,12 +104,12 @@ def get_users(doctype, name):
return frappe.db.get_all(
"DocShare",
fields=[
- "`name`",
- "`user`",
- "`read`",
- "`write`",
- "`submit`",
- "`share`",
+ "name", # Don't understant the need for pseudocolumns here, don't know why get_all supports it?
+ "user",
+ "read",
+ "write",
+ "submit",
+ "share",
"everyone",
"owner",
"creation",
diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py
index 5fae35623d..7b9a307e04 100644
--- a/frappe/tests/test_query.py
+++ b/frappe/tests/test_query.py
@@ -4,7 +4,6 @@ import frappe
from frappe.query_builder import Field
from frappe.query_builder.functions import Abs, Count, Max, Timestamp
from frappe.tests.test_query_builder import db_type_is, run_only_if
-from frappe.query_builder.utils import PseudoColumn
class TestQuery(unittest.TestCase):
@@ -50,7 +49,7 @@ class TestQuery(unittest.TestCase):
frappe.qb.from_("User")
.select(Field("name"), Field("email"))
.where(Field("name") == "Administrator")
- .run()
+ .run(),
)
def test_functions_fields(self):
From 69089d3fd523655467515bae5b7e4bcd6291634c Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 14:47:51 +0530
Subject: [PATCH 0177/2449] refactor: removed unnecesary pseudocolumns
---
frappe/workflow/doctype/workflow_action/workflow_action.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py
index 038a3021d2..545ad6ec77 100644
--- a/frappe/workflow/doctype/workflow_action/workflow_action.py
+++ b/frappe/workflow/doctype/workflow_action/workflow_action.py
@@ -290,7 +290,7 @@ def update_completed_workflow_actions_using_user(doc, user=None):
def get_next_possible_transitions(workflow_name, state, doc=None):
transitions = frappe.get_all(
"Workflow Transition",
- fields=["allowed", "action", "state", "allow_self_approval", "next_state", "`condition`"],
+ fields=["allowed", "action", "state", "allow_self_approval", "next_state", "condition"],
filters=[["parent", "=", workflow_name], ["state", "=", state]],
)
From a7d74266d2b177512c833799c8f74e3e25a3198c Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 15:40:48 +0530
Subject: [PATCH 0178/2449] feat: Flexible pseudocolumns
---
frappe/database/query.py | 2 +-
frappe/tests/test_query.py | 3 +--
2 files changed, 2 insertions(+), 3 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index e4738d3a0c..e9e5521a9f 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -491,7 +491,7 @@ class Engine:
if " as " in field:
field, reference = field.split(" as ")
updated_fields.append(Field(field.strip()).as_(reference))
- elif "`.`" in str(field):
+ elif "`" in str(field):
updated_fields.append(PseudoColumn(field.strip()))
else:
updated_fields.append(Field(field))
diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py
index 7b9a307e04..be5fcc33ac 100644
--- a/frappe/tests/test_query.py
+++ b/frappe/tests/test_query.py
@@ -31,10 +31,9 @@ class TestQuery(unittest.TestCase):
.where(Field("name") == "Administrator")
.get_sql(),
)
-
self.assertEqual(
frappe.qb.engine.get_query(
- "User", fields=["name, email"], filters={"name": "Administrator"}
+ "User", fields=["`name`, `email`"], filters={"name": "Administrator"}
).get_sql(),
frappe.qb.from_("User")
.select(Field("name"), Field("email"))
From 1a74d19bade10fba015daad2dad1a4293447e950 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 16:56:47 +0530
Subject: [PATCH 0179/2449] feat: Added support for pseudocolumns in functions
---
frappe/database/query.py | 13 ++++++++-----
1 file changed, 8 insertions(+), 5 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index e9e5521a9f..846507896f 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -398,7 +398,11 @@ class Engine:
*map(lambda field: Field(field.strip()), arg.split(_operator)),
)
- field = Field(initial_fields) if not has_primitive_operator else field
+ field = (
+ (Field(initial_fields) if "`" not in initial_fields else PseudoColumn(initial_fields))
+ if not has_primitive_operator
+ else field
+ )
else:
field = initial_fields
@@ -416,7 +420,7 @@ class Engine:
def function_objects_from_list(self, fields):
functions = []
for field in fields:
- field = field.casefold() if isinstance(field, str) else field
+ field = field.casefold() if (isinstance(field, str) and "`" not in field) else field
if not issubclass(type(field), Criterion):
if any([f"{func}(" in field for func in SQL_FUNCTIONS]) or "(" in field:
functions.append(field)
@@ -429,7 +433,7 @@ class Engine:
if isinstance(fields, str):
if function.alias:
fields = fields.replace(" as " + function.alias.casefold(), "")
- fields = BRACKETS_PATTERN.sub("", fields.replace(function.name.casefold(), ""))
+ fields = BRACKETS_PATTERN.sub("", fields.casefold().replace(function.name.casefold(), ""))
# Check if only comma is left in fields after stripping functions.
if "," in fields and (len(fields.strip()) == 1):
fields = ""
@@ -464,7 +468,7 @@ class Engine:
is_str = isinstance(fields, str)
if is_str:
- fields = fields.casefold()
+ fields = fields.casefold() if "`" not in fields else fields
function_objects += self.function_objects_from_string(fields=fields)
fields = self.remove_string_functions(fields, function_objects)
@@ -510,7 +514,6 @@ class Engine:
table: str,
fields: list | tuple,
filters: dict[str, str | int] | str | int | list[list | str | int] = None,
- run: bool = False,
**kwargs,
):
# Clean up state before each query
From 4779443466eb814f1ea4eee98dfa990e98d6e643 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Sun, 24 Jul 2022 22:53:24 +0530
Subject: [PATCH 0180/2449] feat: Added support for aliasing in PseudoColumns
---
frappe/database/query.py | 11 ++++++++---
1 file changed, 8 insertions(+), 3 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index 846507896f..570c3e6a63 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -480,9 +480,14 @@ class Engine:
if is_str:
if fields == "*":
return fields
- if " as " in fields:
- fields, reference = fields.split(" as ")
- fields = Field(fields).as_(reference)
+ if "`" in fields:
+ fields = PseudoColumn(fields)
+ if " as " in str(fields):
+ fields, reference = str(fields).split(" as ")
+ if "`" in str(fields):
+ fields = PseudoColumn(f"{fields} as {reference}")
+ else:
+ fields = Field(fields).as_(reference)
if not is_str and fields:
if issubclass(type(fields), Criterion):
From d23705f60ccf142e40448d3a4f267daccd0b3387 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 12:50:49 +0530
Subject: [PATCH 0181/2449] ci: Cleanup install.sh
---
.github/helper/install.sh | 66 ++++++++++++++++++++++-----------------
1 file changed, 37 insertions(+), 29 deletions(-)
diff --git a/.github/helper/install.sh b/.github/helper/install.sh
index 21d4a7b972..1a2c62c973 100644
--- a/.github/helper/install.sh
+++ b/.github/helper/install.sh
@@ -5,55 +5,63 @@ cd ~ || exit
echo "Setting Up Bench..."
pip install frappe-bench
-
bench -v init frappe-bench --skip-assets --python "$(which python)" --frappe-path "${GITHUB_WORKSPACE}"
+cd ./frappe-bench || exit
+
+bench -v setup requirements --dev
+if [ "$TYPE" == "ui" ]; then
+ bench -v setup requirements --node;
+fi
+
+echo "Setting Up Sites & Database..."
mkdir ~/frappe-bench/sites/test_site
cp "${GITHUB_WORKSPACE}/.github/helper/consumer_db/$DB.json" ~/frappe-bench/sites/test_site/site_config.json
if [ "$TYPE" == "server" ]; then
- mkdir ~/frappe-bench/sites/test_site_producer;
- cp "${GITHUB_WORKSPACE}/.github/helper/producer_db/$DB.json" ~/frappe-bench/sites/test_site_producer/site_config.json;
+ mkdir ~/frappe-bench/sites/test_site_producer;
+ cp "${GITHUB_WORKSPACE}/.github/helper/producer_db/$DB.json" ~/frappe-bench/sites/test_site_producer/site_config.json;
fi
-
if [ "$DB" == "mariadb" ];then
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL character_set_server = 'utf8mb4'";
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL character_set_server = 'utf8mb4'";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "SET GLOBAL collation_server = 'utf8mb4_unicode_ci'";
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_consumer";
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_consumer'@'localhost' IDENTIFIED BY 'test_frappe_consumer'";
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_consumer\`.* TO 'test_frappe_consumer'@'localhost'";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_consumer";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_consumer'@'localhost' IDENTIFIED BY 'test_frappe_consumer'";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_consumer\`.* TO 'test_frappe_consumer'@'localhost'";
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_producer";
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_producer'@'localhost' IDENTIFIED BY 'test_frappe_producer'";
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_producer\`.* TO 'test_frappe_producer'@'localhost'";
-
- mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "FLUSH PRIVILEGES";
- fi
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE DATABASE test_frappe_producer";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "CREATE USER 'test_frappe_producer'@'localhost' IDENTIFIED BY 'test_frappe_producer'";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "GRANT ALL PRIVILEGES ON \`test_frappe_producer\`.* TO 'test_frappe_producer'@'localhost'";
+ mariadb --host 127.0.0.1 --port 3306 -u root -ptravis -e "FLUSH PRIVILEGES";
+fi
if [ "$DB" == "postgres" ];then
- echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_consumer" -U postgres;
- echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_consumer WITH PASSWORD 'test_frappe'" -U postgres;
+ echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_consumer" -U postgres;
+ echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_consumer WITH PASSWORD 'test_frappe'" -U postgres;
- echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_producer" -U postgres;
- echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_producer WITH PASSWORD 'test_frappe'" -U postgres;
+ echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE DATABASE test_frappe_producer" -U postgres;
+ echo "travis" | psql -h 127.0.0.1 -p 5432 -c "CREATE USER test_frappe_producer WITH PASSWORD 'test_frappe'" -U postgres;
fi
-cd ./frappe-bench || exit
+echo "Setting Up Procfile..."
sed -i 's/^watch:/# watch:/g' Procfile
sed -i 's/^schedule:/# schedule:/g' Procfile
+if [ "$TYPE" == "server" ]; then
+ sed -i 's/^socketio:/# socketio:/g' Procfile;
+ sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile;
+fi
+if [ "$TYPE" == "ui" ]; then
+ sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile;
+fi
-if [ "$TYPE" == "server" ]; then sed -i 's/^socketio:/# socketio:/g' Procfile; fi
-if [ "$TYPE" == "server" ]; then sed -i 's/^redis_socketio:/# redis_socketio:/g' Procfile; fi
-
-if [ "$TYPE" == "ui" ]; then bench -v setup requirements --node; fi
-bench -v setup requirements --dev
-
-if [ "$TYPE" == "ui" ]; then sed -i 's/^web: bench serve/web: bench serve --with-coverage/g' Procfile; fi
+echo "Starting Bench..."
bench start &> bench_start.log &
bench --site test_site reinstall --yes
-if [ "$TYPE" == "server" ]; then bench --site test_site_producer reinstall --yes; fi
-if [ "$TYPE" == "server" ]; then CI=Yes bench build --app frappe; fi
+if [ "$TYPE" == "server" ]; then
+ bench --site test_site_producer reinstall --yes;
+ CI=Yes bench build --app frappe;
+fi
From 31658e5241b7822bd42a73fdd393c91531f47619 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 13:07:53 +0530
Subject: [PATCH 0182/2449] ci: Install wkhtmltopdf in the background
---
.github/helper/install_dependencies.sh | 7 +++++--
1 file changed, 5 insertions(+), 2 deletions(-)
diff --git a/.github/helper/install_dependencies.sh b/.github/helper/install_dependencies.sh
index f16bd61a53..694b0a9504 100644
--- a/.github/helper/install_dependencies.sh
+++ b/.github/helper/install_dependencies.sh
@@ -3,8 +3,11 @@ set -e
echo "Setting Up System Dependencies..."
-wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
-sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
+install_wkhtmltopdf() {
+ wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox_0.12.6-1.focal_amd64.deb
+ sudo apt install ./wkhtmltox_0.12.6-1.focal_amd64.deb
+}
+install_wkhtmltopdf &
curl -LsS -O https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
sudo bash mariadb_repo_setup --mariadb-server-version=10.6
From 4289e7c7ea24757b51a966a98d6753af00c1acff Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 14:33:30 +0530
Subject: [PATCH 0183/2449] ci: Merge PR linter checks
* Combine jobs to be triggered at pull_request events: commit-lint,
docs-required, linter
* PY310-ize helper scripts
---
.github/helper/documentation.py | 2 +-
.github/helper/translation.py | 15 ++-----
.github/workflows/docs-checker.yml | 28 -------------
.github/workflows/linters.yml | 57 ++++++++++++++++++++++----
.github/workflows/semantic-commits.yml | 30 --------------
5 files changed, 55 insertions(+), 77 deletions(-)
delete mode 100644 .github/workflows/docs-checker.yml
delete mode 100644 .github/workflows/semantic-commits.yml
diff --git a/.github/helper/documentation.py b/.github/helper/documentation.py
index aece5f543b..eb0b373acd 100644
--- a/.github/helper/documentation.py
+++ b/.github/helper/documentation.py
@@ -30,7 +30,7 @@ def docs_link_exists(body):
if __name__ == "__main__":
pr = sys.argv[1]
- response = requests.get("https://api.github.com/repos/frappe/frappe/pulls/{}".format(pr))
+ response = requests.get(f"https://api.github.com/repos/frappe/frappe/pulls/{pr}")
if response.ok:
payload = response.json()
diff --git a/.github/helper/translation.py b/.github/helper/translation.py
index 9146b3b32b..72f661d3e1 100644
--- a/.github/helper/translation.py
+++ b/.github/helper/translation.py
@@ -20,19 +20,12 @@ for _file in files_to_scan:
if 'frappe-lint: disable-translate' in line:
continue
- start_matches = start_pattern.search(line)
- if start_matches:
- starts_with_f = starts_with_f_pattern.search(line)
-
- if starts_with_f:
- has_f_string = f_string_pattern.search(line)
- if has_f_string:
+ if start_matches := start_pattern.search(line):
+ if starts_with_f := starts_with_f_pattern.search(line):
+ if has_f_string := f_string_pattern.search(line):
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
- continue
- else:
- continue
-
+ continue
match = pattern.search(line)
error_found = False
diff --git a/.github/workflows/docs-checker.yml b/.github/workflows/docs-checker.yml
deleted file mode 100644
index e61ee6355a..0000000000
--- a/.github/workflows/docs-checker.yml
+++ /dev/null
@@ -1,28 +0,0 @@
-name: 'Documentation Check'
-on:
- pull_request:
- types: [ opened, synchronize, reopened, edited ]
-
-permissions:
- contents: read
-
-jobs:
- docs-required:
- name: 'Documentation Required'
- runs-on: ubuntu-latest
-
- steps:
- - name: 'Setup Environment'
- uses: actions/setup-python@v4
- with:
- python-version: '3.10'
-
- - name: 'Clone repo'
- uses: actions/checkout@v3
-
- - name: Validate Docs
- env:
- PR_NUMBER: ${{ github.event.number }}
- run: |
- pip install requests --quiet
- python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index 6d1029d51d..e3f71455cd 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -1,12 +1,56 @@
name: Linters
on:
- pull_request: { }
+ pull_request:
+
+permissions:
+ contents: read
+
+concurrency:
+ group: commitcheck-frappe-${{ github.event.number }}
+ cancel-in-progress: true
jobs:
+ commit-lint:
+ name: 'Semantic Commits'
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v3
+ with:
+ fetch-depth: 200
- linters:
- name: Frappe Linter
+ - uses: actions/setup-node@v3
+ with:
+ node-version: 16
+ check-latest: true
+
+ - name: Check commit titles
+ run: |
+ npm install @commitlint/cli @commitlint/config-conventional
+ npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
+
+ docs-required:
+ name: 'Documentation Required'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: 'Setup Environment'
+ uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+
+ - name: 'Clone repo'
+ uses: actions/checkout@v3
+
+ - name: Validate Docs
+ env:
+ PR_NUMBER: ${{ github.event.number }}
+ run: |
+ pip install requests --quiet
+ python $GITHUB_WORKSPACE/.github/helper/documentation.py $PR_NUMBER
+
+ linter:
+ name: 'Frappe Linter'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
@@ -22,8 +66,7 @@ jobs:
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
- - name: Download semgrep
- run: pip install semgrep==0.97.0
-
- name: Run Semgrep rules
- run: semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
+ run: |
+ pip install semgrep==0.97.0
+ semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
diff --git a/.github/workflows/semantic-commits.yml b/.github/workflows/semantic-commits.yml
deleted file mode 100644
index 7afa02d1b9..0000000000
--- a/.github/workflows/semantic-commits.yml
+++ /dev/null
@@ -1,30 +0,0 @@
-name: Semantic Commits
-
-on:
- pull_request: {}
-
-permissions:
- contents: read
-
-concurrency:
- group: commitcheck-frappe-${{ github.event.number }}
- cancel-in-progress: true
-
-jobs:
- commitlint:
- name: Check Commit Titles
- runs-on: ubuntu-latest
- steps:
- - uses: actions/checkout@v3
- with:
- fetch-depth: 200
-
- - uses: actions/setup-node@v3
- with:
- node-version: 16
- check-latest: true
-
- - name: Check commit titles
- run: |
- npm install @commitlint/cli @commitlint/config-conventional
- npx commitlint --verbose --from ${{ github.event.pull_request.base.sha }} --to ${{ github.event.pull_request.head.sha }}
From a7ab78bde1439672fe49fe6a544a5baf372a3dcb Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 14:35:16 +0530
Subject: [PATCH 0184/2449] ci(minor): Actions
* Rename CI actions to add database_type
* Add workflow_dispatch to assets build action on develop
* Rename unit test jobs for better labelling on PR check titles
* Rename Patch action for consistency
---
.github/workflows/patch-mariadb-tests.yml | 10 +++++-----
.github/workflows/publish-assets-develop.yml | 1 +
.github/workflows/server-mariadb-tests.yml | 5 ++---
.github/workflows/server-postgres-tests.yml | 5 ++---
4 files changed, 10 insertions(+), 11 deletions(-)
diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml
index e18cbf53ba..3412fe7503 100644
--- a/.github/workflows/patch-mariadb-tests.yml
+++ b/.github/workflows/patch-mariadb-tests.yml
@@ -1,7 +1,8 @@
-name: Patch
-
-on: [pull_request, workflow_dispatch]
+name: Server (MariaDB)
+on:
+ pull_request:
+ workflow_dispatch:
concurrency:
group: patch-mariadb-develop-${{ github.event.number }}
@@ -12,11 +13,10 @@ permissions:
jobs:
test:
+ name: Patch
runs-on: ubuntu-latest
timeout-minutes: 60
- name: Patch Test
-
services:
mariadb:
image: mariadb:10.6
diff --git a/.github/workflows/publish-assets-develop.yml b/.github/workflows/publish-assets-develop.yml
index 467922e766..12bf9eca55 100644
--- a/.github/workflows/publish-assets-develop.yml
+++ b/.github/workflows/publish-assets-develop.yml
@@ -1,6 +1,7 @@
name: 'Frappe Assets'
on:
+ workflow_dispatch:
push:
branches: [ develop ]
diff --git a/.github/workflows/server-mariadb-tests.yml b/.github/workflows/server-mariadb-tests.yml
index 9e7cffba5d..c8ccfa7862 100644
--- a/.github/workflows/server-mariadb-tests.yml
+++ b/.github/workflows/server-mariadb-tests.yml
@@ -1,4 +1,4 @@
-name: Server
+name: Server (MariaDB)
on:
pull_request:
@@ -16,6 +16,7 @@ permissions:
jobs:
test:
+ name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -24,8 +25,6 @@ jobs:
matrix:
container: [1, 2]
- name: Python Unit Tests (MariaDB)
-
services:
mariadb:
image: mariadb:10.6
diff --git a/.github/workflows/server-postgres-tests.yml b/.github/workflows/server-postgres-tests.yml
index 1741752e6b..9760067197 100644
--- a/.github/workflows/server-postgres-tests.yml
+++ b/.github/workflows/server-postgres-tests.yml
@@ -1,4 +1,4 @@
-name: Server
+name: Server (Postgres)
on:
pull_request:
@@ -15,6 +15,7 @@ permissions:
jobs:
test:
+ name: Unit Tests
runs-on: ubuntu-latest
timeout-minutes: 60
@@ -23,8 +24,6 @@ jobs:
matrix:
container: [1, 2]
- name: Python Unit Tests (Postgres)
-
services:
postgres:
image: postgres:12.4
From de97eaf603aea9e25fbed365c5091a30e0719034 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 15:00:16 +0530
Subject: [PATCH 0185/2449] ci: Merge vulnerable dependency check to linter
action
---
.github/workflows/deps-checker.yml | 22 --------------------
.github/workflows/linters.yml | 32 ++++++++++++++++++++----------
2 files changed, 22 insertions(+), 32 deletions(-)
delete mode 100644 .github/workflows/deps-checker.yml
diff --git a/.github/workflows/deps-checker.yml b/.github/workflows/deps-checker.yml
deleted file mode 100644
index d3fa8c80fb..0000000000
--- a/.github/workflows/deps-checker.yml
+++ /dev/null
@@ -1,22 +0,0 @@
-name: 'Python Dependency Check'
-on:
- pull_request:
- workflow_dispatch:
- push:
- branches: [ develop ]
-
-permissions:
- contents: read
-
-jobs:
- deps-vulnerable-check:
- name: 'Vulnerable Dependency'
- runs-on: ubuntu-latest
-
- steps:
- - uses: actions/setup-python@v4
- with:
- python-version: '3.10'
- - uses: actions/checkout@v3
- - run: pip install pip-audit
- - run: pip-audit ${GITHUB_WORKSPACE}
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index e3f71455cd..c0c44ffe56 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -2,6 +2,9 @@ name: Linters
on:
pull_request:
+ workflow_dispatch:
+ push:
+ branches: [ develop ]
permissions:
contents: read
@@ -14,11 +17,12 @@ jobs:
commit-lint:
name: 'Semantic Commits'
runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 200
-
- uses: actions/setup-node@v3
with:
node-version: 16
@@ -32,15 +36,14 @@ jobs:
docs-required:
name: 'Documentation Required'
runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
steps:
- name: 'Setup Environment'
uses: actions/setup-python@v4
with:
python-version: '3.10'
-
- - name: 'Clone repo'
- uses: actions/checkout@v3
+ - uses: actions/checkout@v3
- name: Validate Docs
env:
@@ -54,14 +57,10 @@ jobs:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
-
- - name: Set up Python
- uses: actions/setup-python@v4
+ - uses: actions/setup-python@v4
with:
python-version: '3.10'
-
- - name: Install and Run Pre-commit
- uses: pre-commit/action@v3.0.0
+ - uses: pre-commit/action@v3.0.0
- name: Download Semgrep rules
run: git clone --depth 1 https://github.com/frappe/semgrep-rules.git frappe-semgrep-rules
@@ -70,3 +69,16 @@ jobs:
run: |
pip install semgrep==0.97.0
semgrep ci --config ./frappe-semgrep-rules/rules --config r/python.lang.correctness
+
+ deps-vulnerable-check:
+ name: 'Vulnerable Dependency Check'
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/setup-python@v4
+ with:
+ python-version: '3.10'
+ - uses: actions/checkout@v3
+ - run: |
+ pip install pip-audit
+ pip-audit ${GITHUB_WORKSPACE}
From 1e40b32ebf3ddec6aac755e2fc1ef6b096b01349 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 15:01:13 +0530
Subject: [PATCH 0186/2449] ci: Combine actions to be triggered 'on release'
* Trigger static asset building & updating release assets
* Trigger Docker release build
---
.github/workflows/docker-release.yml | 20 -------------------
...ish-assets-releases.yml => on_release.yml} | 20 +++++++++++++++++--
2 files changed, 18 insertions(+), 22 deletions(-)
delete mode 100644 .github/workflows/docker-release.yml
rename .github/workflows/{publish-assets-releases.yml => on_release.yml} (71%)
diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml
deleted file mode 100644
index 988c2dcc6c..0000000000
--- a/.github/workflows/docker-release.yml
+++ /dev/null
@@ -1,20 +0,0 @@
-name: 'Trigger Docker build on release'
-on:
- release:
- types: [released]
-permissions:
- contents: read
-
-jobs:
- curl:
- permissions:
- contents: none
- name: 'Trigger Docker build on release'
- runs-on: ubuntu-latest
- container:
- image: alpine:latest
- steps:
- - name: curl
- run: |
- apk add curl bash
- curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'
diff --git a/.github/workflows/publish-assets-releases.yml b/.github/workflows/on_release.yml
similarity index 71%
rename from .github/workflows/publish-assets-releases.yml
rename to .github/workflows/on_release.yml
index ff1656e55d..59e14a8c4d 100644
--- a/.github/workflows/publish-assets-releases.yml
+++ b/.github/workflows/on_release.yml
@@ -1,8 +1,11 @@
-name: 'Frappe Assets'
+name: 'Release'
on:
release:
- types: [ created ]
+ types: [released]
+
+permissions:
+ contents: read
env:
GITHUB_TOKEN: ${{ github.token }}
@@ -47,3 +50,16 @@ jobs:
asset_path: build/assets.tar.gz
asset_name: assets.tar.gz
asset_content_type: application/octet-stream
+
+ docker-release:
+ name: 'Trigger Docker build on release'
+ runs-on: ubuntu-latest
+ permissions:
+ contents: none
+ container:
+ image: alpine:latest
+ steps:
+ - name: curl
+ run: |
+ apk add curl bash
+ curl -X POST -H "Accept: application/vnd.github.v3+json" -H "Authorization: Bearer ${{ secrets.CI_PAT }}" https://api.github.com/repos/frappe/frappe_docker/actions/workflows/build_stable.yml/dispatches -d '{"ref":"main"}'
From 590e983a7f68a1fc131590d0c3f37bb7d129ffa9 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 15:03:06 +0530
Subject: [PATCH 0187/2449] ci: Rename release action
release.yml was a bit ambiguous given we have separate action configs
related to release event or intent.
---
.github/workflows/{release.yml => create-release.yml} | 0
1 file changed, 0 insertions(+), 0 deletions(-)
rename .github/workflows/{release.yml => create-release.yml} (100%)
diff --git a/.github/workflows/release.yml b/.github/workflows/create-release.yml
similarity index 100%
rename from .github/workflows/release.yml
rename to .github/workflows/create-release.yml
From d2a5b8b00212aa5d25c02bf410dccc0671694f60 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Mon, 25 Jul 2022 16:30:11 +0530
Subject: [PATCH 0188/2449] ci: Skip pre-commit on branch builds
---
.github/workflows/linters.yml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/.github/workflows/linters.yml b/.github/workflows/linters.yml
index c0c44ffe56..f56c108f6b 100644
--- a/.github/workflows/linters.yml
+++ b/.github/workflows/linters.yml
@@ -55,6 +55,8 @@ jobs:
linter:
name: 'Frappe Linter'
runs-on: ubuntu-latest
+ if: github.event_name == 'pull_request'
+
steps:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
From 3216529deb6a49c66584d0987e230b7e9e833e6b Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Mon, 25 Jul 2022 17:46:27 +0530
Subject: [PATCH 0189/2449] fix: Letter head image not working (#17608)
---
frappe/printing/doctype/letter_head/letter_head.py | 5 ++++-
1 file changed, 4 insertions(+), 1 deletion(-)
diff --git a/frappe/printing/doctype/letter_head/letter_head.py b/frappe/printing/doctype/letter_head/letter_head.py
index 9edd84a425..c48fd1fe25 100644
--- a/frappe/printing/doctype/letter_head/letter_head.py
+++ b/frappe/printing/doctype/letter_head/letter_head.py
@@ -61,9 +61,12 @@ class LetterHead(Document):
# To preserve the aspect ratio of the image, apply constraints only on
# the greater dimension and allow the other to scale accordingly
- dimension = "width" if width > height else "height"
+ dimension = "width" if self.get(width) > self.get(height) else "height"
dimension_value = self.get(f"{dimension_prefix}{dimension}")
+ if not dimension_value:
+ dimension_value = ""
+
self.set(
html_field,
f"""
From dd767bd6878ce0ff6c90538ccecd2bd222ebff8f Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 25 Jul 2022 17:47:52 +0530
Subject: [PATCH 0190/2449] refactor: move get_involved_users to form
Involved users is a property of the form and doesn't have much to do
with reviews.
---
frappe/public/js/frappe/form/form.js | 23 +++++++++++++++++++
.../public/js/frappe/form/sidebar/review.js | 23 +------------------
2 files changed, 24 insertions(+), 22 deletions(-)
diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js
index 4e38a5ee7e..854a1723d9 100644
--- a/frappe/public/js/frappe/form/form.js
+++ b/frappe/public/js/frappe/form/form.js
@@ -1873,6 +1873,29 @@ frappe.ui.form.Form = class FrappeForm {
get_active_tab() {
return this.active_tab_map && this.active_tab_map[this.docname];
}
+
+ get_involved_users() {
+ const user_fields = this.meta.fields
+ .filter(d => d.fieldtype === 'Link' && d.options === 'User')
+ .map(d => d.fieldname);
+
+ user_fields.push('owner');
+ let involved_users = user_fields.map(field => this.doc[field]);
+
+ const docinfo = this.get_docinfo();
+
+ involved_users = involved_users.concat(
+ docinfo.communications.map(d => d.sender && d.delivery_status === 'sent'),
+ docinfo.comments.map(d => d.owner),
+ docinfo.versions.map(d => d.owner),
+ docinfo.assignments.map(d => d.owner)
+ );
+
+ return involved_users
+ .uniqBy(u => u)
+ .filter(user => !['Administrator', frappe.session.user].includes(user))
+ .filter(Boolean);
+ }
};
frappe.validated = 0;
diff --git a/frappe/public/js/frappe/form/sidebar/review.js b/frappe/public/js/frappe/form/sidebar/review.js
index 2d54ad4329..242f6f0435 100644
--- a/frappe/public/js/frappe/form/sidebar/review.js
+++ b/frappe/public/js/frappe/form/sidebar/review.js
@@ -38,30 +38,9 @@ frappe.ui.form.Review = class Review {
review_button.click(() => this.show_review_dialog());
}
}
- get_involved_users() {
- const user_fields = this.frm.meta.fields
- .filter(d => d.fieldtype === 'Link' && d.options === 'User')
- .map(d => d.fieldname);
- user_fields.push('owner');
- let involved_users = user_fields.map(field => this.frm.doc[field]);
-
- const docinfo = this.frm.get_docinfo();
-
- involved_users = involved_users.concat(
- docinfo.communications.map(d => d.sender && d.delivery_status === 'sent'),
- docinfo.comments.map(d => d.owner),
- docinfo.versions.map(d => d.owner),
- docinfo.assignments.map(d => d.owner)
- );
-
- return involved_users
- .uniqBy(u => u)
- .filter(user => !['Administrator', frappe.session.user].includes(user))
- .filter(Boolean);
- }
show_review_dialog() {
- const user_options = this.get_involved_users();
+ const user_options = this.frm.get_involved_users();
const review_dialog = new frappe.ui.Dialog({
'title': __('Add Review'),
'fields': [{
From 5a2b5001009ba8273a9044a678b31931fe49da96 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 25 Jul 2022 18:31:12 +0530
Subject: [PATCH 0191/2449] fix(UX): mentions - prioritize invovled users
---
.../public/js/frappe/form/controls/text_editor.js | 14 +++++++++++++-
frappe/public/js/frappe/form/form.js | 4 ++--
2 files changed, 15 insertions(+), 3 deletions(-)
diff --git a/frappe/public/js/frappe/form/controls/text_editor.js b/frappe/public/js/frappe/form/controls/text_editor.js
index 50b625f248..be0983fc73 100644
--- a/frappe/public/js/frappe/form/controls/text_editor.js
+++ b/frappe/public/js/frappe/form/controls/text_editor.js
@@ -195,7 +195,9 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
let values = await frappe.xcall(method, {
search_term
});
- renderList(values, search_term);
+
+ let sorted_values = me.prioritize_involved_users_in_mention(values);
+ renderList(sorted_values, search_term);
}, 300),
renderItem(item) {
let value = item.value;
@@ -204,6 +206,16 @@ frappe.ui.form.ControlTextEditor = class ControlTextEditor extends frappe.ui.for
};
}
+ prioritize_involved_users_in_mention(values) {
+ const involved_users = this.frm?.get_involved_users() // input on form
+ || cur_frm?.get_involved_users() // comment box / dialog on active form
+ || [];
+
+ return values
+ .filter(val => involved_users.includes(val.id))
+ .concat(values.filter(val => !involved_users.includes(val.id)));
+ }
+
get_toolbar_options() {
return [
[{ header: [1, 2, 3, false] }],
diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js
index 854a1723d9..ea37c80e5e 100644
--- a/frappe/public/js/frappe/form/form.js
+++ b/frappe/public/js/frappe/form/form.js
@@ -1875,11 +1875,11 @@ frappe.ui.form.Form = class FrappeForm {
}
get_involved_users() {
- const user_fields = this.meta.fields
+ let user_fields = this.meta.fields
.filter(d => d.fieldtype === 'Link' && d.options === 'User')
.map(d => d.fieldname);
- user_fields.push('owner');
+ user_fields = [...user_fields, "owner", "modified_by"];
let involved_users = user_fields.map(field => this.doc[field]);
const docinfo = this.get_docinfo();
From 50414c0ffb9a67ff7bcf05cb4ddec019896ece98 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Tue, 26 Jul 2022 01:07:13 +0200
Subject: [PATCH 0192/2449] fix: pass doc to get_perm
---
frappe/public/js/frappe/model/perm.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js
index 3ea9c6bc95..00640494ea 100644
--- a/frappe/public/js/frappe/model/perm.js
+++ b/frappe/public/js/frappe/model/perm.js
@@ -23,7 +23,7 @@ $.extend(frappe.perm, {
has_perm: (doctype, permlevel, ptype, doc) => {
if (!permlevel) permlevel = 0;
if (!frappe.perm.doctype_perm[doctype]) {
- frappe.perm.doctype_perm[doctype] = frappe.perm.get_perm(doctype);
+ frappe.perm.doctype_perm[doctype] = frappe.perm.get_perm(doctype, doc);
}
let perms = frappe.perm.doctype_perm[doctype];
From 4a04e9f9dcf385dd417a37657bbbc012d684080a Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Tue, 26 Jul 2022 09:56:19 +0200
Subject: [PATCH 0193/2449] fix: appropriate password hint
---
frappe/core/doctype/user/user.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 232e915435..e7ea8b203a 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -1045,10 +1045,9 @@ def notify_admin_access_to_system_manager(login_manager=None):
def handle_password_test_fail(result):
suggestions = result["feedback"]["suggestions"][0] if result["feedback"]["suggestions"] else ""
warning = result["feedback"]["warning"] if "warning" in result["feedback"] else ""
- suggestions += (
- " " + _("Hint: Include symbols, numbers and capital letters in the password") + " "
- )
- frappe.throw(" ".join([_("Invalid Password:"), warning, suggestions]))
+ suggestions += f" {_('Your password is too short or not complex enough.')} "
+
+ frappe.throw(" ".join([warning, suggestions]), title=_("Invalid Password"))
def update_gravatar(name):
From df30f47f4e922b7fe582df70faa7248e506eeddb Mon Sep 17 00:00:00 2001
From: phot0n
Date: Wed, 9 Mar 2022 12:23:34 +0530
Subject: [PATCH 0194/2449] chore: remove razorpay.js
---
frappe/public/js/integrations/razorpay.js | 148 ----------------------
1 file changed, 148 deletions(-)
delete mode 100644 frappe/public/js/integrations/razorpay.js
diff --git a/frappe/public/js/integrations/razorpay.js b/frappe/public/js/integrations/razorpay.js
deleted file mode 100644
index eda4ac1894..0000000000
--- a/frappe/public/js/integrations/razorpay.js
+++ /dev/null
@@ -1,148 +0,0 @@
-/* HOW-TO
-
-Razorpay Payment
-
-1. Include checkout script in your code
- {{ include_script('checkout.bundle.js) }}
-
-2. Create the Order controller in your backend
- def get_razorpay_order(self):
- controller = get_payment_gateway_controller("Razorpay")
-
- payment_details = {
- "amount": 300,
- ...
- "reference_doctype": "Conference Participant",
- "reference_docname": self.name,
- ...
- "receipt": self.name
- }
-
- return controller.create_order(**payment_details)
-
-3. Inititate the payment in client using checkout API
- function make_payment(ticket) {
- var options = {
- "name": "",
- "description": "",
- "image": "",
- "prefill": {
- "name": "",
- "email": "",
- "contact": ""
- },
- "theme": {
- "color": ""
- },
- "doctype": "",
- "docname": " {
-
-
-{% endblock %}
-
-{%- block page_content -%}
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-{% endblock %}
diff --git a/frappe/templates/pages/integrations/braintree_checkout.py b/frappe/templates/pages/integrations/braintree_checkout.py
deleted file mode 100644
index c4c79ea74f..0000000000
--- a/frappe/templates/pages/integrations/braintree_checkout.py
+++ /dev/null
@@ -1,64 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# License: MIT. See LICENSE
-
-import json
-
-import frappe
-from frappe import _
-from frappe.integrations.doctype.braintree_settings.braintree_settings import (
- get_client_token,
- get_gateway_controller,
-)
-from frappe.utils import flt
-
-no_cache = 1
-
-expected_keys = (
- "amount",
- "title",
- "description",
- "reference_doctype",
- "reference_docname",
- "payer_name",
- "payer_email",
- "order_id",
- "currency",
-)
-
-
-def get_context(context):
- context.no_cache = 1
-
- # all these keys exist in form_dict
- if not (set(expected_keys) - set(list(frappe.form_dict))):
- for key in expected_keys:
- context[key] = frappe.form_dict[key]
-
- context.client_token = get_client_token(context.reference_docname)
-
- context["amount"] = flt(context["amount"])
-
- gateway_controller = get_gateway_controller(context.reference_docname)
- context["header_img"] = frappe.db.get_value(
- "Braintree Settings", gateway_controller, "header_img"
- )
-
- else:
- frappe.redirect_to_message(
- _("Some information is missing"),
- _("Looks like someone sent you to an incomplete URL. Please ask them to look into it."),
- )
- frappe.local.flags.redirect_location = frappe.local.response.location
- raise frappe.Redirect
-
-
-@frappe.whitelist(allow_guest=True)
-def make_payment(payload_nonce, data, reference_doctype, reference_docname):
- data = json.loads(data)
-
- data.update({"payload_nonce": payload_nonce})
-
- gateway_controller = get_gateway_controller(reference_docname)
- data = frappe.get_doc("Braintree Settings", gateway_controller).create_payment_request(data)
- frappe.db.commit()
- return data
diff --git a/frappe/templates/pages/integrations/payment-cancel.html b/frappe/templates/pages/integrations/payment-cancel.html
deleted file mode 100644
index a7c327508f..0000000000
--- a/frappe/templates/pages/integrations/payment-cancel.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %}{{ _("Payment Cancelled") }}{% endblock %}
-
-{%- block page_content -%}
-
-
-
- {{ _("Payment Cancelled") }}
-
-
{{ _("Your payment is cancelled.") }}
-
-
-
-
-{% endblock %}
diff --git a/frappe/templates/pages/integrations/payment-failed.html b/frappe/templates/pages/integrations/payment-failed.html
deleted file mode 100644
index 8f416b563a..0000000000
--- a/frappe/templates/pages/integrations/payment-failed.html
+++ /dev/null
@@ -1,22 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %}{{ _("Payment Failed") }}{% endblock %}
-
-{%- block page_content -%}
-
-
-
- {{ _("Payment Failed") }}
-
-
{{ _("Your payment has failed.") }}
-
-
-
-
-{% endblock %}
diff --git a/frappe/templates/pages/integrations/payment-success.html b/frappe/templates/pages/integrations/payment-success.html
deleted file mode 100644
index 76c5db93bf..0000000000
--- a/frappe/templates/pages/integrations/payment-success.html
+++ /dev/null
@@ -1,34 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %}{{ _("Payment Success") }}{% endblock %}
-
-{%- block page_content -%}
-
-
-
- {{ _("Success") }}
-
-
{{ payment_message or _("Your payment was successfully accepted") }}
- {% if not payment_message %}
-
- {% endif %}
-
-
-
-{% endblock %}
diff --git a/frappe/templates/pages/integrations/payment_cancel.py b/frappe/templates/pages/integrations/payment_cancel.py
deleted file mode 100644
index cf2a10f8c7..0000000000
--- a/frappe/templates/pages/integrations/payment_cancel.py
+++ /dev/null
@@ -1,12 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: MIT. See LICENSE
-
-import frappe
-
-
-def get_context(context):
- token = frappe.local.form_dict.token
-
- if token:
- frappe.db.set_value("Integration Request", token, "status", "Cancelled")
- frappe.db.commit()
diff --git a/frappe/templates/pages/integrations/payment_success.py b/frappe/templates/pages/integrations/payment_success.py
deleted file mode 100644
index 8985850a81..0000000000
--- a/frappe/templates/pages/integrations/payment_success.py
+++ /dev/null
@@ -1,15 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: MIT. See LICENSE
-
-import frappe
-
-no_cache = True
-
-
-def get_context(context):
- token = frappe.local.form_dict.token
- doc = frappe.get_doc(frappe.local.form_dict.doctype, frappe.local.form_dict.docname)
-
- context.payment_message = ""
- if hasattr(doc, "get_payment_success_message"):
- context.payment_message = doc.get_payment_success_message()
diff --git a/frappe/templates/pages/integrations/paytm_checkout.html b/frappe/templates/pages/integrations/paytm_checkout.html
deleted file mode 100644
index 168f6597e5..0000000000
--- a/frappe/templates/pages/integrations/paytm_checkout.html
+++ /dev/null
@@ -1,43 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %} Payment {% endblock %}
-
-{%- block header -%}
-
- Merchant Checkout Page
-
-{% endblock %}
-
-{% block script %}
-
-{% endblock %}
-
-{%- block page_content -%}
-
-
-
Please do not refresh this page...
-
-
-
-
-{% endblock %}
-
-{% block style %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/frappe/templates/pages/integrations/paytm_checkout.py b/frappe/templates/pages/integrations/paytm_checkout.py
deleted file mode 100644
index 93097e038b..0000000000
--- a/frappe/templates/pages/integrations/paytm_checkout.py
+++ /dev/null
@@ -1,34 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# License: MIT. See LICENSE
-import json
-
-import frappe
-from frappe import _
-from frappe.integrations.doctype.paytm_settings.paytm_settings import (
- get_paytm_config,
- get_paytm_params,
-)
-
-
-def get_context(context):
- context.no_cache = 1
- paytm_config = get_paytm_config()
-
- try:
- doc = frappe.get_doc("Integration Request", frappe.form_dict["order_id"])
-
- context.payment_details = get_paytm_params(json.loads(doc.data), doc.name, paytm_config)
-
- context.url = paytm_config.url
-
- except Exception:
- frappe.log_error()
- frappe.redirect_to_message(
- _("Invalid Token"),
- _("Seems token you are using is invalid!"),
- http_status_code=400,
- indicator_color="red",
- )
-
- frappe.local.flags.redirect_location = frappe.local.response.location
- raise frappe.Redirect
diff --git a/frappe/templates/pages/integrations/razorpay_checkout.html b/frappe/templates/pages/integrations/razorpay_checkout.html
deleted file mode 100644
index fa1101c216..0000000000
--- a/frappe/templates/pages/integrations/razorpay_checkout.html
+++ /dev/null
@@ -1,28 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %} Payment {% endblock %}
-
-{%- block header -%}{% endblock %}
-
-{% block script %}
-
-
-{% endblock %}
-
-{%- block page_content -%}
-
-
- Loading Payment System
- Confirming Payment
-
-
-{% endblock %}
-
-{% block style %}
-
-{% endblock %}
\ No newline at end of file
diff --git a/frappe/templates/pages/integrations/razorpay_checkout.py b/frappe/templates/pages/integrations/razorpay_checkout.py
deleted file mode 100644
index d0e77f6d8a..0000000000
--- a/frappe/templates/pages/integrations/razorpay_checkout.py
+++ /dev/null
@@ -1,79 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# License: MIT. See LICENSE
-import json
-
-import frappe
-from frappe import _
-from frappe.utils import cint, flt
-
-no_cache = 1
-
-expected_keys = (
- "amount",
- "title",
- "description",
- "reference_doctype",
- "reference_docname",
- "payer_name",
- "payer_email",
- "order_id",
- "currency",
-)
-
-
-def get_context(context):
- context.no_cache = 1
- context.api_key = get_api_key()
-
- try:
- doc = frappe.get_doc("Integration Request", frappe.form_dict["token"])
- payment_details = json.loads(doc.data)
-
- for key in expected_keys:
- context[key] = payment_details[key]
-
- context["token"] = frappe.form_dict["token"]
- context["amount"] = flt(context["amount"])
- context["subscription_id"] = (
- payment_details["subscription_id"] if payment_details.get("subscription_id") else ""
- )
-
- except Exception as e:
- frappe.redirect_to_message(
- _("Invalid Token"),
- _("Seems token you are using is invalid!"),
- http_status_code=400,
- indicator_color="red",
- )
-
- frappe.local.flags.redirect_location = frappe.local.response.location
- raise frappe.Redirect
-
-
-def get_api_key():
- api_key = frappe.db.get_single_value("Razorpay Settings", "api_key")
- if cint(frappe.form_dict.get("use_sandbox")):
- api_key = frappe.conf.sandbox_api_key
-
- return api_key
-
-
-@frappe.whitelist(allow_guest=True)
-def make_payment(razorpay_payment_id, options, reference_doctype, reference_docname, token):
- data = {}
-
- if isinstance(options, str):
- data = json.loads(options)
-
- data.update(
- {
- "razorpay_payment_id": razorpay_payment_id,
- "reference_docname": reference_docname,
- "reference_doctype": reference_doctype,
- "token": token,
- }
- )
-
- data = frappe.get_doc("Razorpay Settings").create_request(data)
- frappe.db.commit()
- return data
diff --git a/frappe/templates/pages/integrations/stripe_checkout.css b/frappe/templates/pages/integrations/stripe_checkout.css
deleted file mode 100644
index a42808aa7f..0000000000
--- a/frappe/templates/pages/integrations/stripe_checkout.css
+++ /dev/null
@@ -1,113 +0,0 @@
-.StripeElement {
- background-color: white;
- height: 40px;
- padding: 10px 12px;
- border-radius: 4px;
- border: 1px solid transparent;
- box-shadow: 0 1px 3px 0 #e6ebf1;
- -webkit-transition: box-shadow 150ms ease;
- transition: box-shadow 150ms ease;
-}
-
-.StripeElement--focus {
- box-shadow: 0 1px 3px 0 #cfd7df;
-}
-
-.StripeElement--invalid {
- border-color: #fa755a;
-}
-
-.StripeElement--webkit-autofill {
- background-color: #fefde5;
-}
-
-.stripe #payment-form {
- margin-top: 80px;
-}
-
-.stripe button {
- float: right;
- display: block;
- background: #5e64ff;
- color: white;
- box-shadow: 0 7px 14px 0 rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
- border-radius: 4px;
- border: 0;
- margin-top: 20px;
- font-size: 15px;
- font-weight: 400;
- max-width: 40%;
- height: 40px;
- line-height: 38px;
- outline: none;
-}
-
-.stripe button:hover, .stripe button:focus {
- background: #2b33ff;
- border-color: #0711ff;
-}
-
-.stripe button:active {
- background: #5e64ff;
-}
-
-.stripe button:disabled {
- background: #515e80;
-}
-
-.stripe .group {
- background: white;
- box-shadow: 2px 7px 14px 2px rgba(49, 49, 93, 0.10), 0 3px 6px 0 rgba(0, 0, 0, 0.08);
- border-radius: 4px;
- margin-bottom: 20px;
-}
-
-.stripe label {
- position: relative;
- color: #8898AA;
- font-weight: 300;
- height: 40px;
- line-height: 40px;
- margin-left: 20px;
- display: block;
-}
-
-.stripe .group label:not(:last-child) {
- border-bottom: 1px solid #F0F5FA;
-}
-
-.stripe label>span {
- width: 20%;
- text-align: right;
- float: left;
-}
-
-.current-card {
- margin-left: 20px;
-}
-
-.field {
- background: transparent;
- font-weight: 300;
- border: 0;
- color: #31325F;
- outline: none;
- padding-right: 10px;
- padding-left: 10px;
- cursor: text;
- width: 70%;
- height: 40px;
- float: right;
-}
-
-.field::-webkit-input-placeholder {
- color: #CFD7E0;
-}
-
-.field::-moz-placeholder {
- color: #CFD7E0;
-}
-
-.field:-ms-input-placeholder {
- color: #CFD7E0;
-}
diff --git a/frappe/templates/pages/integrations/stripe_checkout.html b/frappe/templates/pages/integrations/stripe_checkout.html
deleted file mode 100644
index ec3d9783c5..0000000000
--- a/frappe/templates/pages/integrations/stripe_checkout.html
+++ /dev/null
@@ -1,58 +0,0 @@
-{% extends "templates/web.html" %}
-
-{% block title %} Payment {% endblock %}
-
-{%- block header -%}
-{% endblock %}
-
-{% block script %}
-
-
-{% endblock %}
-
-{%- block page_content -%}
-
-
-
- {% if image %}
-
- {% endif %}
-
{{description}}
-
-
-
-
-
-{% endblock %}
diff --git a/frappe/templates/pages/integrations/stripe_checkout.py b/frappe/templates/pages/integrations/stripe_checkout.py
deleted file mode 100644
index 1c0e20c631..0000000000
--- a/frappe/templates/pages/integrations/stripe_checkout.py
+++ /dev/null
@@ -1,91 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies Pvt. Ltd. and Contributors
-# License: MIT. See LICENSE
-import json
-
-import frappe
-from frappe import _
-from frappe.integrations.doctype.stripe_settings.stripe_settings import get_gateway_controller
-from frappe.utils import cint, fmt_money
-
-no_cache = 1
-
-expected_keys = (
- "amount",
- "title",
- "description",
- "reference_doctype",
- "reference_docname",
- "payer_name",
- "payer_email",
- "order_id",
- "currency",
-)
-
-
-def get_context(context):
- context.no_cache = 1
-
- # all these keys exist in form_dict
- if not (set(expected_keys) - set(list(frappe.form_dict))):
- for key in expected_keys:
- context[key] = frappe.form_dict[key]
-
- gateway_controller = get_gateway_controller(context.reference_doctype, context.reference_docname)
- context.publishable_key = get_api_key(context.reference_docname, gateway_controller)
- context.image = get_header_image(context.reference_docname, gateway_controller)
-
- context["amount"] = fmt_money(amount=context["amount"], currency=context["currency"])
-
- if is_a_subscription(context.reference_doctype, context.reference_docname):
- payment_plan = frappe.db.get_value(
- context.reference_doctype, context.reference_docname, "payment_plan"
- )
- recurrence = frappe.db.get_value("Payment Plan", payment_plan, "recurrence")
-
- context["amount"] = context["amount"] + " " + _(recurrence)
-
- else:
- frappe.redirect_to_message(
- _("Some information is missing"),
- _("Looks like someone sent you to an incomplete URL. Please ask them to look into it."),
- )
- frappe.local.flags.redirect_location = frappe.local.response.location
- raise frappe.Redirect
-
-
-def get_api_key(doc, gateway_controller):
- publishable_key = frappe.db.get_value("Stripe Settings", gateway_controller, "publishable_key")
- if cint(frappe.form_dict.get("use_sandbox")):
- publishable_key = frappe.conf.sandbox_publishable_key
-
- return publishable_key
-
-
-def get_header_image(doc, gateway_controller):
- header_image = frappe.db.get_value("Stripe Settings", gateway_controller, "header_img")
-
- return header_image
-
-
-@frappe.whitelist(allow_guest=True)
-def make_payment(stripe_token_id, data, reference_doctype=None, reference_docname=None):
- data = json.loads(data)
-
- data.update({"stripe_token_id": stripe_token_id})
-
- gateway_controller = get_gateway_controller(reference_doctype, reference_docname)
-
- if is_a_subscription(reference_doctype, reference_docname):
- reference = frappe.get_doc(reference_doctype, reference_docname)
- data = reference.create_subscription("stripe", gateway_controller, data)
- else:
- data = frappe.get_doc("Stripe Settings", gateway_controller).create_request(data)
-
- frappe.db.commit()
- return data
-
-
-def is_a_subscription(reference_doctype, reference_docname):
- if not frappe.get_meta(reference_doctype).has_field("is_a_subscription"):
- return False
- return frappe.db.get_value(reference_doctype, reference_docname, "is_a_subscription")
From 725b8fb13a1b7eaf97df59b4614d6761b5f76abc Mon Sep 17 00:00:00 2001
From: phot0n
Date: Tue, 29 Mar 2022 13:38:54 +0530
Subject: [PATCH 0199/2449] chore: remove payments card from integration
workspace
---
.../workspace/integrations/integrations.json | 152 ++++++------------
1 file changed, 45 insertions(+), 107 deletions(-)
diff --git a/frappe/integrations/workspace/integrations/integrations.json b/frappe/integrations/workspace/integrations/integrations.json
index bbd2e1199f..8d1dfd64af 100644
--- a/frappe/integrations/workspace/integrations/integrations.json
+++ b/frappe/integrations/workspace/integrations/integrations.json
@@ -1,6 +1,6 @@
{
"charts": [],
- "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters \",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Payments\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
+ "content": "[{\"type\":\"header\",\"data\":{\"text\":\"Reports & Masters \",\"col\":12}},{\"type\":\"card\",\"data\":{\"card_name\":\"Backup\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Google Services\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Authentication\",\"col\":4}},{\"type\":\"card\",\"data\":{\"card_name\":\"Settings\",\"col\":4}}]",
"creation": "2020-03-02 15:16:18.714190",
"docstatus": 0,
"doctype": "Workspace",
@@ -106,11 +106,52 @@
{
"hidden": 0,
"is_query_report": 0,
- "label": "Authentication",
+ "label": "Settings",
"link_count": 0,
"onboard": 0,
"type": "Card Break"
},
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Webhook",
+ "link_count": 0,
+ "link_to": "Webhook",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Slack Webhook URL",
+ "link_count": 0,
+ "link_to": "Slack Webhook URL",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "dependencies": "",
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "SMS Settings",
+ "link_count": 0,
+ "link_to": "SMS Settings",
+ "link_type": "DocType",
+ "onboard": 0,
+ "type": "Link"
+ },
+ {
+ "hidden": 0,
+ "is_query_report": 0,
+ "label": "Authentication",
+ "link_count": 4,
+ "onboard": 0,
+ "type": "Card Break"
+ },
{
"dependencies": "",
"hidden": 0,
@@ -154,119 +195,16 @@
"link_type": "DocType",
"onboard": 0,
"type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Payments",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Braintree Settings",
- "link_count": 0,
- "link_to": "Braintree Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "PayPal Settings",
- "link_count": 0,
- "link_to": "PayPal Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Razorpay Settings",
- "link_count": 0,
- "link_to": "Razorpay Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Stripe Settings",
- "link_count": 0,
- "link_to": "Stripe Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Paytm Settings",
- "link_count": 0,
- "link_to": "Paytm Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "hidden": 0,
- "is_query_report": 0,
- "label": "Settings",
- "link_count": 0,
- "onboard": 0,
- "type": "Card Break"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Webhook",
- "link_count": 0,
- "link_to": "Webhook",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "Slack Webhook URL",
- "link_count": 0,
- "link_to": "Slack Webhook URL",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
- },
- {
- "dependencies": "",
- "hidden": 0,
- "is_query_report": 0,
- "label": "SMS Settings",
- "link_count": 0,
- "link_to": "SMS Settings",
- "link_type": "DocType",
- "onboard": 0,
- "type": "Link"
}
],
- "modified": "2022-01-13 17:39:01.292154",
+ "modified": "2022-07-23 18:00:28.805405",
"modified_by": "Administrator",
"module": "Integrations",
"name": "Integrations",
"owner": "Administrator",
"parent_page": "",
"public": 1,
+ "quick_lists": [],
"restrict_to_domain": "",
"roles": [],
"sequence_id": 15.0,
From f3473b059e7ae1b15967dacc3243966c390f1682 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Tue, 5 Apr 2022 23:30:59 +0530
Subject: [PATCH 0200/2449] chore: remove payment gateway and integration
request doctype
---
.../core/doctype/payment_gateway/__init__.py | 0
.../payment_gateway/payment_gateway.js | 8 -
.../payment_gateway/payment_gateway.json | 55 -------
.../payment_gateway/payment_gateway.py | 8 -
.../payment_gateway/test_payment_gateway.py | 9 -
.../doctype/integration_request/__init__.py | 0
.../integration_request.js | 8 -
.../integration_request.json | 154 ------------------
.../integration_request.py | 37 -----
.../test_integration_request.py | 11 --
10 files changed, 290 deletions(-)
delete mode 100644 frappe/core/doctype/payment_gateway/__init__.py
delete mode 100644 frappe/core/doctype/payment_gateway/payment_gateway.js
delete mode 100644 frappe/core/doctype/payment_gateway/payment_gateway.json
delete mode 100644 frappe/core/doctype/payment_gateway/payment_gateway.py
delete mode 100644 frappe/core/doctype/payment_gateway/test_payment_gateway.py
delete mode 100644 frappe/integrations/doctype/integration_request/__init__.py
delete mode 100644 frappe/integrations/doctype/integration_request/integration_request.js
delete mode 100644 frappe/integrations/doctype/integration_request/integration_request.json
delete mode 100644 frappe/integrations/doctype/integration_request/integration_request.py
delete mode 100644 frappe/integrations/doctype/integration_request/test_integration_request.py
diff --git a/frappe/core/doctype/payment_gateway/__init__.py b/frappe/core/doctype/payment_gateway/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/frappe/core/doctype/payment_gateway/payment_gateway.js b/frappe/core/doctype/payment_gateway/payment_gateway.js
deleted file mode 100644
index 0eff5a5608..0000000000
--- a/frappe/core/doctype/payment_gateway/payment_gateway.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Payment Gateway', {
- refresh: function(frm) {
-
- }
-});
diff --git a/frappe/core/doctype/payment_gateway/payment_gateway.json b/frappe/core/doctype/payment_gateway/payment_gateway.json
deleted file mode 100644
index 7195b3949e..0000000000
--- a/frappe/core/doctype/payment_gateway/payment_gateway.json
+++ /dev/null
@@ -1,55 +0,0 @@
-{
- "actions": [],
- "autoname": "field:gateway",
- "creation": "2022-01-24 21:09:47.229371",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "gateway",
- "gateway_settings",
- "gateway_controller"
- ],
- "fields": [
- {
- "fieldname": "gateway",
- "fieldtype": "Data",
- "in_list_view": 1,
- "label": "Gateway",
- "reqd": 1,
- "unique": 1
- },
- {
- "fieldname": "gateway_settings",
- "fieldtype": "Link",
- "label": "Gateway Settings",
- "options": "DocType"
- },
- {
- "fieldname": "gateway_controller",
- "fieldtype": "Dynamic Link",
- "label": "Gateway Controller",
- "options": "gateway_settings"
- }
- ],
- "links": [],
- "modified": "2022-01-24 21:17:03.864719",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Payment Gateway",
- "naming_rule": "By fieldname",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "read": 1,
- "role": "System Manager",
- "write": 1
- }
- ],
- "quick_entry": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": []
-}
\ No newline at end of file
diff --git a/frappe/core/doctype/payment_gateway/payment_gateway.py b/frappe/core/doctype/payment_gateway/payment_gateway.py
deleted file mode 100644
index 74306ae4ad..0000000000
--- a/frappe/core/doctype/payment_gateway/payment_gateway.py
+++ /dev/null
@@ -1,8 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and contributors
-# License: MIT. See LICENSE
-
-from frappe.model.document import Document
-
-
-class PaymentGateway(Document):
- pass
diff --git a/frappe/core/doctype/payment_gateway/test_payment_gateway.py b/frappe/core/doctype/payment_gateway/test_payment_gateway.py
deleted file mode 100644
index 6900e79434..0000000000
--- a/frappe/core/doctype/payment_gateway/test_payment_gateway.py
+++ /dev/null
@@ -1,9 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
-# License: MIT. See LICENSE
-import unittest
-
-# test_records = frappe.get_test_records('Payment Gateway')
-
-
-class TestPaymentGateway(unittest.TestCase):
- pass
diff --git a/frappe/integrations/doctype/integration_request/__init__.py b/frappe/integrations/doctype/integration_request/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/frappe/integrations/doctype/integration_request/integration_request.js b/frappe/integrations/doctype/integration_request/integration_request.js
deleted file mode 100644
index 4b3b9a2de7..0000000000
--- a/frappe/integrations/doctype/integration_request/integration_request.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2016, Frappe Technologies and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('Integration Request', {
- refresh: function(frm) {
-
- }
-});
diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json
deleted file mode 100644
index 98db8ea748..0000000000
--- a/frappe/integrations/doctype/integration_request/integration_request.json
+++ /dev/null
@@ -1,154 +0,0 @@
-{
- "actions": [],
- "creation": "2022-03-28 12:25:29.929952",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "request_id",
- "integration_request_service",
- "is_remote_request",
- "column_break_5",
- "request_description",
- "status",
- "section_break_8",
- "url",
- "request_headers",
- "data",
- "response_section",
- "output",
- "error",
- "reference_section",
- "reference_doctype",
- "column_break_16",
- "reference_docname"
- ],
- "fields": [
- {
- "fieldname": "integration_request_service",
- "fieldtype": "Data",
- "label": "Service",
- "read_only": 1
- },
- {
- "default": "Queued",
- "fieldname": "status",
- "fieldtype": "Select",
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "Status",
- "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed",
- "read_only": 1
- },
- {
- "fieldname": "data",
- "fieldtype": "Code",
- "label": "Request Data",
- "read_only": 1
- },
- {
- "fieldname": "output",
- "fieldtype": "Code",
- "label": "Output",
- "read_only": 1
- },
- {
- "fieldname": "error",
- "fieldtype": "Code",
- "label": "Error",
- "read_only": 1
- },
- {
- "fieldname": "reference_doctype",
- "fieldtype": "Link",
- "label": "Reference Document Type",
- "options": "DocType",
- "read_only": 1
- },
- {
- "fieldname": "reference_docname",
- "fieldtype": "Dynamic Link",
- "label": "Reference Document Name",
- "options": "reference_doctype",
- "read_only": 1
- },
- {
- "default": "0",
- "fieldname": "is_remote_request",
- "fieldtype": "Check",
- "label": "Is Remote Request?",
- "read_only": 1
- },
- {
- "fieldname": "request_description",
- "fieldtype": "Data",
- "label": "Request Description",
- "read_only": 1
- },
- {
- "fieldname": "request_id",
- "fieldtype": "Data",
- "label": "Request ID",
- "read_only": 1
- },
- {
- "fieldname": "column_break_5",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "section_break_8",
- "fieldtype": "Section Break"
- },
- {
- "fieldname": "url",
- "fieldtype": "Data",
- "label": "URL",
- "read_only": 1
- },
- {
- "fieldname": "response_section",
- "fieldtype": "Section Break",
- "label": "Response"
- },
- {
- "depends_on": "eval:doc.reference_doctype",
- "fieldname": "reference_section",
- "fieldtype": "Section Break",
- "label": "Reference"
- },
- {
- "fieldname": "column_break_16",
- "fieldtype": "Column Break"
- },
- {
- "fieldname": "request_headers",
- "fieldtype": "Code",
- "label": "Request Headers",
- "read_only": 1
- }
- ],
- "in_create": 1,
- "links": [],
- "modified": "2022-04-07 11:32:27.557548",
- "modified_by": "Administrator",
- "module": "Integrations",
- "name": "Integration Request",
- "owner": "Administrator",
- "permissions": [
- {
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1
- }
- ],
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": [],
- "title_field": "integration_request_service",
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/integration_request/integration_request.py b/frappe/integrations/doctype/integration_request/integration_request.py
deleted file mode 100644
index 334736bc9b..0000000000
--- a/frappe/integrations/doctype/integration_request/integration_request.py
+++ /dev/null
@@ -1,37 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies and contributors
-# License: MIT. See LICENSE
-
-import json
-
-import frappe
-from frappe.integrations.utils import json_handler
-from frappe.model.document import Document
-
-
-class IntegrationRequest(Document):
- def autoname(self):
- if self.flags._name:
- self.name = self.flags._name
-
- def update_status(self, params, status):
- data = json.loads(self.data)
- data.update(params)
-
- self.data = json.dumps(data)
- self.status = status
- self.save(ignore_permissions=True)
- frappe.db.commit()
-
- def handle_success(self, response):
- """update the output field with the response along with the relevant status"""
- if isinstance(response, str):
- response = json.loads(response)
- self.db_set("status", "Completed")
- self.db_set("output", json.dumps(response, default=json_handler))
-
- def handle_failure(self, response):
- """update the error field with the response along with the relevant status"""
- if isinstance(response, str):
- response = json.loads(response)
- self.db_set("status", "Failed")
- self.db_set("error", json.dumps(response, default=json_handler))
diff --git a/frappe/integrations/doctype/integration_request/test_integration_request.py b/frappe/integrations/doctype/integration_request/test_integration_request.py
deleted file mode 100644
index 45963d5096..0000000000
--- a/frappe/integrations/doctype/integration_request/test_integration_request.py
+++ /dev/null
@@ -1,11 +0,0 @@
-# Copyright (c) 2015, Frappe Technologies and Contributors
-# License: MIT. See LICENSE
-import unittest
-
-import frappe
-
-# test_records = frappe.get_test_records('Integration Request')
-
-
-class TestIntegrationRequest(unittest.TestCase):
- pass
From 5c2cfdd1e91c6627b86275aaf84acab36161f687 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Tue, 5 Apr 2022 23:54:34 +0530
Subject: [PATCH 0201/2449] fix: remove integration request check from
test_is_set_is_not_set
---
frappe/tests/test_db_query.py | 7 +++----
1 file changed, 3 insertions(+), 4 deletions(-)
diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py
index ad9f59b3cd..e031bc87e3 100644
--- a/frappe/tests/test_db_query.py
+++ b/frappe/tests/test_db_query.py
@@ -501,10 +501,9 @@ class TestReportview(unittest.TestCase):
)
def test_is_set_is_not_set(self):
- res = DatabaseQuery("DocType").execute(filters={"autoname": ["is", "not set"]})
- self.assertTrue({"name": "Integration Request"} in res)
- self.assertTrue({"name": "User"} in res)
- self.assertFalse({"name": "Blogger"} in res)
+ res = DatabaseQuery('DocType').execute(filters={'autoname': ['is', 'not set']})
+ self.assertTrue({'name': 'User'} in res)
+ self.assertFalse({'name': 'Blogger'} in res)
res = DatabaseQuery("DocType").execute(filters={"autoname": ["is", "set"]})
self.assertTrue({"name": "DocField"} in res)
From 6c75787d406b9e1eb1d3e48715ba7d201ae52f8b Mon Sep 17 00:00:00 2001
From: phot0n
Date: Wed, 6 Apr 2022 11:34:33 +0530
Subject: [PATCH 0202/2449] chore: remove payments section from webform doctype
---
frappe/website/doctype/web_form/web_form.json | 56 -------------------
1 file changed, 56 deletions(-)
diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json
index 0872c1d654..56b957e274 100644
--- a/frappe/website/doctype/web_form/web_form.json
+++ b/frappe/website/doctype/web_form/web_form.json
@@ -233,62 +233,6 @@
"fieldtype": "Check",
"label": "Show Sidebar"
},
- {
- "default": "0",
- "fieldname": "accept_payment",
- "fieldtype": "Check",
- "label": "Accept Payment"
- },
- {
- "depends_on": "accept_payment",
- "fieldname": "payment_gateway",
- "fieldtype": "Link",
- "label": "Payment Gateway",
- "options": "Payment Gateway"
- },
- {
- "default": "Buy Now",
- "depends_on": "accept_payment",
- "fieldname": "payment_button_label",
- "fieldtype": "Data",
- "label": "Button Label"
- },
- {
- "depends_on": "accept_payment",
- "fieldname": "payment_button_help",
- "fieldtype": "Text",
- "label": "Button Help"
- },
- {
- "fieldname": "column_break_28",
- "fieldtype": "Column Break"
- },
- {
- "default": "0",
- "depends_on": "accept_payment",
- "fieldname": "amount_based_on_field",
- "fieldtype": "Check",
- "label": "Amount Based On Field"
- },
- {
- "depends_on": "eval:doc.accept_payment && doc.amount_based_on_field",
- "fieldname": "amount_field",
- "fieldtype": "Select",
- "label": "Amount Field"
- },
- {
- "depends_on": "eval:doc.accept_payment && !doc.amount_based_on_field",
- "fieldname": "amount",
- "fieldtype": "Currency",
- "label": "Amount"
- },
- {
- "depends_on": "accept_payment",
- "fieldname": "currency",
- "fieldtype": "Link",
- "label": "Currency",
- "options": "Currency"
- },
{
"description": "List as [{\"label\": _(\"Jobs\"), \"route\":\"jobs\"}]",
"fieldname": "breadcrumbs",
From be4b7906c0a0e5f8df39a50d3945f474a7f751c3 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Wed, 6 Apr 2022 23:03:26 +0530
Subject: [PATCH 0203/2449] chore: remove payment stuff from web form
controller class
---
frappe/website/doctype/web_form/web_form.py | 41 +--------------------
1 file changed, 1 insertion(+), 40 deletions(-)
diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py
index e1c9e798e5..836ec8f632 100644
--- a/frappe/website/doctype/web_form/web_form.py
+++ b/frappe/website/doctype/web_form/web_form.py
@@ -10,7 +10,6 @@ from frappe.core.api.file import get_max_file_size
from frappe.core.doctype.file import remove_file_by_url
from frappe.custom.doctype.customize_form.customize_form import docfield_properties
from frappe.desk.form.meta import get_code_files_via_hooks
-from frappe.integrations.utils import get_payment_gateway_controller
from frappe.modules.utils import export_module_json, get_doc_module
from frappe.rate_limiter import rate_limit
from frappe.utils import cstr, dict_with_keys, strip_html
@@ -50,9 +49,6 @@ class WebForm(WebsiteGenerator):
if not frappe.flags.in_import:
self.validate_fields()
- if self.accept_payment:
- self.validate_payment_amount()
-
def validate_fields(self):
"""Validate all fields are present"""
from frappe.model import no_value_fields
@@ -66,12 +62,6 @@ class WebForm(WebsiteGenerator):
if missing:
frappe.throw(_("Following fields are missing:") + " " + " ".join(missing))
- def validate_payment_amount(self):
- if self.amount_based_on_field and not self.amount_field:
- frappe.throw(_("Please select a Amount Field."))
- elif not self.amount_based_on_field and not self.amount > 0:
- frappe.throw(_("Amount must be greater than 0."))
-
def reset_field_parent(self):
"""Convert link fields to select with names as options"""
for df in self.web_form_fields:
@@ -320,36 +310,6 @@ def get_context(context):
context.reference_doc = json.loads(context.reference_doc.as_json())
- def get_payment_gateway_url(self, doc):
- if self.accept_payment:
- controller = get_payment_gateway_controller(self.payment_gateway)
-
- title = f"Payment for {doc.doctype} {doc.name}"
- amount = self.amount
- if self.amount_based_on_field:
- amount = doc.get(self.amount_field)
-
- from decimal import Decimal
-
- if amount is None or Decimal(amount) <= 0:
- return frappe.utils.get_url(self.success_url or self.route)
-
- payment_details = {
- "amount": amount,
- "title": title,
- "description": title,
- "reference_doctype": doc.doctype,
- "reference_docname": doc.name,
- "payer_email": frappe.session.user,
- "payer_name": frappe.utils.get_fullname(frappe.session.user),
- "order_id": doc.name,
- "currency": self.currency,
- "redirect_to": frappe.utils.get_url(self.success_url or self.route),
- }
-
- # Redirect the user to this url
- return controller.get_payment_url(**payment_details)
-
def add_custom_context_and_script(self, context):
"""Update context from module if standard and append script"""
if self.web_form_module:
@@ -591,6 +551,7 @@ def accept(web_form, data, docname=None, for_payment=False):
frappe.flags.web_form_doc = doc
if for_payment:
+ # this is needed for Payments app
return web_form.get_payment_gateway_url(doc)
else:
return doc
From 83fe747f755c369245e7f5eb2b2d633944d094e4 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Thu, 7 Apr 2022 00:03:42 +0530
Subject: [PATCH 0204/2449] chore: remove payment utils and hooks
---
frappe/hooks.py | 3 +-
frappe/integrations/utils.py | 108 -----------------------------------
2 files changed, 1 insertion(+), 110 deletions(-)
diff --git a/frappe/hooks.py b/frappe/hooks.py
index 66820ecd0f..90c64b006f 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -199,8 +199,7 @@ scheduler_events = {
"frappe.email.queue.flush",
"frappe.email.doctype.email_account.email_account.pull",
"frappe.email.doctype.email_account.email_account.notify_unreplied",
- "frappe.integrations.doctype.razorpay_settings.razorpay_settings.capture_payment",
- "frappe.utils.global_search.sync_global_search",
+ 'frappe.utils.global_search.sync_global_search',
"frappe.monitor.flush",
],
"hourly": [
diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py
index f215a73dc6..8d3d23bb85 100644
--- a/frappe/integrations/utils.py
+++ b/frappe/integrations/utils.py
@@ -2,7 +2,6 @@
# License: MIT. See LICENSE
import datetime
-import json
from urllib.parse import parse_qs
import frappe
@@ -28,122 +27,15 @@ def make_request(method, url, auth=None, headers=None, data=None):
frappe.log_error()
raise exc
-
def make_get_request(url, **kwargs):
return make_request("GET", url, **kwargs)
-
def make_post_request(url, **kwargs):
return make_request("POST", url, **kwargs)
-
def make_put_request(url, **kwargs):
return make_request("PUT", url, **kwargs)
-
-def create_request_log(
- data,
- integration_type=None,
- service_name=None,
- name=None,
- error=None,
- request_headers=None,
- output=None,
- **kwargs,
-):
- """
- DEPRECATED: The parameter integration_type will be removed in the next major release.
- Use is_remote_request instead.
- """
- if integration_type == "Remote":
- kwargs["is_remote_request"] = 1
-
- elif integration_type == "Subscription Notification":
- kwargs["request_description"] = integration_type
-
- reference_doctype = reference_docname = None
- if "reference_doctype" not in kwargs:
- if isinstance(data, str):
- data = json.loads(data)
-
- reference_doctype = data.get("reference_doctype")
- reference_docname = data.get("reference_docname")
-
- integration_request = frappe.get_doc(
- {
- "doctype": "Integration Request",
- "integration_request_service": service_name,
- "request_headers": get_json(request_headers),
- "data": get_json(data),
- "output": get_json(output),
- "error": get_json(error),
- "reference_doctype": reference_doctype,
- "reference_docname": reference_docname,
- **kwargs,
- }
- )
-
- if name:
- integration_request.flags._name = name
-
- integration_request.insert(ignore_permissions=True)
- frappe.db.commit()
-
- return integration_request
-
-
-def get_json(obj):
- return obj if isinstance(obj, str) else frappe.as_json(obj, indent=1)
-
-
-def get_payment_gateway_controller(payment_gateway):
- """Return payment gateway controller"""
- gateway = frappe.get_doc("Payment Gateway", payment_gateway)
- if gateway.gateway_controller is None:
- try:
- return frappe.get_doc(f"{payment_gateway} Settings")
- except Exception:
- frappe.throw(_("{0} Settings not found").format(payment_gateway))
- else:
- try:
- return frappe.get_doc(gateway.gateway_settings, gateway.gateway_controller)
- except Exception:
- frappe.throw(_("{0} Settings not found").format(payment_gateway))
-
-
-@frappe.whitelist(allow_guest=True, xss_safe=True)
-def get_checkout_url(**kwargs):
- try:
- if kwargs.get("payment_gateway"):
- doc = frappe.get_doc("{} Settings".format(kwargs.get("payment_gateway")))
- return doc.get_payment_url(**kwargs)
- else:
- raise Exception
- except Exception:
- frappe.respond_as_web_page(
- _("Something went wrong"),
- _(
- "Looks like something is wrong with this site's payment gateway configuration. No payment has been made."
- ),
- indicator_color="red",
- http_status_code=frappe.ValidationError.http_status_code,
- )
-
-
-def create_payment_gateway(gateway, settings=None, controller=None):
- # NOTE: we don't translate Payment Gateway name because it is an internal doctype
- if not frappe.db.exists("Payment Gateway", gateway):
- payment_gateway = frappe.get_doc(
- {
- "doctype": "Payment Gateway",
- "gateway": gateway,
- "gateway_settings": settings,
- "gateway_controller": controller,
- }
- )
- payment_gateway.insert(ignore_permissions=True)
-
-
def json_handler(obj):
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)):
return str(obj)
From 41f62ba0d2594185051ec8d4ca4da0a0f407c9f8 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Tue, 26 Jul 2022 19:33:45 +0530
Subject: [PATCH 0205/2449] fix: converting back to capitalized doctype names
---
frappe/database/query.py | 28 +++++++++++++++++++++-------
frappe/tests/test_query.py | 22 ++++++++++++++++++++++
2 files changed, 43 insertions(+), 7 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index 570c3e6a63..c4c22266e9 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -17,6 +17,7 @@ WORDS_PATTERN = re.compile(r"\w+")
BRACKETS_PATTERN = re.compile(r"\(.*?\)|$")
SQL_FUNCTIONS = [sql_function.value for sql_function in SqlFunctions]
COMMA_PATTERN = re.compile(r",\s*(?![^()]*\))")
+TABLE_PATTERN = re.compile(r"\btab\w+")
def like(key: Field, value: str) -> frappe.qb:
@@ -434,6 +435,11 @@ class Engine:
if function.alias:
fields = fields.replace(" as " + function.alias.casefold(), "")
fields = BRACKETS_PATTERN.sub("", fields.casefold().replace(function.name.casefold(), ""))
+ # Converting back to capitalized doctype names.
+ if "tab" in fields:
+ fields = TABLE_PATTERN.sub(
+ lambda p: p.group(0)[:3] + p.group(0)[3].upper() + p.group(0)[3 + 1 :], fields
+ )
# Check if only comma is left in fields after stripping functions.
if "," in fields and (len(fields.strip()) == 1):
fields = ""
@@ -443,20 +449,24 @@ class Engine:
if isinstance(field, str):
if function.alias:
field = field.replace(" as " + function.alias.casefold(), "")
- field = (
- BRACKETS_PATTERN.sub("", field).strip().casefold().replace(function.name.casefold(), "")
+ substituted_string = (
+ BRACKETS_PATTERN.sub("", field).strip().casefold()
+ if "`" not in field
+ else BRACKETS_PATTERN.sub("", field).strip()
)
- updated_fields.append(field)
-
+ # This is done to avoid casefold of table name.
+ if substituted_string.casefold() == function.name.casefold():
+ replaced_string = substituted_string.casefold().replace(function.name.casefold(), "")
+ else:
+ replaced_string = substituted_string.replace(function.name.casefold(), "")
+ updated_fields.append(replaced_string)
fields = [field for field in updated_fields if field]
-
return fields
def set_fields(self, fields, **kwargs):
fields = kwargs.get("pluck") if kwargs.get("pluck") else fields or "name"
if isinstance(fields, list) and None in fields and Field not in fields:
return None
-
function_objects = []
is_list = isinstance(fields, (list, tuple, set))
if is_list and len(fields) == 1:
@@ -499,7 +509,11 @@ class Engine:
if not isinstance(field, Criterion) and field:
if " as " in field:
field, reference = field.split(" as ")
- updated_fields.append(Field(field.strip()).as_(reference))
+ if "`" in field:
+ updated_fields.append(PseudoColumn(f"{field} as {reference}"))
+ else:
+ updated_fields.append(Field(field.strip()).as_(reference))
+
elif "`" in str(field):
updated_fields.append(PseudoColumn(field.strip()))
else:
diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py
index be5fcc33ac..9df7c3f6e0 100644
--- a/frappe/tests/test_query.py
+++ b/frappe/tests/test_query.py
@@ -51,6 +51,28 @@ class TestQuery(unittest.TestCase):
.run(),
)
+ self.assertEqual(
+ frappe.qb.engine.get_query(
+ "User",
+ fields=["`tabUser`.`name` as owner", "`tabUser`.`email`"],
+ filters={"name": "Administrator"},
+ ).run(as_dict=1),
+ frappe.qb.from_("User")
+ .select(Field("name").as_("owner"), Field("email"))
+ .where(Field("name") == "Administrator")
+ .run(as_dict=1),
+ )
+
+ self.assertEqual(
+ frappe.qb.engine.get_query(
+ "User", fields=["`tabUser`.`name`, Count(`name`) as count"], filters={"name": "Administrator"}
+ ).run(),
+ frappe.qb.from_("User")
+ .select(Field("name"), Count("name").as_("count"))
+ .where(Field("name") == "Administrator")
+ .run(),
+ )
+
def test_functions_fields(self):
self.assertEqual(
frappe.qb.engine.get_query("User", fields="Count(name)", filters={}).get_sql(),
From 90282d968e9fd7f3e82b483c2590562acbbf1731 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Tue, 26 Jul 2022 20:01:17 +0530
Subject: [PATCH 0206/2449] fix: added support for non iterables in "in" and
"not in"
---
frappe/database/query.py | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index c4c22266e9..a1942597af 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -3,7 +3,7 @@ import re
from ast import literal_eval
from functools import cached_property
from types import BuiltinFunctionType
-from typing import Any, Callable
+from typing import Any, Callable, Iterable
import frappe
from frappe import _
@@ -43,6 +43,8 @@ def func_in(key: Field, value: list | tuple) -> frappe.qb:
Returns:
frappe.qb: `frappe.qb object with `IN`
"""
+ if isinstance(value, str):
+ value = value.split(",")
return key.isin(value)
@@ -69,6 +71,8 @@ def func_not_in(key: Field, value: list | tuple):
Returns:
frappe.qb: `frappe.qb object with `NOT IN`
"""
+ if isinstance(value, str):
+ value = value.split(",")
return key.notin(value)
From 70284ad6b0397cdac89bd81f1df57d777b2f71e6 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 26 Jul 2022 15:39:37 +0000
Subject: [PATCH 0207/2449] fix: use comma as separator instead of space for
offset (#17627)
---
frappe/public/js/frappe/ui/filters/filter_list.js | 2 +-
frappe/public/js/frappe/ui/group_by/group_by.js | 2 +-
2 files changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js
index 5e13f086cb..6d90aaa7eb 100644
--- a/frappe/public/js/frappe/ui/filters/filter_list.js
+++ b/frappe/public/js/frappe/ui/filters/filter_list.js
@@ -31,7 +31,7 @@ frappe.ui.FilterGroup = class {
trigger: 'manual',
container: 'body',
placement: 'bottom',
- offset: '-100px 0'
+ offset: '-100px, 0'
});
}
diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js
index 692d675c62..9625e1d2af 100644
--- a/frappe/public/js/frappe/ui/group_by/group_by.js
+++ b/frappe/public/js/frappe/ui/group_by/group_by.js
@@ -42,7 +42,7 @@ frappe.ui.GroupBy = class {
trigger: 'manual',
container: 'body',
placement: 'bottom',
- offset: '-100px 0',
+ offset: '-100px, 0',
});
}
From 84c6c09b804755c3733c6993ec7d4bfb6a0576be Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 26 Jul 2022 15:40:54 +0000
Subject: [PATCH 0208/2449] fix: fallback to unscrubbed table fieldname if
label not found (#17599)
---
frappe/public/js/frappe/form/save.js | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/form/save.js b/frappe/public/js/frappe/form/save.js
index f1cb42250a..d1486ea5d7 100644
--- a/frappe/public/js/frappe/form/save.js
+++ b/frappe/public/js/frappe/form/save.js
@@ -156,7 +156,12 @@ frappe.ui.form.save = function (frm, action, callback, btn) {
if (error_fields.length) {
let meta = frappe.get_meta(doc.doctype);
if (meta.istable) {
- const table_label = __(frappe.meta.docfield_map[doc.parenttype][doc.parentfield].label).bold();
+ const table_field = frappe.meta.docfield_map[doc.parenttype][doc.parentfield];
+
+ const table_label = __(
+ table_field.label || frappe.unscrub(table_field.fieldname)
+ ).bold();
+
var message = __('Mandatory fields required in table {0}, Row {1}', [table_label, doc.idx]);
} else {
var message = __('Mandatory fields required in {0}', [__(doc.doctype)]);
From 244ffb4e233b7de3fc5f3cc7fdb25a9f848f9316 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Tue, 26 Jul 2022 19:18:08 +0200
Subject: [PATCH 0209/2449] refactor: handle_password_test_fail
---
frappe/core/doctype/user/user.py | 17 ++++++++++-------
1 file changed, 10 insertions(+), 7 deletions(-)
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index e7ea8b203a..276c05cbaf 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -540,7 +540,7 @@ class User(Document):
feedback = result.get("feedback", None)
if feedback and not feedback.get("password_policy_validation_passed", False):
- handle_password_test_fail(result)
+ handle_password_test_fail(result["feedback"])
def suggest_username(self):
def _check_suggestion(suggestion):
@@ -686,7 +686,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
feedback = result.get("feedback", None)
if feedback and not feedback.get("password_policy_validation_passed", False):
- handle_password_test_fail(result)
+ handle_password_test_fail(result["feedback"])
res = _get_user_for_update_password(key, old_password)
if res.get("message"):
@@ -1042,12 +1042,15 @@ def notify_admin_access_to_system_manager(login_manager=None):
)
-def handle_password_test_fail(result):
- suggestions = result["feedback"]["suggestions"][0] if result["feedback"]["suggestions"] else ""
- warning = result["feedback"]["warning"] if "warning" in result["feedback"] else ""
- suggestions += f" {_('Your password is too short or not complex enough.')} "
+def handle_password_test_fail(feedback: dict):
+ # Backward compatibility
+ if "feedback" in feedback:
+ feedback = feedback["feedback"]
- frappe.throw(" ".join([warning, suggestions]), title=_("Invalid Password"))
+ suggestions = feedback.get("suggestions", [])
+ warning = feedback.get("warning", "")
+
+ frappe.throw(msg=" ".join([warning] + suggestions), title=_("Invalid Password"))
def update_gravatar(name):
From 3e351fe4fa30c13c407145aee744b0c5771968f9 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Tue, 26 Jul 2022 19:24:52 +0200
Subject: [PATCH 0210/2449] refactor: pass feedback
---
frappe/core/doctype/user/user.py | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py
index 276c05cbaf..a982403935 100644
--- a/frappe/core/doctype/user/user.py
+++ b/frappe/core/doctype/user/user.py
@@ -540,7 +540,7 @@ class User(Document):
feedback = result.get("feedback", None)
if feedback and not feedback.get("password_policy_validation_passed", False):
- handle_password_test_fail(result["feedback"])
+ handle_password_test_fail(feedback)
def suggest_username(self):
def _check_suggestion(suggestion):
@@ -686,7 +686,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
feedback = result.get("feedback", None)
if feedback and not feedback.get("password_policy_validation_passed", False):
- handle_password_test_fail(result["feedback"])
+ handle_password_test_fail(feedback)
res = _get_user_for_update_password(key, old_password)
if res.get("message"):
From a65f8637cf47eabfc1f2979d9e5835af8a660176 Mon Sep 17 00:00:00 2001
From: barredterra <14891507+barredterra@users.noreply.github.com>
Date: Tue, 26 Jul 2022 19:29:59 +0200
Subject: [PATCH 0211/2449] test: password strength test and error
---
frappe/core/doctype/user/test_user.py | 10 ++++++++++
1 file changed, 10 insertions(+)
diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py
index 7582954175..2367f101c9 100644
--- a/frappe/core/doctype/user/test_user.py
+++ b/frappe/core/doctype/user/test_user.py
@@ -8,6 +8,7 @@ from unittest.mock import patch
import frappe
import frappe.exceptions
from frappe.core.doctype.user.user import (
+ handle_password_test_fail,
reset_password,
sign_up,
test_password_strength,
@@ -206,6 +207,15 @@ class TestUser(unittest.TestCase):
user.save()
frappe.flags.in_test = True
+ def test_password_validation(self):
+ result = test_password_strength("P@ssw0rd")
+ feedback = result["feedback"]
+ self.assertEqual(feedback["password_policy_validation_passed"], False)
+ self.assertRaises(frappe.exceptions.ValidationError, handle_password_test_fail, feedback)
+
+ # test backwards compatibility
+ self.assertRaises(frappe.exceptions.ValidationError, handle_password_test_fail, result)
+
def test_comment_mentions(self):
comment = """
From 91b04c21544a6d2e2ac5c79cfa6d2e075b34efcc Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sat, 23 Jul 2022 16:16:06 +0530
Subject: [PATCH 0212/2449] test: fix test_set_field_tables
removed amount_field from groupby and fields
---
frappe/tests/test_db_query.py | 3 +--
1 file changed, 1 insertion(+), 2 deletions(-)
diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py
index e031bc87e3..03bdf8b5ee 100644
--- a/frappe/tests/test_db_query.py
+++ b/frappe/tests/test_db_query.py
@@ -516,8 +516,7 @@ class TestReportview(unittest.TestCase):
data = frappe.db.get_list(
"Web Form",
filters=[["Web Form Field", "reqd", "=", 1]],
- group_by="amount_field",
- fields=["count(*) as count", "`amount_field` as name"],
+ fields=["count(*) as count"],
order_by="count desc",
limit=50,
)
From f71562fd49ba917cffaba2b4d93e15705a672b35 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sat, 23 Jul 2022 16:23:06 +0530
Subject: [PATCH 0213/2449] chore: add integration request doctype back (with
utils)
---
.../doctype/integration_request/__init__.py | 0
.../integration_request.js | 8 +
.../integration_request.json | 154 ++++++++++++++++++
.../integration_request.py | 37 +++++
.../test_integration_request.py | 11 ++
frappe/integrations/utils.py | 54 ++++++
6 files changed, 264 insertions(+)
create mode 100644 frappe/integrations/doctype/integration_request/__init__.py
create mode 100644 frappe/integrations/doctype/integration_request/integration_request.js
create mode 100644 frappe/integrations/doctype/integration_request/integration_request.json
create mode 100644 frappe/integrations/doctype/integration_request/integration_request.py
create mode 100644 frappe/integrations/doctype/integration_request/test_integration_request.py
diff --git a/frappe/integrations/doctype/integration_request/__init__.py b/frappe/integrations/doctype/integration_request/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/frappe/integrations/doctype/integration_request/integration_request.js b/frappe/integrations/doctype/integration_request/integration_request.js
new file mode 100644
index 0000000000..4b3b9a2de7
--- /dev/null
+++ b/frappe/integrations/doctype/integration_request/integration_request.js
@@ -0,0 +1,8 @@
+// Copyright (c) 2016, Frappe Technologies and contributors
+// For license information, please see license.txt
+
+frappe.ui.form.on('Integration Request', {
+ refresh: function(frm) {
+
+ }
+});
diff --git a/frappe/integrations/doctype/integration_request/integration_request.json b/frappe/integrations/doctype/integration_request/integration_request.json
new file mode 100644
index 0000000000..98db8ea748
--- /dev/null
+++ b/frappe/integrations/doctype/integration_request/integration_request.json
@@ -0,0 +1,154 @@
+{
+ "actions": [],
+ "creation": "2022-03-28 12:25:29.929952",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "request_id",
+ "integration_request_service",
+ "is_remote_request",
+ "column_break_5",
+ "request_description",
+ "status",
+ "section_break_8",
+ "url",
+ "request_headers",
+ "data",
+ "response_section",
+ "output",
+ "error",
+ "reference_section",
+ "reference_doctype",
+ "column_break_16",
+ "reference_docname"
+ ],
+ "fields": [
+ {
+ "fieldname": "integration_request_service",
+ "fieldtype": "Data",
+ "label": "Service",
+ "read_only": 1
+ },
+ {
+ "default": "Queued",
+ "fieldname": "status",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Status",
+ "options": "\nQueued\nAuthorized\nCompleted\nCancelled\nFailed",
+ "read_only": 1
+ },
+ {
+ "fieldname": "data",
+ "fieldtype": "Code",
+ "label": "Request Data",
+ "read_only": 1
+ },
+ {
+ "fieldname": "output",
+ "fieldtype": "Code",
+ "label": "Output",
+ "read_only": 1
+ },
+ {
+ "fieldname": "error",
+ "fieldtype": "Code",
+ "label": "Error",
+ "read_only": 1
+ },
+ {
+ "fieldname": "reference_doctype",
+ "fieldtype": "Link",
+ "label": "Reference Document Type",
+ "options": "DocType",
+ "read_only": 1
+ },
+ {
+ "fieldname": "reference_docname",
+ "fieldtype": "Dynamic Link",
+ "label": "Reference Document Name",
+ "options": "reference_doctype",
+ "read_only": 1
+ },
+ {
+ "default": "0",
+ "fieldname": "is_remote_request",
+ "fieldtype": "Check",
+ "label": "Is Remote Request?",
+ "read_only": 1
+ },
+ {
+ "fieldname": "request_description",
+ "fieldtype": "Data",
+ "label": "Request Description",
+ "read_only": 1
+ },
+ {
+ "fieldname": "request_id",
+ "fieldtype": "Data",
+ "label": "Request ID",
+ "read_only": 1
+ },
+ {
+ "fieldname": "column_break_5",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "section_break_8",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "url",
+ "fieldtype": "Data",
+ "label": "URL",
+ "read_only": 1
+ },
+ {
+ "fieldname": "response_section",
+ "fieldtype": "Section Break",
+ "label": "Response"
+ },
+ {
+ "depends_on": "eval:doc.reference_doctype",
+ "fieldname": "reference_section",
+ "fieldtype": "Section Break",
+ "label": "Reference"
+ },
+ {
+ "fieldname": "column_break_16",
+ "fieldtype": "Column Break"
+ },
+ {
+ "fieldname": "request_headers",
+ "fieldtype": "Code",
+ "label": "Request Headers",
+ "read_only": 1
+ }
+ ],
+ "in_create": 1,
+ "links": [],
+ "modified": "2022-04-07 11:32:27.557548",
+ "modified_by": "Administrator",
+ "module": "Integrations",
+ "name": "Integration Request",
+ "owner": "Administrator",
+ "permissions": [
+ {
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1
+ }
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "integration_request_service",
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/integration_request/integration_request.py b/frappe/integrations/doctype/integration_request/integration_request.py
new file mode 100644
index 0000000000..334736bc9b
--- /dev/null
+++ b/frappe/integrations/doctype/integration_request/integration_request.py
@@ -0,0 +1,37 @@
+# Copyright (c) 2015, Frappe Technologies and contributors
+# License: MIT. See LICENSE
+
+import json
+
+import frappe
+from frappe.integrations.utils import json_handler
+from frappe.model.document import Document
+
+
+class IntegrationRequest(Document):
+ def autoname(self):
+ if self.flags._name:
+ self.name = self.flags._name
+
+ def update_status(self, params, status):
+ data = json.loads(self.data)
+ data.update(params)
+
+ self.data = json.dumps(data)
+ self.status = status
+ self.save(ignore_permissions=True)
+ frappe.db.commit()
+
+ def handle_success(self, response):
+ """update the output field with the response along with the relevant status"""
+ if isinstance(response, str):
+ response = json.loads(response)
+ self.db_set("status", "Completed")
+ self.db_set("output", json.dumps(response, default=json_handler))
+
+ def handle_failure(self, response):
+ """update the error field with the response along with the relevant status"""
+ if isinstance(response, str):
+ response = json.loads(response)
+ self.db_set("status", "Failed")
+ self.db_set("error", json.dumps(response, default=json_handler))
diff --git a/frappe/integrations/doctype/integration_request/test_integration_request.py b/frappe/integrations/doctype/integration_request/test_integration_request.py
new file mode 100644
index 0000000000..45963d5096
--- /dev/null
+++ b/frappe/integrations/doctype/integration_request/test_integration_request.py
@@ -0,0 +1,11 @@
+# Copyright (c) 2015, Frappe Technologies and Contributors
+# License: MIT. See LICENSE
+import unittest
+
+import frappe
+
+# test_records = frappe.get_test_records('Integration Request')
+
+
+class TestIntegrationRequest(unittest.TestCase):
+ pass
diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py
index 8d3d23bb85..b05d2ef74d 100644
--- a/frappe/integrations/utils.py
+++ b/frappe/integrations/utils.py
@@ -2,6 +2,7 @@
# License: MIT. See LICENSE
import datetime
+import json
from urllib.parse import parse_qs
import frappe
@@ -36,6 +37,59 @@ def make_post_request(url, **kwargs):
def make_put_request(url, **kwargs):
return make_request("PUT", url, **kwargs)
+def create_request_log(
+ data,
+ integration_type=None,
+ service_name=None,
+ name=None,
+ error=None,
+ request_headers=None,
+ output=None,
+ **kwargs,
+):
+ """
+ DEPRECATED: The parameter integration_type will be removed in the next major release.
+ Use is_remote_request instead.
+ """
+ if integration_type == "Remote":
+ kwargs["is_remote_request"] = 1
+
+ elif integration_type == "Subscription Notification":
+ kwargs["request_description"] = integration_type
+
+ reference_doctype = reference_docname = None
+ if "reference_doctype" not in kwargs:
+ if isinstance(data, str):
+ data = json.loads(data)
+
+ reference_doctype = data.get("reference_doctype")
+ reference_docname = data.get("reference_docname")
+
+ integration_request = frappe.get_doc(
+ {
+ "doctype": "Integration Request",
+ "integration_request_service": service_name,
+ "request_headers": get_json(request_headers),
+ "data": get_json(data),
+ "output": get_json(output),
+ "error": get_json(error),
+ "reference_doctype": reference_doctype,
+ "reference_docname": reference_docname,
+ **kwargs,
+ }
+ )
+
+ if name:
+ integration_request.flags._name = name
+
+ integration_request.insert(ignore_permissions=True)
+ frappe.db.commit()
+
+ return integration_request
+
+def get_json(obj):
+ return obj if isinstance(obj, str) else frappe.as_json(obj, indent=1)
+
def json_handler(obj):
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)):
return str(obj)
From a52483f110cb040ebd2e46a1dade30607c55577a Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sat, 23 Jul 2022 16:23:10 +0530
Subject: [PATCH 0214/2449] Revert "fix: remove integration request check from
test_is_set_is_not_set"
This reverts commit b3f57f0e7774928df90bdf84c7ec23b5d3be53c8.
---
frappe/tests/test_db_query.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/frappe/tests/test_db_query.py b/frappe/tests/test_db_query.py
index 03bdf8b5ee..0eb54c7ab0 100644
--- a/frappe/tests/test_db_query.py
+++ b/frappe/tests/test_db_query.py
@@ -501,9 +501,10 @@ class TestReportview(unittest.TestCase):
)
def test_is_set_is_not_set(self):
- res = DatabaseQuery('DocType').execute(filters={'autoname': ['is', 'not set']})
- self.assertTrue({'name': 'User'} in res)
- self.assertFalse({'name': 'Blogger'} in res)
+ res = DatabaseQuery("DocType").execute(filters={"autoname": ["is", "not set"]})
+ self.assertTrue({"name": "Integration Request"} in res)
+ self.assertTrue({"name": "User"} in res)
+ self.assertFalse({"name": "Blogger"} in res)
res = DatabaseQuery("DocType").execute(filters={"autoname": ["is", "set"]})
self.assertTrue({"name": "DocField"} in res)
From 090d0321a8cf2da00696302093278bc9f8741813 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sat, 23 Jul 2022 17:27:50 +0530
Subject: [PATCH 0215/2449] chore: fix linter
---
frappe/hooks.py | 2 +-
frappe/integrations/utils.py | 6 ++++++
2 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/frappe/hooks.py b/frappe/hooks.py
index 90c64b006f..14e76adc22 100644
--- a/frappe/hooks.py
+++ b/frappe/hooks.py
@@ -199,7 +199,7 @@ scheduler_events = {
"frappe.email.queue.flush",
"frappe.email.doctype.email_account.email_account.pull",
"frappe.email.doctype.email_account.email_account.notify_unreplied",
- 'frappe.utils.global_search.sync_global_search',
+ "frappe.utils.global_search.sync_global_search",
"frappe.monitor.flush",
],
"hourly": [
diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py
index b05d2ef74d..5ae8965c83 100644
--- a/frappe/integrations/utils.py
+++ b/frappe/integrations/utils.py
@@ -28,15 +28,19 @@ def make_request(method, url, auth=None, headers=None, data=None):
frappe.log_error()
raise exc
+
def make_get_request(url, **kwargs):
return make_request("GET", url, **kwargs)
+
def make_post_request(url, **kwargs):
return make_request("POST", url, **kwargs)
+
def make_put_request(url, **kwargs):
return make_request("PUT", url, **kwargs)
+
def create_request_log(
data,
integration_type=None,
@@ -87,9 +91,11 @@ def create_request_log(
return integration_request
+
def get_json(obj):
return obj if isinstance(obj, str) else frappe.as_json(obj, indent=1)
+
def json_handler(obj):
if isinstance(obj, (datetime.date, datetime.timedelta, datetime.datetime)):
return str(obj)
From a02bd94d16368bee40a2ba7bfb6baba7d7751f30 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sat, 23 Jul 2022 18:39:23 +0530
Subject: [PATCH 0216/2449] chore: remove payment gateway libraries
---
pyproject.toml | 4 ----
1 file changed, 4 deletions(-)
diff --git a/pyproject.toml b/pyproject.toml
index 2d9027309d..3c47edc440 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -75,16 +75,12 @@ dependencies = [
# integration dependencies
"boto3~=1.17.53",
- "braintree~=4.8.0",
"dropbox~=11.7.0",
"google-api-python-client~=2.2.0",
"google-auth-httplib2~=0.1.0",
"google-auth-oauthlib~=0.4.4",
"google-auth~=1.29.0",
"googlemaps~=4.4.5",
- "paytmchecksum~=1.7.0",
- "razorpay~=1.2.0",
- "stripe~=2.56.0",
]
[build-system]
From 4b069374f5b3649405d7cde920b86db7cf8e0162 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sat, 23 Jul 2022 18:43:59 +0530
Subject: [PATCH 0217/2449] chore: remove stripe patch
---
frappe/patches.txt | 1 -
.../sync_stripe_settings_before_migrate.py | 25 -------------------
2 files changed, 26 deletions(-)
delete mode 100644 frappe/patches/v11_0/sync_stripe_settings_before_migrate.py
diff --git a/frappe/patches.txt b/frappe/patches.txt
index ee2eb0d2a1..240a96cb7d 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -66,7 +66,6 @@ execute:frappe.delete_doc_if_exists('Page', 'user-permissions')
frappe.patches.v10_0.set_no_copy_to_workflow_state
frappe.patches.v10_0.increase_single_table_column_length
frappe.patches.v11_0.create_contact_for_user
-frappe.patches.v11_0.sync_stripe_settings_before_migrate
frappe.patches.v11_0.update_list_user_settings
frappe.patches.v11_0.rename_workflow_action_to_workflow_action_master #13-06-2018
frappe.patches.v11_0.rename_email_alert_to_notification #13-06-2018
diff --git a/frappe/patches/v11_0/sync_stripe_settings_before_migrate.py b/frappe/patches/v11_0/sync_stripe_settings_before_migrate.py
deleted file mode 100644
index 019ecef67c..0000000000
--- a/frappe/patches/v11_0/sync_stripe_settings_before_migrate.py
+++ /dev/null
@@ -1,25 +0,0 @@
-import frappe
-from frappe.utils.password import get_decrypted_password
-
-
-def execute():
- publishable_key = frappe.db.sql(
- "select value from tabSingles where doctype='Stripe Settings' and field='publishable_key'"
- )
- if publishable_key:
- secret_key = get_decrypted_password(
- "Stripe Settings", "Stripe Settings", fieldname="secret_key", raise_exception=False
- )
- if secret_key:
- frappe.reload_doc("integrations", "doctype", "stripe_settings")
- frappe.db.commit()
-
- settings = frappe.new_doc("Stripe Settings")
- settings.gateway_name = (
- frappe.db.get_value("Global Defaults", None, "default_company") or "Stripe Settings"
- )
- settings.publishable_key = publishable_key
- settings.secret_key = secret_key
- settings.save(ignore_permissions=True)
-
- frappe.db.delete("Singles", {"doctype": "Stripe Settings"})
From a2571b5490726cd1454b3de1ab1b3a0fb769fc4b Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sun, 24 Jul 2022 01:40:35 +0530
Subject: [PATCH 0218/2449] chore: patch for deleting payment doctypes
---
frappe/patches.txt | 1 +
frappe/patches/v14_0/delete_payment_gateways.py | 16 ++++++++++++++++
2 files changed, 17 insertions(+)
create mode 100644 frappe/patches/v14_0/delete_payment_gateways.py
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 240a96cb7d..0dce4b9f71 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -195,6 +195,7 @@ frappe.patches.v14_0.clear_long_pending_stale_logs
frappe.patches.v14_0.log_settings_migration
frappe.patches.v14_0.setup_likes_from_feedback
frappe.patches.v14_0.update_webforms
+frappe.patches.v14_0.delete_payment_gateways
[post_model_sync]
frappe.patches.v14_0.drop_data_import_legacy
diff --git a/frappe/patches/v14_0/delete_payment_gateways.py b/frappe/patches/v14_0/delete_payment_gateways.py
new file mode 100644
index 0000000000..c06f63a2d3
--- /dev/null
+++ b/frappe/patches/v14_0/delete_payment_gateways.py
@@ -0,0 +1,16 @@
+import frappe
+
+
+def execute():
+ if "payments" in frappe.get_installed_apps():
+ return
+
+ for doctype in (
+ "Payment Gateway",
+ "Razorpay Settings",
+ "Braintree Settings",
+ "PayPal Settings",
+ "Paytm Settings",
+ "Stripe Settings",
+ ):
+ frappe.delete_doc_if_exists("DocType", doctype, force=True)
From 332919317d2f8c0b02de951a74d6b46e8d1ef606 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sun, 24 Jul 2022 20:31:00 +0530
Subject: [PATCH 0219/2449] chore: remove payments_tab from web form
---
frappe/website/doctype/web_form/web_form.json | 21 ++-----------------
1 file changed, 2 insertions(+), 19 deletions(-)
diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json
index 56b957e274..f9ebb3a4e7 100644
--- a/frappe/website/doctype/web_form/web_form.json
+++ b/frappe/website/doctype/web_form/web_form.json
@@ -48,17 +48,7 @@
"website_sidebar",
"scripting_style_tab",
"client_script",
- "custom_css",
- "payments_tab",
- "accept_payment",
- "payment_gateway",
- "payment_button_label",
- "payment_button_help",
- "column_break_28",
- "amount_based_on_field",
- "amount_field",
- "amount",
- "currency"
+ "custom_css"
],
"fields": [
{
@@ -329,20 +319,13 @@
"fieldname": "scripting_style_tab",
"fieldtype": "Tab Break",
"label": "Scripting / Style"
- },
- {
- "collapsible": 1,
- "collapsible_depends_on": "accept_payment",
- "fieldname": "payments_tab",
- "fieldtype": "Tab Break",
- "label": "Payments"
}
],
"has_web_view": 1,
"icon": "icon-edit",
"is_published_field": "published",
"links": [],
- "modified": "2022-07-18 15:51:15.288860",
+ "modified": "2022-07-24 20:29:23.059834",
"modified_by": "Administrator",
"module": "Website",
"name": "Web Form",
From a1ffcb37ee5a00e9129d60ee2952eed77d73f9e2 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Mon, 25 Jul 2022 14:07:33 +0530
Subject: [PATCH 0220/2449] chore: remove payments fields from edit_profile,
request_data & request_to_delete_data webforms
---
frappe/core/web_form/edit_profile/edit_profile.json | 3 ---
frappe/website/web_form/request_data/request_data.json | 5 -----
.../request_to_delete_data/request_to_delete_data.json | 5 -----
3 files changed, 13 deletions(-)
diff --git a/frappe/core/web_form/edit_profile/edit_profile.json b/frappe/core/web_form/edit_profile/edit_profile.json
index cedef71c0e..8abb2164f9 100644
--- a/frappe/core/web_form/edit_profile/edit_profile.json
+++ b/frappe/core/web_form/edit_profile/edit_profile.json
@@ -1,13 +1,10 @@
{
- "accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 1,
"allow_incomplete": 0,
"allow_multiple": 0,
"allow_print": 0,
- "amount": 0.0,
- "amount_based_on_field": 0,
"apply_document_permissions": 0,
"breadcrumbs": "[{\"title\": _(\"My Account\"), \"route\": \"me\"}]",
"creation": "2016-09-19 05:16:59.242754",
diff --git a/frappe/website/web_form/request_data/request_data.json b/frappe/website/web_form/request_data/request_data.json
index c52a2f6203..e895ec9ff4 100644
--- a/frappe/website/web_form/request_data/request_data.json
+++ b/frappe/website/web_form/request_data/request_data.json
@@ -1,19 +1,15 @@
{
- "accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 0,
"allow_incomplete": 0,
"allow_multiple": 0,
"allow_print": 0,
- "amount": 0.0,
- "amount_based_on_field": 0,
"apply_document_permissions": 0,
"breadcrumbs": "",
"button_label": "Request Data",
"client_script": "",
"creation": "2019-01-24 16:19:26.886096",
- "currency": "INR",
"doc_type": "Personal Data Download Request",
"docstatus": 0,
"doctype": "Web Form",
@@ -29,7 +25,6 @@
"module": "Website",
"name": "request-data",
"owner": "Administrator",
- "payment_button_label": "Buy Now",
"published": 1,
"route": "request-data",
"route_to_success_link": 1,
diff --git a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json
index ce11666a34..b3bf7d6b42 100644
--- a/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json
+++ b/frappe/website/web_form/request_to_delete_data/request_to_delete_data.json
@@ -1,19 +1,15 @@
{
- "accept_payment": 0,
"allow_comments": 0,
"allow_delete": 0,
"allow_edit": 0,
"allow_incomplete": 0,
"allow_multiple": 0,
"allow_print": 0,
- "amount": 0.0,
- "amount_based_on_field": 0,
"apply_document_permissions": 0,
"breadcrumbs": "",
"button_label": "Submit",
"client_script": "",
"creation": "2019-01-25 14:24:12.588810",
- "currency": "INR",
"custom_css": "[data-doctype=\"Web Form\"] {\n width: 50%;\n margin: 6rem auto;\n}",
"doc_type": "Personal Data Deletion Request",
"docstatus": 0,
@@ -30,7 +26,6 @@
"module": "Website",
"name": "request-to-delete-data",
"owner": "Administrator",
- "payment_button_label": "Buy Now",
"published": 1,
"route": "request-for-account-deletion",
"route_to_success_link": 0,
From cd2664bf998d5b1804f0382fb24dfe0cadd889f5 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Tue, 26 Jul 2022 23:18:23 +0530
Subject: [PATCH 0221/2449] chore: remove get_payment_gateway_controller safe
global
---
frappe/utils/safe_exec.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py
index 8e983f11b2..6fdfba19e3 100644
--- a/frappe/utils/safe_exec.py
+++ b/frappe/utils/safe_exec.py
@@ -146,7 +146,6 @@ def get_safe_globals():
),
make_get_request=frappe.integrations.utils.make_get_request,
make_post_request=frappe.integrations.utils.make_post_request,
- get_payment_gateway_controller=frappe.integrations.utils.get_payment_gateway_controller,
socketio_port=frappe.conf.socketio_port,
get_hooks=get_hooks,
enqueue=safe_enqueue,
From 0b9a9ebfac598d732be656e99e4e326bb170d620 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Wed, 27 Jul 2022 10:42:01 +0530
Subject: [PATCH 0222/2449] fix: load avatars of user in notification dropdown
(#17630)
---
.../notification_log/notification_log.py | 16 +++++++++++++++
.../frappe/ui/notifications/notifications.js | 20 ++++++++++---------
frappe/public/js/frappe/utils/user.js | 10 ++++++++++
3 files changed, 37 insertions(+), 9 deletions(-)
diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py
index 17734635dd..482f404e65 100644
--- a/frappe/desk/doctype/notification_log/notification_log.py
+++ b/frappe/desk/doctype/notification_log/notification_log.py
@@ -133,6 +133,22 @@ def get_email_header(doc):
return header_map[doc.type or "Default"]
+@frappe.whitelist()
+def get_notification_logs(limit=20):
+ notification_logs = frappe.db.get_list(
+ "Notification Log", fields=["*"], limit=limit, order_by="creation desc"
+ )
+
+ users = [log.from_user for log in notification_logs]
+ users = [*set(users)] # remove duplicates
+ user_info = frappe._dict()
+
+ for user in users:
+ frappe.utils.add_user_info(user, user_info)
+
+ return {"notification_logs": notification_logs, "user_info": user_info}
+
+
@frappe.whitelist()
def mark_all_as_read():
unread_docs_list = frappe.db.get_all(
diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js
index bf1cee2cbf..4d20ef1414 100644
--- a/frappe/public/js/frappe/ui/notifications/notifications.js
+++ b/frappe/public/js/frappe/ui/notifications/notifications.js
@@ -189,19 +189,22 @@ class NotificationsView extends BaseNotificationsView {
);
this.setup_notification_listeners();
- this.get_notifications_list(this.max_length).then(list => {
- this.dropdown_items = list;
+ this.get_notifications_list(this.max_length).then(r => {
+ if (!r.message) return;
+ this.dropdown_items = r.message.notification_logs;
+ frappe.update_user_info(r.message.user_info);
this.render_notifications_dropdown();
if (this.settings.seen == 0) {
this.toggle_notification_icon(false);
}
});
-
}
update_dropdown() {
this.get_notifications_list(1).then(r => {
- let new_item = r[0];
+ if (!r.message) return;
+ let new_item = r.message.notification_logs[0];
+ frappe.update_user_info(r.message.user_info);
this.dropdown_items.unshift(new_item);
if (this.dropdown_items.length > this.max_length) {
this.container
@@ -322,11 +325,10 @@ class NotificationsView extends BaseNotificationsView {
}
get_notifications_list(limit) {
- return frappe.db.get_list('Notification Log', {
- fields: ['*'],
- limit: limit,
- order_by: 'creation desc'
- });
+ return frappe.call(
+ 'frappe.desk.doctype.notification_log.notification_log.get_notification_logs',
+ { limit: limit }
+ );
}
get_item_link(notification_doc) {
diff --git a/frappe/public/js/frappe/utils/user.js b/frappe/public/js/frappe/utils/user.js
index a5a7801cc1..e888fbcd9a 100644
--- a/frappe/public/js/frappe/utils/user.js
+++ b/frappe/public/js/frappe/utils/user.js
@@ -14,6 +14,16 @@ frappe.user_info = function(uid) {
return user_info;
};
+frappe.update_user_info = function(user_info) {
+ for (let user in user_info) {
+ if (frappe.boot.user_info[user]) {
+ Object.assign(frappe.boot.user_info[user], user_info[user]);
+ } else {
+ frappe.boot.user_info[user] = user_info[user];
+ }
+ }
+};
+
frappe.provide('frappe.user');
$.extend(frappe.user, {
From ed39d2c6ed9e9203a516a11ef67c80102f8d8489 Mon Sep 17 00:00:00 2001
From: phot0n
Date: Wed, 27 Jul 2022 02:16:48 +0530
Subject: [PATCH 0223/2449] chore: remove payments params from webform's
whitelisted accept fuction
---
frappe/public/js/frappe/web_form/web_form.js | 2 ++
frappe/website/doctype/web_form/web_form.py | 14 ++------------
2 files changed, 4 insertions(+), 12 deletions(-)
diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js
index 21d88eac49..8cb30be529 100644
--- a/frappe/public/js/frappe/web_form/web_form.js
+++ b/frappe/public/js/frappe/web_form/web_form.js
@@ -280,6 +280,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
if (!doc_values) return;
if (window.saving) return;
+ // TODO: remove this (used for payments app)
let for_payment = Boolean(this.accept_payment && !this.doc.paid);
Object.assign(this.doc, doc_values);
@@ -342,6 +343,7 @@ export default class WebForm extends frappe.ui.FieldGroup {
}
handle_success(data) {
+ // TODO: remove this (used for payments app)
if (this.accept_payment && !this.doc.paid) {
window.location.href = data;
}
diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py
index 836ec8f632..95f58e12fd 100644
--- a/frappe/website/doctype/web_form/web_form.py
+++ b/frappe/website/doctype/web_form/web_form.py
@@ -454,10 +454,9 @@ def get_context(context):
@frappe.whitelist(allow_guest=True)
@rate_limit(key="web_form", limit=5, seconds=60, methods=["POST"])
-def accept(web_form, data, docname=None, for_payment=False):
+def accept(web_form, data, docname=None):
"""Save the web form"""
data = frappe._dict(json.loads(data))
- for_payment = frappe.parse_json(for_payment)
files = []
files_to_delete = []
@@ -495,10 +494,6 @@ def accept(web_form, data, docname=None, for_payment=False):
doc.set(fieldname, value)
- if for_payment:
- web_form.validate_mandatory(doc)
- doc.run_method("validate_payment")
-
if doc.name:
if web_form.has_web_form_permission(doc.doctype, doc.name, "write"):
doc.save(ignore_permissions=True)
@@ -549,12 +544,7 @@ def accept(web_form, data, docname=None, for_payment=False):
remove_file_by_url(f, doctype=doc.doctype, name=doc.name)
frappe.flags.web_form_doc = doc
-
- if for_payment:
- # this is needed for Payments app
- return web_form.get_payment_gateway_url(doc)
- else:
- return doc
+ return doc
@frappe.whitelist()
From f26894936e30876d29678c68549fed9fee87ffe6 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Wed, 27 Jul 2022 11:27:23 +0530
Subject: [PATCH 0224/2449] fix: grid search breaks if filter from other grid
pages (#17632)
---
frappe/public/js/frappe/form/grid.js | 8 ++++----
frappe/public/js/frappe/form/grid_row.js | 14 ++++++++------
2 files changed, 12 insertions(+), 10 deletions(-)
diff --git a/frappe/public/js/frappe/form/grid.js b/frappe/public/js/frappe/form/grid.js
index 7bbe4b123a..6e8ee75ffd 100644
--- a/frappe/public/js/frappe/form/grid.js
+++ b/frappe/public/js/frappe/form/grid.js
@@ -298,15 +298,14 @@ export default class Grid {
show_search: true
});
- Object.keys(this.filter).length !== 0 &&
- this.update_search_columns();
+ this.filter_applied && this.update_search_columns();
}
update_search_columns() {
for (const field in this.filter) {
if (this.filter[field] && !this.header_search.search_columns[field]) {
delete this.filter[field];
- this.data = this.get_data(Object.keys(this.filter).length !== 0);
+ this.data = this.get_data(this.filter_applied);
break;
}
@@ -323,7 +322,8 @@ export default class Grid {
refresh() {
if (this.frm && this.frm.setting_dependency) return;
- this.data = this.get_data(Object.keys(this.filter).length !== 0);
+ this.filter_applied = Object.keys(this.filter).length !== 0;
+ this.data = this.get_data(this.filter_applied);
!this.wrapper && this.make();
let $rows = $(this.parent).find('.rows');
diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js
index a1c3dce91f..0d71b3b305 100644
--- a/frappe/public/js/frappe/form/grid_row.js
+++ b/frappe/public/js/frappe/form/grid_row.js
@@ -14,7 +14,7 @@ export default class GridRow {
make() {
var me = this;
- this.wrapper = $('
').appendTo(this.parent).data("grid_row", this);
+ this.wrapper = $('
');
this.row = $('
').appendTo(this.wrapper)
.on("click", function(e) {
if($(e.target).hasClass('grid-row-check') || $(e.target).hasClass('row-index') || $(e.target).parent().hasClass('row-index')) {
@@ -33,9 +33,9 @@ export default class GridRow {
} else {
this.render_row();
}
- if(this.doc) {
- this.set_data();
- }
+
+ this.set_data();
+ this.wrapper.appendTo(this.parent);
}
set_docfields(update=false) {
@@ -55,8 +55,9 @@ export default class GridRow {
set_data() {
this.wrapper.data({
- "doc": this.doc
- })
+ "grid_row": this,
+ "doc": this.doc || "",
+ });
}
set_row_index() {
if(this.doc) {
@@ -750,6 +751,7 @@ export default class GridRow {
.option('disabled', Object.keys(this.grid.filter).length !== 0);
this.grid.prevent_build = true;
+ this.grid.grid_pagination.go_to_page(1);
this.grid.refresh();
this.grid.prevent_build = false;
}, 500));
From 21bbe18cc497ed21ed7526c46e5d7f71c7a476d5 Mon Sep 17 00:00:00 2001
From: Faris Ansari
Date: Tue, 26 Jul 2022 01:41:08 +0530
Subject: [PATCH 0225/2449] fix: absolute option for terminal progress bar
---
frappe/utils/__init__.py | 7 ++++---
1 file changed, 4 insertions(+), 3 deletions(-)
diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py
index 82e1cb3510..47eae314f7 100644
--- a/frappe/utils/__init__.py
+++ b/frappe/utils/__init__.py
@@ -559,7 +559,7 @@ def is_cli() -> bool:
return invoked_from_terminal
-def update_progress_bar(txt, i, l):
+def update_progress_bar(txt, i, l, absolute=False):
if os.environ.get("CI"):
if i == 0:
sys.stdout.write(txt)
@@ -581,8 +581,9 @@ def update_progress_bar(txt, i, l):
complete = int(float(i + 1) / l * col)
completion_bar = ("=" * complete).ljust(col, " ")
- percent_complete = str(int(float(i + 1) / l * 100))
- sys.stdout.write(f"\r{txt}: [{completion_bar}] {percent_complete}%")
+ percent_complete = f"{str(int(float(i + 1) / l * 100))}%"
+ status = f"{i} of {l}" if absolute else percent_complete
+ sys.stdout.write(f"\r{txt}: [{completion_bar}] {status}")
sys.stdout.flush()
From 65e0251e70958e01a985627664f296428b7845ae Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Wed, 27 Jul 2022 18:38:02 +0530
Subject: [PATCH 0226/2449] feat: Added support for "`" in alias name
---
frappe/database/query.py | 15 +++++++++++++--
1 file changed, 13 insertions(+), 2 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index a1942597af..0ebafc3960 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -412,6 +412,9 @@ class Engine:
field = initial_fields
_args.append(field)
+
+ if alias and "`" in alias:
+ alias = alias.replace("`", "")
try:
return getattr(functions, func)(*_args, alias=alias or None)
except AttributeError:
@@ -437,7 +440,11 @@ class Engine:
for function in function_objects:
if isinstance(fields, str):
if function.alias:
- fields = fields.replace(" as " + function.alias.casefold(), "")
+ to_replace = " as " + function.alias.casefold()
+ if to_replace in fields:
+ fields = fields.replace(to_replace, "")
+ elif " as " + f"`{function.alias.casefold()}" in fields:
+ fields = fields.replace(" as " + f"`{function.alias.casefold()}`", "")
fields = BRACKETS_PATTERN.sub("", fields.casefold().replace(function.name.casefold(), ""))
# Converting back to capitalized doctype names.
if "tab" in fields:
@@ -452,7 +459,11 @@ class Engine:
for field in fields:
if isinstance(field, str):
if function.alias:
- field = field.replace(" as " + function.alias.casefold(), "")
+ to_replace = " as " + function.alias.casefold()
+ if to_replace in field:
+ field = field.replace(to_replace, "")
+ elif " as " + f"`{function.alias.casefold()}" in field:
+ field = field.replace(" as " + f"`{function.alias.casefold()}`", "")
substituted_string = (
BRACKETS_PATTERN.sub("", field).strip().casefold()
if "`" not in field
From 8624afdf6c4792ebd1d7d3c63f2a9ed87f9c7061 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Wed, 27 Jul 2022 18:39:31 +0530
Subject: [PATCH 0227/2449] test: Added tests for "`" in alias name
---
frappe/tests/test_query.py | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py
index 9df7c3f6e0..47856f29e6 100644
--- a/frappe/tests/test_query.py
+++ b/frappe/tests/test_query.py
@@ -73,6 +73,26 @@ class TestQuery(unittest.TestCase):
.run(),
)
+ self.assertEqual(
+ frappe.qb.engine.get_query(
+ "User", fields=["`tabUser`.`name`, Count(`name`) as `count`"], filters={"name": "Administrator"}
+ ).run(),
+ frappe.qb.from_("User")
+ .select(Field("name"), Count("name").as_("count"))
+ .where(Field("name") == "Administrator")
+ .run(),
+ )
+
+ self.assertEqual(
+ frappe.qb.engine.get_query(
+ "User", fields="`tabUser`.`name`, Count(`name`) as `count`", filters={"name": "Administrator"}
+ ).run(),
+ frappe.qb.from_("User")
+ .select(Field("name"), Count("name").as_("count"))
+ .where(Field("name") == "Administrator")
+ .run(),
+ )
+
def test_functions_fields(self):
self.assertEqual(
frappe.qb.engine.get_query("User", fields="Count(name)", filters={}).get_sql(),
From 97429e801281e5c92d16e1d648040d5b299fc4d6 Mon Sep 17 00:00:00 2001
From: Aditya Hase
Date: Wed, 27 Jul 2022 19:21:13 +0530
Subject: [PATCH 0228/2449] fix(report-view): Honor disable_auto_refresh and
disable_count (#17641)
---
frappe/public/js/frappe/list/base_list.js | 8 ++++++++
frappe/public/js/frappe/list/list_view.js | 8 --------
.../public/js/frappe/views/reports/report_view.js | 13 +++++++++++++
3 files changed, 21 insertions(+), 8 deletions(-)
diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js
index e5272ccd91..912016d330 100644
--- a/frappe/public/js/frappe/list/base_list.js
+++ b/frappe/public/js/frappe/list/base_list.js
@@ -69,6 +69,14 @@ frappe.views.BaseList = class BaseList {
];
}
+ get_list_view_settings() {
+ return frappe
+ .call("frappe.desk.listview.get_list_settings", {
+ doctype: this.doctype,
+ })
+ .then((doc) => (this.list_view_settings = doc.message || {}));
+ }
+
setup_fields() {
this.set_fields();
this.build_fields();
diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js
index cbeda50e53..9c5dba3370 100644
--- a/frappe/public/js/frappe/list/list_view.js
+++ b/frappe/public/js/frappe/list/list_view.js
@@ -107,14 +107,6 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList {
return this.get_list_view_settings();
}
- get_list_view_settings() {
- return frappe
- .call("frappe.desk.listview.get_list_settings", {
- doctype: this.doctype,
- })
- .then((doc) => (this.list_view_settings = doc.message || {}));
- }
-
on_sort_change(sort_by, sort_order) {
this.sort_by = sort_by;
this.sort_order = sort_order;
diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js
index 2ea780c13d..945ec83439 100644
--- a/frappe/public/js/frappe/views/reports/report_view.js
+++ b/frappe/public/js/frappe/views/reports/report_view.js
@@ -43,6 +43,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
this.add_totals_row = this.view_user_settings.add_totals_row || 0;
this.chart_args = this.view_user_settings.chart_args;
}
+ return this.get_list_view_settings();
}
setup_view() {
@@ -53,6 +54,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
}
setup_events() {
+ if (this.list_view_settings?.disable_auto_refresh) {
+ return;
+ }
frappe.realtime.on("list_update", (data) => this.on_update(data));
}
@@ -216,6 +220,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
}
render_count() {
+ if (this.list_view_settings?.disable_count) {
+ return;
+ }
let $list_count = this.$paging_area.find('.list-count');
if (!$list_count.length) {
$list_count = $('')
@@ -1540,6 +1547,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView {
});
}
+ if (frappe.user.has_role("System Manager")) {
+ if (this.get_view_settings) {
+ items.push(this.get_view_settings());
+ }
+ }
+
return items.map(i => Object.assign(i, { standard: true }));
}
From 1ae3b7f16b02f9ac4836ee84e343b3a495fca288 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Wed, 27 Jul 2022 21:19:08 +0530
Subject: [PATCH 0229/2449] feat: Added support for fields in Locate
---
frappe/query_builder/functions.py | 3 +++
frappe/tests/test_query.py | 4 +++-
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/frappe/query_builder/functions.py b/frappe/query_builder/functions.py
index 824de7fbf5..1c48f8a402 100644
--- a/frappe/query_builder/functions.py
+++ b/frappe/query_builder/functions.py
@@ -17,6 +17,9 @@ class Concat_ws(Function):
class Locate(Function):
def __init__(self, *terms, **kwargs):
+ terms = list(terms)
+ if not isinstance(terms[0], str):
+ terms[0] = terms[0].get_sql()
super().__init__("LOCATE", *terms, **kwargs)
diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py
index 47856f29e6..c3b42061a9 100644
--- a/frappe/tests/test_query.py
+++ b/frappe/tests/test_query.py
@@ -75,7 +75,9 @@ class TestQuery(unittest.TestCase):
self.assertEqual(
frappe.qb.engine.get_query(
- "User", fields=["`tabUser`.`name`, Count(`name`) as `count`"], filters={"name": "Administrator"}
+ "User",
+ fields=["`tabUser`.`name`, Count(`name`) as `count`"],
+ filters={"name": "Administrator"},
).run(),
frappe.qb.from_("User")
.select(Field("name"), Count("name").as_("count"))
From cb6438158b3d184249d2e6a353ed07438b87ab48 Mon Sep 17 00:00:00 2001
From: Ritwik Puri
Date: Wed, 27 Jul 2022 22:46:56 +0530
Subject: [PATCH 0230/2449] fix: don't use cache for sequence in mariadb
(#17640)
* fix: don't use cache for sequence in mariadb
* chore: update sequence related comments
---
frappe/database/database.py | 17 +++++++++++++++++
frappe/database/mariadb/database.py | 9 ---------
frappe/database/mariadb/schema.py | 2 +-
frappe/database/postgres/database.py | 6 ------
4 files changed, 18 insertions(+), 16 deletions(-)
diff --git a/frappe/database/database.py b/frappe/database/database.py
index 68286507ba..c55704eb64 100644
--- a/frappe/database/database.py
+++ b/frappe/database/database.py
@@ -54,6 +54,23 @@ class Database:
CHILD_TABLE_COLUMNS = ("parent", "parenttype", "parentfield")
MAX_WRITES_PER_TRANSACTION = 200_000
+ # NOTE:
+ # FOR MARIADB - using no cache - as during backup, if the sequence was used in anyform,
+ # it drops the cache and uses the next non cached value in setval query and
+ # puts that in the backup file, which will start the counter
+ # from that value when inserting any new record in the doctype.
+ # By default the cache is 1000 which will mess up the sequence when
+ # using the system after a restore.
+ #
+ # Another case could be if the cached values expire then also there is a chance of
+ # the cache being skipped.
+ #
+ # FOR POSTGRES - The sequence cache for postgres is per connection.
+ # Since we're opening and closing connections for every request this results in skipping the cache
+ # to the next non-cached value hence not using cache in postgres.
+ # ref: https://stackoverflow.com/questions/21356375/postgres-9-0-4-sequence-skipping-numbers
+ SEQUENCE_CACHE = 0
+
class InvalidColumnName(frappe.ValidationError):
pass
diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py
index 5e6d62f842..303098049a 100644
--- a/frappe/database/mariadb/database.py
+++ b/frappe/database/mariadb/database.py
@@ -129,15 +129,6 @@ class MariaDBConnectionUtil:
class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database):
REGEX_CHARACTER = "regexp"
-
- # NOTE: using a very small cache - as during backup, if the sequence was used in anyform,
- # it drops the cache and uses the next non cached value in setval query and
- # puts that in the backup file, which will start the counter
- # from that value when inserting any new record in the doctype.
- # By default the cache is 1000 which will mess up the sequence when
- # using the system after a restore.
- # issue link: https://jira.mariadb.org/browse/MDEV-21786
- SEQUENCE_CACHE = 50
CONVERSION_MAP = conversions | {
FIELD_TYPE.NEWDECIMAL: float,
FIELD_TYPE.DATETIME: get_datetime,
diff --git a/frappe/database/mariadb/schema.py b/frappe/database/mariadb/schema.py
index 24a78012e1..99297fbab2 100644
--- a/frappe/database/mariadb/schema.py
+++ b/frappe/database/mariadb/schema.py
@@ -44,7 +44,7 @@ class MariaDBTable(DBTable):
# NOTE: not used nextval func as default as the ability to restore
# database with sequences has bugs in mariadb and gives a scary error.
- # issue link: https://jira.mariadb.org/browse/MDEV-21786
+ # issue link: https://jira.mariadb.org/browse/MDEV-20070
name_column = "name bigint primary key"
# create table
diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py
index 58b63e6547..cb566736ad 100644
--- a/frappe/database/postgres/database.py
+++ b/frappe/database/postgres/database.py
@@ -102,12 +102,6 @@ class PostgresExceptionUtil:
class PostgresDatabase(PostgresExceptionUtil, Database):
REGEX_CHARACTER = "~"
-
- # NOTE; The sequence cache for postgres is per connection.
- # Since we're opening and closing connections for every transaction this results in skipping the cache
- # to the next non-cached value hence not using cache in postgres.
- # ref: https://stackoverflow.com/questions/21356375/postgres-9-0-4-sequence-skipping-numbers
- SEQUENCE_CACHE = 0
default_port = "5432"
def setup_type_map(self):
From ed4412c55a56eab85d282fd0ad9c75be3edcefe5 Mon Sep 17 00:00:00 2001
From: Suraj Shetty
Date: Thu, 28 Jul 2022 10:37:08 +0530
Subject: [PATCH 0231/2449] fix: Show notification indicator badge only if
there are unread notifications
---
frappe/public/js/frappe/ui/notifications/notifications.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js
index bf1cee2cbf..0c36e151ec 100644
--- a/frappe/public/js/frappe/ui/notifications/notifications.js
+++ b/frappe/public/js/frappe/ui/notifications/notifications.js
@@ -192,7 +192,7 @@ class NotificationsView extends BaseNotificationsView {
this.get_notifications_list(this.max_length).then(list => {
this.dropdown_items = list;
this.render_notifications_dropdown();
- if (this.settings.seen == 0) {
+ if (this.settings.seen == 0 && this.dropdown_items.length > 0) {
this.toggle_notification_icon(false);
}
});
@@ -323,7 +323,7 @@ class NotificationsView extends BaseNotificationsView {
get_notifications_list(limit) {
return frappe.db.get_list('Notification Log', {
- fields: ['*'],
+ fields: ['subject', "type", "document_type", "document_name", "creation", "from_user", "name"],
limit: limit,
order_by: 'creation desc'
});
From ef234da171cd3cb0aa59c0039cd095895688e770 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 26 Jul 2022 15:40:13 +0530
Subject: [PATCH 0232/2449] fix: Don't retry asset caching in CI or
developer_mode
---
esbuild/utils.js | 13 +++++++++----
1 file changed, 9 insertions(+), 4 deletions(-)
diff --git a/esbuild/utils.js b/esbuild/utils.js
index 82490adb36..b2a4e98d54 100644
--- a/esbuild/utils.js
+++ b/esbuild/utils.js
@@ -112,16 +112,21 @@ function log(...args) {
function get_redis_subscriber(kind) {
// get redis subscriber that aborts after 10 connection attempts
- let { get_redis_subscriber: get_redis } = require("../node_utils");
- return get_redis(kind, {
- retry_strategy: function(options) {
+ let retry_strategy;
+ let { get_redis_subscriber: get_redis, get_conf } = require("../node_utils");
+
+ if (process.env.CI == 1 || get_conf().developer_mode == 1) {
+ retry_strategy = () => { }
+ } else {
+ retry_strategy = function (options) {
// abort after 10 connection attempts
if (options.attempt > 10) {
return undefined;
}
return Math.min(options.attempt * 100, 2000);
}
- });
+ }
+ return get_redis(kind, { retry_strategy });
}
module.exports = {
From 8fe4fa72ef36d465eb1a3712cd2faaa40f921afb Mon Sep 17 00:00:00 2001
From: Nabin Hait
Date: Thu, 28 Jul 2022 18:01:53 +0530
Subject: [PATCH 0233/2449] fix: hide border for a section break (#17653)
---
frappe/public/scss/desk/form.scss | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/frappe/public/scss/desk/form.scss b/frappe/public/scss/desk/form.scss
index f56d9da59a..655c0a1539 100644
--- a/frappe/public/scss/desk/form.scss
+++ b/frappe/public/scss/desk/form.scss
@@ -62,6 +62,10 @@
border-bottom: none;
}
+.form-section.card-section.hide-border {
+ border-bottom: none;
+}
+
.form-dashboard-section {
.section-body:first-child {
margin-top: 0;
From 4fadc21d85d6dab8468b9ed1ad04e650b0e2ff5a Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Thu, 28 Jul 2022 18:29:33 +0530
Subject: [PATCH 0234/2449] feat: Role based permission for Dashboard Chart
(#17634)
---
.../doctype/dashboard_chart/dashboard_chart.json | 13 +++++++++++--
.../desk/doctype/dashboard_chart/dashboard_chart.py | 7 ++++++-
2 files changed, 17 insertions(+), 3 deletions(-)
diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json
index a5d30c10e5..a5aa6cc20a 100644
--- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json
+++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json
@@ -42,7 +42,8 @@
"column_break_2",
"color",
"section_break_10",
- "last_synced_on"
+ "last_synced_on",
+ "roles"
],
"fields": [
{
@@ -277,13 +278,20 @@
"fieldtype": "Link",
"label": "Parent Document Type",
"options": "DocType"
+ },
+ {
+ "fieldname": "roles",
+ "fieldtype": "Table",
+ "label": "Roles",
+ "options": "Has Role"
}
],
"links": [],
- "modified": "2021-11-09 17:18:11.456145",
+ "modified": "2022-07-27 11:09:09.203236",
"modified_by": "Administrator",
"module": "Desk",
"name": "Dashboard Chart",
+ "naming_rule": "By fieldname",
"owner": "Administrator",
"permissions": [
{
@@ -323,5 +331,6 @@
],
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py
index 75f230a901..aa76932050 100644
--- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py
+++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py
@@ -11,7 +11,7 @@ from frappe.config import get_modules_from_all_apps_for_user
from frappe.model.document import Document
from frappe.model.naming import append_number_if_name_exists
from frappe.modules.export_file import export_to_files
-from frappe.utils import cint, get_datetime, getdate, now_datetime, nowdate
+from frappe.utils import cint, get_datetime, getdate, has_common, now_datetime, nowdate
from frappe.utils.dashboard import cache_source
from frappe.utils.data import format_date
from frappe.utils.dateutils import (
@@ -87,6 +87,11 @@ def has_permission(doc, ptype, user):
if doc.document_type in allowed_doctypes:
return True
+ if doc.roles:
+ allowed = [d.role for d in doc.roles]
+ if has_common(roles, allowed):
+ return True
+
return False
From 6c905233c5f040869349c74761bcffc9779b408a Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Thu, 28 Jul 2022 23:55:58 +0530
Subject: [PATCH 0235/2449] feat: Added support for string filters in query
---
frappe/database/query.py | 37 ++++++++++++++++++-------------
frappe/query_builder/functions.py | 8 +++++++
frappe/tests/test_query.py | 10 ++++++++-
3 files changed, 38 insertions(+), 17 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index 0ebafc3960..31c5d20ae3 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -2,6 +2,7 @@ import operator
import re
from ast import literal_eval
from functools import cached_property
+import sys
from types import BuiltinFunctionType
from typing import Any, Callable, Iterable
@@ -120,20 +121,6 @@ def func_timespan(key: Field, value: str) -> frappe.qb:
return func_between(key, get_timespan_date_range(value))
-
-def make_function(key: Any, value: int | str):
- """returns fucntion query
-
- Args:
- key (Any): field
- value (Union[int, str]): criterion
-
- Returns:
- frappe.qb: frappe.qb object
- """
- return OPERATOR_MAP[value[0].casefold()](key, value[1])
-
-
def change_orderby(order: str):
"""Convert orderby to standart Order object
@@ -161,6 +148,13 @@ def literal_eval_(literal):
return literal
+def has_function(field):
+ _field = field.casefold() if (isinstance(field, str) and "`" not in field) else field
+ if not issubclass(type(_field), Criterion):
+ if any([f"{func}(" in _field for func in SQL_FUNCTIONS]) or "(" in _field:
+ return True
+
+
# default operators
OPERATOR_MAP: dict[str, Callable] = {
"+": operator.add,
@@ -305,7 +299,7 @@ class Engine:
else:
_operator = self.OPERATOR_MAP[filters[1].casefold()]
if not isinstance(filters[0], str):
- conditions = make_function(filters[0], filters[2])
+ conditions = self.make_function_for_filters(filters[0], filters[2])
break
conditions = conditions.where(_operator(Field(filters[0]), filters[2]))
break
@@ -331,12 +325,15 @@ class Engine:
if isinstance(value, bool):
filters.update({key: str(int(value))})
+ filters = {
+ (self.get_function_object(k) if has_function(k) else k): v for k, v in filters.items()
+ }
for key in filters:
value = filters.get(key)
_operator = self.OPERATOR_MAP["="]
if not isinstance(key, str):
- conditions = conditions.where(make_function(key, value))
+ conditions = conditions.where(self.make_function_for_filters(key, value))
continue
if isinstance(value, (list, tuple)):
_operator = self.OPERATOR_MAP[value[0].casefold()]
@@ -378,6 +375,12 @@ class Engine:
return criterion
+ def make_function_for_filters(self, key: Any, value: int | str):
+ value = list(value)
+ if isinstance(value[1], str) and has_function(value[1]):
+ value[1] = self.get_function_object(value[1])
+ return OPERATOR_MAP[value[0].casefold()](key, value[1])
+
def get_function_object(self, field: str) -> "Function":
"""Expects field to look like 'SUM(*)' or 'name' or something similar. Returns PyPika Function object"""
func = field.split("(", maxsplit=1)[0].capitalize()
@@ -416,6 +419,8 @@ class Engine:
if alias and "`" in alias:
alias = alias.replace("`", "")
try:
+ if func.casefold() == "now":
+ return getattr(functions, func)()
return getattr(functions, func)(*_args, alias=alias or None)
except AttributeError:
# Fall back for functions not present in `SqlFunctions``
diff --git a/frappe/query_builder/functions.py b/frappe/query_builder/functions.py
index 1c48f8a402..f355eb05bc 100644
--- a/frappe/query_builder/functions.py
+++ b/frappe/query_builder/functions.py
@@ -22,6 +22,13 @@ class Locate(Function):
terms[0] = terms[0].get_sql()
super().__init__("LOCATE", *terms, **kwargs)
+class Ifnull(IfNull):
+ def __init__(self, condition, term, **kwargs):
+ if not isinstance(condition, str):
+ condition = condition.get_sql()
+ if not isinstance(term, str):
+ term = term.get_sql()
+ super().__init__(condition, term, **kwargs)
class Timestamp(Function):
def __init__(self, term: str, time=None, alias=None):
@@ -108,6 +115,7 @@ class SqlFunctions(Enum):
Min = "min"
Abs = "abs"
Timestamp = "timestamp"
+ IfNull = "ifnull"
def _max(dt, fieldname, filters=None, **kwargs):
diff --git a/frappe/tests/test_query.py b/frappe/tests/test_query.py
index c3b42061a9..3322ab9e01 100644
--- a/frappe/tests/test_query.py
+++ b/frappe/tests/test_query.py
@@ -2,7 +2,7 @@ import unittest
import frappe
from frappe.query_builder import Field
-from frappe.query_builder.functions import Abs, Count, Max, Timestamp
+from frappe.query_builder.functions import Abs, Count, Ifnull, Max, Now, Timestamp
from frappe.tests.test_query_builder import db_type_is, run_only_if
@@ -178,3 +178,11 @@ class TestQuery(unittest.TestCase):
.select(user_doctype.email.as_("id"), Count(Field("name")).as_("count"))
.get_sql(),
)
+
+ def test_filters(self):
+ self.assertEqual(
+ frappe.qb.engine.get_query(
+ "User", filters={"IfNull(name, " ")": ("<", Now())}, fields=["Max(name)"]
+ ).run(),
+ frappe.qb.from_("User").select(Max(Field("name"))).where(Ifnull("name", "") < Now()).run(),
+ )
\ No newline at end of file
From a772cc0796158d720bb5f5bd8eb6434a73ee0dcf Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Fri, 29 Jul 2022 10:24:49 +0530
Subject: [PATCH 0236/2449] fix: removing unused import line (#17654)
---
frappe/database/query.py | 2 --
1 file changed, 2 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index e23c0b4b63..8dbb564edc 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -187,8 +187,6 @@ class Engine:
@cached_property
def OPERATOR_MAP(self):
- from frappe.boot import get_additional_filters_from_hooks
-
# default operators
all_operators = OPERATOR_MAP.copy()
From 8fa2caa4a2f69650bef69394b31bb5a824412459 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Fri, 29 Jul 2022 07:19:13 +0200
Subject: [PATCH 0237/2449] fix: return promise (#17646)
---
frappe/public/js/frappe/ui/tree.js | 25 ++++++++++++-------------
1 file changed, 12 insertions(+), 13 deletions(-)
diff --git a/frappe/public/js/frappe/ui/tree.js b/frappe/public/js/frappe/ui/tree.js
index c32d92aa32..4b2c0bc12b 100644
--- a/frappe/public/js/frappe/ui/tree.js
+++ b/frappe/public/js/frappe/ui/tree.js
@@ -134,7 +134,7 @@ frappe.ui.Tree = class {
}
reload_node(node) {
- this.load_children(node);
+ return this.load_children(node);
}
toggle() {
@@ -150,21 +150,20 @@ frappe.ui.Tree = class {
}
load_children(node, deep=false) {
- let lab = node.label, value = node.data.value, is_root = node.is_root;
+ const value = node.data.value,
+ is_root = node.is_root;
- if(!deep) {
- frappe.run_serially([
+ return deep
+ ? frappe.run_serially([
+ () => this.get_all_nodes(value, is_root, node.label),
+ data_list => this.render_children_of_all_nodes(data_list),
+ () => this.set_selected_node(node),
+ ])
+ : frappe.run_serially([
() => this.get_nodes(value, is_root),
- (data_set) => this.render_node_children(node, data_set),
- () => this.set_selected_node(node)
+ data_set => this.render_node_children(node, data_set),
+ () => this.set_selected_node(node),
]);
- } else {
- frappe.run_serially([
- () => this.get_all_nodes(value, is_root, lab),
- (data_list) => this.render_children_of_all_nodes(data_list),
- () => this.set_selected_node(node)
- ]);
- }
}
render_children_of_all_nodes(data_list) {
From 6102c1be4e63ec31a51b15eeafa31ef099ef9d35 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Fri, 29 Jul 2022 06:33:06 +0000
Subject: [PATCH 0238/2449] chore: remove obsolete whitelisting from
`get_link_title_doctypes` (#17664)
---
frappe/boot.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/frappe/boot.py b/frappe/boot.py
index e43446f352..ab559d7c4d 100644
--- a/frappe/boot.py
+++ b/frappe/boot.py
@@ -391,7 +391,6 @@ def get_notification_settings():
return frappe.get_cached_doc("Notification Settings", frappe.session.user)
-@frappe.whitelist()
def get_link_title_doctypes():
dts = frappe.get_all("DocType", {"show_title_field_in_link": 1})
custom_dts = frappe.get_all(
From b2cf378918bb55bcb4223277737fb3b8adf1e786 Mon Sep 17 00:00:00 2001
From: vishdha
Date: Fri, 29 Jul 2022 14:52:50 +0530
Subject: [PATCH 0239/2449] fix: Dropdown selection list in Reports should be
translatable
---
frappe/public/js/frappe/list/list_view_select.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/list/list_view_select.js b/frappe/public/js/frappe/list/list_view_select.js
index 475019d6c1..f531516f55 100644
--- a/frappe/public/js/frappe/list/list_view_select.js
+++ b/frappe/public/js/frappe/list/list_view_select.js
@@ -235,7 +235,7 @@ frappe.views.ListViewSelect = class ListViewSelect {
// don't repeat
added.push(route);
reports_to_add.push({
- name: r.title || r.name,
+ name: __(r.title || r.name),
route: route
});
}
From fac7460fb8bb37b765cb8cba11d635cb7871c80f Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Fri, 29 Jul 2022 09:40:01 +0000
Subject: [PATCH 0240/2449] chore: remove duplicate field in `get_version`
(#17674)
---
frappe/desk/form/document_follow.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py
index 2e4bcedf5a..c5d611f7f4 100644
--- a/frappe/desk/form/document_follow.py
+++ b/frappe/desk/form/document_follow.py
@@ -163,7 +163,7 @@ def get_version(doctype, doc_name, frequency, user):
timeline = []
filters = get_filters("docname", doc_name, frequency, user)
version = frappe.get_all(
- "Version", filters=filters, fields=["ref_doctype", "data", "modified", "modified", "modified_by"]
+ "Version", filters=filters, fields=["ref_doctype", "data", "modified", "modified_by"]
)
if version:
for v in version:
From a560a7f7aaf2121c305bb8bdca68c8273151e590 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Fri, 29 Jul 2022 13:03:12 +0200
Subject: [PATCH 0241/2449] fix: remove redundant lines
---
frappe/public/js/frappe/model/perm.js | 10 +---------
1 file changed, 1 insertion(+), 9 deletions(-)
diff --git a/frappe/public/js/frappe/model/perm.js b/frappe/public/js/frappe/model/perm.js
index 00640494ea..c6c94ce6c1 100644
--- a/frappe/public/js/frappe/model/perm.js
+++ b/frappe/public/js/frappe/model/perm.js
@@ -30,15 +30,7 @@ $.extend(frappe.perm, {
if (!perms || !perms[permlevel]) return false;
- let perm = !!perms[permlevel][ptype];
-
- if (permlevel === 0 && perm && doc) {
- let docinfo = frappe.model.get_docinfo(doctype, doc.name);
- if (docinfo && !docinfo.permissions[ptype])
- perm = false;
- }
-
- return perm;
+ return !!perms[permlevel][ptype];
},
get_perm: (doctype, doc) => {
From 734bc7681b88042d8979107e1709dff49cdbc87a Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 21 Jul 2022 19:48:46 +0530
Subject: [PATCH 0242/2449] fix(realtime): "X is viewing" doc broken
---
socketio.js | 17 +++++------------
1 file changed, 5 insertions(+), 12 deletions(-)
diff --git a/socketio.js b/socketio.js
index d393d7145b..9efde8c890 100644
--- a/socketio.js
+++ b/socketio.js
@@ -292,21 +292,14 @@ function send_users(args, action) {
const room = action == 'view' ? open_doc_room: get_typing_room(args.socket, args.doctype, args.docname);
- const socketio_room = io.sockets.adapter.rooms[room] || {};
- // for compatibility with both v1.3.7 and 1.4.4
- const clients_dict = ('sockets' in socketio_room) ? socketio_room.sockets : socketio_room;
-
- // socket ids connected to this room
- const clients = Object.keys(clients_dict || {});
+ const clients = Array.from(io.sockets.adapter.rooms.get(room) || []);
let users = [];
- for (let i in io.sockets.sockets) {
- const s = io.sockets.sockets[i];
- if (clients.indexOf(s.id) !== -1) {
- // this socket is connected to the room
- users.push(s.user);
+ io.sockets.sockets.forEach((sock) => {
+ if (clients.includes(sock.id)) {
+ users.push(sock.user);
}
- }
+ })
const emit_event = action == 'view' ? 'doc_viewers' : 'doc_typers';
From c8843e8703b37cadeaa8eedcb91c9e7018ee5883 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Fri, 29 Jul 2022 16:16:59 +0530
Subject: [PATCH 0243/2449] ci: dont install from mariadb repo
---
.github/helper/install_dependencies.sh | 4 +---
1 file changed, 1 insertion(+), 3 deletions(-)
diff --git a/.github/helper/install_dependencies.sh b/.github/helper/install_dependencies.sh
index 694b0a9504..2306ccc94a 100644
--- a/.github/helper/install_dependencies.sh
+++ b/.github/helper/install_dependencies.sh
@@ -9,8 +9,6 @@ install_wkhtmltopdf() {
}
install_wkhtmltopdf &
-curl -LsS -O https://downloads.mariadb.com/MariaDB/mariadb_repo_setup
-sudo bash mariadb_repo_setup --mariadb-server-version=10.6
sudo apt update
-sudo apt install libcups2-dev redis-server libmariadb3 libmariadb-dev mariadb-client
+sudo apt install libcups2-dev redis-server mariadb-client-10.3
From 1b9aef40348eb0cd329f1fa80f9f66d80698c111 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Fri, 29 Jul 2022 20:52:45 +0530
Subject: [PATCH 0244/2449] fix: Rating field value is faulty in list view
(#17672)
---
frappe/public/js/frappe/form/controls/rating.js | 1 +
frappe/public/js/frappe/form/formatters.js | 1 +
2 files changed, 2 insertions(+)
diff --git a/frappe/public/js/frappe/form/controls/rating.js b/frappe/public/js/frappe/form/controls/rating.js
index 981168457a..2bdfcb7d45 100644
--- a/frappe/public/js/frappe/form/controls/rating.js
+++ b/frappe/public/js/frappe/form/controls/rating.js
@@ -72,6 +72,7 @@ frappe.ui.form.ControlRating = class ControlRating extends frappe.ui.form.Contro
set_formatted_input(value) {
let out_of_ratings = this.df.options || 5;
value = value * out_of_ratings;
+ value = Math.round(value * 2) / 2; // roundoff number to nearest 0.5
let el = $(this.input_area).find('svg');
el.children('svg').prevObject.each( function(e) {
if (e < value) {
diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js
index 5a15b4fd45..22225f5501 100644
--- a/frappe/public/js/frappe/form/formatters.js
+++ b/frappe/public/js/frappe/form/formatters.js
@@ -88,6 +88,7 @@ frappe.form.formatters = {
let rating_html = '';
let number_of_stars = docfield.options || 5;
value = value * number_of_stars;
+ value = Math.round(value * 2) / 2; // roundoff number to nearest 0.5
Array.from({length: cint(number_of_stars)}, (_, i) => i + 1).forEach(i => {
rating_html += `
From 16f9a2e811b649317910c2a7cb694151ba89f16c Mon Sep 17 00:00:00 2001
From: phot0n
Date: Sat, 30 Jul 2022 00:03:02 +0530
Subject: [PATCH 0245/2449] fix: clear password when saving account with Oauth
---
frappe/email/doctype/email_account/email_account.py | 1 +
1 file changed, 1 insertion(+)
diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py
index 589ddf42f0..c1ed170b6a 100755
--- a/frappe/email/doctype/email_account/email_account.py
+++ b/frappe/email/doctype/email_account/email_account.py
@@ -90,6 +90,7 @@ class EmailAccount(Document):
if use_oauth:
# no need for awaiting password for oauth
self.awaiting_password = 0
+ self.password = None
elif self.refresh_token:
# clear access & refresh token
From 05c1cb30c65f07cb93e802462e500c492dcb0c19 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 18:59:51 +0530
Subject: [PATCH 0246/2449] fix: get correct doc when checking child table
permission
---
frappe/permissions.py | 41 +++++++++++++++++------------------------
1 file changed, 17 insertions(+), 24 deletions(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index acbdf76989..d07da20a5f 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -70,14 +70,14 @@ def has_permission(
if not user:
user = frappe.session.user
+ if user == "Administrator":
+ return True
+
if not doc and hasattr(doctype, "doctype"):
# first argument can be doc or doctype
doc = doctype
doctype = doc.doctype
- if user == "Administrator":
- return True
-
if frappe.is_table(doctype):
return has_child_table_permission(
doctype, ptype, doc, verbose, user, raise_exception, parent_doctype
@@ -667,32 +667,27 @@ def has_child_table_permission(
raise_exception=True,
parent_doctype=None,
):
- parent_doc = None
-
if child_doc:
- parent_doctype = child_doc.get("parenttype")
- parent_doc = frappe.get_cached_doc(
- {"doctype": parent_doctype, "docname": child_doc.get("parent")}
- )
+ parent_doctype = child_doc.parenttype
- if parent_doctype:
- if not is_parent_valid(child_doctype, parent_doctype):
- frappe.throw(
- _("{0} is not a valid parent DocType for {1}").format(
- frappe.bold(parent_doctype), frappe.bold(child_doctype)
- ),
- title=_("Invalid Parent DocType"),
- )
- else:
+ if not parent_doctype:
frappe.throw(
_("Please specify a valid parent DocType for {0}").format(frappe.bold(child_doctype)),
title=_("Parent DocType Required"),
)
+ if not is_parent_valid(child_doctype, parent_doctype):
+ frappe.throw(
+ _("{0} is not a valid parent DocType for {1}").format(
+ frappe.bold(parent_doctype), frappe.bold(child_doctype)
+ ),
+ title=_("Invalid Parent DocType"),
+ )
+
return has_permission(
parent_doctype,
ptype=ptype,
- doc=parent_doc,
+ doc=getattr(child_doc, "parent_doc", child_doc.parent),
verbose=verbose,
user=user,
raise_exception=raise_exception,
@@ -700,10 +695,8 @@ def has_child_table_permission(
def is_parent_valid(child_doctype, parent_doctype):
- from frappe.core.utils import find
-
parent_meta = frappe.get_meta(parent_doctype)
- child_table_field_exists = find(
- parent_meta.get_table_fields(), lambda d: d.options == child_doctype
+
+ return not parent_meta.istable and any(
+ df.options == child_doctype for df in parent_meta.get_table_fields()
)
- return not parent_meta.istable and child_table_field_exists
From 35671295ee770f0202c8778ba37f08832453c5f7 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 19:51:28 +0530
Subject: [PATCH 0247/2449] fix: handle permlevel
---
frappe/permissions.py | 51 +++++++++++++++++++++++++++++--------------
1 file changed, 35 insertions(+), 16 deletions(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index d07da20a5f..f55a26a3c3 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -667,36 +667,55 @@ def has_child_table_permission(
raise_exception=True,
parent_doctype=None,
):
+ parent_doc = None
+
if child_doc:
+ if isinstance(child_doc, str):
+ child_doc = frappe.get_doc(child_doctype, child_doc)
+
parent_doctype = child_doc.parenttype
+ parent_doc = getattr(child_doc, "parent_doc", None)
+
+ if not parent_doc and child_doc.parent:
+ parent_doc = frappe.get_doc(parent_doctype, child_doc.parent)
+ child_doc.parent_doc = parent_doc
if not parent_doctype:
- frappe.throw(
- _("Please specify a valid parent DocType for {0}").format(frappe.bold(child_doctype)),
- title=_("Parent DocType Required"),
+ push_perm_check_log(
+ _("Please specify a valid parent DocType for {0}").format(frappe.bold(child_doctype))
)
+ return False
- if not is_parent_valid(child_doctype, parent_doctype):
- frappe.throw(
+ parent_meta = parent_doc.meta if parent_doc else frappe.get_meta(parent_doctype)
+
+ if not parent_meta.istable and any(
+ df.options == child_doctype for df in parent_meta.get_table_fields()
+ ):
+ push_perm_check_log(
_("{0} is not a valid parent DocType for {1}").format(
frappe.bold(parent_doctype), frappe.bold(child_doctype)
- ),
- title=_("Invalid Parent DocType"),
+ )
)
+ return False
+
+ if (
+ child_doc
+ and child_doc.parentfield
+ and (
+ parent_meta.get_field(child_doc.parentfield).permlevel
+ not in parent_meta.get_permlevel_access(ptype)
+ )
+ ):
+ push_perm_check_log(
+ _("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype))
+ )
+ return False
return has_permission(
parent_doctype,
ptype=ptype,
- doc=getattr(child_doc, "parent_doc", child_doc.parent),
+ doc=parent_doc,
verbose=verbose,
user=user,
raise_exception=raise_exception,
)
-
-
-def is_parent_valid(child_doctype, parent_doctype):
- parent_meta = frappe.get_meta(parent_doctype)
-
- return not parent_meta.istable and any(
- df.options == child_doctype for df in parent_meta.get_table_fields()
- )
From 30175f426e95e92443ec905bb8f9c15a50e3ed8b Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 20:02:32 +0530
Subject: [PATCH 0248/2449] fix: dont init child doc
---
frappe/permissions.py | 18 ++++++++----------
1 file changed, 8 insertions(+), 10 deletions(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index f55a26a3c3..5c2b9276d8 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -667,18 +667,16 @@ def has_child_table_permission(
raise_exception=True,
parent_doctype=None,
):
- parent_doc = None
-
if child_doc:
if isinstance(child_doc, str):
- child_doc = frappe.get_doc(child_doctype, child_doc)
+ child_doc = frappe.db.get_value(
+ child_doctype,
+ child_doc,
+ ("parent", "parenttype", "parentfield"),
+ as_dict=True,
+ )
parent_doctype = child_doc.parenttype
- parent_doc = getattr(child_doc, "parent_doc", None)
-
- if not parent_doc and child_doc.parent:
- parent_doc = frappe.get_doc(parent_doctype, child_doc.parent)
- child_doc.parent_doc = parent_doc
if not parent_doctype:
push_perm_check_log(
@@ -686,7 +684,7 @@ def has_child_table_permission(
)
return False
- parent_meta = parent_doc.meta if parent_doc else frappe.get_meta(parent_doctype)
+ parent_meta = frappe.get_meta(parent_doctype)
if not parent_meta.istable and any(
df.options == child_doctype for df in parent_meta.get_table_fields()
@@ -714,7 +712,7 @@ def has_child_table_permission(
return has_permission(
parent_doctype,
ptype=ptype,
- doc=parent_doc,
+ doc=child_doc and getattr(child_doc, "parent_doc", child_doc.parent),
verbose=verbose,
user=user,
raise_exception=raise_exception,
From 4c4d9ac60e5bcc3b0a891db64ff87e7522a04c32 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 20:49:25 +0530
Subject: [PATCH 0249/2449] fix: check permlevel only if > 0
---
frappe/permissions.py | 22 ++++++++++------------
1 file changed, 10 insertions(+), 12 deletions(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 5c2b9276d8..7b2b5ae65b 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -667,15 +667,15 @@ def has_child_table_permission(
raise_exception=True,
parent_doctype=None,
):
- if child_doc:
- if isinstance(child_doc, str):
- child_doc = frappe.db.get_value(
- child_doctype,
- child_doc,
- ("parent", "parenttype", "parentfield"),
- as_dict=True,
- )
+ if isinstance(child_doc, str):
+ child_doc = frappe.db.get_value(
+ child_doctype,
+ child_doc,
+ ("parent", "parenttype", "parentfield"),
+ as_dict=True,
+ )
+ if child_doc:
parent_doctype = child_doc.parenttype
if not parent_doctype:
@@ -699,10 +699,8 @@ def has_child_table_permission(
if (
child_doc
and child_doc.parentfield
- and (
- parent_meta.get_field(child_doc.parentfield).permlevel
- not in parent_meta.get_permlevel_access(ptype)
- )
+ and (permlevel := parent_meta.get_field(child_doc.parentfield).permlevel) > 0
+ and permlevel not in parent_meta.get_permlevel_access(ptype)
):
push_perm_check_log(
_("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype))
From 7183efa60d2519ac47bf1ae8a719676b8505f211 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 21:07:02 +0530
Subject: [PATCH 0250/2449] fix: assume parentfield to be set and valid
---
frappe/permissions.py | 1 -
1 file changed, 1 deletion(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 7b2b5ae65b..cde043c816 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -698,7 +698,6 @@ def has_child_table_permission(
if (
child_doc
- and child_doc.parentfield
and (permlevel := parent_meta.get_field(child_doc.parentfield).permlevel) > 0
and permlevel not in parent_meta.get_permlevel_access(ptype)
):
From a63363d433c1036ca7e49ed5041685589a88a5cc Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 21:56:35 +0530
Subject: [PATCH 0251/2449] refactor: `has_child_table_permission` =>
`has_child_permission`
---
frappe/permissions.py | 6 ++----
1 file changed, 2 insertions(+), 4 deletions(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index cde043c816..50e4799410 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -79,9 +79,7 @@ def has_permission(
doctype = doc.doctype
if frappe.is_table(doctype):
- return has_child_table_permission(
- doctype, ptype, doc, verbose, user, raise_exception, parent_doctype
- )
+ return has_child_permission(doctype, ptype, doc, verbose, user, raise_exception, parent_doctype)
meta = frappe.get_meta(doctype)
@@ -658,7 +656,7 @@ def push_perm_check_log(log):
frappe.flags.get("has_permission_check_logs").append(_(log))
-def has_child_table_permission(
+def has_child_permission(
child_doctype,
ptype="read",
child_doc=None,
From 2b873b34dd68b9cf6c0253d22a8ed37bd5a23ceb Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 23:42:38 +0530
Subject: [PATCH 0252/2449] refactor: remove `verbose` parameter; add `user`
parameter to `Meta.get_permlevel_access`
---
frappe/model/meta.py | 4 ++--
frappe/permissions.py | 19 ++++++++++++++-----
2 files changed, 16 insertions(+), 7 deletions(-)
diff --git a/frappe/model/meta.py b/frappe/model/meta.py
index 014dd5faf1..51f9241e58 100644
--- a/frappe/model/meta.py
+++ b/frappe/model/meta.py
@@ -539,9 +539,9 @@ class Meta(Document):
return self.high_permlevel_fields
- def get_permlevel_access(self, permission_type="read", parenttype=None):
+ def get_permlevel_access(self, permission_type="read", parenttype=None, *, user=None):
has_access_to = []
- roles = frappe.get_roles()
+ roles = frappe.get_roles(user)
for perm in self.get_permissions(parenttype):
if perm.role in roles and perm.get(permission_type):
if perm.permlevel not in has_access_to:
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 50e4799410..791b1d0c3e 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -65,8 +65,19 @@ def has_permission(
"""Returns True if user has permission `ptype` for given `doctype`.
If `doc` is passed, it also checks user, share and owner permissions.
- Note: if Table DocType is passed, it always returns True.
+ :param doctype: DocType to check permission for
+ :param ptype: Permission Type to check
+ :param doc: Check User Permissions for specified document.
+ :param verbose: DEPRECATED, will be removed in a future version.
+ :param user: User to check permission for. Defaults to session user.
+ :param raise_exception:
+ DOES NOT raise an exception.
+ If True, will print a message explaining why permission check failed.
+
+ :param parent_doctype:
+ Required when checking permission for a child DocType (unless doc is specified)
"""
+
if not user:
user = frappe.session.user
@@ -79,7 +90,7 @@ def has_permission(
doctype = doc.doctype
if frappe.is_table(doctype):
- return has_child_permission(doctype, ptype, doc, verbose, user, raise_exception, parent_doctype)
+ return has_child_permission(doctype, ptype, doc, user, raise_exception, parent_doctype)
meta = frappe.get_meta(doctype)
@@ -660,7 +671,6 @@ def has_child_permission(
child_doctype,
ptype="read",
child_doc=None,
- verbose=False,
user=None,
raise_exception=True,
parent_doctype=None,
@@ -697,7 +707,7 @@ def has_child_permission(
if (
child_doc
and (permlevel := parent_meta.get_field(child_doc.parentfield).permlevel) > 0
- and permlevel not in parent_meta.get_permlevel_access(ptype)
+ and permlevel not in parent_meta.get_permlevel_access(ptype, user=user)
):
push_perm_check_log(
_("Insufficient Permission Level for {0}").format(frappe.bold(parent_doctype))
@@ -708,7 +718,6 @@ def has_child_permission(
parent_doctype,
ptype=ptype,
doc=child_doc and getattr(child_doc, "parent_doc", child_doc.parent),
- verbose=verbose,
user=user,
raise_exception=raise_exception,
)
From d6aa17cc1409ddadc68b9c08ba8b70a79b3ff6c6 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sun, 31 Jul 2022 00:22:41 +0530
Subject: [PATCH 0253/2449] chore: add deprecation warning everywhere verbose
is used
---
frappe/__init__.py | 17 +++++++++++++----
frappe/model/db_query.py | 1 +
frappe/model/document.py | 11 +++++++----
frappe/permissions.py | 3 ++-
4 files changed, 23 insertions(+), 9 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index e1fa902eba..2f49cab5a6 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -905,15 +905,25 @@ def only_has_select_perm(doctype, user=None, ignore_permissions=False):
def has_permission(
- doctype=None, ptype="read", doc=None, user=None, verbose=False, throw=False, parent_doctype=None
+ doctype=None,
+ ptype="read",
+ doc=None,
+ user=None,
+ verbose=False,
+ throw=False,
+ *,
+ parent_doctype=None,
):
- """Raises `frappe.PermissionError` if not permitted.
+ """
+ Raises `frappe.PermissionError` if not permitted.
:param doctype: DocType for which permission is to be check.
:param ptype: Permission type (`read`, `write`, `create`, `submit`, `cancel`, `amend`). Default: `read`.
: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)."""
+ :param verbose: DEPRECATED, will be removed in a future release.
+ :param parent_doctype: Required when checking permission for a child DocType (unless doc is specified).
+ """
import frappe.permissions
if not doctype and doc:
@@ -923,7 +933,6 @@ def has_permission(
doctype,
ptype,
doc=doc,
- verbose=verbose,
user=user,
raise_exception=throw,
parent_doctype=parent_doctype,
diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py
index a29ede37bf..9ce4cc4942 100644
--- a/frappe/model/db_query.py
+++ b/frappe/model/db_query.py
@@ -97,6 +97,7 @@ class DatabaseQuery:
strict=True,
pluck=None,
ignore_ddl=False,
+ *,
parent_doctype=None,
) -> list:
diff --git a/frappe/model/document.py b/frappe/model/document.py
index cadfa573d0..e4d927feec 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -194,15 +194,18 @@ class Document(BaseDocument):
self.raise_no_permission_to(permlevel or permtype)
def has_permission(self, permtype="read", verbose=False) -> bool:
- """Call `frappe.has_permission` if `self.flags.ignore_permissions`
- is not set.
+ """
+ Call `frappe.has_permission` if `self.flags.ignore_permissions` is not set.
- :param permtype: one of `read`, `write`, `submit`, `cancel`, `delete`"""
+ :param permtype: one of `read`, `write`, `submit`, `cancel`, `delete`
+ :param verbose: DEPRECATED, will be removed in a future release.
+ """
import frappe.permissions
if self.flags.ignore_permissions:
return True
- return frappe.permissions.has_permission(self.doctype, permtype, self, verbose=verbose)
+
+ return frappe.permissions.has_permission(self.doctype, permtype, self)
def raise_no_permission_to(self, perm_type):
"""Raise `frappe.PermissionError`."""
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 791b1d0c3e..e250cb1635 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -60,6 +60,7 @@ def has_permission(
verbose=False,
user=None,
raise_exception=True,
+ *,
parent_doctype=None,
):
"""Returns True if user has permission `ptype` for given `doctype`.
@@ -68,7 +69,7 @@ def has_permission(
:param doctype: DocType to check permission for
:param ptype: Permission Type to check
:param doc: Check User Permissions for specified document.
- :param verbose: DEPRECATED, will be removed in a future version.
+ :param verbose: DEPRECATED, will be removed in a future release.
:param user: User to check permission for. Defaults to session user.
:param raise_exception:
DOES NOT raise an exception.
From 038aed7e27b2219cdc587898c33d0622537e35c8 Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Mon, 1 Aug 2022 09:16:58 +0200
Subject: [PATCH 0254/2449] feat: number shortening in German (#17662)
* feat: number shortening in German
* feat: don't forget Liechtenstein
* feat: number system
- extract into a separate file
- add other countries with indian system
- add german translations
* refactor: get_number_system
---
.../public/js/frappe/utils/number_systems.js | 34 ++++++++++++++
frappe/public/js/frappe/utils/utils.js | 45 +++----------------
frappe/translations/de.csv | 4 ++
3 files changed, 45 insertions(+), 38 deletions(-)
create mode 100644 frappe/public/js/frappe/utils/number_systems.js
diff --git a/frappe/public/js/frappe/utils/number_systems.js b/frappe/public/js/frappe/utils/number_systems.js
new file mode 100644
index 0000000000..2a5cd6c4f7
--- /dev/null
+++ b/frappe/public/js/frappe/utils/number_systems.js
@@ -0,0 +1,34 @@
+export default {
+ default: [
+ {
+ divisor: 1.0e12,
+ symbol: "T"
+ },
+ {
+ divisor: 1.0e9,
+ symbol: "B"
+ },
+ {
+ divisor: 1.0e6,
+ symbol: "M"
+ },
+ {
+ divisor: 1.0e3,
+ symbol: "K"
+ }
+ ],
+ indian: [
+ {
+ divisor: 1.0e7,
+ symbol: "Cr"
+ },
+ {
+ divisor: 1.0e5,
+ symbol: "Lakh"
+ },
+ {
+ divisor: 1.0e3,
+ symbol: "K"
+ }
+ ]
+};
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index 78c9859b35..43128cfd4e 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -2,6 +2,7 @@
// MIT License. See license.txt
import deep_equal from "fast-deep-equal";
+import number_systems from "./number_systems";
frappe.provide("frappe.utils");
@@ -1136,43 +1137,11 @@ Object.assign(frappe.utils, {
},
get_number_system: function (country) {
- let number_system_map = {
- 'India':
- [{
- divisor: 1.0e+7,
- symbol: 'Cr'
- },
- {
- divisor: 1.0e+5,
- symbol: 'Lakh'
- },
- {
- divisor: 1.0e+3,
- symbol: 'K',
- }
- ],
- '':
- [{
- divisor: 1.0e+12,
- symbol: 'T'
- },
- {
- divisor: 1.0e+9,
- symbol: 'B'
- },
- {
- divisor: 1.0e+6,
- symbol: 'M'
- },
- {
- divisor: 1.0e+3,
- symbol: 'K',
- }]
- };
-
- if (!Object.keys(number_system_map).includes(country)) country = '';
-
- return number_system_map[country];
+ if (['Bangladesh', 'India', 'Myanmar', 'Pakistan'].includes(country)) {
+ return number_systems.indian;
+ } else {
+ return number_systems.default;
+ }
},
map_defaults: {
@@ -1342,7 +1311,7 @@ Object.assign(frappe.utils, {
result = no_of_decimals > max_no_of_decimals
? result.toFixed(max_no_of_decimals)
: result;
- return result + ' ' + map.symbol;
+ return result + ' ' + __(map.symbol, null, "Number system");
}
}
diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv
index 2ad0ae62a8..34ffed5597 100644
--- a/frappe/translations/de.csv
+++ b/frappe/translations/de.csv
@@ -4808,3 +4808,7 @@ See all Activity,Alle Aktivitäten anzeigen,
Go to Notification Settings List,Gehe zur Listenansicht Benachrichtigungseinstellungen,
Load more,Mehr laden,
Edit Full Form,Vollständiges Formular bearbeiten,
+K,Tsd,Number system
+M,Mio,Number system
+B,Mrd,Number system
+T,Bio,Number system
From d9e62303a48cfb910061abf984bd55648626c2c7 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 1 Aug 2022 14:20:59 +0530
Subject: [PATCH 0255/2449] fix: translatable short number symbol (#17689)
---
frappe/public/js/frappe/utils/number_systems.js | 8 ++++----
frappe/public/js/frappe/utils/utils.js | 2 +-
2 files changed, 5 insertions(+), 5 deletions(-)
diff --git a/frappe/public/js/frappe/utils/number_systems.js b/frappe/public/js/frappe/utils/number_systems.js
index 2a5cd6c4f7..846341e46d 100644
--- a/frappe/public/js/frappe/utils/number_systems.js
+++ b/frappe/public/js/frappe/utils/number_systems.js
@@ -2,19 +2,19 @@ export default {
default: [
{
divisor: 1.0e12,
- symbol: "T"
+ symbol: __("T", null, "Number system")
},
{
divisor: 1.0e9,
- symbol: "B"
+ symbol: __("B", null, "Number system")
},
{
divisor: 1.0e6,
- symbol: "M"
+ symbol: __("M", null, "Number system")
},
{
divisor: 1.0e3,
- symbol: "K"
+ symbol: __("K", null, "Number system")
}
],
indian: [
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index 43128cfd4e..1a633d04be 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1311,7 +1311,7 @@ Object.assign(frappe.utils, {
result = no_of_decimals > max_no_of_decimals
? result.toFixed(max_no_of_decimals)
: result;
- return result + ' ' + __(map.symbol, null, "Number system");
+ return result + ' ' + symbol;
}
}
From 9485c31f165594bd70b9875ec3596fb0a5bf5f63 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 1 Aug 2022 14:31:53 +0530
Subject: [PATCH 0256/2449] chore: typo
missed out in last commit
---
frappe/public/js/frappe/utils/utils.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js
index 1a633d04be..8dfd57199e 100644
--- a/frappe/public/js/frappe/utils/utils.js
+++ b/frappe/public/js/frappe/utils/utils.js
@@ -1311,7 +1311,7 @@ Object.assign(frappe.utils, {
result = no_of_decimals > max_no_of_decimals
? result.toFixed(max_no_of_decimals)
: result;
- return result + ' ' + symbol;
+ return result + ' ' + map.symbol;
}
}
From c478673367405024ee43a90f47b1d58a12421c2e Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Mon, 1 Aug 2022 14:50:40 +0530
Subject: [PATCH 0257/2449] fix: allow to import time field (#17677)
---
frappe/core/doctype/data_import/importer.py | 20 ++++++++++++++------
frappe/utils/data.py | 5 +++++
2 files changed, 19 insertions(+), 6 deletions(-)
diff --git a/frappe/core/doctype/data_import/importer.py b/frappe/core/doctype/data_import/importer.py
index 9e741ab590..25136db74b 100644
--- a/frappe/core/doctype/data_import/importer.py
+++ b/frappe/core/doctype/data_import/importer.py
@@ -6,7 +6,7 @@ import json
import os
import re
import timeit
-from datetime import date, datetime
+from datetime import date, datetime, time
import frappe
from frappe import _
@@ -937,11 +937,13 @@ class Column:
"""
def guess_date_format(d):
- if isinstance(d, (datetime, date)):
+ if isinstance(d, (datetime, date, time)):
if self.df.fieldtype == "Date":
return "%Y-%m-%d"
if self.df.fieldtype == "Datetime":
return "%Y-%m-%d %H:%M:%S"
+ if self.df.fieldtype == "Time":
+ return "%H:%M:%S"
if isinstance(d, str):
return frappe.utils.guess_date_format(d)
@@ -994,16 +996,22 @@ class Column:
}
)
elif self.df.fieldtype in ("Date", "Time", "Datetime"):
- # guess date format
+ # guess date/time format
self.date_format = self.guess_date_format_for_column()
if not self.date_format:
- self.date_format = "%Y-%m-%d"
+ if self.df.fieldtype == "Time":
+ self.date_format = "%H:%M:%S"
+ format = "HH:mm:ss"
+ else:
+ self.date_format = "%Y-%m-%d"
+ format = "yyyy-mm-dd"
+
self.warnings.append(
{
"col": self.column_number,
"message": _(
- "Date format could not be determined from the values in this column. Defaulting to yyyy-mm-dd."
- ),
+ "{0} format could not be determined from the values in this column. Defaulting to {1}."
+ ).format(self.df.fieldtype, format),
"type": "info",
}
)
diff --git a/frappe/utils/data.py b/frappe/utils/data.py
index 262cc3fc7d..bdef60b930 100644
--- a/frappe/utils/data.py
+++ b/frappe/utils/data.py
@@ -2024,6 +2024,11 @@ def guess_date_format(date_string: str) -> str:
if date_format:
return date_format
+ # check if time format can be guessed
+ time_format = _get_time_format(date_string)
+ if time_format:
+ return time_format
+
# date_string doesnt look like date, it can have a time part too
# split the date string into date and time parts
if " " in date_string:
From 01fbd035a7769b657d18f67920464b749073cde6 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 1 Aug 2022 15:30:03 +0530
Subject: [PATCH 0258/2449] ci: label test requirement (#17691)
[skip ci]
---
.github/labeler.yml | 4 ++++
.github/workflows/labeller.yml | 12 ++++++++++++
frappe/geo/doctype/country/country.py | 2 +-
3 files changed, 17 insertions(+), 1 deletion(-)
create mode 100644 .github/labeler.yml
create mode 100644 .github/workflows/labeller.yml
diff --git a/.github/labeler.yml b/.github/labeler.yml
new file mode 100644
index 0000000000..42e52e553f
--- /dev/null
+++ b/.github/labeler.yml
@@ -0,0 +1,4 @@
+# Any python files modifed but no test files modified
+add-test-cases:
+- any: ['frappe/**/*.py']
+ all: ['!frappe/**/test*.py']
diff --git a/.github/workflows/labeller.yml b/.github/workflows/labeller.yml
new file mode 100644
index 0000000000..a774400611
--- /dev/null
+++ b/.github/workflows/labeller.yml
@@ -0,0 +1,12 @@
+name: "Pull Request Labeler"
+on:
+ pull_request_target:
+ types: [opened, reopened]
+
+jobs:
+ triage:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/labeler@v3
+ with:
+ repo-token: "${{ secrets.GITHUB_TOKEN }}"
diff --git a/frappe/geo/doctype/country/country.py b/frappe/geo/doctype/country/country.py
index c6edb38e94..f6be7a078d 100644
--- a/frappe/geo/doctype/country/country.py
+++ b/frappe/geo/doctype/country/country.py
@@ -1,4 +1,4 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
from frappe.model.document import Document
From 4b9493a9215ec88c4e4fd8b329f5537104bf03c5 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Mon, 1 Aug 2022 17:45:29 +0530
Subject: [PATCH 0259/2449] fix: import translate.js before utils.js (#17693)
---
frappe/public/js/frappe-web.bundle.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe-web.bundle.js b/frappe/public/js/frappe-web.bundle.js
index 21703f83b8..e0e88b7839 100644
--- a/frappe/public/js/frappe-web.bundle.js
+++ b/frappe/public/js/frappe-web.bundle.js
@@ -3,13 +3,13 @@ import "./frappe/class.js";
import "./frappe/polyfill.js";
import "./lib/moment.js";
import "./frappe/provide.js";
+import "./frappe/translate.js";
import "./frappe/form/formatters.js";
import "./frappe/format.js";
import "./frappe/utils/number_format.js";
import "./frappe/utils/utils.js";
import "./frappe/utils/common.js";
import "./frappe/ui/messages.js";
-import "./frappe/translate.js";
import "./frappe/utils/pretty_date.js";
import "./frappe/utils/datetime.js";
import "./frappe/microtemplate.js";
From 2e85a49fd25823978069b8d9d3b8c6b2a6e19862 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Mon, 1 Aug 2022 19:05:13 +0530
Subject: [PATCH 0260/2449] refactor: remove test doctype from core (#17598)
This was commited by mistake, instead of using the test doctype, create
it on demand for tests.
---
frappe/core/doctype/test/__init__.py | 0
frappe/core/doctype/test/test.js | 8 --
frappe/core/doctype/test/test.json | 44 --------
frappe/core/doctype/test/test.py | 75 -------------
frappe/core/doctype/test/test_test.py | 67 -----------
frappe/model/utils/__init__.py | 1 -
frappe/tests/test_virtual_doctype.py | 153 ++++++++++++++++++++++++++
7 files changed, 153 insertions(+), 195 deletions(-)
delete mode 100644 frappe/core/doctype/test/__init__.py
delete mode 100644 frappe/core/doctype/test/test.js
delete mode 100644 frappe/core/doctype/test/test.json
delete mode 100644 frappe/core/doctype/test/test.py
delete mode 100644 frappe/core/doctype/test/test_test.py
create mode 100644 frappe/tests/test_virtual_doctype.py
diff --git a/frappe/core/doctype/test/__init__.py b/frappe/core/doctype/test/__init__.py
deleted file mode 100644
index e69de29bb2..0000000000
diff --git a/frappe/core/doctype/test/test.js b/frappe/core/doctype/test/test.js
deleted file mode 100644
index e423c58686..0000000000
--- a/frappe/core/doctype/test/test.js
+++ /dev/null
@@ -1,8 +0,0 @@
-// Copyright (c) 2021, Frappe Technologies and contributors
-// For license information, please see license.txt
-
-frappe.ui.form.on('test', {
- // refresh: function(frm) {
-
- // }
-});
diff --git a/frappe/core/doctype/test/test.json b/frappe/core/doctype/test/test.json
deleted file mode 100644
index 4187984d2b..0000000000
--- a/frappe/core/doctype/test/test.json
+++ /dev/null
@@ -1,44 +0,0 @@
-{
- "actions": [],
- "creation": "2021-03-31 10:06:57.919697",
- "doctype": "DocType",
- "editable_grid": 1,
- "engine": "InnoDB",
- "field_order": [
- "test"
- ],
- "fields": [
- {
- "fieldname": "test",
- "fieldtype": "Data",
- "label": "Test"
- }
- ],
- "index_web_pages_for_search": 1,
- "is_virtual": 1,
- "links": [],
- "modified": "2022-07-22 03:00:59.560061",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "test",
- "owner": "Administrator",
- "permissions": [
- {
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "share": 1,
- "write": 1
- }
- ],
- "read_only": 1,
- "sort_field": "modified",
- "sort_order": "DESC",
- "states": [],
- "track_changes": 1
-}
\ No newline at end of file
diff --git a/frappe/core/doctype/test/test.py b/frappe/core/doctype/test/test.py
deleted file mode 100644
index 0219fd10ab..0000000000
--- a/frappe/core/doctype/test/test.py
+++ /dev/null
@@ -1,75 +0,0 @@
-# Copyright (c) 2021, Frappe Technologies and contributors
-# License: MIT. See LICENSE
-
-""" This is a virtual doctype controller for test/demo purposes.
-
-- It uses a JSON file on disk as "backend".
-- Key is docname and value is the document itself.
-
-Example:
-{
- "doc1": {"name": "doc1", ...}
- "doc2": {"name": "doc2", ...}
-}
-"""
-import json
-import os
-
-import frappe
-from frappe.model.document import Document
-
-DATA_FILE = "data_file.json"
-
-
-def get_current_data() -> dict[str, dict]:
- """Read data from disk"""
- if not os.path.exists(DATA_FILE):
- return {}
-
- with open(DATA_FILE) as f:
- return json.load(f)
-
-
-def update_data(data: dict[str, dict]) -> None:
- """Flush updated data to disk"""
- with open(DATA_FILE, "w+") as data_file:
- json.dump(data, data_file)
-
-
-class test(Document):
- def db_insert(self, *args, **kwargs):
- d = self.get_valid_dict(convert_dates_to_str=True)
-
- data = get_current_data()
- data[d.name] = d
-
- update_data(data)
-
- def load_from_db(self):
- data = get_current_data()
- d = data.get(self.name)
- super(Document, self).__init__(d)
-
- def db_update(self, *args, **kwargs):
- # For this example insert and update are same operation,
- # it might be different for you
- self.db_insert(*args, **kwargs)
-
- def delete(self):
- data = get_current_data()
- data.pop(self.name, None)
- update_data(data)
-
- @staticmethod
- def get_list(args):
- data = get_current_data()
- return [frappe._dict(doc) for name, doc in data.items()]
-
- @staticmethod
- def get_count(args):
- data = get_current_data()
- return len(data)
-
- @staticmethod
- def get_stats(args):
- return {}
diff --git a/frappe/core/doctype/test/test_test.py b/frappe/core/doctype/test/test_test.py
deleted file mode 100644
index 70ba1fd1e1..0000000000
--- a/frappe/core/doctype/test/test_test.py
+++ /dev/null
@@ -1,67 +0,0 @@
-import json
-import os
-
-import frappe
-from frappe.core.doctype.test.test import DATA_FILE
-from frappe.core.doctype.test.test import test as VirtDocType
-from frappe.desk.form.save import savedocs
-from frappe.tests.utils import FrappeTestCase
-
-
-class Testtest(FrappeTestCase):
- def tearDown(self):
- if os.path.exists(DATA_FILE):
- os.remove(DATA_FILE)
-
- def test_insert_update_and_load_from_desk(self):
- """Insert, update, reload and assert changes"""
-
- frappe.response.docs = []
- doc = json.dumps(
- {
- "docstatus": 0,
- "doctype": "test",
- "name": "new-test-1",
- "__islocal": 1,
- "__unsaved": 1,
- "owner": "Administrator",
- "test": "Original Data",
- }
- )
- savedocs(doc, "Save")
-
- docname = frappe.response.docs[0]["name"]
-
- doc = frappe.get_doc("test", docname)
- doc.test = "New Data"
-
- savedocs(doc.as_json(), "Save")
-
- doc.reload()
- self.assertEqual(doc.test, "New Data")
-
- def test_multiple_doc_insert_and_get_list(self):
- doc1 = frappe.get_doc(doctype="test", test="first").insert()
- doc2 = frappe.get_doc(doctype="test", test="second").insert()
-
- docs = {doc1.name, doc2.name}
-
- doc2.reload()
- doc1.reload()
- updated_docs = {doc1.name, doc2.name}
- self.assertEqual(docs, updated_docs)
-
- listed_docs = {d.name for d in VirtDocType.get_list({})}
- self.assertEqual(docs, listed_docs)
-
- def test_get_count(self):
- args = {"doctype": "test", "filters": [], "fields": []}
- self.assertIsInstance(VirtDocType.get_count(args), int)
-
- def test_delete_doc(self):
- doc = frappe.get_doc(doctype="test", test="data").insert()
-
- frappe.delete_doc(doc.doctype, doc.name)
-
- listed_docs = {d.name for d in VirtDocType.get_list({})}
- self.assertNotIn(doc.name, listed_docs)
diff --git a/frappe/model/utils/__init__.py b/frappe/model/utils/__init__.py
index 2e940decb6..1445999639 100644
--- a/frappe/model/utils/__init__.py
+++ b/frappe/model/utils/__init__.py
@@ -1,6 +1,5 @@
# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
-import io
import re
import frappe
diff --git a/frappe/tests/test_virtual_doctype.py b/frappe/tests/test_virtual_doctype.py
new file mode 100644
index 0000000000..f8a7d1cf81
--- /dev/null
+++ b/frappe/tests/test_virtual_doctype.py
@@ -0,0 +1,153 @@
+import json
+import os
+import unittest
+from unittest.mock import patch
+
+import frappe
+import frappe.modules.utils
+from frappe.core.doctype.doctype.test_doctype import new_doctype
+from frappe.desk.form.save import savedocs
+from frappe.model.document import Document
+
+TEST_DOCTYPE_NAME = "VirtualDoctypeTest"
+
+
+class VirtualDoctypeTest(Document):
+ """This is a virtual doctype controller for test/demo purposes.
+
+ - It uses a JSON file on disk as "backend".
+ - Key is docname and value is the document itself.
+
+ Example:
+ {
+ "doc1": {"name": "doc1", ...}
+ "doc2": {"name": "doc2", ...}
+ }
+ """
+
+ DATA_FILE = "data_file.json"
+
+ @staticmethod
+ def get_current_data() -> dict[str, dict]:
+ """Read data from disk"""
+ if not os.path.exists(VirtualDoctypeTest.DATA_FILE):
+ return {}
+
+ with open(VirtualDoctypeTest.DATA_FILE) as f:
+ return json.load(f)
+
+ @staticmethod
+ def update_data(data: dict[str, dict]) -> None:
+ """Flush updated data to disk"""
+ with open(VirtualDoctypeTest.DATA_FILE, "w+") as data_file:
+ json.dump(data, data_file)
+
+ def db_insert(self, *args, **kwargs):
+ d = self.get_valid_dict(convert_dates_to_str=True)
+
+ data = self.get_current_data()
+ data[d.name] = d
+
+ self.update_data(data)
+
+ def load_from_db(self):
+ data = self.get_current_data()
+ d = data.get(self.name)
+ super(Document, self).__init__(d)
+
+ def db_update(self, *args, **kwargs):
+ # For this example insert and update are same operation,
+ # it might be different for you
+ self.db_insert(*args, **kwargs)
+
+ def delete(self):
+ data = self.get_current_data()
+ data.pop(self.name, None)
+ self.update_data(data)
+
+ @staticmethod
+ def get_list(args):
+ data = VirtualDoctypeTest.get_current_data()
+ return [frappe._dict(doc) for name, doc in data.items()]
+
+ @staticmethod
+ def get_count(args):
+ data = VirtualDoctypeTest.get_current_data()
+ return len(data)
+
+ @staticmethod
+ def get_stats(args):
+ return {}
+
+
+class TestVirtualDoctypes(unittest.TestCase):
+ @classmethod
+ def setUpClass(cls):
+ frappe.flags.allow_doctype_export = True
+ cls.addClassCleanup(frappe.flags.pop, "allow_doctype_export", None)
+
+ vdt = new_doctype(name=TEST_DOCTYPE_NAME, is_virtual=1, custom=0).insert()
+ cls.addClassCleanup(vdt.delete)
+
+ patch_virtual_doc = patch(
+ "frappe.controllers", new={frappe.local.site: {TEST_DOCTYPE_NAME: VirtualDoctypeTest}}
+ )
+ patch_virtual_doc.start()
+ cls.addClassCleanup(patch_virtual_doc.stop)
+
+ def tearDown(self):
+ if os.path.exists(VirtualDoctypeTest.DATA_FILE):
+ os.remove(VirtualDoctypeTest.DATA_FILE)
+
+ def test_insert_update_and_load_from_desk(self):
+ """Insert, update, reload and assert changes"""
+
+ frappe.response.docs = []
+ doc = json.dumps(
+ {
+ "docstatus": 0,
+ "doctype": TEST_DOCTYPE_NAME,
+ "name": "new-doctype-1",
+ "__islocal": 1,
+ "__unsaved": 1,
+ "owner": "Administrator",
+ TEST_DOCTYPE_NAME: "Original Data",
+ }
+ )
+ savedocs(doc, "Save")
+
+ docname = frappe.response.docs[0]["name"]
+
+ doc = frappe.get_doc(TEST_DOCTYPE_NAME, docname)
+ doc.some_fieldname = "New Data"
+
+ savedocs(doc.as_json(), "Save")
+
+ doc.reload()
+ self.assertEqual(doc.some_fieldname, "New Data")
+
+ def test_multiple_doc_insert_and_get_list(self):
+ doc1 = frappe.get_doc(doctype=TEST_DOCTYPE_NAME, some_fieldname="first").insert()
+ doc2 = frappe.get_doc(doctype=TEST_DOCTYPE_NAME, some_fieldname="second").insert()
+
+ docs = {doc1.name, doc2.name}
+
+ doc2.reload()
+ doc1.reload()
+ updated_docs = {doc1.name, doc2.name}
+ self.assertEqual(docs, updated_docs)
+
+ listed_docs = {d.name for d in VirtualDoctypeTest.get_list({})}
+ self.assertEqual(docs, listed_docs)
+
+ def test_get_count(self):
+ args = {"doctype": TEST_DOCTYPE_NAME, "filters": [], "fields": []}
+ self.assertIsInstance(VirtualDoctypeTest.get_count(args), int)
+
+ def test_delete_doc(self):
+ doc = frappe.get_doc(doctype=TEST_DOCTYPE_NAME, some_fieldname="data").insert()
+
+ frappe.delete_doc(doc.doctype, doc.name)
+
+ listed_docs = {d.name for d in VirtualDoctypeTest.get_list({})}
+ self.assertNotIn(doc.name, listed_docs)
From 392752287335284279fa46fdb683ba17b5e5c9c2 Mon Sep 17 00:00:00 2001
From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
Date: Mon, 1 Aug 2022 19:05:30 +0530
Subject: [PATCH 0261/2449] fix: Show Report & Dashboard View for File Doctype
(#17688)
---
frappe/public/js/frappe/list/list_factory.js | 11 ++++++++---
frappe/public/js/frappe/list/list_view_select.js | 5 ++++-
2 files changed, 12 insertions(+), 4 deletions(-)
diff --git a/frappe/public/js/frappe/list/list_factory.js b/frappe/public/js/frappe/list/list_factory.js
index ef48af4937..b0868b56b2 100644
--- a/frappe/public/js/frappe/list/list_factory.js
+++ b/frappe/public/js/frappe/list/list_factory.js
@@ -5,13 +5,18 @@ frappe.provide('frappe.views.list_view');
window.cur_list = null;
frappe.views.ListFactory = class ListFactory extends frappe.views.Factory {
- make (route) {
+ make(route) {
const me = this;
const doctype = route[1];
// List / Gantt / Kanban / etc
+ let view_name = frappe.utils.to_title_case(route[2] || 'List');
+
// File is a special view
- const view_name = doctype !== 'File' ? frappe.utils.to_title_case(route[2] || 'List') : 'File';
+ if (doctype == "File" && !["Report", "Dashboard"].includes(view_name)) {
+ view_name = "File";
+ }
+
let view_class = frappe.views[view_name + 'View'];
if (!view_class) view_class = frappe.views.ListView;
@@ -48,7 +53,7 @@ frappe.views.ListFactory = class ListFactory extends frappe.views.Factory {
const last_route = frappe.route_history.slice(-2)[0];
if (
this.route[0] === 'List' &&
- this.route.length === 2 &&
+ this.route.length === 2 &&
frappe.views.list_view[doctype] &&
last_route &&
last_route[0] === 'List' &&
diff --git a/frappe/public/js/frappe/list/list_view_select.js b/frappe/public/js/frappe/list/list_view_select.js
index f531516f55..c3796e77a9 100644
--- a/frappe/public/js/frappe/list/list_view_select.js
+++ b/frappe/public/js/frappe/list/list_view_select.js
@@ -8,6 +8,9 @@ frappe.views.ListViewSelect = class ListViewSelect {
}
add_view_to_menu(view, action) {
+ if (this.doctype == "File" && view == "List") {
+ view = "File";
+ }
let $el = this.page.add_custom_menu_item(
this.parent,
__(view),
@@ -116,7 +119,7 @@ frappe.views.ListViewSelect = class ListViewSelect {
action: () => this.set_route("tree")
},
Kanban: {
- condition: true,
+ condition: this.doctype != "File",
action: () => this.setup_kanban_boards(),
current_view_handler: () => {
frappe.views.KanbanView.get_kanbans(this.doctype).then(
From e72a02e42c3b640cd853baae3e3e1eb47ceacf3a Mon Sep 17 00:00:00 2001
From: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Date: Mon, 1 Aug 2022 15:38:02 +0200
Subject: [PATCH 0262/2449] feat: translate indian number system symbols
(#17694)
---
frappe/public/js/frappe/utils/number_systems.js | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frappe/public/js/frappe/utils/number_systems.js b/frappe/public/js/frappe/utils/number_systems.js
index 846341e46d..3707b8622f 100644
--- a/frappe/public/js/frappe/utils/number_systems.js
+++ b/frappe/public/js/frappe/utils/number_systems.js
@@ -20,15 +20,15 @@ export default {
indian: [
{
divisor: 1.0e7,
- symbol: "Cr"
+ symbol: __("Cr", null, "Number system")
},
{
divisor: 1.0e5,
- symbol: "Lakh"
+ symbol: __("Lakh", null, "Number system")
},
{
divisor: 1.0e3,
- symbol: "K"
+ symbol: __("K", null, "Number system")
}
]
};
From 61ec02671299e46c34c38c57390220c0a43c648f Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Sat, 30 Jul 2022 22:24:41 +0530
Subject: [PATCH 0263/2449] refactor: improve `frappe.only_for`
---
frappe/__init__.py | 21 +++++++++++--------
.../permitted_documents_for_user.py | 13 +++---------
frappe/permissions.py | 5 +++++
3 files changed, 20 insertions(+), 19 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index e1fa902eba..d017a2cc6b 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -818,21 +818,24 @@ def write_only():
return innfn
-def only_for(roles: list[str] | str, message=False):
- """Raise `frappe.PermissionError` if the user does not have any of the given **Roles**.
+def only_for(roles: list[str] | tuple[str] | str, message=False):
+ """
+ Raises `frappe.PermissionError` if the user does not have any of the permitted roles.
- :param roles: List of roles to check."""
- if local.flags.in_test:
+ :param roles: Permitted role(s)
+ """
+
+ if local.flags.in_test or local.session.user == "Administrator":
return
- if not isinstance(roles, (tuple, list)):
+ if isinstance(roles, str):
roles = (roles,)
- roles = set(roles)
- myroles = set(get_roles())
- if not roles.intersection(myroles):
+
+ if not set(roles).intersection(get_roles()):
if message:
msgprint(
- _("This action is only allowed for {}").format(bold(", ".join(roles))), _("Not Permitted")
+ _("This action is only allowed for {}").format(bold(", ".join(roles))),
+ _("Not Permitted"),
)
raise PermissionError
diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py
index 362cc6b105..a7eff77ed0 100644
--- a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py
+++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.py
@@ -4,19 +4,18 @@
import frappe
import frappe.utils.user
from frappe.model import data_fieldtypes
-from frappe.permissions import check_admin_or_system_manager, rights
+from frappe.permissions import rights
def execute(filters=None):
+ frappe.only_for("System Manager")
+
user, doctype, show_permissions = (
filters.get("user"),
filters.get("doctype"),
filters.get("show_permissions"),
)
- if not validate(user, doctype):
- return [], []
-
columns, fields = get_columns_and_fields(doctype)
data = frappe.get_list(doctype, fields=fields, as_list=True, user=user)
@@ -30,12 +29,6 @@ def execute(filters=None):
return columns, data
-def validate(user, doctype):
- # check if current user is System Manager
- check_admin_or_system_manager()
- return user and doctype
-
-
def get_columns_and_fields(doctype):
columns = [f"Name:Link/{doctype}:200"]
fields = ["`name`"]
diff --git a/frappe/permissions.py b/frappe/permissions.py
index acbdf76989..98786ce789 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -28,6 +28,11 @@ rights = (
def check_admin_or_system_manager(user=None):
+ """
+ DEPRECATED: This function will be removed in version 15.
+ Use `frappe.only_for` instead.
+ """
+
if not user:
user = frappe.session.user
From 56717602b4c9ff6bab78a64b3bc61f71bdef3fb5 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 04:08:50 +0530
Subject: [PATCH 0264/2449] fix: reverse logic for failing permission check
---
frappe/permissions.py | 4 ++--
frappe/tests/test_permissions.py | 26 +++++---------------------
2 files changed, 7 insertions(+), 23 deletions(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index e250cb1635..2fd39fdafd 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -695,8 +695,8 @@ def has_child_permission(
parent_meta = frappe.get_meta(parent_doctype)
- if not parent_meta.istable and any(
- df.options == child_doctype for df in parent_meta.get_table_fields()
+ if parent_meta.istable or all(
+ df.options != child_doctype for df in parent_meta.get_table_fields()
):
push_perm_check_log(
_("{0} is not a valid parent DocType for {1}").format(
diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py
index b4f0402a16..ed0f815d3d 100644
--- a/frappe/tests/test_permissions.py
+++ b/frappe/tests/test_permissions.py
@@ -649,29 +649,13 @@ class TestPermissions(FrappeTestCase):
# reset the user
frappe.set_user(current_user)
- def test_child_table_permissions(self):
+ def test_child_permissions(self):
frappe.set_user("test@example.com")
self.assertIsInstance(frappe.get_list("Has Role", parent_doctype="User", limit=1), list)
- self.assertRaisesRegex(
- frappe.exceptions.ValidationError,
- ".* is not a valid parent DocType for .*",
- frappe.get_list,
- doctype="Has Role",
- parent_doctype="ToDo",
- )
- self.assertRaisesRegex(
- frappe.exceptions.ValidationError,
- "Please specify a valid parent DocType for .*",
- frappe.get_list,
- "Has Role",
- )
- self.assertRaisesRegex(
- frappe.exceptions.ValidationError,
- ".* is not a valid parent DocType for .*",
- frappe.get_list,
- doctype="Has Role",
- parent_doctype="Has Role",
- )
+
+ self.assertRaises(frappe.PermissionError, frappe.get_list, "Has Role")
+ self.assertRaises(frappe.PermissionError, frappe.get_list, "Has Role", parent_doctype="ToDo")
+ self.assertRaises(frappe.PermissionError, frappe.get_list, "Has Role", parent_doctype="Has Role")
def test_select_user(self):
"""If test3@example.com is restricted by a User Permission to see only
From b66f1d44e332f7c91bfdde04eaacf9dab69d1679 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 04:46:41 +0530
Subject: [PATCH 0265/2449] test: add tests for `frappe.get_doc`
---
frappe/tests/test_permissions.py | 25 +++++++++++++++++++++----
1 file changed, 21 insertions(+), 4 deletions(-)
diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py
index ed0f815d3d..64c8bdc3b8 100644
--- a/frappe/tests/test_permissions.py
+++ b/frappe/tests/test_permissions.py
@@ -651,11 +651,28 @@ class TestPermissions(FrappeTestCase):
def test_child_permissions(self):
frappe.set_user("test@example.com")
- self.assertIsInstance(frappe.get_list("Has Role", parent_doctype="User", limit=1), list)
+ self.assertIsInstance(frappe.get_list("DefaultValue", parent_doctype="User", limit=1), list)
- self.assertRaises(frappe.PermissionError, frappe.get_list, "Has Role")
- self.assertRaises(frappe.PermissionError, frappe.get_list, "Has Role", parent_doctype="ToDo")
- self.assertRaises(frappe.PermissionError, frappe.get_list, "Has Role", parent_doctype="Has Role")
+ # frappe.get_list
+ self.assertRaises(frappe.PermissionError, frappe.get_list, "DefaultValue")
+ self.assertRaises(frappe.PermissionError, frappe.get_list, "DefaultValue", parent_doctype="ToDo")
+ self.assertRaises(
+ frappe.PermissionError, frappe.get_list, "DefaultValue", parent_doctype="DefaultValue"
+ )
+
+ # frappe.get_doc
+ user = frappe.get_doc("User", frappe.session.user)
+ doc = user.append("defaults")
+ doc.check_permission()
+
+ # false by permlevel
+ doc = user.append("roles")
+ self.assertRaises(frappe.PermissionError, doc.check_permission)
+
+ # false by user permission
+ user = frappe.get_doc("User", "Administrator")
+ doc = user.append("defaults")
+ self.assertRaises(frappe.PermissionError, doc.check_permission)
def test_select_user(self):
"""If test3@example.com is restricted by a User Permission to see only
From a9b69351e66b5c0bc6163b4fd1ea92a98dff87e5 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 05:01:10 +0530
Subject: [PATCH 0266/2449] test: use different user
---
frappe/tests/test_permissions.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/tests/test_permissions.py b/frappe/tests/test_permissions.py
index 64c8bdc3b8..f48273135a 100644
--- a/frappe/tests/test_permissions.py
+++ b/frappe/tests/test_permissions.py
@@ -650,7 +650,7 @@ class TestPermissions(FrappeTestCase):
frappe.set_user(current_user)
def test_child_permissions(self):
- frappe.set_user("test@example.com")
+ frappe.set_user("test3@example.com")
self.assertIsInstance(frappe.get_list("DefaultValue", parent_doctype="User", limit=1), list)
# frappe.get_list
From 183f60232cbbbe1d16afc6f3ce4d7830b550dc99 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 06:09:04 +0530
Subject: [PATCH 0267/2449] perf: specify reference doctype in filters
---
frappe/desk/form/document_follow.py | 38 +++++++++++++++++------------
1 file changed, 23 insertions(+), 15 deletions(-)
diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py
index c5d611f7f4..f12e44fe61 100644
--- a/frappe/desk/form/document_follow.py
+++ b/frappe/desk/form/document_follow.py
@@ -161,9 +161,14 @@ def get_document_followed_by_user(user):
def get_version(doctype, doc_name, frequency, user):
timeline = []
- filters = get_filters("docname", doc_name, frequency, user)
version = frappe.get_all(
- "Version", filters=filters, fields=["ref_doctype", "data", "modified", "modified_by"]
+ "Version",
+ filters=[
+ ["ref_doctype", "=", doctype],
+ ["docname", "=", doc_name],
+ *_get_filters(frequency, user),
+ ],
+ fields=["data", "modified", "modified_by"],
)
if version:
for v in version:
@@ -186,9 +191,14 @@ def get_comments(doctype, doc_name, frequency, user):
from frappe.core.utils import html2text
timeline = []
- filters = get_filters("reference_name", doc_name, frequency, user)
comments = frappe.get_all(
- "Comment", filters=filters, fields=["content", "modified", "modified_by", "comment_type"]
+ "Comment",
+ filters=[
+ ["reference_doctype", "=", doctype],
+ ["reference_name", "=", doc_name],
+ *_get_filters(frequency, user),
+ ],
+ fields=["content", "modified", "modified_by", "comment_type"],
)
for comment in comments:
if comment.comment_type == "Like":
@@ -306,29 +316,27 @@ def send_weekly_updates():
send_document_follow_mails("Weekly")
-def get_filters(search_by, name, frequency, user):
- filters = []
+def _get_filters(frequency, user):
+ filters = [
+ ["modified_by", "!=", user],
+ ]
if frequency == "Weekly":
- filters = [
- [search_by, "=", name],
+ filters += [
["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(), -7)],
["modified", "<", frappe.utils.nowdate()],
- ["modified_by", "!=", user],
]
+
elif frequency == "Daily":
- filters = [
- [search_by, "=", name],
+ filters += [
["modified", ">", frappe.utils.add_days(frappe.utils.nowdate(), -1)],
["modified", "<", frappe.utils.nowdate()],
- ["modified_by", "!=", user],
]
+
elif frequency == "Hourly":
- filters = [
- [search_by, "=", name],
+ filters += [
["modified", ">", frappe.utils.add_to_date(frappe.utils.now_datetime(), hours=-1)],
["modified", "<", frappe.utils.now_datetime()],
- ["modified_by", "!=", user],
]
return filters
From d3343a6cd8ae2d040ca1b94cc58d076a490644fb Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Tue, 2 Aug 2022 11:35:56 +0530
Subject: [PATCH 0268/2449] ci: mergify v14 hotfix and develop backports
---
.mergify.yml | 20 ++++++++++++++++++++
1 file changed, 20 insertions(+)
diff --git a/.mergify.yml b/.mergify.yml
index a863ee67dd..d6a9272d5f 100644
--- a/.mergify.yml
+++ b/.mergify.yml
@@ -78,6 +78,26 @@ pull_request_rules:
assignees:
- "{{ author }}"
+ - name: backport to version-14-hotfix
+ conditions:
+ - label="backport version-14-hotfix"
+ actions:
+ backport:
+ branches:
+ - version-14-hotfix
+ assignees:
+ - "{{ author }}"
+
+ - name: backport to develop
+ conditions:
+ - label="backport develop"
+ actions:
+ backport:
+ branches:
+ - develop
+ assignees:
+ - "{{ author }}"
+
- name: backport to version-13-pre-release
conditions:
- label="backport version-13-pre-release"
From 3c4840f208dfc75616fdfa68da814f15ca255b2b Mon Sep 17 00:00:00 2001
From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com>
Date: Tue, 2 Aug 2022 11:50:48 +0530
Subject: [PATCH 0269/2449] chore(deps): bump actions/labeler from 3 to 4
(#17698)
Bumps [actions/labeler](https://github.com/actions/labeler) from 3 to 4.
- [Release notes](https://github.com/actions/labeler/releases)
- [Commits](https://github.com/actions/labeler/compare/v3...v4)
---
updated-dependencies:
- dependency-name: actions/labeler
dependency-type: direct:production
update-type: version-update:semver-major
...
Signed-off-by: dependabot[bot]
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
---
.github/workflows/labeller.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/.github/workflows/labeller.yml b/.github/workflows/labeller.yml
index a774400611..97fa4a1a2c 100644
--- a/.github/workflows/labeller.yml
+++ b/.github/workflows/labeller.yml
@@ -7,6 +7,6 @@ jobs:
triage:
runs-on: ubuntu-latest
steps:
- - uses: actions/labeler@v3
+ - uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
From 3863d9bb803d92b49f01095071719991d95d9a86 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 2 Aug 2022 14:49:04 +0530
Subject: [PATCH 0270/2449] fix(global_search): Trigger rebuilding on Custom
Field's property change
---
frappe/custom/doctype/customize_form/customize_form.py | 5 +++++
frappe/utils/global_search.py | 3 ---
2 files changed, 5 insertions(+), 3 deletions(-)
diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py
index 4923bfc525..dc625d1a58 100644
--- a/frappe/custom/doctype/customize_form/customize_form.py
+++ b/frappe/custom/doctype/customize_form/customize_form.py
@@ -374,6 +374,9 @@ class CustomizeForm(Document):
d.insert()
df.fieldname = d.fieldname
+ if df.get("in_global_search"):
+ self.flags.rebuild_doctype_for_global_search = True
+
def update_in_custom_field(self, df, i):
meta = frappe.get_meta(self.doc_type)
meta_df = meta.get("fields", {"fieldname": df.fieldname})
@@ -387,6 +390,8 @@ class CustomizeForm(Document):
if df.get(prop) != custom_field.get(prop):
if prop == "fieldtype":
self.validate_fieldtype_change(df, meta_df[0].get(prop), df.get(prop))
+ if prop == "in_global_search":
+ self.flags.rebuild_doctype_for_global_search = True
custom_field.set(prop, df.get(prop))
changed = True
diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py
index 14dcc0fdda..ba22251f6c 100644
--- a/frappe/utils/global_search.py
+++ b/frappe/utils/global_search.py
@@ -73,9 +73,6 @@ def rebuild_for_doctype(doctype):
if frappe.local.conf.get("disable_global_search"):
return
- if frappe.local.conf.get("disable_global_search"):
- return
-
def _get_filters():
filters = frappe._dict({"docstatus": ["!=", 2]})
if meta.has_field("enabled"):
From 2d1fe02dba4c02ce898e6187e494df34d5415c3e Mon Sep 17 00:00:00 2001
From: P-Godfroid <109596710+P-Godfroid@users.noreply.github.com>
Date: Tue, 2 Aug 2022 11:50:19 +0200
Subject: [PATCH 0271/2449] fix: Append to condition misleading message
(#17696)
* Update Append to doctype description
Hello,
To append email to a doctype to enable automatic creation, there are two required fields listed

However, there is a third one required which is lacking in the above description (as seen below), because the email settings of the document must be enabled.

I propose to simply modify the sentence to the following :
Append as communication against this DocType (must have fields ("Status", "Subject") and "Sender" defined in the related doctype Email Settings).
To avoid any problem, in the code, it becomes the following :
Append as communication against this DocType (must have fields (\"Status\", \"Subject\") and \"Sender\" defined in the related doctype Email Settings).
The same must be applied in email_domain.json and in lots of csv in translations of course.
Pierre
* Update email_domain.json
* Updated sentence email account
* Updated sentence of email domain
* More brackets
* More brackets
* Typo
* Update email_account.json
* No dot
---
frappe/email/doctype/email_account/email_account.json | 4 ++--
frappe/email/doctype/email_domain/email_domain.json | 4 ++--
2 files changed, 4 insertions(+), 4 deletions(-)
diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json
index 9395526fe4..740f6039ed 100644
--- a/frappe/email/doctype/email_account/email_account.json
+++ b/frappe/email/doctype/email_account/email_account.json
@@ -214,7 +214,7 @@
},
{
"depends_on": "eval: doc.enable_incoming && !doc.use_imap",
- "description": "Append as communication against this DocType (must have fields, \"Status\", \"Subject\")",
+ "description": "Append as communication against this DocType (must have field \"Status\" and both \"Sender\" and \"Subject\" defined in the related doctype Email Settings)",
"fieldname": "append_to",
"fieldtype": "Link",
"hide_days": 1,
@@ -630,4 +630,4 @@
"sort_order": "DESC",
"states": [],
"track_changes": 1
-}
\ No newline at end of file
+}
diff --git a/frappe/email/doctype/email_domain/email_domain.json b/frappe/email/doctype/email_domain/email_domain.json
index a4ca19a0bd..aba5a8569f 100644
--- a/frappe/email/doctype/email_domain/email_domain.json
+++ b/frappe/email/doctype/email_domain/email_domain.json
@@ -73,7 +73,7 @@
"label": "Attachment Limit (MB)"
},
{
- "description": "Append as communication against this DocType (must have fields, \"Status\", \"Subject\")",
+ "description": "Append as communication against this DocType (must have field \"Status\" and both \"Sender\" and \"Subject\" defined in the related doctype Email Settings)",
"fieldname": "append_to",
"fieldtype": "Link",
"hidden": 1,
@@ -143,4 +143,4 @@
],
"sort_field": "modified",
"sort_order": "DESC"
-}
\ No newline at end of file
+}
From ebb0cd13fee4fcffec119b26afdea1e4e3d542c1 Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 10:08:39 +0000
Subject: [PATCH 0272/2449] perf: reduce DB call in `frappe.client.get`
(#17665)
---
frappe/client.py | 17 +++++------------
1 file changed, 5 insertions(+), 12 deletions(-)
diff --git a/frappe/client.py b/frappe/client.py
index 0b097909ca..129c73a0cf 100644
--- a/frappe/client.py
+++ b/frappe/client.py
@@ -78,16 +78,9 @@ def get(doctype, name=None, filters=None, parent=None):
if frappe.is_table(doctype):
check_parent_permission(parent, doctype)
- if filters and not name:
- name = frappe.db.get_value(doctype, frappe.parse_json(filters))
- if not name:
- frappe.throw(_("No document found for given filters"))
-
- doc = frappe.get_doc(doctype, name)
- if not doc.has_permission("read"):
- raise frappe.PermissionError
-
- return frappe.get_doc(doctype, name).as_dict()
+ doc = frappe.get_doc(doctype, name or frappe.parse_json(filters))
+ doc.check_permission()
+ return doc.as_dict()
@frappe.whitelist()
@@ -144,8 +137,8 @@ def get_value(doctype, fieldname, filters=None, as_dict=True, debug=False, paren
def get_single_value(doctype, field):
if not frappe.has_permission(doctype):
frappe.throw(_("No permission for {0}").format(_(doctype)), frappe.PermissionError)
- value = frappe.db.get_single_value(doctype, field)
- return value
+
+ return frappe.db.get_single_value(doctype, field)
@frappe.whitelist(methods=["POST", "PUT"])
From d88d9f51865cdfe7cbf0a8c906862e7caa64fd08 Mon Sep 17 00:00:00 2001
From: Sagar Sharma
Date: Tue, 2 Aug 2022 15:48:54 +0530
Subject: [PATCH 0273/2449] fix: max_positive_value for Integer types (#17712)
* fix: max_positive_value for Integer types
* style: formatting
Co-authored-by: Ankush Menat
---
frappe/model/base_document.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py
index 0207571e14..1162ceacd3 100644
--- a/frappe/model/base_document.py
+++ b/frappe/model/base_document.py
@@ -20,7 +20,7 @@ from frappe.modules import load_doctype_module
from frappe.utils import cast_fieldtype, cint, cstr, flt, now, sanitize_html, strip_html
from frappe.utils.html_utils import unescape_html
-max_positive_value = {"smallint": 2**15, "int": 2**31, "bigint": 2**63}
+max_positive_value = {"smallint": 2**15 - 1, "int": 2**31 - 1, "bigint": 2**63 - 1}
DOCTYPE_TABLE_FIELDS = [
_dict(fieldname="fields", options="DocField"),
From e32ecb394d5c2e90ed3de9d6e2fc99c0d92323cc Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 26 Apr 2022 11:29:06 +0530
Subject: [PATCH 0274/2449] refactor: Oauth20 tests
Use App client app directly instead of requests. This removes dependency
on needing a web server running for your tests. Also, contributes to
coverage now. We can see which lines are impacted with each use case.
---
frappe/tests/test_oauth20.py | 330 ++++++++++++++++++-----------------
1 file changed, 167 insertions(+), 163 deletions(-)
diff --git a/frappe/tests/test_oauth20.py b/frappe/tests/test_oauth20.py
index a634ace62a..8ebff2bca6 100644
--- a/frappe/tests/test_oauth20.py
+++ b/frappe/tests/test_oauth20.py
@@ -1,88 +1,111 @@
-# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
+# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
# License: MIT. See LICENSE
import unittest
+from typing import TYPE_CHECKING, Dict, Optional
from urllib.parse import parse_qs, urljoin, urlparse
import jwt
import requests
+from werkzeug.test import TestResponse
import frappe
from frappe.integrations.oauth2 import encode_params
from frappe.test_runner import make_test_records
+from frappe.tests.test_api import get_test_client, make_request, suppress_stdout
+
+if TYPE_CHECKING:
+ from frappe.integrations.doctype.social_login_key.social_login_key import SocialLoginKey
-class TestOAuth20(unittest.TestCase):
- def setUp(self):
- make_test_records("OAuth Client")
+class FrappeRequestTestCase(unittest.TestCase):
+ TEST_CLIENT = get_test_client()
+
+ @property
+ def sid(self) -> str:
+ if not getattr(self, "_sid", None):
+ from frappe.auth import CookieManager, LoginManager
+ from frappe.utils import set_request
+
+ set_request(path="/")
+ frappe.local.cookie_manager = CookieManager()
+ frappe.local.login_manager = LoginManager()
+ frappe.local.login_manager.login_as("test@example.com")
+ self._sid = frappe.session.sid
+
+ return self._sid
+
+ def get(self, path: str, params: Optional[Dict] = None, **kwargs) -> TestResponse:
+ return make_request(target=self.TEST_CLIENT.get, args=(path,), kwargs={"data": params, **kwargs})
+
+ def post(self, path, data, **kwargs) -> TestResponse:
+ return make_request(target=self.TEST_CLIENT.post, args=(path,), kwargs={"data": data, **kwargs})
+
+ def put(self, path, data, **kwargs) -> TestResponse:
+ return make_request(target=self.TEST_CLIENT.put, args=(path,), kwargs={"data": data, **kwargs})
+
+ def delete(self, path, **kwargs) -> TestResponse:
+ return make_request(target=self.TEST_CLIENT.delete, args=(path,), kwargs=kwargs)
+
+
+class TestOAuth20(FrappeRequestTestCase):
+ @classmethod
+ def setUpClass(cls):
+ make_test_records("OAuth Client", force=True)
make_test_records("User")
+
client = frappe.get_all("OAuth Client", fields=["*"])[0]
- self.client_id = client.get("client_id")
- self.client_secret = client.get("client_secret")
- self.form_header = {"content-type": "application/x-www-form-urlencoded"}
- self.scope = "all openid"
- self.redirect_uri = "http://localhost"
+ cls.client_id = client.get("client_id")
+ cls.client_secret = client.get("client_secret")
+ cls.form_header = {"content-type": "application/x-www-form-urlencoded"}
+ cls.scope = "all openid"
+ cls.redirect_uri = "http://localhost"
# Set Frappe server URL reqired for id_token generation
- try:
- frappe_login_key = frappe.get_doc("Social Login Key", "frappe")
- except frappe.DoesNotExistError:
- frappe_login_key = frappe.new_doc("Social Login Key")
-
+ frappe_login_key: "SocialLoginKey" = frappe.new_doc("Social Login Key")
frappe_login_key.get_social_login_provider("Frappe", initialize=True)
frappe_login_key.base_url = frappe.utils.get_url()
frappe_login_key.enable_social_login = 0
- frappe_login_key.save()
+ frappe_login_key.insert(ignore_if_duplicate=True)
frappe.db.commit()
def test_invalid_login(self):
- self.assertFalse(check_valid_openid_response())
+ with suppress_stdout():
+ self.assertFalse(check_valid_openid_response(client=self))
def test_login_using_authorization_code(self):
update_client_for_auth_code_grant(self.client_id)
- session = requests.Session()
- login(session)
-
- redirect_destination = None
-
# Go to Authorize url
- try:
- session.get(
- get_full_url("/api/method/frappe.integrations.oauth2.authorize"),
- params=encode_params(
- {
- "client_id": self.client_id,
- "scope": self.scope,
- "response_type": "code",
- "redirect_uri": self.redirect_uri,
- }
- ),
- )
- except requests.exceptions.ConnectionError as ex:
- redirect_destination = ex.request.url
-
- # Get authorization code from redirected URL
- query = parse_qs(urlparse(redirect_destination).query)
+ resp = self.get(
+ "/api/method/frappe.integrations.oauth2.authorize",
+ {
+ "sid": self.sid,
+ "client_id": self.client_id,
+ "scope": self.scope,
+ "response_type": "code",
+ "redirect_uri": self.redirect_uri,
+ },
+ follow_redirects=True,
+ )
+ query = parse_qs(resp.request.environ["QUERY_STRING"])
auth_code = query.get("code")[0]
# Request for bearer token
- token_response = requests.post(
- get_full_url("/api/method/frappe.integrations.oauth2.get_token"),
+ token_response = self.post(
+ "/api/method/frappe.integrations.oauth2.get_token",
headers=self.form_header,
- data=encode_params(
- {
- "grant_type": "authorization_code",
- "code": auth_code,
- "redirect_uri": self.redirect_uri,
- "client_id": self.client_id,
- "scope": self.scope,
- }
- ),
+ data={
+ "grant_type": "authorization_code",
+ "code": auth_code,
+ "redirect_uri": self.redirect_uri,
+ "client_id": self.client_id,
+ "scope": self.scope,
+ },
)
# Parse bearer token json
- bearer_token = token_response.json()
+ bearer_token = token_response.json
self.assertTrue(bearer_token.get("access_token"))
self.assertTrue(bearer_token.get("expires_in"))
@@ -90,7 +113,9 @@ class TestOAuth20(unittest.TestCase):
self.assertTrue(bearer_token.get("refresh_token"))
self.assertTrue(bearer_token.get("scope"))
self.assertTrue(bearer_token.get("token_type") == "Bearer")
- self.assertTrue(check_valid_openid_response(bearer_token.get("access_token")))
+ self.assertTrue(
+ check_valid_openid_response(access_token=bearer_token.get("access_token"), client=self)
+ )
decoded_token = self.decode_id_token(bearer_token.get("id_token"))
self.assertEqual(decoded_token["email"], "test@example.com")
@@ -98,51 +123,41 @@ class TestOAuth20(unittest.TestCase):
def test_login_using_authorization_code_with_pkce(self):
update_client_for_auth_code_grant(self.client_id)
- session = requests.Session()
- login(session)
-
- redirect_destination = None
-
# Go to Authorize url
- try:
- session.get(
- get_full_url("/api/method/frappe.integrations.oauth2.authorize"),
- params=encode_params(
- {
- "client_id": self.client_id,
- "scope": self.scope,
- "response_type": "code",
- "redirect_uri": self.redirect_uri,
- "code_challenge_method": "S256",
- "code_challenge": "21XaP8MJjpxCMRxgEzBP82sZ73PRLqkyBUta1R309J0",
- }
- ),
- )
- except requests.exceptions.ConnectionError as ex:
- redirect_destination = ex.request.url
+ resp = self.get(
+ "/api/method/frappe.integrations.oauth2.authorize",
+ {
+ "sid": self.sid,
+ "client_id": self.client_id,
+ "scope": self.scope,
+ "response_type": "code",
+ "redirect_uri": self.redirect_uri,
+ "code_challenge_method": "S256",
+ "code_challenge": "21XaP8MJjpxCMRxgEzBP82sZ73PRLqkyBUta1R309J0",
+ },
+ follow_redirects=True,
+ )
# Get authorization code from redirected URL
- query = parse_qs(urlparse(redirect_destination).query)
+ query = parse_qs(resp.request.environ["QUERY_STRING"])
auth_code = query.get("code")[0]
# Request for bearer token
- token_response = requests.post(
- get_full_url("/api/method/frappe.integrations.oauth2.get_token"),
+ token_response = self.post(
+ "/api/method/frappe.integrations.oauth2.get_token",
headers=self.form_header,
- data=encode_params(
- {
- "grant_type": "authorization_code",
- "code": auth_code,
- "redirect_uri": self.redirect_uri,
- "client_id": self.client_id,
- "scope": self.scope,
- "code_verifier": "420",
- }
- ),
+ data={
+ "grant_type": "authorization_code",
+ "code": auth_code,
+ "redirect_uri": self.redirect_uri,
+ "client_id": self.client_id,
+ "scope": self.scope,
+ "code_verifier": "420",
+ },
)
# Parse bearer token json
- bearer_token = token_response.json()
+ bearer_token = token_response.json
self.assertTrue(bearer_token.get("access_token"))
self.assertTrue(bearer_token.get("id_token"))
@@ -157,51 +172,41 @@ class TestOAuth20(unittest.TestCase):
client.save()
frappe.db.commit()
- session = requests.Session()
- login(session)
-
- redirect_destination = None
-
# Go to Authorize url
- try:
- session.get(
- get_full_url("/api/method/frappe.integrations.oauth2.authorize"),
- params=encode_params(
- {
- "client_id": self.client_id,
- "scope": self.scope,
- "response_type": "code",
- "redirect_uri": self.redirect_uri,
- }
- ),
- )
- except requests.exceptions.ConnectionError as ex:
- redirect_destination = ex.request.url
+ resp = self.get(
+ "/api/method/frappe.integrations.oauth2.authorize",
+ {
+ "sid": self.sid,
+ "client_id": self.client_id,
+ "scope": self.scope,
+ "response_type": "code",
+ "redirect_uri": self.redirect_uri,
+ },
+ follow_redirects=True,
+ )
# Get authorization code from redirected URL
- query = parse_qs(urlparse(redirect_destination).query)
+ query = parse_qs(resp.request.environ["QUERY_STRING"])
auth_code = query.get("code")[0]
# Request for bearer token
- token_response = requests.post(
- get_full_url("/api/method/frappe.integrations.oauth2.get_token"),
+ token_response = self.post(
+ "/api/method/frappe.integrations.oauth2.get_token",
headers=self.form_header,
- data=encode_params(
- {
- "grant_type": "authorization_code",
- "code": auth_code,
- "redirect_uri": self.redirect_uri,
- "client_id": self.client_id,
- }
- ),
+ data={
+ "grant_type": "authorization_code",
+ "code": auth_code,
+ "redirect_uri": self.redirect_uri,
+ "client_id": self.client_id,
+ },
)
# Parse bearer token json
- bearer_token = token_response.json()
+ bearer_token = token_response.json
# Revoke Token
- revoke_token_response = requests.post(
- get_full_url("/api/method/frappe.integrations.oauth2.revoke_token"),
+ revoke_token_response = self.post(
+ "/api/method/frappe.integrations.oauth2.revoke_token",
headers=self.form_header,
data={"token": bearer_token.get("access_token")},
)
@@ -209,7 +214,9 @@ class TestOAuth20(unittest.TestCase):
self.assertTrue(revoke_token_response.status_code == 200)
# Check revoked token
- self.assertFalse(check_valid_openid_response(bearer_token.get("access_token")))
+ self.assertFalse(
+ check_valid_openid_response(access_token=bearer_token.get("access_token"), client=self)
+ )
def test_resource_owner_password_credentials_grant(self):
client = frappe.get_doc("OAuth Client", self.client_id)
@@ -219,31 +226,32 @@ class TestOAuth20(unittest.TestCase):
frappe.db.commit()
# Request for bearer token
- token_response = requests.post(
- get_full_url("/api/method/frappe.integrations.oauth2.get_token"),
+ token_response = self.post(
+ "/api/method/frappe.integrations.oauth2.get_token",
+ data={
+ "grant_type": "password",
+ "username": "test@example.com",
+ "password": "Eastern_43A1W",
+ "client_id": self.client_id,
+ "scope": self.scope,
+ },
headers=self.form_header,
- data=encode_params(
- {
- "grant_type": "password",
- "username": "test@example.com",
- "password": "Eastern_43A1W",
- "client_id": self.client_id,
- "scope": self.scope,
- }
- ),
)
# Parse bearer token json
- bearer_token = token_response.json()
+ bearer_token = token_response.json
# Check token for valid response
- self.assertTrue(check_valid_openid_response(bearer_token.get("access_token")))
+ self.assertTrue(
+ check_valid_openid_response(access_token=bearer_token.get("access_token"), client=self)
+ )
def test_login_using_implicit_token(self):
oauth_client = frappe.get_doc("OAuth Client", self.client_id)
oauth_client.grant_type = "Implicit"
oauth_client.response_type = "Token"
oauth_client.save()
+ oauth_client_before = oauth_client.get_doc_before_save()
frappe.db.commit()
session = requests.Session()
@@ -274,41 +282,34 @@ class TestOAuth20(unittest.TestCase):
self.assertTrue(response_dict.get("scope"))
self.assertTrue(response_dict.get("token_type"))
self.assertTrue(check_valid_openid_response(response_dict.get("access_token")[0]))
+ oauth_client.delete(force=True)
+ oauth_client_before.insert()
+ frappe.db.commit()
def test_openid_code_id_token(self):
client = update_client_for_auth_code_grant(self.client_id)
-
- session = requests.Session()
- login(session)
-
- redirect_destination = None
-
nonce = frappe.generate_hash()
# Go to Authorize url
- try:
- session.get(
- get_full_url("/api/method/frappe.integrations.oauth2.authorize"),
- params=encode_params(
- {
- "client_id": self.client_id,
- "scope": self.scope,
- "response_type": "code",
- "redirect_uri": self.redirect_uri,
- "nonce": nonce,
- }
- ),
- )
- except requests.exceptions.ConnectionError as ex:
- redirect_destination = ex.request.url
+ resp = self.get(
+ "/api/method/frappe.integrations.oauth2.authorize",
+ {
+ "client_id": self.client_id,
+ "scope": self.scope,
+ "response_type": "code",
+ "redirect_uri": self.redirect_uri,
+ "nonce": nonce,
+ },
+ follow_redirects=True,
+ )
# Get authorization code from redirected URL
- query = parse_qs(urlparse(redirect_destination).query)
+ query = parse_qs(resp.request.environ["QUERY_STRING"])
auth_code = query.get("code")[0]
# Request for bearer token
- token_response = requests.post(
- get_full_url("/api/method/frappe.integrations.oauth2.get_token"),
+ token_response = self.post(
+ "/api/method/frappe.integrations.oauth2.get_token",
headers=self.form_header,
data=encode_params(
{
@@ -322,7 +323,7 @@ class TestOAuth20(unittest.TestCase):
)
# Parse bearer token json
- bearer_token = token_response.json()
+ bearer_token = token_response.json
payload = self.decode_id_token(bearer_token.get("id_token"))
self.assertEqual(payload["email"], "test@example.com")
@@ -338,17 +339,20 @@ class TestOAuth20(unittest.TestCase):
)
-def check_valid_openid_response(access_token=None):
+def check_valid_openid_response(access_token=None, client: "FrappeRequestTestCase" = None):
"""Return True for valid response."""
# Use token in header
headers = {}
+ URL = "/api/method/frappe.integrations.oauth2.openid_profile"
+
if access_token:
- headers["Authorization"] = "Bearer " + access_token
+ headers["Authorization"] = f"Bearer {access_token}"
# check openid for email test@example.com
- openid_response = requests.get(
- get_full_url("/api/method/frappe.integrations.oauth2.openid_profile"), headers=headers
- )
+ if client:
+ openid_response = client.get(URL, headers=headers)
+ else:
+ openid_response = requests.get(get_full_url(URL), headers=headers)
return openid_response.status_code == 200
From d6ba7caf923271380b9875cb11ac07fb2c91446c Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 26 Apr 2022 11:46:59 +0530
Subject: [PATCH 0275/2449] chore: Add typing for Document.doc_before_save
---
frappe/model/document.py | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index cadfa573d0..a183acae63 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -419,7 +419,7 @@ class Document(BaseDocument):
df.options, {"parent": self.name, "parenttype": self.doctype, "parentfield": fieldname}
)
- def get_doc_before_save(self):
+ def get_doc_before_save(self) -> "Document":
return getattr(self, "_doc_before_save", None)
def has_value_changed(self, fieldname):
From b14f8f4e038020a548657a1ea708b2ebbf38067b Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 26 Apr 2022 11:47:50 +0530
Subject: [PATCH 0276/2449] feat(minor): Expose force to doc.delete
---
frappe/model/document.py | 8 ++++++--
1 file changed, 6 insertions(+), 2 deletions(-)
diff --git a/frappe/model/document.py b/frappe/model/document.py
index a183acae63..c5b6607da6 100644
--- a/frappe/model/document.py
+++ b/frappe/model/document.py
@@ -1025,10 +1025,14 @@ class Document(BaseDocument):
"""Rename the document to `name`. This transforms the current object."""
return self._rename(name=name, merge=merge, force=force, validate_rename=validate_rename)
- def delete(self, ignore_permissions=False):
+ def delete(self, ignore_permissions=False, force=False):
"""Delete document."""
return frappe.delete_doc(
- self.doctype, self.name, ignore_permissions=ignore_permissions, flags=self.flags
+ self.doctype,
+ self.name,
+ ignore_permissions=ignore_permissions,
+ flags=self.flags,
+ force=force,
)
def run_before_save_methods(self):
From 7f2c9e84b34a98e5579f088e7fd01d12f1aadf5a Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Tue, 26 Apr 2022 11:48:37 +0530
Subject: [PATCH 0277/2449] feat(minor): Expose use_cookies kwarg to test
client
---
frappe/tests/test_oauth20.py | 2 +-
frappe/utils/__init__.py | 4 ++--
2 files changed, 3 insertions(+), 3 deletions(-)
diff --git a/frappe/tests/test_oauth20.py b/frappe/tests/test_oauth20.py
index 8ebff2bca6..a5bd3739d3 100644
--- a/frappe/tests/test_oauth20.py
+++ b/frappe/tests/test_oauth20.py
@@ -35,7 +35,7 @@ class FrappeRequestTestCase(unittest.TestCase):
return self._sid
- def get(self, path: str, params: Optional[Dict] = None, **kwargs) -> TestResponse:
+ def get(self, path: str, params: dict | None = None, **kwargs) -> TestResponse:
return make_request(target=self.TEST_CLIENT.get, args=(path,), kwargs={"data": params, **kwargs})
def post(self, path, data, **kwargs) -> TestResponse:
diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py
index 47eae314f7..db176a5c0b 100644
--- a/frappe/utils/__init__.py
+++ b/frappe/utils/__init__.py
@@ -525,11 +525,11 @@ def touch_file(path):
return path
-def get_test_client() -> Client:
+def get_test_client(use_cookies=True) -> Client:
"""Returns an test instance of the Frappe WSGI"""
from frappe.app import application
- return Client(application)
+ return Client(application, use_cookies=use_cookies)
def get_hook_method(hook_name, fallback=None):
From 53118367b2c38b4c8182191d30e0ec0b98c67ebf Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 19:08:02 +0530
Subject: [PATCH 0278/2449] fix: use warn util
---
frappe/permissions.py | 11 +++++++----
1 file changed, 7 insertions(+), 4 deletions(-)
diff --git a/frappe/permissions.py b/frappe/permissions.py
index 98786ce789..50d7366626 100644
--- a/frappe/permissions.py
+++ b/frappe/permissions.py
@@ -28,10 +28,13 @@ rights = (
def check_admin_or_system_manager(user=None):
- """
- DEPRECATED: This function will be removed in version 15.
- Use `frappe.only_for` instead.
- """
+ from frappe.utils.commands import warn
+
+ warn(
+ "The function check_admin_or_system_manager will be deprecated in version 15."
+ 'Please use frappe.only_for("System Manager") instead.',
+ category=PendingDeprecationWarning,
+ )
if not user:
user = frappe.session.user
From 74c26ac34d32ca4e35c2f8b62523512ddab00bec Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 19:16:53 +0530
Subject: [PATCH 0279/2449] fix: use `throw`
---
frappe/__init__.py | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index d017a2cc6b..c2a24e6ad0 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -832,12 +832,14 @@ def only_for(roles: list[str] | tuple[str] | str, message=False):
roles = (roles,)
if not set(roles).intersection(get_roles()):
- if message:
- msgprint(
- _("This action is only allowed for {}").format(bold(", ".join(roles))),
- _("Not Permitted"),
- )
- raise PermissionError
+ if not message:
+ raise PermissionError
+
+ throw(
+ _("This action is only allowed for {}").format(bold(", ".join(roles))),
+ PermissionError,
+ _("Not Permitted"),
+ )
def get_domain_data(module):
From eb1c9fff689cf5235db6b3fda5eaed367512788e Mon Sep 17 00:00:00 2001
From: Sagar Vora
Date: Tue, 2 Aug 2022 19:27:16 +0530
Subject: [PATCH 0280/2449] fix: translate each role
---
frappe/__init__.py | 4 +++-
1 file changed, 3 insertions(+), 1 deletion(-)
diff --git a/frappe/__init__.py b/frappe/__init__.py
index c2a24e6ad0..16e5f5f53c 100644
--- a/frappe/__init__.py
+++ b/frappe/__init__.py
@@ -836,7 +836,9 @@ def only_for(roles: list[str] | tuple[str] | str, message=False):
raise PermissionError
throw(
- _("This action is only allowed for {}").format(bold(", ".join(roles))),
+ _("This action is only allowed for {}").format(
+ ", ".join(bold(_(role)) for role in roles),
+ ),
PermissionError,
_("Not Permitted"),
)
From 9b620bb648df0ff66b2fdc3208911e5c6eb51a27 Mon Sep 17 00:00:00 2001
From: Ritwik Puri
Date: Wed, 3 Aug 2022 11:53:10 +0530
Subject: [PATCH 0281/2449] fix: set /app as redirect for pageview home button
(#17715)
---
frappe/public/js/frappe/views/pageview.js | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/frappe/public/js/frappe/views/pageview.js b/frappe/public/js/frappe/views/pageview.js
index c8944e272a..e8c9e34a7e 100644
--- a/frappe/public/js/frappe/views/pageview.js
+++ b/frappe/public/js/frappe/views/pageview.js
@@ -138,7 +138,7 @@ frappe.show_message_page = function(opts) {
\
%(img)s\
%(message)s
\
-
%(home)s \
+
%(home)s \
\
', {
img: opts.img || "",
From f7d5cb504a951058e9e6e618de7796fcc9bc4e63 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 3 Aug 2022 12:00:09 +0530
Subject: [PATCH 0282/2449] test(oauth-client): Generate new client for each
test
---
.../doctype/oauth_client/test_records.json | 15 ----------
frappe/tests/test_oauth20.py | 30 ++++++++++++++++---
2 files changed, 26 insertions(+), 19 deletions(-)
delete mode 100644 frappe/integrations/doctype/oauth_client/test_records.json
diff --git a/frappe/integrations/doctype/oauth_client/test_records.json b/frappe/integrations/doctype/oauth_client/test_records.json
deleted file mode 100644
index 11e6338a87..0000000000
--- a/frappe/integrations/doctype/oauth_client/test_records.json
+++ /dev/null
@@ -1,15 +0,0 @@
-[
- {
- "app_name": "_Test OAuth Client",
- "client_secret": "test_client_secret",
- "default_redirect_uri": "http://localhost",
- "docstatus": 0,
- "doctype": "OAuth Client",
- "grant_type": "Authorization Code",
- "name": "test_client_id",
- "redirect_uris": "http://localhost",
- "response_type": "Code",
- "scopes": "all openid",
- "skip_authorization": 1
- }
-]
diff --git a/frappe/tests/test_oauth20.py b/frappe/tests/test_oauth20.py
index a5bd3739d3..544a11d201 100644
--- a/frappe/tests/test_oauth20.py
+++ b/frappe/tests/test_oauth20.py
@@ -51,12 +51,8 @@ class FrappeRequestTestCase(unittest.TestCase):
class TestOAuth20(FrappeRequestTestCase):
@classmethod
def setUpClass(cls):
- make_test_records("OAuth Client", force=True)
make_test_records("User")
- client = frappe.get_all("OAuth Client", fields=["*"])[0]
- cls.client_id = client.get("client_id")
- cls.client_secret = client.get("client_secret")
cls.form_header = {"content-type": "application/x-www-form-urlencoded"}
cls.scope = "all openid"
cls.redirect_uri = "http://localhost"
@@ -69,6 +65,32 @@ class TestOAuth20(FrappeRequestTestCase):
frappe_login_key.insert(ignore_if_duplicate=True)
frappe.db.commit()
+ def setUp(self):
+ self.oauth_client = frappe.new_doc("OAuth Client")
+ self.oauth_client.update(
+ {
+ "app_name": "_Test OAuth Client",
+ "client_secret": "test_client_secret",
+ "default_redirect_uri": "http://localhost",
+ "docstatus": 0,
+ "doctype": "OAuth Client",
+ "grant_type": "Authorization Code",
+ "name": "test_client_id",
+ "redirect_uris": "http://localhost",
+ "response_type": "Code",
+ "scopes": "all openid",
+ "skip_authorization": 1,
+ }
+ )
+ self.oauth_client.insert()
+
+ self.client_id = self.oauth_client.get("client_id")
+ self.client_secret = self.oauth_client.get("client_secret")
+
+ def tearDown(self):
+ self.oauth_client.delete(force=True)
+ frappe.db.rollback()
+
def test_invalid_login(self):
with suppress_stdout():
self.assertFalse(check_valid_openid_response(client=self))
From 750618ca7cd16e90cb7f92c4406ff461dd25849e Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 3 Aug 2022 12:01:12 +0530
Subject: [PATCH 0283/2449] fix: Re-raise original exception from tenacity's
retry
---
frappe/core/doctype/access_log/access_log.py | 6 +++++-
frappe/utils/background_jobs.py | 1 +
2 files changed, 6 insertions(+), 1 deletion(-)
diff --git a/frappe/core/doctype/access_log/access_log.py b/frappe/core/doctype/access_log/access_log.py
index b7a6d77206..ca2909b970 100644
--- a/frappe/core/doctype/access_log/access_log.py
+++ b/frappe/core/doctype/access_log/access_log.py
@@ -35,7 +35,11 @@ def make_access_log(
@frappe.write_only()
-@retry(stop=stop_after_attempt(3), retry=retry_if_exception_type(frappe.DuplicateEntryError))
+@retry(
+ stop=stop_after_attempt(3),
+ retry=retry_if_exception_type(frappe.DuplicateEntryError),
+ reraise=True,
+)
def _make_access_log(
doctype=None,
document=None,
diff --git a/frappe/utils/background_jobs.py b/frappe/utils/background_jobs.py
index 3d3df3504d..dde0d64169 100755
--- a/frappe/utils/background_jobs.py
+++ b/frappe/utils/background_jobs.py
@@ -295,6 +295,7 @@ def validate_queue(queue, default_queue_list=None):
retry=retry_if_exception_type(BusyLoadingError) | retry_if_exception_type(ConnectionError),
stop=stop_after_attempt(10),
wait=wait_fixed(1),
+ reraise=True,
)
def get_redis_conn(username=None, password=None):
if not hasattr(frappe.local, "conf"):
From 2ae50f911ade8fbaac256a692d8a778969c0c557 Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 3 Aug 2022 12:01:36 +0530
Subject: [PATCH 0284/2449] chore: Minimize OAuth Client DocType
---
.../doctype/oauth_client/oauth_client.json | 449 ++----------------
1 file changed, 38 insertions(+), 411 deletions(-)
diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.json b/frappe/integrations/doctype/oauth_client/oauth_client.json
index d0d45c36ab..3368d94fb0 100644
--- a/frappe/integrations/doctype/oauth_client/oauth_client.json
+++ b/frappe/integrations/doctype/oauth_client/oauth_client.json
@@ -1,517 +1,144 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
+ "actions": [],
"creation": "2016-08-24 14:07:21.955052",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "client_id",
+ "app_name",
+ "user",
+ "cb_1",
+ "client_secret",
+ "skip_authorization",
+ "sb_1",
+ "scopes",
+ "cb_3",
+ "redirect_uris",
+ "default_redirect_uri",
+ "sb_advanced",
+ "grant_type",
+ "cb_2",
+ "response_type"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
"fieldname": "client_id",
"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": "App Client ID",
- "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,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "app_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": "App Name",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"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": "User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "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,
- "unique": 0
+ "options": "User"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "cb_1",
- "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,
- "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,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "client_secret",
"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": "App Client Secret",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"description": "If checked, users will not see the Confirm Access dialog.",
"fieldname": "skip_authorization",
"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": "Skip Authorization",
- "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,
- "unique": 0
+ "label": "Skip Authorization"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
"fieldname": "sb_1",
- "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,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "all openid",
"description": "A list of resources which the Client App will have access to after the user allows it. e.g. project",
"fieldname": "scopes",
"fieldtype": "Text",
- "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": "Scopes",
- "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,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "cb_3",
- "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,
- "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,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n e.g. http://hostname//api/method/frappe.www.login.login_via_facebook",
"fieldname": "redirect_uris",
"fieldtype": "Text",
- "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": "Redirect URIs",
- "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,
- "unique": 0
+ "label": "Redirect URIs"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "default_redirect_uri",
"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": "Default Redirect URI",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
"collapsible": 1,
"collapsible_depends_on": "1",
- "columns": 0,
"fieldname": "sb_advanced",
"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": "Advanced Settings",
- "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,
- "unique": 0
+ "label": "Advanced Settings"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "grant_type",
"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": "Grant Type",
- "length": 0,
- "no_copy": 0,
- "options": "Authorization Code\nImplicit",
- "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,
- "unique": 0
+ "options": "Authorization Code\nImplicit"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "cb_2",
- "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,
- "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,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "Code",
"fieldname": "response_type",
"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": "Response Type",
- "length": 0,
- "no_copy": 0,
- "options": "Code\nToken",
- "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,
- "unique": 0
+ "options": "Code\nToken"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-04-07 21:07:39.476360",
+ "links": [],
+ "modified": "2022-08-03 11:51:27.709726",
"modified_by": "Administrator",
"module": "Integrations",
"name": "OAuth Client",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "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
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "app_name",
- "track_changes": 1,
- "track_seen": 0
+ "track_changes": 1
}
\ No newline at end of file
From 85e3ee940353d7b0b517b33815148672e9a8b15b Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 3 Aug 2022 12:22:03 +0530
Subject: [PATCH 0285/2449] chore: Minified DocType JSON notation from old
verbose notation
---
.../doctype/milestone/milestone.json | 183 +---
.../milestone_tracker/milestone_tracker.json | 127 +--
.../address_template/address_template.json | 195 ++--
frappe/contacts/doctype/gender/gender.json | 140 +--
.../doctype/salutation/salutation.json | 170 +---
.../doctype/block_module/block_module.json | 92 +-
.../core/doctype/custom_role/custom_role.json | 200 +---
.../core/doctype/data_export/data_export.json | 304 ++-----
.../doctype/defaultvalue/defaultvalue.json | 121 +--
.../domain_settings/domain_settings.json | 187 +---
.../error_snapshot/error_snapshot.json | 490 +++-------
.../core/doctype/has_domain/has_domain.json | 94 +-
frappe/core/doctype/has_role/has_role.json | 88 +-
frappe/core/doctype/page/page.json | 506 +++--------
.../role_permission_for_page_and_report.json | 390 ++------
.../doctype/sms_parameter/sms_parameter.json | 161 +---
.../success_action/success_action.json | 215 +----
.../transaction_log/transaction_log.json | 420 +--------
.../user_social_login/user_social_login.json | 229 +----
frappe/core/doctype/version/version.json | 300 ++----
frappe/core/doctype/view_log/view_log.json | 133 +--
.../desk/doctype/bulk_update/bulk_update.json | 255 ++----
.../doctype/desktop_icon/desktop_icon.json | 853 +++---------------
.../event_participants.json | 148 +--
.../desk/doctype/list_filter/list_filter.json | 154 +---
.../doctype/note_seen_by/note_seen_by.json | 85 +-
.../email/doctype/email_rule/email_rule.json | 101 +--
.../email_unsubscribe/email_unsubscribe.json | 139 +--
.../unhandled_email/unhandled_email.json | 248 +----
.../doctype/oauth_client/oauth_client.json | 449 +--------
.../oauth_provider_settings.json | 115 +--
.../doctype/webhook_data/webhook_data.json | 159 +---
.../webhook_header/webhook_header.json | 127 +--
.../doctype/print_style/print_style.json | 263 ++----
.../doctype/review_level/review_level.json | 114 +--
.../about_us_team_member.json | 138 +--
.../company_history/company_history.json | 108 +--
.../doctype/help_category/help_category.json | 249 ++---
.../portal_menu_item/portal_menu_item.json | 181 +---
.../website_meta_tag/website_meta_tag.json | 89 +-
.../website_script/website_script.json | 113 +--
.../website_sidebar/website_sidebar.json | 155 +---
.../website_sidebar_item.json | 155 +---
.../website_slideshow/website_slideshow.json | 237 ++---
.../workflow_action_master.json | 117 +--
.../workflow_state/workflow_state.json | 193 ++--
46 files changed, 1958 insertions(+), 7732 deletions(-)
diff --git a/frappe/automation/doctype/milestone/milestone.json b/frappe/automation/doctype/milestone/milestone.json
index 8360ce7bf4..aa2dd35891 100644
--- a/frappe/automation/doctype/milestone/milestone.json
+++ b/frappe/automation/doctype/milestone/milestone.json
@@ -1,230 +1,81 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
+ "actions": [],
"creation": "2019-04-17 09:39:15.647817",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
"engine": "InnoDB",
+ "field_order": [
+ "reference_type",
+ "reference_name",
+ "track_field",
+ "value",
+ "milestone_tracker"
+ ],
"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": "reference_type",
"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": 0,
"label": "Document Type",
- "length": 0,
- "no_copy": 0,
"options": "DocType",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
"reqd": 1,
- "search_index": 1,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "search_index": 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": "reference_name",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Document",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 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": "track_field",
"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": "Track Field",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 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": "value",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Value",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
"read_only": 1,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 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": "milestone_tracker",
"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": "Milestone Tracker",
- "length": 0,
- "no_copy": 0,
- "options": "Milestone Tracker",
- "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
+ "options": "Milestone Tracker"
}
],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 0,
"in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-04-17 16:01:21.430344",
+ "links": [],
+ "modified": "2022-08-03 12:20:55.076769",
"modified_by": "Administrator",
"module": "Automation",
"name": "Milestone",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "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
}
],
"quick_entry": 1,
- "read_only": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
+ "states": [],
"title_field": "reference_type",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.json b/frappe/automation/doctype/milestone_tracker/milestone_tracker.json
index 8e22e3e199..8d4ed94dcd 100644
--- a/frappe/automation/doctype/milestone_tracker/milestone_tracker.json
+++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.json
@@ -1,162 +1,61 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
"autoname": "format:{document_type}-{track_field}",
- "beta": 0,
"creation": "2019-04-17 09:36:41.774774",
- "custom": 0,
"description": "Track milestones for any document",
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
"engine": "InnoDB",
+ "field_order": [
+ "document_type",
+ "track_field",
+ "disabled"
+ ],
"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": "document_type",
"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": 0,
"label": "Document Type to Track",
- "length": 0,
- "no_copy": 0,
"options": "DocType",
- "permlevel": 0,
- "precision": "",
- "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": "track_field",
"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": 0,
"label": "Field to Track",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fetch_if_empty": 0,
+ "default": "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,
- "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
+ "label": "Disabled"
}
],
- "has_web_view": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-04-22 16:03:32.848937",
+ "links": [],
+ "modified": "2022-08-03 12:20:54.955953",
"modified_by": "Administrator",
"module": "Automation",
"name": "Milestone Tracker",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "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
}
],
- "quick_entry": 0,
- "read_only": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/contacts/doctype/address_template/address_template.json b/frappe/contacts/doctype/address_template/address_template.json
index e27d97daad..48eacc0fc7 100644
--- a/frappe/contacts/doctype/address_template/address_template.json
+++ b/frappe/contacts/doctype/address_template/address_template.json
@@ -1,152 +1,65 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:country",
- "beta": 0,
- "creation": "2014-06-05 02:22:36.029850",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:country",
+ "creation": "2014-06-05 02:22:36.029850",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "country",
+ "is_default",
+ "template"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "country",
- "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": "Country",
- "length": 0,
- "no_copy": 0,
- "options": "Country",
- "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": 1,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "country",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Country",
+ "options": "Country",
+ "reqd": 1,
+ "search_index": 1,
+ "unique": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "This format is used if country specific format is not found",
- "fieldname": "is_default",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Is Default",
- "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,
- "unique": 0
- },
+ "default": "0",
+ "description": "This format is used if country specific format is not found",
+ "fieldname": "is_default",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Is Default"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
- "description": "Default Template \nUses Jinja Templating and all the fields of Address (including Custom Fields if any) will be available
\n{{ address_line1 }}<br>\n{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}\n{{ city }}<br>\n{% if state %}{{ state }}<br>{% endif -%}\n{% if pincode %} PIN: {{ pincode }}<br>{% endif -%}\n{{ country }}<br>\n{% if phone %}Phone: {{ phone }}<br>{% endif -%}\n{% if fax %}Fax: {{ fax }}<br>{% endif -%}\n{% if email_id %}Email: {{ email_id }}<br>{% endif -%}\n ",
- "fieldname": "template",
- "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": "Template",
- "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,
- "unique": 0
+ "description": "Default Template \nUses Jinja Templating and all the fields of Address (including Custom Fields if any) will be available
\n{{ address_line1 }}<br>\n{% if address_line2 %}{{ address_line2 }}<br>{% endif -%}\n{{ city }}<br>\n{% if state %}{{ state }}<br>{% endif -%}\n{% if pincode %} PIN: {{ pincode }}<br>{% endif -%}\n{{ country }}<br>\n{% if phone %}Phone: {{ phone }}<br>{% endif -%}\n{% if fax %}Fax: {{ fax }}<br>{% endif -%}\n{% if email_id %}Email: {{ email_id }}<br>{% endif -%}\n ",
+ "fieldname": "template",
+ "fieldtype": "Code",
+ "label": "Template"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-map-marker",
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-04-10 13:09:53.761009",
- "modified_by": "Administrator",
- "module": "Contacts",
- "name": "Address Template",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-map-marker",
+ "links": [],
+ "modified": "2022-08-03 12:20:49.095228",
+ "modified_by": "Administrator",
+ "module": "Contacts",
+ "name": "Address Template",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 0,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 1,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "export": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "set_user_permissions": 1,
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/contacts/doctype/gender/gender.json b/frappe/contacts/doctype/gender/gender.json
index 86a066cf0f..20d0210f05 100644
--- a/frappe/contacts/doctype/gender/gender.json
+++ b/frappe/contacts/doctype/gender/gender.json
@@ -1,113 +1,47 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:gender",
- "beta": 0,
- "creation": "2017-04-10 12:11:36.526508",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "field:gender",
+ "creation": "2017-04-10 12:11:36.526508",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "gender"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "gender",
- "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": "Gender",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "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,
- "unique": 0
+ "fieldname": "gender",
+ "fieldtype": "Data",
+ "label": "Gender",
+ "unique": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-04-10 12:17:04.848338",
- "modified_by": "Administrator",
- "module": "Contacts",
- "name": "Gender",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2022-08-03 12:20:48.408685",
+ "modified_by": "Administrator",
+ "module": "Contacts",
+ "name": "Gender",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "All",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "read": 1,
+ "role": "All"
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/contacts/doctype/salutation/salutation.json b/frappe/contacts/doctype/salutation/salutation.json
index 579f176aa7..c80faf1cda 100644
--- a/frappe/contacts/doctype/salutation/salutation.json
+++ b/frappe/contacts/doctype/salutation/salutation.json
@@ -1,132 +1,60 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:salutation",
- "beta": 0,
- "creation": "2017-04-10 12:17:58.071915",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:salutation",
+ "creation": "2017-04-10 12:17:58.071915",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "salutation"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "salutation",
- "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": "Salutation",
- "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,
- "unique": 0
+ "fieldname": "salutation",
+ "fieldtype": "Data",
+ "label": "Salutation",
+ "unique": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-09-14 12:55:18.855578",
- "modified_by": "Administrator",
- "module": "Contacts",
- "name": "Salutation",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2022-08-03 12:20:48.954912",
+ "modified_by": "Administrator",
+ "module": "Contacts",
+ "name": "Salutation",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "All",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "read": 1,
+ "role": "All"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/block_module/block_module.json b/frappe/core/doctype/block_module/block_module.json
index 64deff66ee..9711aaa001 100644
--- a/frappe/core/doctype/block_module/block_module.json
+++ b/frappe/core/doctype/block_module/block_module.json
@@ -1,71 +1,31 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2015-03-24 14:28:15.882903",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Other",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2015-03-24 14:28:15.882903",
+ "doctype": "DocType",
+ "document_type": "Other",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "module"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "module",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Module",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "precision": "",
- "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,
- "unique": 0
+ "fieldname": "module",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Module",
+ "reqd": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-10-31 19:36:18.586834",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Block Module",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:52.738977",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Block Module",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/core/doctype/custom_role/custom_role.json b/frappe/core/doctype/custom_role/custom_role.json
index 55af8e2acd..7504882caf 100644
--- a/frappe/core/doctype/custom_role/custom_role.json
+++ b/frappe/core/doctype/custom_role/custom_role.json
@@ -1,240 +1,76 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
+ "actions": [],
"allow_import": 1,
- "allow_rename": 0,
"autoname": "hash",
- "beta": 0,
"creation": "2017-02-13 14:53:36.240122",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "page",
+ "report",
+ "permission_rules",
+ "roles",
+ "response",
+ "ref_doctype"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "page",
"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": "Page",
- "length": 0,
- "no_copy": 0,
- "options": "Page",
- "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,
- "unique": 0
+ "options": "Page"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "report",
"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": "Report",
- "length": 0,
- "no_copy": 0,
- "options": "Report",
- "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,
- "unique": 0
+ "options": "Report"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": "Permission Rules",
- "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,
- "unique": 0
+ "label": "Permission Rules"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": "Role",
- "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,
- "unique": 0
+ "options": "Has Role"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "response",
"fieldtype": "HTML",
- "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": "response",
- "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,
- "unique": 0
+ "label": "response"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "ref_doctype",
"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 Document Type",
- "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,
- "unique": 0
+ "label": "Reference Document Type"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
+ "links": [],
+ "modified": "2022-08-03 12:20:52.985554",
"modified_by": "Administrator",
"module": "Core",
"name": "Custom Role",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "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
}
],
- "quick_entry": 0,
"read_only": 1,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/core/doctype/data_export/data_export.json b/frappe/core/doctype/data_export/data_export.json
index 8304430fdb..01a680503d 100644
--- a/frappe/core/doctype/data_export/data_export.json
+++ b/frappe/core/doctype/data_export/data_export.json
@@ -1,250 +1,76 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-03-07 10:09:49.794764",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2018-03-07 10:09:49.794764",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "reference_doctype",
+ "column_break_2",
+ "file_type",
+ "section_break",
+ "filter_list",
+ "fields_multicheck"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reference_doctype",
- "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": "Select Doctype",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "permlevel": 0,
- "precision": "",
- "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_2",
- "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,
- "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "CSV",
- "fieldname": "file_type",
- "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": 0,
- "label": "File Type",
- "length": 0,
- "no_copy": 0,
- "options": "Excel\nCSV",
- "permlevel": 0,
- "precision": "",
- "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "reference_doctype",
- "fieldname": "section_break",
- "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,
- "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_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "filter_list",
- "fieldtype": "HTML",
- "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": "Filter List",
- "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
+ "fieldname": "reference_doctype",
+ "fieldtype": "Link",
+ "label": "Select Doctype",
+ "options": "DocType",
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "fields_multicheck",
- "fieldtype": "HTML",
- "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": "Fields Multicheck",
- "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
+ "fieldname": "column_break_2",
+ "fieldtype": "Column Break"
+ },
+ {
+ "default": "CSV",
+ "fieldname": "file_type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "File Type",
+ "options": "Excel\nCSV",
+ "reqd": 1
+ },
+ {
+ "depends_on": "reference_doctype",
+ "fieldname": "section_break",
+ "fieldtype": "Section Break"
+ },
+ {
+ "fieldname": "filter_list",
+ "fieldtype": "HTML",
+ "label": "Filter List"
+ },
+ {
+ "fieldname": "fields_multicheck",
+ "fieldtype": "HTML",
+ "label": "Fields Multicheck"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 1,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-03-21 13:23:05.623052",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Data Export",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "hide_toolbar": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:53.658574",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Data Export",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/core/doctype/defaultvalue/defaultvalue.json b/frappe/core/doctype/defaultvalue/defaultvalue.json
index 35b08c2dca..22e2583774 100644
--- a/frappe/core/doctype/defaultvalue/defaultvalue.json
+++ b/frappe/core/doctype/defaultvalue/defaultvalue.json
@@ -1,90 +1,47 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:27:32",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:27:32",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "defkey",
+ "defvalue"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "defkey",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Key",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "defkey",
- "oldfieldtype": "Data",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "200px",
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "defkey",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Key",
+ "oldfieldname": "defkey",
+ "oldfieldtype": "Data",
+ "print_width": "200px",
+ "reqd": 1,
"width": "200px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "defvalue",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Value",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "defvalue",
- "oldfieldtype": "Text",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "200px",
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "defvalue",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Value",
+ "oldfieldname": "defvalue",
+ "oldfieldtype": "Text",
+ "print_width": "200px",
"width": "200px"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-07-11 03:27:59.126216",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "DefaultValue",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:54.832785",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "DefaultValue",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/core/doctype/domain_settings/domain_settings.json b/frappe/core/doctype/domain_settings/domain_settings.json
index 8efd296da6..c363529cbd 100644
--- a/frappe/core/doctype/domain_settings/domain_settings.json
+++ b/frappe/core/doctype/domain_settings/domain_settings.json
@@ -1,153 +1,56 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-05-03 16:28:11.295095",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-05-03 16:28:11.295095",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "active_domains_sb",
+ "domains_html",
+ "active_domains"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "active_domains_sb",
- "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": "Active Domains",
- "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,
- "unique": 0
- },
+ "fieldname": "active_domains_sb",
+ "fieldtype": "Section Break",
+ "label": "Active Domains"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "domains_html",
- "fieldtype": "HTML",
- "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": "Domains HTML",
- "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,
- "unique": 0
- },
+ "fieldname": "domains_html",
+ "fieldtype": "HTML",
+ "label": "Domains HTML"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "active_domains",
- "fieldtype": "Table",
- "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": "Active Domains",
- "length": 0,
- "no_copy": 0,
- "options": "Has Domain",
- "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,
- "unique": 0
+ "fieldname": "active_domains",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Active Domains",
+ "options": "Has Domain",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-12-05 17:36:46.842134",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Domain Settings",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:53.256607",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Domain Settings",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/error_snapshot/error_snapshot.json b/frappe/core/doctype/error_snapshot/error_snapshot.json
index 1333fe0d5b..b92db8f99a 100644
--- a/frappe/core/doctype/error_snapshot/error_snapshot.json
+++ b/frappe/core/doctype/error_snapshot/error_snapshot.json
@@ -1,398 +1,130 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2015-11-28 00:57:39.766888",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "System",
- "editable_grid": 0,
+ "actions": [],
+ "creation": "2015-11-28 00:57:39.766888",
+ "doctype": "DocType",
+ "document_type": "System",
+ "engine": "InnoDB",
+ "field_order": [
+ "view",
+ "seen",
+ "evalue",
+ "timestamp",
+ "relapses",
+ "etype",
+ "traceback",
+ "parent_error_snapshot",
+ "pyver",
+ "exception",
+ "locals",
+ "frames"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "view",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Snapshot View",
- "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,
- "unique": 0
- },
+ "fieldname": "view",
+ "fieldtype": "HTML",
+ "label": "Snapshot View"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "seen",
- "fieldtype": "Check",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 1,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Seen",
- "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,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "seen",
+ "fieldtype": "Check",
+ "hidden": 1,
+ "in_filter": 1,
+ "label": "Seen"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "evalue",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Friendly Title",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "evalue",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "in_list_view": 1,
+ "label": "Friendly Title",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "timestamp",
- "fieldtype": "Datetime",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Timestamp",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "timestamp",
+ "fieldtype": "Datetime",
+ "hidden": 1,
+ "label": "Timestamp",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "1",
- "fieldname": "relapses",
- "fieldtype": "Int",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Relapses",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "default": "1",
+ "fieldname": "relapses",
+ "fieldtype": "Int",
+ "hidden": 1,
+ "in_list_view": 1,
+ "label": "Relapses",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "etype",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Exception Type",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "etype",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Exception Type",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "traceback",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Traceback",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "traceback",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Traceback",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "parent_error_snapshot",
- "fieldtype": "Data",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Parent Error Snapshot",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "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,
- "unique": 0
- },
+ "fieldname": "parent_error_snapshot",
+ "fieldtype": "Data",
+ "hidden": 1,
+ "label": "Parent Error Snapshot"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "pyver",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Pyver",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "pyver",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Pyver",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "exception",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Exception",
- "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,
- "unique": 0
- },
+ "fieldname": "exception",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Exception"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "locals",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Locals",
- "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,
- "unique": 0
- },
+ "fieldname": "locals",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Locals"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "frames",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Frames",
- "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,
- "unique": 0
+ "fieldname": "frames",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Frames"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 1,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2021-10-25 14:40:38.619106",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Error Snapshot",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "in_create": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:53.504160",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Error Snapshot",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Administrator",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "timestamp",
- "sort_order": "DESC",
- "title_field": "evalue",
- "track_seen": 0
-}
+ ],
+ "sort_field": "timestamp",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "evalue"
+}
\ No newline at end of file
diff --git a/frappe/core/doctype/has_domain/has_domain.json b/frappe/core/doctype/has_domain/has_domain.json
index e2b646b457..c34626b269 100644
--- a/frappe/core/doctype/has_domain/has_domain.json
+++ b/frappe/core/doctype/has_domain/has_domain.json
@@ -1,72 +1,32 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-05-03 15:20:22.326623",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-05-03 15:20:22.326623",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "domain"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "domain",
- "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": 0,
- "label": "Domain",
- "length": 0,
- "no_copy": 0,
- "options": "Domain",
- "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,
- "unique": 0
+ "fieldname": "domain",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Domain",
+ "options": "Domain"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2020-09-18 17:26:09.703215",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Has Domain",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:53.381248",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Has Domain",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/has_role/has_role.json b/frappe/core/doctype/has_role/has_role.json
index e0759dcd7e..689e80480e 100644
--- a/frappe/core/doctype/has_role/has_role.json
+++ b/frappe/core/doctype/has_role/has_role.json
@@ -1,64 +1,34 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2013-02-22 01:27:34",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2013-02-22 01:27:34",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "role"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "role",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Role",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "role",
- "oldfieldtype": "Link",
- "options": "Role",
- "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,
- "unique": 0
+ "fieldname": "role",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Role",
+ "oldfieldname": "role",
+ "oldfieldtype": "Link",
+ "options": "Role"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-02-13 14:00:08.116312",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Has Role",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:54.382064",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Has Role",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/core/doctype/page/page.json b/frappe/core/doctype/page/page.json
index 0c586643d4..e913f126af 100644
--- a/frappe/core/doctype/page/page.json
+++ b/frappe/core/doctype/page/page.json
@@ -1,415 +1,133 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:page_name",
- "beta": 0,
- "creation": "2012-12-20 17:16:49",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "System",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:page_name",
+ "creation": "2012-12-20 17:16:49",
+ "doctype": "DocType",
+ "document_type": "System",
+ "engine": "InnoDB",
+ "field_order": [
+ "system_page",
+ "page_html",
+ "page_name",
+ "title",
+ "icon",
+ "column_break0",
+ "module",
+ "restrict_to_domain",
+ "standard",
+ "section_break0",
+ "roles"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "system_page",
- "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": "System Page",
- "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,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "system_page",
+ "fieldtype": "Check",
+ "label": "System Page"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "page_html",
- "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": "Page HTML",
- "length": 0,
- "no_copy": 0,
- "oldfieldtype": "Section Break",
- "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,
- "unique": 0
- },
+ "fieldname": "page_html",
+ "fieldtype": "Section Break",
+ "label": "Page HTML",
+ "oldfieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "page_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Page Name",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "page_name",
- "oldfieldtype": "Data",
- "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,
- "unique": 0
- },
+ "fieldname": "page_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Page Name",
+ "oldfieldname": "page_name",
+ "oldfieldtype": "Data",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Title",
- "length": 0,
- "no_copy": 1,
- "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,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "no_copy": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": 1,
- "in_standard_filter": 0,
- "label": "icon",
- "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,
- "unique": 0
- },
+ "fieldname": "icon",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "icon"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break0",
- "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,
- "unique": 0
- },
+ "fieldname": "column_break0",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": 1,
- "label": "Module",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "module",
- "oldfieldtype": "Select",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "module",
+ "fieldtype": "Link",
+ "in_standard_filter": 1,
+ "label": "Module",
+ "oldfieldname": "module",
+ "oldfieldtype": "Select",
+ "options": "Module Def",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "restrict_to_domain",
- "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": "Restrict To Domain",
- "length": 0,
- "no_copy": 0,
- "options": "Domain",
- "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,
- "unique": 0
- },
+ "fieldname": "restrict_to_domain",
+ "fieldtype": "Link",
+ "label": "Restrict To Domain",
+ "options": "Domain"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "standard",
- "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": "Standard",
- "length": 0,
- "no_copy": 0,
- "oldfieldname": "standard",
- "oldfieldtype": "Select",
- "options": "Yes\nNo",
- "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": 1,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "standard",
+ "fieldtype": "Select",
+ "label": "Standard",
+ "oldfieldname": "standard",
+ "oldfieldtype": "Select",
+ "options": "Yes\nNo",
+ "reqd": 1,
+ "search_index": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break0",
- "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,
- "unique": 0
- },
+ "fieldname": "section_break0",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.standard == 'Yes'",
- "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,
- "oldfieldname": "roles",
- "oldfieldtype": "Table",
- "options": "Has Role",
- "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,
- "unique": 0
+ "depends_on": "eval:doc.standard == 'Yes'",
+ "fieldname": "roles",
+ "fieldtype": "Table",
+ "label": "Roles",
+ "oldfieldname": "roles",
+ "oldfieldtype": "Table",
+ "options": "Has Role"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-file",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-11-13 16:37:04.422547",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Page",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-file",
+ "idx": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:54.219236",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Page",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Administrator",
+ "share": 1,
"write": 1
- },
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.json b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.json
index 8a5393b872..09982cf639 100644
--- a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.json
+++ b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.json
@@ -1,327 +1,95 @@
{
- "allow_copy": 1,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-02-13 17:33:25.157332",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_copy": 1,
+ "creation": "2017-02-13 17:33:25.157332",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "set_role_for",
+ "page",
+ "report",
+ "column_break_4",
+ "disable_prepared_report",
+ "roles_permission",
+ "roles_html",
+ "roles"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "set_role_for",
- "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": 0,
- "label": "Set Role For",
- "length": 0,
- "no_copy": 0,
- "options": "\nPage\nReport",
- "permlevel": 0,
- "precision": "",
- "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
- },
+ "fieldname": "set_role_for",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Set Role For",
+ "options": "\nPage\nReport",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.set_role_for == 'Page'",
- "fieldname": "page",
- "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": "Page",
- "length": 0,
- "no_copy": 0,
- "options": "Page",
- "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
- },
+ "depends_on": "eval:doc.set_role_for == 'Page'",
+ "fieldname": "page",
+ "fieldtype": "Link",
+ "label": "Page",
+ "options": "Page"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:doc.set_role_for == 'Report'",
- "fieldname": "report",
- "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": "Report",
- "length": 0,
- "no_copy": 0,
- "options": "Report",
- "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
- },
+ "depends_on": "eval:doc.set_role_for == 'Report'",
+ "fieldname": "report",
+ "fieldtype": "Link",
+ "label": "Report",
+ "options": "Report"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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,
- "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
- },
+ "fieldname": "column_break_4",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "report",
- "fetch_from": "",
- "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
- },
+ "default": "0",
+ "depends_on": "report",
+ "fieldname": "disable_prepared_report",
+ "fieldtype": "Check",
+ "label": "Disable Prepared Report"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "roles_permission",
- "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": "Allow Roles",
- "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
- },
+ "fieldname": "roles_permission",
+ "fieldtype": "Section Break",
+ "label": "Allow Roles"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "",
- "fieldname": "roles_html",
- "fieldtype": "HTML",
- "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 Html",
- "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
- },
+ "fieldname": "roles_html",
+ "fieldtype": "HTML",
+ "label": "Roles Html"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "roles",
- "fieldtype": "Table",
- "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": "Roles",
- "length": 0,
- "no_copy": 0,
- "options": "Has Role",
- "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
+ "fieldname": "roles",
+ "fieldtype": "Table",
+ "hidden": 1,
+ "label": "Roles",
+ "options": "Has Role",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 1,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-01-25 12:08:57.250719",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Role Permission for Page and Report",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "hide_toolbar": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:54.079809",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Role Permission for Page and 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": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/core/doctype/sms_parameter/sms_parameter.json b/frappe/core/doctype/sms_parameter/sms_parameter.json
index 43b93ed182..98972f9e7d 100755
--- a/frappe/core/doctype/sms_parameter/sms_parameter.json
+++ b/frappe/core/doctype/sms_parameter/sms_parameter.json
@@ -1,128 +1,51 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-02-22 01:27:58",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2013-02-22 01:27:58",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "parameter",
+ "value",
+ "header"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "parameter",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Parameter",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "parameter",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Parameter",
+ "print_width": "150px",
+ "reqd": 1,
"width": "150px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "value",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Value",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "150px",
- "read_only": 0,
- "remember_last_selected_value": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "value",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Value",
+ "print_width": "150px",
+ "reqd": 1,
"width": "150px"
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "header",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Header",
- "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,
- "unique": 0
+ "default": "0",
+ "fieldname": "header",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Header"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-10-13 16:48:00.518463",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "SMS Parameter",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:53.129765",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "SMS Parameter",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/core/doctype/success_action/success_action.json b/frappe/core/doctype/success_action/success_action.json
index 25c8e79a05..749fa6764f 100644
--- a/frappe/core/doctype/success_action/success_action.json
+++ b/frappe/core/doctype/success_action/success_action.json
@@ -1,259 +1,84 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
+ "actions": [],
"autoname": "field:ref_doctype",
- "beta": 0,
"creation": "2018-04-15 18:07:35.316870",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "ref_doctype",
+ "first_success_message",
+ "message",
+ "next_actions_html",
+ "next_actions",
+ "action_timeout"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": 0,
"label": "Reference Document Type",
- "length": 0,
- "no_copy": 0,
"options": "DocType",
- "permlevel": 0,
- "precision": "",
- "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
+ "unique": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "Congratulations on first creations",
"fieldname": "first_success_message",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "First Success Message",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "Successfully created",
"fieldname": "message",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Message",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "next_actions_html",
"fieldtype": "HTML",
- "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": "Next Actions HTML",
- "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
+ "label": "Next Actions HTML"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "next_actions",
"fieldtype": "Data",
- "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,
- "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
+ "hidden": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "7",
"fieldname": "action_timeout",
"fieldtype": "Int",
- "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": "Action Timeout (Seconds)",
- "default": 7,
- "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
+ "label": "Action Timeout (Seconds)"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
"hide_toolbar": 1,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
+ "links": [],
+ "modified": "2022-08-03 12:20:54.532708",
"modified_by": "Administrator",
"module": "Core",
"name": "Success Action",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "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
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/transaction_log/transaction_log.json b/frappe/core/doctype/transaction_log/transaction_log.json
index 5c6aa5bc8b..2135976add 100644
--- a/frappe/core/doctype/transaction_log/transaction_log.json
+++ b/frappe/core/doctype/transaction_log/transaction_log.json
@@ -1,476 +1,124 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2018-02-06 11:48:51.270524",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "row_index",
+ "section_break_2",
+ "reference_doctype",
+ "document_name",
+ "column_break_5",
+ "timestamp",
+ "checksum_version",
+ "section_break_8",
+ "previous_hash",
+ "transaction_hash",
+ "chaining_hash",
+ "data",
+ "amended_from"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "row_index",
"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": "Row Index",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_2",
- "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,
- "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
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "reference_doctype",
"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 Document Type",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "document_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": "Document Name",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "column_break_5",
- "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,
- "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
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "timestamp",
"fieldtype": "Datetime",
- "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": "Timestamp",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "checksum_version",
"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": "Checksum Version",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "section_break_8",
- "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,
- "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
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "previous_hash",
"fieldtype": "Small Text",
"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": "Previous Hash",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "transaction_hash",
"fieldtype": "Small Text",
- "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": "Transaction Hash",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "chaining_hash",
"fieldtype": "Small Text",
"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": "Chaining Hash",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "data",
"fieldtype": "Long Text",
"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": "Data",
- "length": 0,
- "no_copy": 0,
- "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
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "amended_from",
"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": "Amended From",
- "length": 0,
"no_copy": 1,
"options": "Transaction Log",
- "permlevel": 0,
"print_hide": 1,
- "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
+ "read_only": 1
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
"in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
+ "links": [],
+ "modified": "2022-08-03 12:20:54.684305",
"modified_by": "Administrator",
"module": "Core",
"name": "Transaction Log",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "Administrator",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
- "write": 0
+ "share": 1
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/user_social_login/user_social_login.json b/frappe/core/doctype/user_social_login/user_social_login.json
index 3cac838016..6b4b1822d1 100644
--- a/frappe/core/doctype/user_social_login/user_social_login.json
+++ b/frappe/core/doctype/user_social_login/user_social_login.json
@@ -1,189 +1,58 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-12-02 13:01:20.507112",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-12-02 13:01:20.507112",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "provider",
+ "section_break_0",
+ "username",
+ "column_break_0",
+ "userid"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "provider",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Provider",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "provider",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Provider",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_0",
- "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,
- "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,
- "unique": 0
- },
+ "fieldname": "section_break_0",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "username",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Username",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "username",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Username",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_0",
- "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,
- "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,
- "unique": 0
- },
+ "fieldname": "column_break_0",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "userid",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "User ID",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
+ "fieldname": "userid",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "User ID",
+ "read_only": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-12-02 15:37:58.397062",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "User Social Login",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:53.800689",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "User Social Login",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/version/version.json b/frappe/core/doctype/version/version.json
index 463a7d3cba..13c82fa2b2 100644
--- a/frappe/core/doctype/version/version.json
+++ b/frappe/core/doctype/version/version.json
@@ -1,247 +1,81 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "hash",
- "beta": 0,
- "creation": "2014-02-20 17:22:37",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "autoname": "hash",
+ "creation": "2014-02-20 17:22:37",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "ref_doctype",
+ "column_break_3",
+ "docname",
+ "data",
+ "section_break_4",
+ "table_html"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "ref_doctype",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 1,
- "label": "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,
- "unique": 0
- },
+ "fieldname": "ref_doctype",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "DocType",
+ "options": "DocType",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "fieldtype": "Column Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "docname",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Document 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,
- "unique": 0
- },
+ "fieldname": "docname",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Document Name",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "data",
- "fieldtype": "Code",
- "hidden": 1,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Data",
- "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,
- "unique": 0
- },
+ "fieldname": "data",
+ "fieldtype": "Code",
+ "hidden": 1,
+ "label": "Data"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_4",
- "fieldtype": "Section Break",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "section_break_4",
+ "fieldtype": "Section Break"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "table_html",
- "fieldtype": "HTML",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Table HTML",
- "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,
- "unique": 0
+ "fieldname": "table_html",
+ "fieldtype": "HTML",
+ "label": "Table HTML"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-copy",
- "idx": 1,
- "image_view": 0,
- "in_create": 1,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-04-10 14:39:45.926836",
- "modified_by": "Administrator",
- "module": "Core",
- "name": "Version",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-copy",
+ "idx": 1,
+ "in_create": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:53.929691",
+ "modified_by": "Administrator",
+ "module": "Core",
+ "name": "Version",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
- },
+ "export": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager"
+ },
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 1,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "Administrator",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "delete": 1,
+ "read": 1,
+ "role": "Administrator"
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "ASC",
- "title_field": "docname",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "states": [],
+ "title_field": "docname",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/core/doctype/view_log/view_log.json b/frappe/core/doctype/view_log/view_log.json
index 3c4486c944..6b19cdd507 100644
--- a/frappe/core/doctype/view_log/view_log.json
+++ b/frappe/core/doctype/view_log/view_log.json
@@ -1,163 +1,58 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2018-05-27 02:20:11.193944",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "viewed_by",
+ "reference_doctype",
+ "reference_name"
+ ],
"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": "viewed_by",
"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": "Viewed By",
- "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": 1,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 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": "reference_doctype",
"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": "Reference Document Type",
- "length": 0,
- "no_copy": 0,
"options": "DocType",
- "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": 1,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 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": "reference_name",
"fieldtype": "Dynamic 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": "Reference name",
- "length": 0,
- "no_copy": 0,
"options": "reference_doctype",
- "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": 1,
- "set_only_once": 1,
- "translatable": 0,
- "unique": 0
+ "set_only_once": 1
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2021-10-25 14:22:27.664645",
+ "links": [],
+ "modified": "2022-08-03 12:20:52.857103",
"modified_by": "Administrator",
"module": "Core",
"name": "View Log",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
"export": 1,
- "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": 0
+ "share": 1
}
],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_seen": 0,
- "track_views": 0
-}
+ "states": []
+}
\ No newline at end of file
diff --git a/frappe/desk/doctype/bulk_update/bulk_update.json b/frappe/desk/doctype/bulk_update/bulk_update.json
index 0ec29a0dda..93458516fd 100644
--- a/frappe/desk/doctype/bulk_update/bulk_update.json
+++ b/frappe/desk/doctype/bulk_update/bulk_update.json
@@ -1,204 +1,77 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-07-15 05:51:29.224123",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-07-15 05:51:29.224123",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "document_type",
+ "field",
+ "update_value",
+ "condition",
+ "limit"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "document_type",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Document Type",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "permlevel": 0,
- "precision": "",
- "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,
- "unique": 0
- },
+ "fieldname": "document_type",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Document Type",
+ "options": "DocType",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "field",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Field",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "field",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Field",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "update_value",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Update Value",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "update_value",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Update Value",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "description": "SQL Conditions. Example: status=\"Open\"",
- "fieldname": "condition",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Condition",
- "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,
- "unique": 0
- },
+ "bold": 1,
+ "description": "SQL Conditions. Example: status=\"Open\"",
+ "fieldname": "condition",
+ "fieldtype": "Small Text",
+ "label": "Condition"
+ },
{
- "allow_on_submit": 0,
- "bold": 1,
- "collapsible": 0,
- "columns": 0,
- "default": "500",
- "description": "Max 500 records at a time",
- "fieldname": "limit",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Limit",
- "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,
- "unique": 0
+ "bold": 1,
+ "default": "500",
+ "description": "Max 500 records at a time",
+ "fieldname": "limit",
+ "fieldtype": "Int",
+ "label": "Limit"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-12-29 14:40:31.929701",
- "modified_by": "Administrator",
- "module": "Desk",
- "name": "Bulk Update",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:50.742376",
+ "modified_by": "Administrator",
+ "module": "Desk",
+ "name": "Bulk Update",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.json b/frappe/desk/doctype/desktop_icon/desktop_icon.json
index 59c95953ad..ef88346f53 100644
--- a/frappe/desk/doctype/desktop_icon/desktop_icon.json
+++ b/frappe/desk/doctype/desktop_icon/desktop_icon.json
@@ -1,736 +1,175 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-02-22 03:47:45.387068",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-02-22 03:47:45.387068",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "module_name",
+ "label",
+ "standard",
+ "custom",
+ "column_break_3",
+ "app",
+ "description",
+ "category",
+ "hidden",
+ "blocked",
+ "force_show",
+ "section_break_7",
+ "type",
+ "_doctype",
+ "_report",
+ "link",
+ "column_break_10",
+ "color",
+ "icon",
+ "reverse",
+ "idx"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "module_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": "Module Name",
- "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
- },
+ "fieldname": "module_name",
+ "fieldtype": "Data",
+ "label": "Module Name"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "label",
- "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": "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
- },
+ "fieldname": "label",
+ "fieldtype": "Data",
+ "label": "Label"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "standard",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Standard",
- "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
- },
+ "default": "0",
+ "fieldname": "standard",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Standard"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "custom",
- "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": "Custom",
- "length": 0,
- "no_copy": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "custom",
+ "fieldtype": "Check",
+ "label": "Custom",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_3",
- "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,
- "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
- },
+ "fieldname": "column_break_3",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "app",
- "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": "App",
- "length": 0,
- "no_copy": 0,
- "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
- },
+ "fieldname": "app",
+ "fieldtype": "Data",
+ "label": "App",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "description",
- "fieldtype": "Small Text",
- "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": "Description",
- "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
- },
+ "fieldname": "description",
+ "fieldtype": "Small Text",
+ "label": "Description"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "category",
- "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": "Category",
- "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
- },
+ "fieldname": "category",
+ "fieldtype": "Data",
+ "label": "Category"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "hidden",
- "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": "Hidden",
- "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
- },
+ "default": "0",
+ "fieldname": "hidden",
+ "fieldtype": "Check",
+ "label": "Hidden"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "blocked",
- "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": "Blocked",
- "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
- },
+ "default": "0",
+ "fieldname": "blocked",
+ "fieldtype": "Check",
+ "label": "Blocked"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "force_show",
- "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": "Force Show",
- "length": 0,
- "no_copy": 0,
- "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
- },
+ "default": "0",
+ "fieldname": "force_show",
+ "fieldtype": "Check",
+ "label": "Force Show",
+ "read_only": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "section_break_7",
- "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,
- "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
- },
+ "fieldname": "section_break_7",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "type",
- "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": "Type",
- "length": 0,
- "no_copy": 0,
- "options": "module\nlist\nlink\npage\nquery-report",
- "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
- },
+ "fieldname": "type",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "in_standard_filter": 1,
+ "label": "Type",
+ "options": "module\nlist\nlink\npage\nquery-report"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "_doctype",
- "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": "_doctype",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "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
- },
+ "fieldname": "_doctype",
+ "fieldtype": "Link",
+ "label": "_doctype",
+ "options": "DocType"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "_report",
- "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": "_report",
- "length": 0,
- "no_copy": 0,
- "options": "Report",
- "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
- },
+ "fieldname": "_report",
+ "fieldtype": "Link",
+ "label": "_report",
+ "options": "Report"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "link",
- "fieldtype": "Small Text",
- "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": "Link",
- "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
- },
+ "fieldname": "link",
+ "fieldtype": "Small Text",
+ "label": "Link"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "column_break_10",
- "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,
- "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
- },
+ "fieldname": "column_break_10",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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
- },
+ "fieldname": "color",
+ "fieldtype": "Data",
+ "label": "Color"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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
- },
+ "fieldname": "icon",
+ "fieldtype": "Data",
+ "label": "Icon"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reverse",
- "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": "Reverse Icon 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
- },
+ "default": "0",
+ "fieldname": "reverse",
+ "fieldtype": "Check",
+ "label": "Reverse Icon Color"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "idx",
- "fieldtype": "Int",
- "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": "Idx",
- "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
+ "fieldname": "idx",
+ "fieldtype": "Int",
+ "label": "Idx"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-01-24 04:58:58.720618",
- "modified_by": "Administrator",
- "module": "Desk",
- "name": "Desktop Icon",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "in_create": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:50.577580",
+ "modified_by": "Administrator",
+ "module": "Desk",
+ "name": "Desktop Icon",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 1,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "title_field": "module_name",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "read_only": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "title_field": "module_name",
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/desk/doctype/event_participants/event_participants.json b/frappe/desk/doctype/event_participants/event_participants.json
index 86cf2670c9..1b40e7042b 100644
--- a/frappe/desk/doctype/event_participants/event_participants.json
+++ b/frappe/desk/doctype/event_participants/event_participants.json
@@ -1,108 +1,42 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2018-09-21 15:44:58.836156",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
- "fields": [
- {
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reference_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": 0,
- "label": "Reference Document Type",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "permlevel": 0,
- "precision": "",
- "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,
- "fieldname": "reference_docname",
- "fieldtype": "Dynamic 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": 0,
- "label": "Reference Name",
- "length": 0,
- "no_copy": 0,
- "options": "reference_doctype",
- "permlevel": 0,
- "precision": "",
- "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
- }
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
- "modified_by": "Administrator",
- "module": "Desk",
- "name": "Event Participants",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
- }
\ No newline at end of file
+ "actions": [],
+ "creation": "2018-09-21 15:44:58.836156",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "reference_doctype",
+ "reference_docname"
+ ],
+ "fields": [
+ {
+ "fieldname": "reference_doctype",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Reference Document Type",
+ "options": "DocType",
+ "reqd": 1
+ },
+ {
+ "fieldname": "reference_docname",
+ "fieldtype": "Dynamic Link",
+ "in_list_view": 1,
+ "label": "Reference Name",
+ "options": "reference_doctype",
+ "reqd": 1
+ }
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:50.466370",
+ "modified_by": "Administrator",
+ "module": "Desk",
+ "name": "Event Participants",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/desk/doctype/list_filter/list_filter.json b/frappe/desk/doctype/list_filter/list_filter.json
index dad62bf8d6..257bbc6d45 100644
--- a/frappe/desk/doctype/list_filter/list_filter.json
+++ b/frappe/desk/doctype/list_filter/list_filter.json
@@ -1,188 +1,62 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2018-02-22 15:10:24.401801",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "filter_name",
+ "reference_doctype",
+ "for_user",
+ "filters"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "filter_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": "Filter Name",
- "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
+ "label": "Filter Name"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "reference_doctype",
"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": "Reference Document Type",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "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
+ "options": "DocType"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "for_user",
"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": "For User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "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
+ "options": "User"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "filters",
"fieldtype": "Long Text",
- "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": "Filters",
- "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
+ "label": "Filters"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
"in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
+ "links": [],
+ "modified": "2022-08-03 12:20:50.889979",
"modified_by": "Administrator",
"module": "Desk",
"name": "List Filter",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "All",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
}
],
- "quick_entry": 0,
"read_only": 1,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/desk/doctype/note_seen_by/note_seen_by.json b/frappe/desk/doctype/note_seen_by/note_seen_by.json
index 7ee423e347..f54559d2f5 100644
--- a/frappe/desk/doctype/note_seen_by/note_seen_by.json
+++ b/frappe/desk/doctype/note_seen_by/note_seen_by.json
@@ -1,64 +1,31 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-08-29 05:29:16.726172",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2016-08-29 05:29:16.726172",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "user"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "user",
- "fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "permlevel": 0,
- "precision": "",
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "user",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "User",
+ "options": "User"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-08-29 06:02:41.531341",
- "modified_by": "Administrator",
- "module": "Desk",
- "name": "Note Seen By",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:51.030908",
+ "modified_by": "Administrator",
+ "module": "Desk",
+ "name": "Note Seen By",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/email/doctype/email_rule/email_rule.json b/frappe/email/doctype/email_rule/email_rule.json
index b4e505b8c6..20e296290d 100644
--- a/frappe/email/doctype/email_rule/email_rule.json
+++ b/frappe/email/doctype/email_rule/email_rule.json
@@ -1,128 +1,49 @@
{
+ "actions": [],
"allow_copy": 1,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
"autoname": "field:email_id",
- "beta": 0,
"creation": "2017-03-13 09:20:56.387135",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "email_id",
+ "is_spam"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "email_id",
"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": "Email ID",
- "length": 0,
- "no_copy": 0,
"options": "Email",
- "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": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"fieldname": "is_spam",
"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": "Is Spam",
- "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
+ "label": "Is Spam"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-08-31 06:08:12.645682",
+ "links": [],
+ "modified": "2022-08-03 12:20:51.443237",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Rule",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
"email": 1,
"export": 1,
- "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": 0
+ "share": 1
}
],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0,
- "track_views": 0
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json
index bf633ead4b..38df531c35 100644
--- a/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json
+++ b/frappe/email/doctype/email_unsubscribe/email_unsubscribe.json
@@ -1,175 +1,70 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2015-03-18 09:41:20.216320",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
"document_type": "System",
- "editable_grid": 0,
"engine": "InnoDB",
+ "field_order": [
+ "email",
+ "reference_doctype",
+ "reference_name",
+ "global_unsubscribe"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "email",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Email",
- "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": 1,
- "search_index": 1,
- "set_only_once": 0,
- "unique": 0
+ "search_index": 1
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "reference_doctype",
"fieldtype": "Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_list_view": 1,
"in_standard_filter": 1,
"label": "Reference Document Type",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "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,
- "unique": 0
+ "options": "DocType"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "reference_name",
"fieldtype": "Dynamic Link",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Reference Name",
- "length": 0,
- "no_copy": 0,
- "options": "reference_doctype",
- "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,
- "unique": 0
+ "options": "reference_doctype"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"fieldname": "global_unsubscribe",
"fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Global Unsubscribe",
- "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,
- "unique": 0
+ "label": "Global Unsubscribe"
}
],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
+ "links": [],
+ "modified": "2022-08-03 12:20:51.694626",
"modified_by": "Administrator",
"module": "Email",
"name": "Email Unsubscribe",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
"print": 1,
"read": 1,
"report": 1,
"role": "System Manager",
- "set_user_permissions": 0,
"share": 1,
- "submit": 0,
"write": 1
}
],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
-}
+ "states": [],
+ "track_changes": 1
+}
\ No newline at end of file
diff --git a/frappe/email/doctype/unhandled_email/unhandled_email.json b/frappe/email/doctype/unhandled_email/unhandled_email.json
index de4407f38f..d904536936 100644
--- a/frappe/email/doctype/unhandled_email/unhandled_email.json
+++ b/frappe/email/doctype/unhandled_email/unhandled_email.json
@@ -1,212 +1,60 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-04-14 09:41:45.892975",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "actions": [],
+ "creation": "2016-04-14 09:41:45.892975",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "email_account",
+ "uid",
+ "reason",
+ "message_id",
+ "raw"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "email_account",
- "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": 0,
- "label": "Email Account",
- "length": 0,
- "no_copy": 0,
- "options": "Email Account",
- "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,
- "unique": 0
- },
+ "fieldname": "email_account",
+ "fieldtype": "Link",
+ "in_list_view": 1,
+ "label": "Email Account",
+ "options": "Email Account"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "uid",
- "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": "UID",
- "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,
- "unique": 0
- },
+ "fieldname": "uid",
+ "fieldtype": "Data",
+ "label": "UID"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "reason",
- "fieldtype": "Long Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Reason",
- "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,
- "unique": 0
- },
+ "fieldname": "reason",
+ "fieldtype": "Long Text",
+ "in_list_view": 1,
+ "label": "Reason"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "message_id",
- "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": "Message-id",
- "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,
- "unique": 0
- },
+ "fieldname": "message_id",
+ "fieldtype": "Code",
+ "label": "Message-id"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "raw",
- "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": "Raw Email",
- "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,
- "unique": 0
+ "fieldname": "raw",
+ "fieldtype": "Code",
+ "label": "Raw Email"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 1,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-09-19 16:28:00.042256",
- "modified_by": "Administrator",
- "module": "Email",
- "name": "Unhandled Email",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "in_create": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:51.822287",
+ "modified_by": "Administrator",
+ "module": "Email",
+ "name": "Unhandled Email",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 0,
- "delete": 0,
- "email": 0,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 0,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 0,
- "submit": 0,
- "write": 0
+ "read": 1,
+ "role": "System Manager"
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.json b/frappe/integrations/doctype/oauth_client/oauth_client.json
index d0d45c36ab..159c0f2cd9 100644
--- a/frappe/integrations/doctype/oauth_client/oauth_client.json
+++ b/frappe/integrations/doctype/oauth_client/oauth_client.json
@@ -1,517 +1,144 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
+ "actions": [],
"creation": "2016-08-24 14:07:21.955052",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
"document_type": "Document",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "client_id",
+ "app_name",
+ "user",
+ "cb_1",
+ "client_secret",
+ "skip_authorization",
+ "sb_1",
+ "scopes",
+ "cb_3",
+ "redirect_uris",
+ "default_redirect_uri",
+ "sb_advanced",
+ "grant_type",
+ "cb_2",
+ "response_type"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "default": "",
"fieldname": "client_id",
"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": "App Client ID",
- "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,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "app_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": "App Name",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "user",
"fieldtype": "Link",
"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": "User",
- "length": 0,
- "no_copy": 0,
- "options": "User",
- "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,
- "unique": 0
+ "options": "User"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "cb_1",
- "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,
- "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,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "client_secret",
"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": "App Client Secret",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
+ "read_only": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
+ "default": "0",
"description": "If checked, users will not see the Confirm Access dialog.",
"fieldname": "skip_authorization",
"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": "Skip Authorization",
- "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,
- "unique": 0
+ "label": "Skip Authorization"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "",
"fieldname": "sb_1",
- "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,
- "unique": 0
+ "fieldtype": "Section Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "all openid",
"description": "A list of resources which the Client App will have access to after the user allows it. e.g. project",
"fieldname": "scopes",
"fieldtype": "Text",
- "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": "Scopes",
- "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,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "cb_3",
- "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,
- "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,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"description": "URIs for receiving authorization code once the user allows access, as well as failure responses. Typically a REST endpoint exposed by the Client App.\n e.g. http://hostname//api/method/frappe.www.login.login_via_facebook",
"fieldname": "redirect_uris",
"fieldtype": "Text",
- "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": "Redirect URIs",
- "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,
- "unique": 0
+ "label": "Redirect URIs"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "default_redirect_uri",
"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": "Default Redirect URI",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
"collapsible": 1,
"collapsible_depends_on": "1",
- "columns": 0,
"fieldname": "sb_advanced",
"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": "Advanced Settings",
- "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,
- "unique": 0
+ "label": "Advanced Settings"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "grant_type",
"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": "Grant Type",
- "length": 0,
- "no_copy": 0,
- "options": "Authorization Code\nImplicit",
- "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,
- "unique": 0
+ "options": "Authorization Code\nImplicit"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "cb_2",
- "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,
- "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,
- "unique": 0
+ "fieldtype": "Column Break"
},
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"default": "Code",
"fieldname": "response_type",
"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": "Response Type",
- "length": 0,
- "no_copy": 0,
- "options": "Code\nToken",
- "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,
- "unique": 0
+ "options": "Code\nToken"
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2020-04-07 21:07:39.476360",
+ "links": [],
+ "modified": "2022-08-03 12:20:52.062755",
"modified_by": "Administrator",
"module": "Integrations",
"name": "OAuth Client",
- "name_case": "",
"owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
"create": 1,
"delete": 1,
"email": 1,
"export": 1,
- "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
}
],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
+ "states": [],
"title_field": "app_name",
- "track_changes": 1,
- "track_seen": 0
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.json b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.json
index bf19eee6b1..219a87f2f4 100644
--- a/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.json
+++ b/frappe/integrations/doctype/oauth_provider_settings/oauth_provider_settings.json
@@ -1,90 +1,43 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-09-03 11:42:42.575525",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-09-03 11:42:42.575525",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "skip_authorization"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "skip_authorization",
- "fieldtype": "Select",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Skip Authorization",
- "length": 0,
- "no_copy": 0,
- "options": "Force\nAuto",
- "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,
- "unique": 0
+ "fieldname": "skip_authorization",
+ "fieldtype": "Select",
+ "label": "Skip Authorization",
+ "options": "Force\nAuto"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-12-29 14:40:30.718685",
- "modified_by": "Administrator",
- "module": "Integrations",
- "name": "OAuth Provider Settings",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:52.328415",
+ "modified_by": "Administrator",
+ "module": "Integrations",
+ "name": "OAuth Provider Settings",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/webhook_data/webhook_data.json b/frappe/integrations/doctype/webhook_data/webhook_data.json
index 96ae7f786a..2ace6a9237 100644
--- a/frappe/integrations/doctype/webhook_data/webhook_data.json
+++ b/frappe/integrations/doctype/webhook_data/webhook_data.json
@@ -1,130 +1,43 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-09-14 12:08:50.302810",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-09-14 12:08:50.302810",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "fieldname",
+ "cb_doc_data",
+ "key"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "fieldname",
- "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": 0,
- "label": "Fieldname",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "fieldname",
+ "fieldtype": "Select",
+ "in_list_view": 1,
+ "label": "Fieldname",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "cb_doc_data",
- "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,
- "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,
- "unique": 0
- },
+ "fieldname": "cb_doc_data",
+ "fieldtype": "Column Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "key",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Key",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "fieldname": "key",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Key",
+ "reqd": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-09-14 13:16:58.252176",
- "modified_by": "Administrator",
- "module": "Integrations",
- "name": "Webhook Data",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:52.208987",
+ "modified_by": "Administrator",
+ "module": "Integrations",
+ "name": "Webhook Data",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/integrations/doctype/webhook_header/webhook_header.json b/frappe/integrations/doctype/webhook_header/webhook_header.json
index 315d28335f..4aea5d02ed 100644
--- a/frappe/integrations/doctype/webhook_header/webhook_header.json
+++ b/frappe/integrations/doctype/webhook_header/webhook_header.json
@@ -1,101 +1,38 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2017-09-08 16:27:39.195379",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2017-09-08 16:27:39.195379",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "key",
+ "value"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "key",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Key",
- "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,
- "unique": 0
- },
+ "fieldname": "key",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Key"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "value",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Value",
- "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,
- "unique": 0
+ "fieldname": "value",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Value"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2017-09-08 16:28:20.025612",
- "modified_by": "Administrator",
- "module": "Integrations",
- "name": "Webhook Header",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:51.949422",
+ "modified_by": "Administrator",
+ "module": "Integrations",
+ "name": "Webhook Header",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/printing/doctype/print_style/print_style.json b/frappe/printing/doctype/print_style/print_style.json
index 29e88a460a..1d3c9a6189 100644
--- a/frappe/printing/doctype/print_style/print_style.json
+++ b/frappe/printing/doctype/print_style/print_style.json
@@ -1,214 +1,75 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:print_style_name",
- "beta": 0,
- "creation": "2017-08-17 01:25:56.910716",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:print_style_name",
+ "creation": "2017-08-17 01:25:56.910716",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "print_style_name",
+ "disabled",
+ "standard",
+ "css",
+ "preview"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "print_style_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": "Print Style Name",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "print_style_name",
+ "fieldtype": "Data",
+ "label": "Print Style Name",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 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": 1,
- "in_standard_filter": 0,
- "label": "Disabled",
- "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,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "disabled",
+ "fieldtype": "Check",
+ "in_list_view": 1,
+ "label": "Disabled"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "standard",
- "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": "Standard",
- "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,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "standard",
+ "fieldtype": "Check",
+ "label": "Standard"
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "css",
- "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": "CSS",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "css",
+ "fieldtype": "Code",
+ "label": "CSS",
+ "reqd": 1
+ },
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "preview",
- "fieldtype": "Attach Image",
- "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": "Preview",
- "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,
- "unique": 0
+ "fieldname": "preview",
+ "fieldtype": "Attach Image",
+ "hidden": 1,
+ "label": "Preview"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_field": "preview",
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2017-08-17 02:18:08.132853",
- "modified_by": "Administrator",
- "module": "Printing",
- "name": "Print Style",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "image_field": "preview",
+ "links": [],
+ "modified": "2022-08-03 12:20:51.295775",
+ "modified_by": "Administrator",
+ "module": "Printing",
+ "name": "Print Style",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/social/doctype/review_level/review_level.json b/frappe/social/doctype/review_level/review_level.json
index 4450467dc5..06e3f397bc 100644
--- a/frappe/social/doctype/review_level/review_level.json
+++ b/frappe/social/doctype/review_level/review_level.json
@@ -1,143 +1,51 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2019-03-19 13:16:12.762352",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "level_name",
+ "role",
+ "review_points"
+ ],
"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": "level_name",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Level Name",
- "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": 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": "role",
"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": 0,
"label": "Role",
- "length": 0,
- "no_copy": 0,
"options": "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": 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": "review_points",
"fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Review Points",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
"istable": 1,
- "max_attachments": 0,
- "modified": "2019-03-21 15:42:51.431424",
+ "links": [],
+ "modified": "2022-08-03 12:20:51.571158",
"modified_by": "Administrator",
"module": "Social",
"name": "Review Level",
- "name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/website/doctype/about_us_team_member/about_us_team_member.json b/frappe/website/doctype/about_us_team_member/about_us_team_member.json
index 37ec04e62f..22a9d41ef9 100644
--- a/frappe/website/doctype/about_us_team_member/about_us_team_member.json
+++ b/frappe/website/doctype/about_us_team_member/about_us_team_member.json
@@ -1,109 +1,49 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-03-07 11:55:11",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2013-03-07 11:55:11",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "full_name",
+ "image_link",
+ "bio"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "full_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Full Name",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "full_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Full Name",
+ "reqd": 1,
"width": "150px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "image_link",
- "fieldtype": "Attach",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Image Link",
- "length": 0,
- "no_copy": 0,
- "options": "",
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "image_link",
+ "fieldtype": "Attach",
+ "in_list_view": 1,
+ "label": "Image Link",
"width": "150px"
- },
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "bio",
- "fieldtype": "Small Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Bio",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "bio",
+ "fieldtype": "Small Text",
+ "in_list_view": 1,
+ "label": "Bio",
+ "reqd": 1,
"width": "200px"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-07-11 03:27:57.756510",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "About Us Team Member",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:50.254472",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "About Us Team Member",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/website/doctype/company_history/company_history.json b/frappe/website/doctype/company_history/company_history.json
index 2c55d807a9..e87be6e7d4 100644
--- a/frappe/website/doctype/company_history/company_history.json
+++ b/frappe/website/doctype/company_history/company_history.json
@@ -1,83 +1,39 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2013-02-22 01:28:08",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 1,
+ "actions": [],
+ "creation": "2013-02-22 01:28:08",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "year",
+ "highlight"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "year",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Year",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "year",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Year"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "fieldname": "highlight",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "label": "Highlight",
- "length": 0,
- "no_copy": 0,
- "permlevel": 0,
- "print_hide": 0,
- "print_hide_if_no_value": 0,
- "print_width": "300px",
- "read_only": 0,
- "report_hide": 0,
- "reqd": 0,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0,
+ "fieldname": "highlight",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Highlight",
+ "print_width": "300px",
"width": "300px"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-07-11 03:27:58.848351",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Company History",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "track_seen": 0
+ ],
+ "idx": 1,
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:50.361022",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Company History",
+ "owner": "Administrator",
+ "permissions": [],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/website/doctype/help_category/help_category.json b/frappe/website/doctype/help_category/help_category.json
index ef598a5045..c706d000e5 100644
--- a/frappe/website/doctype/help_category/help_category.json
+++ b/frappe/website/doctype/help_category/help_category.json
@@ -1,202 +1,73 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "",
- "beta": 0,
- "creation": "2014-10-30 14:23:30.958074",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 0,
+ "actions": [],
+ "allow_import": 1,
+ "creation": "2014-10-30 14:23:30.958074",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "category_name",
+ "category_description",
+ "published",
+ "help_articles",
+ "route"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "category_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Category Name",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "category_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Category Name",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "category_description",
- "fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Category Description",
- "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,
- "unique": 0
- },
+ "fieldname": "category_description",
+ "fieldtype": "Text",
+ "in_list_view": 1,
+ "label": "Category Description"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "published",
- "fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Published",
- "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,
- "unique": 0
- },
+ "default": "0",
+ "fieldname": "published",
+ "fieldtype": "Check",
+ "label": "Published"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "help_articles",
- "fieldtype": "Int",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Help Articles",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
- },
+ "fieldname": "help_articles",
+ "fieldtype": "Int",
+ "in_list_view": 1,
+ "label": "Help Articles",
+ "read_only": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "route",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Route",
- "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,
- "unique": 0
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "label": "Route"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "icon-list",
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-12-29 14:39:56.092427",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Help Category",
- "name_case": "Title Case",
- "owner": "Administrator",
+ ],
+ "icon": "icon-list",
+ "links": [],
+ "modified": "2022-08-03 12:20:50.025294",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Help Category",
+ "name_case": "Title Case",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Website Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Website Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/website/doctype/portal_menu_item/portal_menu_item.json b/frappe/website/doctype/portal_menu_item/portal_menu_item.json
index 4f288e1a2c..a692368313 100644
--- a/frappe/website/doctype/portal_menu_item/portal_menu_item.json
+++ b/frappe/website/doctype/portal_menu_item/portal_menu_item.json
@@ -1,216 +1,75 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2016-03-30 01:39:20.586927",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "enabled",
+ "route",
+ "reference_doctype",
+ "role",
+ "target"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"columns": 2,
"fieldname": "title",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Title",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"columns": 1,
+ "default": "0",
"fieldname": "enabled",
"fieldtype": "Check",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Enabled",
- "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,
- "unique": 0
+ "label": "Enabled"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"columns": 3,
"fieldname": "route",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Route",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"columns": 2,
- "description": "",
"fieldname": "reference_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": 0,
"label": "Reference Document Type",
- "length": 0,
- "no_copy": 0,
- "options": "DocType",
- "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,
- "unique": 0
+ "options": "DocType"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
"columns": 2,
"fieldname": "role",
"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": 0,
"label": "Role",
- "length": 0,
- "no_copy": 0,
- "options": "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,
- "unique": 0
+ "options": "Role"
},
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "target",
"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": "Target",
- "length": 0,
- "no_copy": 0,
- "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,
- "unique": 0
+ "read_only": 1
}
],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
"istable": 1,
- "max_attachments": 0,
- "modified": "2019-09-05 14:22:27.664645",
+ "links": [],
+ "modified": "2022-08-03 12:20:49.792747",
"modified_by": "Administrator",
"module": "Website",
"name": "Portal Menu Item",
- "name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 0,
- "track_seen": 0
+ "states": []
}
\ No newline at end of file
diff --git a/frappe/website/doctype/website_meta_tag/website_meta_tag.json b/frappe/website/doctype/website_meta_tag/website_meta_tag.json
index 0729b288ef..14b834051a 100644
--- a/frappe/website/doctype/website_meta_tag/website_meta_tag.json
+++ b/frappe/website/doctype/website_meta_tag/website_meta_tag.json
@@ -1,107 +1,40 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
+ "actions": [],
"creation": "2019-02-13 23:39:12.802543",
- "custom": 0,
- "docstatus": 0,
"doctype": "DocType",
- "document_type": "",
"editable_grid": 1,
"engine": "InnoDB",
+ "field_order": [
+ "key",
+ "value"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "key",
"fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Key",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
},
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
"fieldname": "value",
"fieldtype": "Text",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
"in_list_view": 1,
- "in_standard_filter": 0,
"label": "Value",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "translatable": 0,
- "unique": 0
+ "reqd": 1
}
],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
"istable": 1,
- "max_attachments": 0,
- "modified": "2019-02-13 23:39:12.802543",
+ "links": [],
+ "modified": "2022-08-03 12:20:49.259886",
"modified_by": "Administrator",
"module": "Website",
"name": "Website Meta Tag",
- "name_case": "",
"owner": "Administrator",
"permissions": [],
"quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
"sort_field": "modified",
"sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/website/doctype/website_script/website_script.json b/frappe/website/doctype/website_script/website_script.json
index 1b0b3ec7e7..40f8430016 100644
--- a/frappe/website/doctype/website_script/website_script.json
+++ b/frappe/website/doctype/website_script/website_script.json
@@ -1,88 +1,43 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2012-12-27 11:51:24",
- "custom": 0,
- "description": "Script to attach to all web pages.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Other",
- "editable_grid": 0,
+ "actions": [],
+ "creation": "2012-12-27 11:51:24",
+ "description": "Script to attach to all web pages.",
+ "doctype": "DocType",
+ "document_type": "Other",
+ "engine": "InnoDB",
+ "field_order": [
+ "javascript"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "javascript",
- "fieldtype": "Code",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Javascript",
- "length": 0,
- "no_copy": 0,
- "options": "Javascript",
- "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,
- "unique": 0
+ "fieldname": "javascript",
+ "fieldtype": "Code",
+ "label": "Javascript",
+ "options": "Javascript"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-code",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 1,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-12-29 14:40:38.777912",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Website Script",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-code",
+ "idx": 1,
+ "issingle": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:49.917059",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Website Script",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 0,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "Website Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "Website Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/website/doctype/website_sidebar/website_sidebar.json b/frappe/website/doctype/website_sidebar/website_sidebar.json
index 20e6ef0bf0..5232244404 100644
--- a/frappe/website/doctype/website_sidebar/website_sidebar.json
+++ b/frappe/website/doctype/website_sidebar/website_sidebar.json
@@ -1,119 +1,56 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 1,
- "autoname": "field:title",
- "beta": 0,
- "creation": "2016-12-29 07:48:06.319665",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "allow_rename": 1,
+ "autoname": "field:title",
+ "creation": "2016-12-29 07:48:06.319665",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "sidebar_items"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Title",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "sidebar_items",
- "fieldtype": "Table",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 0,
- "in_standard_filter": 0,
- "label": "Sidebar Items",
- "length": 0,
- "no_copy": 0,
- "options": "Website Sidebar Item",
- "permlevel": 0,
- "precision": "",
- "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,
- "unique": 0
+ "fieldname": "sidebar_items",
+ "fieldtype": "Table",
+ "label": "Sidebar Items",
+ "options": "Website Sidebar Item",
+ "reqd": 1
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2016-12-29 07:50:05.633460",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Website Sidebar",
- "name_case": "",
- "owner": "Administrator",
+ ],
+ "links": [],
+ "modified": "2022-08-03 12:20:49.508223",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Website Sidebar",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 1,
- "if_owner": 0,
- "import": 0,
- "is_custom": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 1,
- "role": "Website Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "export": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Website Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/website/doctype/website_sidebar_item/website_sidebar_item.json b/frappe/website/doctype/website_sidebar_item/website_sidebar_item.json
index ab0c13bf9d..055abf74ab 100644
--- a/frappe/website/doctype/website_sidebar_item/website_sidebar_item.json
+++ b/frappe/website/doctype/website_sidebar_item/website_sidebar_item.json
@@ -1,123 +1,46 @@
{
- "allow_copy": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "beta": 0,
- "creation": "2016-12-29 07:42:26.246725",
- "custom": 0,
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "",
- "editable_grid": 1,
- "engine": "InnoDB",
+ "actions": [],
+ "creation": "2016-12-29 07:42:26.246725",
+ "doctype": "DocType",
+ "editable_grid": 1,
+ "engine": "InnoDB",
+ "field_order": [
+ "title",
+ "route",
+ "group"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "title",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Title",
- "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": 1,
- "search_index": 0,
- "set_only_once": 0,
- "unique": 0
- },
+ "fieldname": "title",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Title",
+ "reqd": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "route",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Route",
- "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,
- "unique": 0
- },
+ "fieldname": "route",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Route"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "group",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Group",
- "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,
- "unique": 0
+ "fieldname": "group",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Group"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "idx": 0,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 1,
- "max_attachments": 0,
- "modified": "2016-12-29 07:42:26.246725",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Website Sidebar Item",
- "name_case": "",
- "owner": "Administrator",
- "permissions": [],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "sort_field": "modified",
- "sort_order": "DESC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "istable": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:49.377257",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Website Sidebar Item",
+ "owner": "Administrator",
+ "permissions": [],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/website/doctype/website_slideshow/website_slideshow.json b/frappe/website/doctype/website_slideshow/website_slideshow.json
index 6d8eab1a85..de4c6ee5c2 100644
--- a/frappe/website/doctype/website_slideshow/website_slideshow.json
+++ b/frappe/website/doctype/website_slideshow/website_slideshow.json
@@ -1,192 +1,71 @@
{
- "allow_copy": 0,
- "allow_events_in_timeline": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:slideshow_name",
- "beta": 0,
- "creation": "2013-03-07 15:53:15",
- "custom": 0,
- "description": "Slideshow like display for the website",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Document",
- "editable_grid": 0,
+ "actions": [],
+ "autoname": "field:slideshow_name",
+ "creation": "2013-03-07 15:53:15",
+ "description": "Slideshow like display for the website",
+ "doctype": "DocType",
+ "document_type": "Document",
+ "engine": "InnoDB",
+ "field_order": [
+ "slideshow_name",
+ "sb0",
+ "slideshow_items",
+ "header"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "slideshow_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Slideshow 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,
+ "fieldname": "slideshow_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Slideshow Name",
+ "reqd": 1,
"unique": 1
- },
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!doc.__islocal",
- "description": "Note: For best results, images must be of the same size and width must be greater than height.",
- "fieldname": "sb0",
- "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,
- "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
- },
+ "depends_on": "eval:!doc.__islocal",
+ "description": "Note: For best results, images must be of the same size and width must be greater than height.",
+ "fieldname": "sb0",
+ "fieldtype": "Section Break"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!doc.__islocal",
- "fieldname": "slideshow_items",
- "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": "Slideshow Items",
- "length": 0,
- "no_copy": 0,
- "options": "Website Slideshow Item",
- "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
- },
+ "depends_on": "eval:!doc.__islocal",
+ "fieldname": "slideshow_items",
+ "fieldtype": "Table",
+ "label": "Slideshow Items",
+ "options": "Website Slideshow Item"
+ },
{
- "allow_bulk_edit": 0,
- "allow_in_quick_entry": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "depends_on": "eval:!doc.__islocal",
- "description": "This goes above the slideshow.",
- "fieldname": "header",
- "fieldtype": "HTML Editor",
- "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": "Header",
- "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
+ "depends_on": "eval:!doc.__islocal",
+ "description": "This goes above the slideshow.",
+ "fieldname": "header",
+ "fieldtype": "HTML Editor",
+ "label": "Header"
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-play",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 10,
- "modified": "2019-02-21 15:05:32.218696",
- "modified_by": "Administrator",
- "module": "Website",
- "name": "Website Slideshow",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-play",
+ "idx": 1,
+ "links": [],
+ "max_attachments": 10,
+ "modified": "2022-08-03 12:20:49.654403",
+ "modified_by": "Administrator",
+ "module": "Website",
+ "name": "Website Slideshow",
+ "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": "Website Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "report": 1,
+ "role": "Website Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 0,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 1,
- "track_seen": 0,
- "track_views": 0
+ ],
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/workflow/doctype/workflow_action_master/workflow_action_master.json b/frappe/workflow/doctype/workflow_action_master/workflow_action_master.json
index 4931ac5b13..d47923950d 100644
--- a/frappe/workflow/doctype/workflow_action_master/workflow_action_master.json
+++ b/frappe/workflow/doctype/workflow_action_master/workflow_action_master.json
@@ -1,89 +1,46 @@
{
- "allow_copy": 0,
- "allow_guest_to_view": 0,
- "allow_import": 0,
- "allow_rename": 0,
- "autoname": "field:workflow_action_name",
- "beta": 0,
- "creation": "2012-12-28 10:49:56",
- "custom": 0,
- "description": "Workflow Action Master",
- "docstatus": 0,
- "doctype": "DocType",
- "editable_grid": 0,
+ "actions": [],
+ "autoname": "field:workflow_action_name",
+ "creation": "2012-12-28 10:49:56",
+ "description": "Workflow Action Master",
+ "doctype": "DocType",
+ "engine": "InnoDB",
+ "field_order": [
+ "workflow_action_name"
+ ],
"fields": [
{
- "allow_bulk_edit": 0,
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "workflow_action_name",
- "fieldtype": "Data",
- "hidden": 0,
- "ignore_user_permissions": 0,
- "ignore_xss_filter": 0,
- "in_filter": 0,
- "in_global_search": 0,
- "in_list_view": 1,
- "in_standard_filter": 0,
- "label": "Workflow Action 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": 0
+ "fieldname": "workflow_action_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "Workflow Action Name",
+ "reqd": 1,
+ "unique": 1
}
- ],
- "has_web_view": 0,
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-flag",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2018-05-18 10:31:09.040701",
- "modified_by": "Administrator",
- "module": "Workflow",
- "name": "Workflow Action Master",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-flag",
+ "idx": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:52.449982",
+ "modified_by": "Administrator",
+ "module": "Workflow",
+ "name": "Workflow Action Master",
+ "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": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 0,
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "sort_field": "modified",
+ "sort_order": "DESC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
diff --git a/frappe/workflow/doctype/workflow_state/workflow_state.json b/frappe/workflow/doctype/workflow_state/workflow_state.json
index be5804f390..e6b902938f 100644
--- a/frappe/workflow/doctype/workflow_state/workflow_state.json
+++ b/frappe/workflow/doctype/workflow_state/workflow_state.json
@@ -1,153 +1,70 @@
{
- "allow_copy": 0,
- "allow_import": 1,
- "allow_rename": 0,
- "autoname": "field:workflow_state_name",
- "beta": 0,
- "creation": "2012-12-28 10:49:56",
- "custom": 0,
- "description": "Workflow state represents the current state of a document.",
- "docstatus": 0,
- "doctype": "DocType",
- "document_type": "Setup",
- "editable_grid": 0,
+ "actions": [],
+ "allow_import": 1,
+ "autoname": "field:workflow_state_name",
+ "creation": "2012-12-28 10:49:56",
+ "description": "Workflow state represents the current state of a document.",
+ "doctype": "DocType",
+ "document_type": "Setup",
+ "engine": "InnoDB",
+ "field_order": [
+ "workflow_state_name",
+ "icon",
+ "style"
+ ],
"fields": [
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "fieldname": "workflow_state_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": "State",
- "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,
- "unique": 0
- },
+ "fieldname": "workflow_state_name",
+ "fieldtype": "Data",
+ "in_list_view": 1,
+ "label": "State",
+ "reqd": 1,
+ "unique": 1
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Icon will appear on the button",
- "fieldname": "icon",
- "fieldtype": "Select",
- "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": "Icon",
- "length": 0,
- "no_copy": 0,
- "options": "\nglass\nmusic\nsearch\nenvelope\nheart\nstar\nstar-empty\nuser\nfilm\nth-large\nth\nth-list\nok\nremove\nzoom-in\nzoom-out\noff\nsignal\ncog\ntrash\nhome\nfile\ntime\nroad\ndownload-alt\ndownload\nupload\ninbox\nplay-circle\nrepeat\nrefresh\nlist-alt\nlock\nflag\nheadphones\nvolume-off\nvolume-down\nvolume-up\nqrcode\nbarcode\ntag\ntags\nbook\nbookmark\nprint\ncamera\nfont\nbold\nitalic\ntext-height\ntext-width\nalign-left\nalign-center\nalign-right\nalign-justify\nlist\nindent-left\nindent-right\nfacetime-video\npicture\npencil\nmap-marker\nadjust\ntint\nedit\nshare\ncheck\nmove\nstep-backward\nfast-backward\nbackward\nplay\npause\nstop\nforward\nfast-forward\nstep-forward\neject\nchevron-left\nchevron-right\nplus-sign\nminus-sign\nremove-sign\nok-sign\nquestion-sign\ninfo-sign\nscreenshot\nremove-circle\nok-circle\nban-circle\narrow-left\narrow-right\narrow-up\narrow-down\nshare-alt\nresize-full\nresize-small\nplus\nminus\nasterisk\nexclamation-sign\ngift\nleaf\nfire\neye-open\neye-close\nwarning-sign\nplane\ncalendar\nrandom\ncomment\nmagnet\nchevron-up\nchevron-down\nretweet\nshopping-cart\nfolder-close\nfolder-open\nresize-vertical\nresize-horizontal\nhdd\nbullhorn\nbell\ncertificate\nthumbs-up\nthumbs-down\nhand-right\nhand-left\nhand-up\nhand-down\ncircle-arrow-right\ncircle-arrow-left\ncircle-arrow-up\ncircle-arrow-down\nglobe\nwrench\ntasks\nfilter\nbriefcase\nfullscreen",
- "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,
- "unique": 0
- },
+ "description": "Icon will appear on the button",
+ "fieldname": "icon",
+ "fieldtype": "Select",
+ "hidden": 1,
+ "label": "Icon",
+ "options": "\nglass\nmusic\nsearch\nenvelope\nheart\nstar\nstar-empty\nuser\nfilm\nth-large\nth\nth-list\nok\nremove\nzoom-in\nzoom-out\noff\nsignal\ncog\ntrash\nhome\nfile\ntime\nroad\ndownload-alt\ndownload\nupload\ninbox\nplay-circle\nrepeat\nrefresh\nlist-alt\nlock\nflag\nheadphones\nvolume-off\nvolume-down\nvolume-up\nqrcode\nbarcode\ntag\ntags\nbook\nbookmark\nprint\ncamera\nfont\nbold\nitalic\ntext-height\ntext-width\nalign-left\nalign-center\nalign-right\nalign-justify\nlist\nindent-left\nindent-right\nfacetime-video\npicture\npencil\nmap-marker\nadjust\ntint\nedit\nshare\ncheck\nmove\nstep-backward\nfast-backward\nbackward\nplay\npause\nstop\nforward\nfast-forward\nstep-forward\neject\nchevron-left\nchevron-right\nplus-sign\nminus-sign\nremove-sign\nok-sign\nquestion-sign\ninfo-sign\nscreenshot\nremove-circle\nok-circle\nban-circle\narrow-left\narrow-right\narrow-up\narrow-down\nshare-alt\nresize-full\nresize-small\nplus\nminus\nasterisk\nexclamation-sign\ngift\nleaf\nfire\neye-open\neye-close\nwarning-sign\nplane\ncalendar\nrandom\ncomment\nmagnet\nchevron-up\nchevron-down\nretweet\nshopping-cart\nfolder-close\nfolder-open\nresize-vertical\nresize-horizontal\nhdd\nbullhorn\nbell\ncertificate\nthumbs-up\nthumbs-down\nhand-right\nhand-left\nhand-up\nhand-down\ncircle-arrow-right\ncircle-arrow-left\ncircle-arrow-up\ncircle-arrow-down\nglobe\nwrench\ntasks\nfilter\nbriefcase\nfullscreen"
+ },
{
- "allow_on_submit": 0,
- "bold": 0,
- "collapsible": 0,
- "columns": 0,
- "description": "Style represents the button color: Success - Green, Danger - Red, Inverse - Black, Primary - Dark Blue, Info - Light Blue, Warning - Orange",
- "fieldname": "style",
- "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": "Style",
- "length": 0,
- "no_copy": 0,
- "options": "\nPrimary\nInfo\nSuccess\nWarning\nDanger\nInverse",
- "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,
- "unique": 0
+ "description": "Style represents the button color: Success - Green, Danger - Red, Inverse - Black, Primary - Dark Blue, Info - Light Blue, Warning - Orange",
+ "fieldname": "style",
+ "fieldtype": "Select",
+ "label": "Style",
+ "options": "\nPrimary\nInfo\nSuccess\nWarning\nDanger\nInverse"
}
- ],
- "hide_heading": 0,
- "hide_toolbar": 0,
- "icon": "fa fa-flag",
- "idx": 1,
- "image_view": 0,
- "in_create": 0,
-
- "is_submittable": 0,
- "issingle": 0,
- "istable": 0,
- "max_attachments": 0,
- "modified": "2021-11-22 17:56:40.495232",
- "modified_by": "Administrator",
- "module": "Workflow",
- "name": "Workflow State",
- "owner": "Administrator",
+ ],
+ "icon": "fa fa-flag",
+ "idx": 1,
+ "links": [],
+ "modified": "2022-08-03 12:20:52.588427",
+ "modified_by": "Administrator",
+ "module": "Workflow",
+ "name": "Workflow State",
+ "owner": "Administrator",
"permissions": [
{
- "amend": 0,
- "apply_user_permissions": 0,
- "cancel": 0,
- "create": 1,
- "delete": 1,
- "email": 1,
- "export": 0,
- "if_owner": 0,
- "import": 0,
- "permlevel": 0,
- "print": 1,
- "read": 1,
- "report": 0,
- "role": "System Manager",
- "set_user_permissions": 0,
- "share": 1,
- "submit": 0,
+ "create": 1,
+ "delete": 1,
+ "email": 1,
+ "print": 1,
+ "read": 1,
+ "role": "System Manager",
+ "share": 1,
"write": 1
},
{
"role": "All",
"select": 1
}
- ],
- "quick_entry": 1,
- "read_only": 0,
- "read_only_onload": 0,
- "show_name_in_global_search": 1,
- "sort_order": "ASC",
- "track_changes": 1,
- "track_seen": 0
+ ],
+ "quick_entry": 1,
+ "show_name_in_global_search": 1,
+ "sort_field": "modified",
+ "sort_order": "ASC",
+ "states": [],
+ "track_changes": 1
}
\ No newline at end of file
From 8def87331f2d596c21f6659a93810a3e8078843e Mon Sep 17 00:00:00 2001
From: Gavin D'souza
Date: Wed, 3 Aug 2022 12:26:38 +0530
Subject: [PATCH 0286/2449] chore: Add minification to blame ignore
---
.git-blame-ignore-revs | 3 +++
1 file changed, 3 insertions(+)
diff --git a/.git-blame-ignore-revs b/.git-blame-ignore-revs
index c3ad43c5be..6ff4e47366 100644
--- a/.git-blame-ignore-revs
+++ b/.git-blame-ignore-revs
@@ -25,3 +25,6 @@ c0c5b2ebdddbe8898ce2d5e5365f4931ff73b6bf
# update python code to use 3.10 supported features
81b37cb7d2160866afa2496873656afe53f0c145
+
+# mass minified JSON schema
+85e3ee940353d7b0b517b33815148672e9a8b15b
From bdeb032fbaca171646d417e7754692de2a4c1516 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 3 Aug 2022 13:52:02 +0530
Subject: [PATCH 0287/2449] refactor: use separate config key for encryption
(#17720)
---
frappe/commands/site.py | 8 +++----
frappe/patches.txt | 3 ++-
.../patches/v14_0/different_encryption_key.py | 16 ++++++++++++++
frappe/utils/backups.py | 21 ++++++++++++++++---
4 files changed, 40 insertions(+), 8 deletions(-)
create mode 100644 frappe/patches/v14_0/different_encryption_key.py
diff --git a/frappe/commands/site.py b/frappe/commands/site.py
index e3c7de32a3..ab599be121 100644
--- a/frappe/commands/site.py
+++ b/frappe/commands/site.py
@@ -142,7 +142,7 @@ def restore(
is_partial,
validate_database_sql,
)
- from frappe.utils.backups import Backup
+ from frappe.utils.backups import Backup, get_or_generate_backup_encryption_key
_backup = Backup(sql_file_path)
@@ -171,7 +171,7 @@ def restore(
else:
click.secho("Encrypted backup file detected. Decrypting using site config.", fg="yellow")
- encryption_key = frappe.get_site_config().encryption_key
+ encryption_key = get_or_generate_backup_encryption_key()
_backup.backup_decryption(encryption_key)
# Rollback on unsuccessful decryrption
@@ -268,7 +268,7 @@ def restore(
@pass_context
def partial_restore(context, sql_file_path, verbose, encryption_key=None):
from frappe.installer import extract_sql_from_archive, partial_restore
- from frappe.utils.backups import Backup
+ from frappe.utils.backups import Backup, get_or_generate_backup_encryption_key
if not os.path.exists(sql_file_path):
print("Invalid path", sql_file_path)
@@ -304,7 +304,7 @@ def partial_restore(context, sql_file_path, verbose, encryption_key=None):
else:
click.secho("Encrypted backup file detected. Decrypting using site config.", fg="yellow")
- key = frappe.get_site_config().encryption_key
+ key = get_or_generate_backup_encryption_key()
_backup.backup_decryption(key)
diff --git a/frappe/patches.txt b/frappe/patches.txt
index 0dce4b9f71..2f6ebd334a 100644
--- a/frappe/patches.txt
+++ b/frappe/patches.txt
@@ -208,4 +208,5 @@ frappe.patches.v14_0.update_auto_account_deletion_duration
frappe.patches.v14_0.update_integration_request
frappe.patches.v14_0.set_document_expiry_default
frappe.patches.v14_0.delete_data_migration_tool
-frappe.patches.v14_0.set_suspend_email_queue_default
\ No newline at end of file
+frappe.patches.v14_0.set_suspend_email_queue_default
+frappe.patches.v14_0.different_encryption_key
diff --git a/frappe/patches/v14_0/different_encryption_key.py b/frappe/patches/v14_0/different_encryption_key.py
new file mode 100644
index 0000000000..3b80e15a73
--- /dev/null
+++ b/frappe/patches/v14_0/different_encryption_key.py
@@ -0,0 +1,16 @@
+import pathlib
+
+import frappe
+from frappe.installer import update_site_config
+from frappe.utils.backups import BACKUP_ENCRYPTION_CONFIG_KEY, get_backup_path
+
+
+def execute():
+ if frappe.conf.get(BACKUP_ENCRYPTION_CONFIG_KEY):
+ return
+
+ backup_path = pathlib.Path(get_backup_path())
+ encrypted_backups_present = bool(list(backup_path.glob("*-enc*")))
+
+ if encrypted_backups_present:
+ update_site_config(BACKUP_ENCRYPTION_CONFIG_KEY, frappe.local.conf.encryption_key)
diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py
index f5e13c0873..b5722def4f 100644
--- a/frappe/utils/backups.py
+++ b/frappe/utils/backups.py
@@ -11,12 +11,12 @@ from shutil import which
# imports - third party imports
import click
+from cryptography.fernet import Fernet
# imports - module imports
import frappe
from frappe import conf
from frappe.utils import cint, get_file_size, get_url, now, now_datetime
-from frappe.utils.password import get_encryption_key
# backup variable for backwards compatibility
verbose = False
@@ -24,6 +24,8 @@ compress = False
_verbose = verbose
base_tables = ["__Auth", "__global_search", "__UserSettings"]
+BACKUP_ENCRYPTION_CONFIG_KEY = "backup_encryption_key"
+
class BackupGenerator:
"""
@@ -230,7 +232,7 @@ class BackupGenerator:
cmd_string = "gpg --yes --passphrase {passphrase} --pinentry-mode loopback -c {filelocation}"
try:
command = cmd_string.format(
- passphrase=get_encryption_key(),
+ passphrase=get_or_generate_backup_encryption_key(),
filelocation=path,
)
@@ -628,7 +630,20 @@ def get_backup_path():
@frappe.whitelist()
def get_backup_encryption_key():
frappe.only_for("System Manager")
- return frappe.conf.encryption_key
+ return frappe.conf.get(BACKUP_ENCRYPTION_CONFIG_KEY)
+
+
+def get_or_generate_backup_encryption_key():
+ from frappe.installer import update_site_config
+
+ key = frappe.conf.get(BACKUP_ENCRYPTION_CONFIG_KEY)
+ if key:
+ return key
+
+ key = Fernet.generate_key().decode()
+ update_site_config(BACKUP_ENCRYPTION_CONFIG_KEY, key)
+
+ return key
class Backup:
From 5792265f7713b52d7c6fcd343e442922501185b4 Mon Sep 17 00:00:00 2001
From: Aradhya
Date: Wed, 3 Aug 2022 16:31:04 +0530
Subject: [PATCH 0288/2449] feat: Added support for fieldnames from child
tables
---
frappe/database/query.py | 36 +++++++++++++++++++++++++++++++-----
1 file changed, 31 insertions(+), 5 deletions(-)
diff --git a/frappe/database/query.py b/frappe/database/query.py
index 89e3857f9e..b873844ff6 100644
--- a/frappe/database/query.py
+++ b/frappe/database/query.py
@@ -4,7 +4,7 @@ from ast import literal_eval
from functools import cached_property
import sys
from types import BuiltinFunctionType
-from typing import Any, Callable, Iterable
+from typing import Any, Callable
import frappe
from frappe import _
@@ -481,10 +481,30 @@ class Engine:
fields = [field for field in updated_fields if field]
return fields
- def set_fields(self, fields, **kwargs):
+ def get_fieldnames_from_child_table(self, doctype, fields):
+ # convert child_table.fieldname to `tabChild DocType`.`fieldname`
+ linked_doctype, fieldname = None, None
+ for field in fields:
+ if "." in field and "tab" not in field:
+ original_field = field
+ alias = None
+ if " as " in field:
+ field, alias = field.split(" as ")
+ linked_fieldname, fieldname = field.split(".")
+ linked_field = frappe.get_meta(doctype).get_field(linked_fieldname)
+ linked_doctype = linked_field.options
+ field = f"`tab{linked_doctype}`.`{fieldname}`"
+ if alias:
+ field = f"{field} as {alias}"
+ fields[fields.index(original_field)] = field
+
+ return fields, linked_doctype, fieldname
+
+ def set_fields(self, table, fields, **kwargs):
fields = kwargs.get("pluck") if kwargs.get("pluck") else fields or "name"
if isinstance(fields, list) and None in fields and Field not in fields:
return None
+ linked_doctype, linked_field = None, None
function_objects = []
is_list = isinstance(fields, (list, tuple, set))
if is_list and len(fields) == 1:
@@ -523,6 +543,7 @@ class Engine:
updated_fields = []
if "*" in fields:
return fields
+ fields, linked_doctype, linked_field = self.get_fieldnames_from_child_table(doctype=table, fields=fields)
for field in fields:
if not isinstance(field, Criterion) and field:
if " as " in field:
@@ -538,13 +559,12 @@ class Engine:
updated_fields.append(Field(field))
fields = updated_fields
-
# Need to check instance again since fields modified.
if not isinstance(fields, (list, tuple, set)):
fields = [fields] if fields else []
fields.extend(function_objects)
- return fields
+ return fields, linked_doctype, linked_field
def get_query(
self,
@@ -556,7 +576,13 @@ class Engine:
# Clean up state before each query
self.tables = {}
criterion = self.build_conditions(table, filters, **kwargs)
- fields = self.set_fields(kwargs.get("field_objects") or fields, **kwargs)
+ fields, linked_doctype, linked_field = self.set_fields(table, kwargs.get("field_objects") or fields, **kwargs)
+
+ if linked_doctype:
+ linked_doctype = frappe.qb.DocType(linked_doctype)
+ criterion = criterion.left_join(linked_doctype).on(
+ linked_doctype.name == frappe.qb.DocType(table).linked_field
+ )
join = kwargs.get("join").replace(" ", "_") if kwargs.get("join") else "left_join"
From a1eaefefda1aa91f45081fbf68d1e69cf80f7587 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sat, 2 Jul 2022 22:48:12 +0530
Subject: [PATCH 0289/2449] refactor: simplfiy translate pattern
\s already includes \n
---
frappe/translate.py | 6 +++---
1 file changed, 3 insertions(+), 3 deletions(-)
diff --git a/frappe/translate.py b/frappe/translate.py
index c640179723..4c98a7ebce 100644
--- a/frappe/translate.py
+++ b/frappe/translate.py
@@ -24,14 +24,14 @@ from frappe.query_builder import DocType, Field
from frappe.utils import cstr, get_bench_path, is_html, strip, strip_html_tags
TRANSLATE_PATTERN = re.compile(
- r"_\([\s\n]*" # starts with literal `_(`, ignore following whitespace/newlines
+ r"_\(\s*" # starts with literal `_(`, ignore following whitespace/newlines
# BEGIN: message search
r"([\"']{,3})" # start of message string identifier - allows: ', ", """, '''; 1st capture group
r"(?P((?!\1).)*)" # Keep matching until string closing identifier is met which is same as 1st capture group
r"\1" # match exact string closing identifier
# END: message search
# BEGIN: python context search
- r"([\s\n]*,[\s\n]*context\s*=\s*" # capture `context=` with ignoring whitespace
+ r"(\s*,\s*context\s*=\s*" # capture `context=` with ignoring whitespace
r"([\"'])" # start of context string identifier; 5th capture group
r"(?P((?!\5).)*)" # capture context string till closing id is found
r"\5" # match context string closure
@@ -45,7 +45,7 @@ TRANSLATE_PATTERN = re.compile(
r")*"
r")*" # match one or more context string
# END: JS context search
- r"[\s\n]*\)" # Closing function call ignore leading whitespace/newlines
+ r"\s*\)" # Closing function call ignore leading whitespace/newlines
)
REPORT_TRANSLATE_PATTERN = re.compile('"([^:,^"]*):')
CSV_STRIP_WHITESPACE_PATTERN = re.compile(r"{\s?([0-9]+)\s?}")
From 7c1b96bc623b139254495a1d02de0f1dd1432c19 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Sat, 2 Jul 2022 21:59:26 +0530
Subject: [PATCH 0290/2449] style: autoformat JS files
---
.editorconfig | 1 +
.pre-commit-config.yaml | 16 ++++++++++++++++
2 files changed, 17 insertions(+)
diff --git a/.editorconfig b/.editorconfig
index f4c7f1528c..a3b1ef0924 100644
--- a/.editorconfig
+++ b/.editorconfig
@@ -12,3 +12,4 @@ charset = utf-8
[{*.py,*.js,*.vue,*.css,*.scss,*.html}]
indent_style = tab
indent_size = 4
+max_line_length = 99
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index b231221517..e4da469f8a 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -32,6 +32,22 @@ repos:
- id: black
additional_dependencies: ['click==8.0.4']
+ - repo: https://github.com/pre-commit/mirrors-prettier
+ rev: v2.7.1
+ hooks:
+ - id: prettier
+ types_or: [javascript]
+ exclude: |
+ (?x)^(
+ frappe/public/dist/.*|
+ .*node_modules.*|
+ .*boilerplate.*|
+ frappe/www/website_script.js|
+ frappe/templates/includes/.*|
+ frappe/public/js/lib/.*
+ )$
+
+
- repo: https://github.com/timothycrosley/isort
rev: 5.9.1
hooks:
From 015937a03aabd820e185ad57cf14eb7dd8b6424d Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Wed, 13 Jul 2022 14:39:25 +0530
Subject: [PATCH 0291/2449] test: fix anticipiated test failures
---
.pre-commit-config.yaml | 1 +
frappe/tests/test_website.py | 4 ++--
2 files changed, 3 insertions(+), 2 deletions(-)
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index e4da469f8a..27fae671c9 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -37,6 +37,7 @@ repos:
hooks:
- id: prettier
types_or: [javascript]
+ # Ignore any files that might contain jinja / bundles
exclude: |
(?x)^(
frappe/public/dist/.*|
diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py
index 9478c4cf5f..e8cca765a0 100644
--- a/frappe/tests/test_website.py
+++ b/frappe/tests/test_website.py
@@ -263,13 +263,13 @@ class TestWebsite(unittest.TestCase):
def test_colocated_assets(self):
content = get_response_content("/_test/_test_folder/_test_page")
- self.assertIn("", content)
+ self.assertIn("""""", content)
self.assertIn("background-color: var(--bg-color);", content)
def test_raw_assets_are_loaded(self):
content = get_response_content("/_test/assets/js_asset.min.js")
# minified js files should not be passed through jinja renderer
- self.assertEqual("//{% if title %} {{title}} {% endif %}\nconsole.log('in');", content)
+ self.assertEqual("""//{% if title %} {{title}} {% endif %}\nconsole.log("in");\n""", content)
content = get_response_content("/_test/assets/css_asset.css")
self.assertEqual("""body{color:red}""", content)
From 40f27f908a3890c9a90d2d96794fc31fcea63c59 Mon Sep 17 00:00:00 2001
From: Ankush Menat
Date: Thu, 4 Aug 2022 14:51:01 +0530
Subject: [PATCH 0292/2449] style: format JS files with prettier
---
commitlint.config.js | 34 +-
cypress/fixtures/child_table_doctype.js | 12 +-
cypress/fixtures/child_table_doctype_1.js | 22 +-
.../fixtures/custom_submittable_doctype.js | 54 +-
.../fixtures/data_field_validation_doctype.js | 74 +-
cypress/fixtures/datetime_doctype.js | 48 +-
cypress/fixtures/doctype_to_link.js | 32 +-
cypress/fixtures/doctype_with_child_table.js | 20 +-
cypress/fixtures/doctype_with_phone.js | 41 +-
cypress/fixtures/doctype_with_tab_break.js | 54 +-
cypress/integration/api.js | 51 +-
cypress/integration/awesome_bar.js | 61 +-
cypress/integration/control_attach.js | 93 +-
cypress/integration/control_autocomplete.js | 71 +-
cypress/integration/control_barcode.js | 60 +-
cypress/integration/control_color.js | 81 +-
cypress/integration/control_data.js | 189 +--
cypress/integration/control_date.js | 79 +-
cypress/integration/control_date_range.js | 50 +-
cypress/integration/control_duration.js | 58 +-
cypress/integration/control_dynamic_link.js | 206 +--
cypress/integration/control_float.js | 44 +-
cypress/integration/control_icon.js | 65 +-
cypress/integration/control_link.js | 542 +++----
.../integration/control_markdown_editor.js | 9 +-
cypress/integration/control_phone.js | 32 +-
cypress/integration/control_rating.js | 60 +-
cypress/integration/control_select.js | 49 +-
cypress/integration/custom_buttons.js | 5 +-
cypress/integration/customize_form.js | 12 +-
cypress/integration/dashboard_chart.js | 26 +-
cypress/integration/dashboard_links.js | 107 +-
.../integration/data_field_form_validation.js | 44 +-
cypress/integration/datetime.js | 119 +-
.../datetime_field_form_validation.js | 2 +-
cypress/integration/depends_on.js | 247 ++--
cypress/integration/discussions.js | 110 +-
cypress/integration/file_uploader.js | 94 +-
cypress/integration/first_day_of_the_week.js | 50 +-
cypress/integration/folder_navigation.js | 86 +-
cypress/integration/form.js | 164 ++-
cypress/integration/form_tab_break.js | 17 +-
cypress/integration/form_tour.js | 80 +-
cypress/integration/grid.js | 168 ++-
cypress/integration/grid_configuration.js | 26 +-
cypress/integration/grid_keyboard_shortcut.js | 49 +-
cypress/integration/grid_pagination.js | 106 +-
cypress/integration/grid_search.js | 158 +-
cypress/integration/kanban.js | 117 +-
cypress/integration/list_paging.js | 47 +-
cypress/integration/list_view.js | 91 +-
cypress/integration/list_view_settings.js | 46 +-
cypress/integration/login.js | 76 +-
cypress/integration/multi_select_dialog.js | 125 +-
cypress/integration/navigation.js | 38 +-
cypress/integration/number_card.js | 26 +-
cypress/integration/query_report.js | 120 +-
cypress/integration/recorder.js | 86 +-
cypress/integration/report_view.js | 46 +-
cypress/integration/sidebar.js | 53 +-
cypress/integration/table_multiselect.js | 80 +-
cypress/integration/theme_switcher_dialog.js | 37 +-
cypress/integration/timeline.js | 78 +-
cypress/integration/timeline_email.js | 97 +-
cypress/integration/url_data_field.js | 47 +-
cypress/integration/web_form.js | 300 ++--
cypress/integration/workspace.js | 249 ++--
cypress/integration/workspace_blocks.js | 176 +--
cypress/plugins/index.js | 4 +-
cypress/support/commands.js | 423 +++---
cypress/support/index.js | 10 +-
esbuild/build-cleanup.js | 35 +-
esbuild/esbuild.js | 139 +-
esbuild/frappe-html.js | 18 +-
esbuild/ignore-assets.js | 6 +-
esbuild/sass_options.js | 16 +-
esbuild/utils.js | 24 +-
.../assignment_rule/assignment_rule.js | 77 +-
.../doctype/auto_repeat/auto_repeat.js | 72 +-
.../doctype/auto_repeat/auto_repeat_list.js | 12 +-
.../automation/doctype/milestone/milestone.js | 3 +-
.../milestone_tracker/milestone_tracker.js | 24 +-
frappe/contacts/doctype/address/address.js | 58 +-
.../address_template/address_template.js | 16 +-
frappe/contacts/doctype/contact/contact.js | 120 +-
.../contacts/doctype/contact/contact_list.js | 4 +-
frappe/contacts/doctype/gender/gender.js | 6 +-
.../contacts/doctype/salutation/salutation.js | 6 +-
.../addresses_and_contacts.js | 44 +-
frappe/core/doctype/access_log/access_log.js | 10 +-
.../core/doctype/activity_log/activity_log.js | 6 +-
.../doctype/activity_log/activity_log_list.js | 13 +-
frappe/core/doctype/comment/comment.js | 3 +-
.../doctype/communication/communication.js | 347 +++--
.../communication/communication_list.js | 29 +-
.../doctype/custom_docperm/custom_docperm.js | 6 +-
.../core/doctype/custom_role/custom_role.js | 6 +-
.../core/doctype/data_export/data_export.js | 115 +-
.../core/doctype/data_import/data_import.js | 377 ++---
.../doctype/data_import/data_import_list.js | 40 +-
.../data_import_log/data_import_log.js | 3 +-
.../deleted_document/deleted_document.js | 22 +-
.../deleted_document/deleted_document_list.js | 21 +-
frappe/core/doctype/docshare/docshare.js | 6 +-
frappe/core/doctype/doctype/doctype.js | 77 +-
.../document_naming_rule.js | 60 +-
.../document_naming_rule_condition.js | 3 +-
.../document_naming_settings.js | 25 +-
.../document_share_key/document_share_key.js | 3 +-
frappe/core/doctype/domain/domain.js | 6 +-
.../domain_settings/domain_settings.js | 45 +-
frappe/core/doctype/error_log/error_log.js | 4 +-
.../core/doctype/error_log/error_log_list.js | 10 +-
.../doctype/error_snapshot/error_snapshot.js | 14 +-
.../error_snapshot/error_snapshot_list.js | 16 +-
frappe/core/doctype/file/file.js | 36 +-
.../installed_applications.js | 3 +-
frappe/core/doctype/language/language.js | 6 +-
.../log_setting_user/log_setting_user.js | 3 +-
.../core/doctype/log_settings/log_settings.js | 4 +-
frappe/core/doctype/module_def/module_def.js | 12 +-
.../doctype/module_profile/module_profile.js | 2 +-
.../core/doctype/navbar_item/navbar_item.js | 3 +-
.../navbar_settings/navbar_settings.js | 3 +-
frappe/core/doctype/package/package.js | 21 +-
.../doctype/package_import/package_import.js | 3 +-
.../package_release/package_release.js | 3 +-
frappe/core/doctype/page/page.js | 10 +-
frappe/core/doctype/patch_log/patch_log.js | 6 +-
.../prepared_report/prepared_report.js | 20 +-
.../prepared_report/prepared_report_list.js | 14 +-
frappe/core/doctype/report/report.js | 72 +-
frappe/core/doctype/role/role.js | 16 +-
.../role_permission_for_page_and_report.js | 106 +-
.../core/doctype/role_profile/role_profile.js | 9 +-
.../scheduled_job_log/scheduled_job_log.js | 3 +-
.../scheduled_job_log_list.js | 4 +-
.../scheduled_job_type/scheduled_job_type.js | 3 +-
.../doctype/server_script/server_script.js | 26 +-
.../session_default_settings.js | 12 +-
.../doctype/success_action/success_action.js | 36 +-
.../system_settings/system_settings.js | 18 +-
.../transaction_log/transaction_log.js | 4 +-
.../core/doctype/translation/translation.js | 7 +-
frappe/core/doctype/user/user.js | 333 +++--
frappe/core/doctype/user/user_list.js | 12 +-
frappe/core/doctype/user_group/user_group.js | 3 +-
.../user_group_member/user_group_member.js | 3 +-
.../user_permission/user_permission.js | 55 +-
.../user_permission/user_permission_list.js | 275 ++--
frappe/core/doctype/user_type/user_type.js | 59 +-
.../core/doctype/user_type/user_type_list.js | 4 +-
frappe/core/doctype/version/version.js | 15 +-
frappe/core/doctype/view_log/view_log.js | 6 +-
.../page/background_jobs/background_jobs.js | 57 +-
.../page/dashboard_view/dashboard_view.js | 119 +-
.../permission_manager/permission_manager.js | 225 +--
frappe/core/page/recorder/recorder.js | 14 +-
.../permitted_documents_for_user.js | 48 +-
.../transaction_log_report.js | 8 +-
.../web_form/edit_profile/edit_profile.js | 4 +-
.../doctype/client_script/client_script.js | 71 +-
.../client_script/ui_test_client_script.js | 23 +-
.../doctype/custom_field/custom_field.js | 111 +-
.../doctype/customize_form/customize_form.js | 157 +-
.../doctype/doctype_layout/doctype_layout.js | 26 +-
.../property_setter/property_setter.js | 10 +-
.../desk/doctype/bulk_update/bulk_update.js | 90 +-
.../doctype/calendar_view/calendar_view.js | 35 +-
.../desk/doctype/console_log/console_log.js | 3 +-
frappe/desk/doctype/dashboard/dashboard.js | 18 +-
.../desk/doctype/dashboard/dashboard_list.js | 10 +-
.../dashboard_chart/dashboard_chart.js | 469 +++---
.../dashboard_chart_source.js | 3 +-
.../dashboard_settings/dashboard_settings.js | 3 +-
.../desk/doctype/desktop_icon/desktop_icon.js | 6 +-
frappe/desk/doctype/event/event.js | 85 +-
frappe/desk/doctype/event/event_calendar.js | 22 +-
frappe/desk/doctype/event/event_list.js | 10 +-
frappe/desk/doctype/form_tour/form_tour.js | 82 +-
.../global_search_settings.js | 23 +-
.../desk/doctype/kanban_board/kanban_board.js | 58 +-
.../list_view_settings/list_view_settings.js | 5 +-
.../module_onboarding/module_onboarding.js | 4 +-
frappe/desk/doctype/note/note.js | 17 +-
frappe/desk/doctype/note/note_list.js | 12 +-
.../notification_log/notification_log.js | 18 +-
.../notification_settings.js | 23 +-
.../desk/doctype/number_card/number_card.js | 381 ++---
.../onboarding_permission.js | 3 +-
.../onboarding_step/onboarding_step.js | 24 +-
.../doctype/route_history/route_history.js | 6 +-
.../route_history/route_history_list.js | 4 +-
.../doctype/system_console/system_console.js | 61 +-
frappe/desk/doctype/tag/tag.js | 3 +-
frappe/desk/doctype/tag_link/tag_link.js | 3 +-
frappe/desk/doctype/todo/todo.js | 63 +-
frappe/desk/doctype/todo/todo_calendar.js | 34 +-
frappe/desk/doctype/todo/todo_list.js | 38 +-
frappe/desk/doctype/workspace/workspace.js | 28 +-
frappe/desk/page/activity/activity.js | 151 +-
frappe/desk/page/backups/backups.js | 22 +-
frappe/desk/page/leaderboard/leaderboard.js | 274 ++--
frappe/desk/page/setup_wizard/setup_wizard.js | 205 +--
.../page/translation_tool/translation_tool.js | 374 ++---
frappe/desk/page/user_profile/user_profile.js | 4 +-
.../user_profile/user_profile_controller.js | 407 ++---
frappe/desk/report/todo/todo.js | 6 +-
.../auto_email_report/auto_email_report.js | 151 +-
.../document_follow/document_follow.js | 4 +-
.../doctype/email_account/email_account.js | 205 +--
.../email_account/email_account_list.js | 25 +-
.../doctype/email_domain/email_domain.js | 25 +-
.../email_flag_queue/email_flag_queue.js | 6 +-
.../email/doctype/email_group/email_group.js | 106 +-
.../email_group_member/email_group_member.js | 6 +-
.../email/doctype/email_queue/email_queue.js | 28 +-
.../doctype/email_queue/email_queue_list.js | 50 +-
frappe/email/doctype/email_rule/email_rule.js | 6 +-
.../doctype/email_template/email_template.js | 6 +-
.../email_unsubscribe/email_unsubscribe.js | 6 +-
frappe/email/doctype/newsletter/newsletter.js | 199 ++-
.../doctype/newsletter/newsletter_list.js | 6 +-
.../doctype/notification/notification.js | 159 +-
.../document_type_mapping.js | 31 +-
.../doctype/event_consumer/event_consumer.js | 24 +-
.../doctype/event_producer/event_producer.js | 30 +-
.../event_producer_last_update.js | 3 +-
.../doctype/event_sync_log/event_sync_log.js | 16 +-
.../event_sync_log/event_sync_log_list.js | 10 +-
.../event_update_log/event_update_log.js | 3 +-
frappe/geo/doctype/country/country.js | 6 +-
frappe/geo/doctype/currency/currency.js | 6 +-
.../doctype/connected_app/connected_app.js | 34 +-
.../dropbox_settings/dropbox_settings.js | 55 +-
.../google_calendar/google_calendar.js | 53 +-
.../google_contacts/google_contacts.js | 63 +-
.../doctype/google_drive/google_drive.js | 49 +-
.../google_settings/google_settings.js | 14 +-
.../integration_request.js | 6 +-
.../doctype/ldap_settings/ldap_settings.js | 6 +-
.../oauth_authorization_code.js | 6 +-
.../oauth_bearer_token/oauth_bearer_token.js | 6 +-
.../doctype/oauth_client/oauth_client.js | 6 +-
.../oauth_provider_settings.js | 6 +-
.../s3_backup_settings/s3_backup_settings.js | 16 +-
.../slack_webhook_url/slack_webhook_url.js | 4 +-
.../social_login_key/social_login_key.js | 66 +-
.../doctype/token_cache/token_cache.js | 3 +-
.../integrations/doctype/webhook/webhook.js | 63 +-
.../webhook_request_log.js | 3 +-
.../doctype/letter_head/letter_head.js | 6 +-
.../network_printer_settings.js | 26 +-
.../doctype/print_format/print_format.js | 35 +-
.../print_format_field_template.js | 3 +-
.../doctype/print_heading/print_heading.js | 6 +-
.../doctype/print_settings/print_settings.js | 20 +-
.../doctype/print_style/print_style.js | 12 +-
frappe/printing/page/print/print.js | 482 +++---
.../print_format_builder.js | 584 ++++----
.../print_format_builder_beta.js | 54 +-
frappe/public/js/bootstrap-4-web.bundle.js | 27 +-
frappe/public/js/form.bundle.js | 2 +-
frappe/public/js/frappe-web.bundle.js | 1 -
frappe/public/js/frappe/assets.js | 115 +-
.../build_events/build_events.bundle.js | 20 +-
frappe/public/js/frappe/class.js | 93 +-
.../js/frappe/color_picker/color_picker.js | 71 +-
frappe/public/js/frappe/color_picker/utils.js | 58 +-
.../js/frappe/data_import/data_exporter.js | 235 ++-
.../js/frappe/data_import/import_preview.js | 203 ++-
frappe/public/js/frappe/data_import/index.js | 4 +-
frappe/public/js/frappe/db.js | 114 +-
frappe/public/js/frappe/defaults.js | 61 +-
frappe/public/js/frappe/desk.js | 385 ++---
frappe/public/js/frappe/doctype/index.js | 68 +-
frappe/public/js/frappe/dom.js | 202 +--
frappe/public/js/frappe/event_emitter.js | 8 +-
.../public/js/frappe/file_uploader/index.js | 88 +-
frappe/public/js/frappe/form/column.js | 6 +-
.../public/js/frappe/form/controls/attach.js | 38 +-
.../js/frappe/form/controls/attach_image.js | 10 +-
.../js/frappe/form/controls/autocomplete.js | 113 +-
.../public/js/frappe/form/controls/barcode.js | 30 +-
.../js/frappe/form/controls/base_control.js | 150 +-
.../js/frappe/form/controls/base_input.js | 51 +-
.../public/js/frappe/form/controls/button.js | 18 +-
.../public/js/frappe/form/controls/check.js | 8 +-
frappe/public/js/frappe/form/controls/code.js | 113 +-
.../public/js/frappe/form/controls/color.js | 104 +-
.../public/js/frappe/form/controls/comment.js | 59 +-
.../public/js/frappe/form/controls/control.js | 89 +-
.../js/frappe/form/controls/currency.js | 2 +-
frappe/public/js/frappe/form/controls/data.js | 157 +-
frappe/public/js/frappe/form/controls/date.js | 82 +-
.../js/frappe/form/controls/date_range.js | 31 +-
.../frappe/form/controls/datepicker_i18n.js | 146 +-
.../js/frappe/form/controls/datetime.js | 16 +-
.../js/frappe/form/controls/duration.js | 9 +-
.../js/frappe/form/controls/dynamic_link.js | 4 +-
.../public/js/frappe/form/controls/float.js | 5 +-
.../js/frappe/form/controls/geolocation.js | 128 +-
frappe/public/js/frappe/form/controls/html.js | 4 +-
.../js/frappe/form/controls/html_editor.js | 10 +-
frappe/public/js/frappe/form/controls/icon.js | 86 +-
.../public/js/frappe/form/controls/image.js | 17 +-
frappe/public/js/frappe/form/controls/int.js | 17 +-
frappe/public/js/frappe/form/controls/json.js | 4 +-
frappe/public/js/frappe/form/controls/link.js | 345 ++---
.../frappe/form/controls/markdown_editor.js | 56 +-
.../js/frappe/form/controls/multicheck.js | 72 +-
.../js/frappe/form/controls/multiselect.js | 65 +-
.../frappe/form/controls/multiselect_list.js | 127 +-
.../frappe/form/controls/multiselect_pills.js | 57 +-
.../js/frappe/form/controls/password.js | 36 +-
.../public/js/frappe/form/controls/phone.js | 114 +-
.../controls/quill-mention/blots/mention.js | 63 +-
.../controls/quill-mention/constants/keys.js | 10 +-
.../controls/quill-mention/quill.mention.js | 641 ++++----
.../public/js/frappe/form/controls/rating.js | 75 +-
.../public/js/frappe/form/controls/select.js | 81 +-
.../js/frappe/form/controls/signature.js | 56 +-
.../public/js/frappe/form/controls/table.js | 48 +-
.../frappe/form/controls/table_multiselect.js | 74 +-
frappe/public/js/frappe/form/controls/text.js | 10 +-
.../js/frappe/form/controls/text_editor.js | 211 +--
frappe/public/js/frappe/form/controls/time.js | 54 +-
frappe/public/js/frappe/form/dashboard.js | 215 +--
.../js/frappe/form/footer/base_timeline.js | 32 +-
frappe/public/js/frappe/form/footer/footer.js | 50 +-
.../js/frappe/form/footer/form_timeline.js | 317 ++--
.../version_timeline_content_builder.js | 159 +-
frappe/public/js/frappe/form/form.js | 1086 ++++++++------
frappe/public/js/frappe/form/form_tour.js | 81 +-
frappe/public/js/frappe/form/form_viewers.js | 31 +-
frappe/public/js/frappe/form/formatters.js | 266 ++--
frappe/public/js/frappe/form/grid.js | 540 ++++---
.../public/js/frappe/form/grid_pagination.js | 73 +-
frappe/public/js/frappe/form/grid_row.js | 709 +++++----
frappe/public/js/frappe/form/grid_row_form.js | 99 +-
frappe/public/js/frappe/form/layout.js | 177 ++-
frappe/public/js/frappe/form/link_selector.js | 246 ++--
frappe/public/js/frappe/form/linked_with.js | 38 +-
.../js/frappe/form/multi_select_dialog.js | 270 ++--
frappe/public/js/frappe/form/print_utils.js | 83 +-
frappe/public/js/frappe/form/quick_entry.js | 122 +-
frappe/public/js/frappe/form/save.js | 132 +-
.../public/js/frappe/form/script_helpers.js | 45 +-
.../public/js/frappe/form/script_manager.js | 128 +-
frappe/public/js/frappe/form/section.js | 24 +-
.../js/frappe/form/sidebar/assign_to.js | 187 +--
.../js/frappe/form/sidebar/attachments.js | 111 +-
.../js/frappe/form/sidebar/document_follow.js | 139 +-
.../js/frappe/form/sidebar/form_sidebar.js | 143 +-
.../frappe/form/sidebar/form_sidebar_users.js | 31 +-
.../public/js/frappe/form/sidebar/review.js | 185 +--
frappe/public/js/frappe/form/sidebar/share.js | 189 +--
.../js/frappe/form/sidebar/user_image.js | 103 +-
.../public/js/frappe/form/success_action.js | 99 +-
frappe/public/js/frappe/form/tab.js | 32 +-
frappe/public/js/frappe/form/toolbar.js | 580 +++++---
frappe/public/js/frappe/form/undo_manager.js | 9 +-
frappe/public/js/frappe/form/workflow.js | 92 +-
frappe/public/js/frappe/format.js | 30 +-
.../js/frappe/icon_picker/icon_picker.js | 28 +-
frappe/public/js/frappe/list/base_list.js | 166 +--
.../public/js/frappe/list/bulk_operations.js | 407 ++---
frappe/public/js/frappe/list/list_factory.js | 21 +-
frappe/public/js/frappe/list/list_filter.js | 89 +-
frappe/public/js/frappe/list/list_settings.js | 111 +-
frappe/public/js/frappe/list/list_sidebar.js | 151 +-
.../js/frappe/list/list_sidebar_group_by.js | 163 +-
frappe/public/js/frappe/list/list_view.js | 504 +++----
.../public/js/frappe/list/list_view_select.js | 136 +-
frappe/public/js/frappe/logtypes.js | 15 +-
frappe/public/js/frappe/meta_tag.js | 27 +-
frappe/public/js/frappe/microtemplate.js | 142 +-
frappe/public/js/frappe/model/create_new.js | 143 +-
frappe/public/js/frappe/model/indicator.js | 87 +-
frappe/public/js/frappe/model/meta.js | 279 ++--
frappe/public/js/frappe/model/model.js | 545 ++++---
frappe/public/js/frappe/model/perm.js | 80 +-
frappe/public/js/frappe/model/sync.js | 68 +-
.../public/js/frappe/model/user_settings.js | 35 +-
frappe/public/js/frappe/model/workflow.js | 60 +-
frappe/public/js/frappe/module_editor.js | 24 +-
.../js/frappe/phone_picker/phone_picker.js | 22 +-
frappe/public/js/frappe/polyfill.js | 34 +-
frappe/public/js/frappe/provide.js | 29 +-
frappe/public/js/frappe/query_string.js | 24 +-
frappe/public/js/frappe/recorder/recorder.js | 22 +-
frappe/public/js/frappe/request.js | 376 ++---
frappe/public/js/frappe/roles_editor.js | 74 +-
frappe/public/js/frappe/router.js | 165 ++-
frappe/public/js/frappe/router_history.js | 27 +-
frappe/public/js/frappe/scanner/index.js | 15 +-
frappe/public/js/frappe/socketio_client.js | 189 +--
frappe/public/js/frappe/translate.js | 12 +-
.../js/frappe/ui/alt_keyboard_shortcuts.js | 85 +-
frappe/public/js/frappe/ui/app_icon.js | 55 +-
frappe/public/js/frappe/ui/capture.js | 77 +-
frappe/public/js/frappe/ui/chart.js | 8 +-
frappe/public/js/frappe/ui/colors.js | 99 +-
frappe/public/js/frappe/ui/datatable.js | 2 +-
frappe/public/js/frappe/ui/dialog.js | 136 +-
frappe/public/js/frappe/ui/driver.js | 4 +-
frappe/public/js/frappe/ui/field_group.js | 90 +-
.../js/frappe/ui/filters/field_select.js | 106 +-
frappe/public/js/frappe/ui/filters/filter.js | 301 ++--
.../js/frappe/ui/filters/filter_list.js | 131 +-
frappe/public/js/frappe/ui/find.js | 16 +-
.../public/js/frappe/ui/group_by/group_by.js | 191 ++-
frappe/public/js/frappe/ui/iconbar.js | 39 +-
frappe/public/js/frappe/ui/keyboard.js | 236 +--
frappe/public/js/frappe/ui/like.js | 91 +-
frappe/public/js/frappe/ui/link_preview.js | 80 +-
frappe/public/js/frappe/ui/messages.js | 323 ++--
.../frappe/ui/notifications/notifications.js | 211 ++-
frappe/public/js/frappe/ui/page.js | 346 +++--
frappe/public/js/frappe/ui/sidebar.js | 34 +-
frappe/public/js/frappe/ui/slides.js | 154 +-
frappe/public/js/frappe/ui/sort_selector.js | 118 +-
frappe/public/js/frappe/ui/tag_editor.js | 53 +-
frappe/public/js/frappe/ui/tags.js | 36 +-
frappe/public/js/frappe/ui/theme_switcher.js | 33 +-
frappe/public/js/frappe/ui/toolbar/about.js | 53 +-
.../js/frappe/ui/toolbar/awesome_bar.js | 240 +--
.../js/frappe/ui/toolbar/fuzzy_match.js | 15 +-
frappe/public/js/frappe/ui/toolbar/search.js | 160 +-
.../js/frappe/ui/toolbar/search_utils.js | 427 +++---
.../public/js/frappe/ui/toolbar/tag_utils.js | 32 +-
frappe/public/js/frappe/ui/toolbar/toolbar.js | 212 +--
frappe/public/js/frappe/ui/tree.js | 159 +-
frappe/public/js/frappe/upload.js | 4 +-
.../js/frappe/utils/address_and_contact.js | 52 +-
frappe/public/js/frappe/utils/common.js | 269 ++--
.../public/js/frappe/utils/dashboard_utils.js | 174 +--
frappe/public/js/frappe/utils/datatable.js | 10 +-
frappe/public/js/frappe/utils/datatype.js | 69 +-
frappe/public/js/frappe/utils/datetime.js | 167 ++-
.../js/frappe/utils/energy_point_utils.js | 40 +-
frappe/public/js/frappe/utils/file_manager.js | 36 +-
frappe/public/js/frappe/utils/help.js | 17 +-
frappe/public/js/frappe/utils/help_links.js | 112 +-
.../public/js/frappe/utils/number_format.js | 55 +-
.../public/js/frappe/utils/number_systems.js | 20 +-
frappe/public/js/frappe/utils/pretty_date.js | 41 +-
.../public/js/frappe/utils/preview_email.js | 33 +-
frappe/public/js/frappe/utils/tools.js | 43 +-
frappe/public/js/frappe/utils/urllib.js | 75 +-
frappe/public/js/frappe/utils/user.js | 88 +-
frappe/public/js/frappe/utils/utils.js | 748 +++++-----
frappe/public/js/frappe/utils/web_template.js | 16 +-
frappe/public/js/frappe/views/breadcrumbs.js | 83 +-
.../js/frappe/views/calendar/calendar.js | 259 ++--
.../public/js/frappe/views/communication.js | 269 ++--
frappe/public/js/frappe/views/container.js | 42 +-
.../frappe/views/dashboard/dashboard_view.js | 384 ++---
frappe/public/js/frappe/views/factory.js | 12 +-
.../public/js/frappe/views/file/file_view.js | 136 +-
frappe/public/js/frappe/views/formview.js | 35 +-
.../js/frappe/views/gantt/gantt_view.js | 132 +-
.../js/frappe/views/image/image_view.js | 75 +-
.../js/frappe/views/inbox/inbox_view.js | 74 +-
frappe/public/js/frappe/views/interaction.js | 261 ++--
.../views/kanban/kanban_board.bundle.js | 649 ++++----
.../js/frappe/views/kanban/kanban_settings.js | 85 +-
.../js/frappe/views/kanban/kanban_view.js | 224 +--
frappe/public/js/frappe/views/map/map_view.js | 82 +-
frappe/public/js/frappe/views/modules_home.js | 8 +-
frappe/public/js/frappe/views/pageview.js | 68 +-
.../js/frappe/views/reports/query_report.js | 1309 +++++++++--------
.../js/frappe/views/reports/report_factory.js | 4 +-
.../js/frappe/views/reports/report_utils.js | 100 +-
.../js/frappe/views/reports/report_view.js | 879 +++++------
.../js/frappe/views/translation_manager.js | 113 +-
frappe/public/js/frappe/views/treeview.js | 285 ++--
.../js/frappe/views/workspace/blocks/block.js | 188 +--
.../js/frappe/views/workspace/blocks/card.js | 20 +-
.../js/frappe/views/workspace/blocks/chart.js | 20 +-
.../frappe/views/workspace/blocks/header.js | 46 +-
.../views/workspace/blocks/header_size.js | 51 +-
.../js/frappe/views/workspace/blocks/index.js | 2 +-
.../views/workspace/blocks/onboarding.js | 49 +-
.../views/workspace/blocks/paragraph.js | 94 +-
.../views/workspace/blocks/quick_list.js | 20 +-
.../frappe/views/workspace/blocks/shortcut.js | 38 +-
.../frappe/views/workspace/blocks/spacer.js | 24 +-
.../js/frappe/views/workspace/workspace.js | 956 ++++++------
frappe/public/js/frappe/web_form/web_form.js | 91 +-
.../js/frappe/web_form/web_form_list.js | 155 +-
.../js/frappe/web_form/webform_script.js | 18 +-
.../public/js/frappe/widgets/base_widget.js | 38 +-
.../public/js/frappe/widgets/chart_widget.js | 368 ++---
.../public/js/frappe/widgets/links_widget.js | 29 +-
frappe/public/js/frappe/widgets/new_widget.js | 5 +-
.../js/frappe/widgets/number_card_widget.js | 185 ++-
.../js/frappe/widgets/onboarding_widget.js | 120 +-
.../js/frappe/widgets/quick_list_widget.js | 76 +-
.../js/frappe/widgets/shortcut_widget.js | 8 +-
.../public/js/frappe/widgets/widget_dialog.js | 190 +--
.../public/js/frappe/widgets/widget_group.js | 8 +-
.../js/integrations/google_drive_picker.js | 35 +-
frappe/public/js/jquery-bootstrap.js | 2 +-
frappe/public/js/logtypes.bundle.js | 2 +-
.../print_format_builder.bundle.js | 25 +-
.../public/js/print_format_builder/store.js | 105 +-
.../public/js/print_format_builder/utils.js | 27 +-
.../energy_point_log/energy_point_log.js | 57 +-
.../energy_point_log/energy_point_log_list.js | 32 +-
.../energy_point_rule/energy_point_rule.js | 36 +-
.../energy_point_settings.js | 60 +-
.../doctype/review_level/review_level.js | 3 +-
frappe/templates/discussions/discussions.js | 45 +-
.../about_us_settings/about_us_settings.js | 8 +-
.../doctype/blog_category/blog_category.js | 6 +-
frappe/website/doctype/blog_post/blog_post.js | 20 +-
.../doctype/blog_post/blog_post_list.js | 8 +-
.../doctype/blog_post/ui_test_blog_post.js | 46 +-
.../doctype/blog_settings/blog_settings.js | 8 +-
frappe/website/doctype/blogger/blogger.js | 6 +-
frappe/website/doctype/color/color.js | 3 +-
.../contact_us_settings.js | 9 +-
.../discussion_reply/discussion_reply.js | 3 +-
.../discussion_topic/discussion_topic.js | 3 +-
.../doctype/help_article/help_article.js | 6 +-
.../doctype/help_category/help_category.js | 6 +-
.../personal_data_deletion_request.js | 16 +-
.../personal_data_download_request.js | 4 +-
.../portal_settings/portal_settings.js | 32 +-
frappe/website/doctype/web_form/web_form.js | 103 +-
.../website/doctype/web_form/web_form_list.js | 8 +-
frappe/website/doctype/web_page/web_page.js | 81 +-
.../website/doctype/web_page/web_page_list.js | 8 +-
.../doctype/web_page_view/web_page_view.js | 3 +-
.../doctype/web_template/web_template.js | 16 +-
.../web_template_field/web_template_field.js | 3 +-
.../website_route_meta/website_route_meta.js | 16 +-
.../doctype/website_script/website_script.js | 6 +-
.../website_settings/website_settings.js | 110 +-
.../website_sidebar/website_sidebar.js | 6 +-
.../website_slideshow/website_slideshow.js | 73 +-
.../doctype/website_theme/website_theme.js | 73 +-
frappe/website/js/bootstrap-4.js | 28 +-
frappe/website/js/syntax_highlight.js | 30 +-
frappe/website/js/website.js | 496 ++++---
.../website_analytics/website_analytics.js | 16 +-
.../web_form/request_data/request_data.js | 4 +-
.../request_to_delete_data.js | 11 +-
frappe/workflow/doctype/workflow/workflow.js | 134 +-
.../doctype/workflow/workflow_list.js | 10 +-
.../workflow_action/workflow_action.js | 6 +-
.../workflow_action/workflow_action_list.js | 20 +-
.../workflow_action_master.js | 6 +-
.../doctype/workflow_state/workflow_state.js | 6 +-
frappe/www/_test/_test_folder/_test_page.js | 2 +-
frappe/www/_test/assets/js_asset.min.js | 2 +-
generate_bootstrap_theme.js | 45 +-
node_utils.js | 24 +-
socketio.js | 129 +-
560 files changed, 26258 insertions(+), 23373 deletions(-)
diff --git a/commitlint.config.js b/commitlint.config.js
index 8847564e53..09de8b8272 100644
--- a/commitlint.config.js
+++ b/commitlint.config.js
@@ -1,24 +1,24 @@
module.exports = {
- parserPreset: 'conventional-changelog-conventionalcommits',
+ parserPreset: "conventional-changelog-conventionalcommits",
rules: {
- 'subject-empty': [2, 'never'],
- 'type-case': [2, 'always', 'lower-case'],
- 'type-empty': [2, 'never'],
- 'type-enum': [
+ "subject-empty": [2, "never"],
+ "type-case": [2, "always", "lower-case"],
+ "type-empty": [2, "never"],
+ "type-enum": [
2,
- 'always',
+ "always",
[
- 'build',
- 'chore',
- 'ci',
- 'docs',
- 'feat',
- 'fix',
- 'perf',
- 'refactor',
- 'revert',
- 'style',
- 'test',
+ "build",
+ "chore",
+ "ci",
+ "docs",
+ "feat",
+ "fix",
+ "perf",
+ "refactor",
+ "revert",
+ "style",
+ "test",
],
],
},
diff --git a/cypress/fixtures/child_table_doctype.js b/cypress/fixtures/child_table_doctype.js
index f65e5d1765..88a925aca3 100644
--- a/cypress/fixtures/child_table_doctype.js
+++ b/cypress/fixtures/child_table_doctype.js
@@ -13,8 +13,8 @@ export default {
fieldtype: "Data",
in_list_view: 1,
label: "Title",
- unique: 1
- }
+ unique: 1,
+ },
],
links: [],
istable: 1,
@@ -24,7 +24,7 @@ export default {
naming_rule: "By fieldname",
owner: "Administrator",
permissions: [],
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
-};
\ No newline at end of file
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
+};
diff --git a/cypress/fixtures/child_table_doctype_1.js b/cypress/fixtures/child_table_doctype_1.js
index 4657d63e2e..abf8873bff 100644
--- a/cypress/fixtures/child_table_doctype_1.js
+++ b/cypress/fixtures/child_table_doctype_1.js
@@ -12,38 +12,38 @@ export default {
fieldname: "data",
fieldtype: "Data",
in_list_view: 1,
- label: "Data"
+ label: "Data",
},
{
fieldname: "barcode",
fieldtype: "Barcode",
in_list_view: 1,
- label: "Barcode"
+ label: "Barcode",
},
{
fieldname: "check",
fieldtype: "Check",
in_list_view: 1,
- label: "Check"
+ label: "Check",
},
{
fieldname: "rating",
fieldtype: "Rating",
in_list_view: 1,
- label: "Rating"
+ label: "Rating",
},
{
fieldname: "duration",
fieldtype: "Duration",
in_list_view: 1,
- label: "Duration"
+ label: "Duration",
},
{
fieldname: "date",
fieldtype: "Date",
in_list_view: 1,
- label: "Date"
- }
+ label: "Date",
+ },
],
links: [],
istable: 1,
@@ -53,7 +53,7 @@ export default {
naming_rule: "By fieldname",
owner: "Administrator",
permissions: [],
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
-};
\ No newline at end of file
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
+};
diff --git a/cypress/fixtures/custom_submittable_doctype.js b/cypress/fixtures/custom_submittable_doctype.js
index c88d37b373..30aa698db4 100644
--- a/cypress/fixtures/custom_submittable_doctype.js
+++ b/cypress/fixtures/custom_submittable_doctype.js
@@ -1,37 +1,37 @@
export default {
- name: 'Custom Submittable DocType',
+ name: "Custom Submittable DocType",
custom: 1,
actions: [],
is_submittable: 1,
- creation: '2019-12-10 06:29:07.215072',
- doctype: 'DocType',
+ creation: "2019-12-10 06:29:07.215072",
+ doctype: "DocType",
editable_grid: 1,
- engine: 'InnoDB',
+ engine: "InnoDB",
fields: [
{
- fieldname: 'enabled',
- fieldtype: 'Check',
- label: 'Enabled',
+ fieldname: "enabled",
+ fieldtype: "Check",
+ label: "Enabled",
allow_on_submit: 1,
- reqd: 1
+ reqd: 1,
},
{
- fieldname: 'title',
- fieldtype: 'Data',
- label: 'title',
- reqd: 1
+ fieldname: "title",
+ fieldtype: "Data",
+ label: "title",
+ reqd: 1,
},
{
- fieldname: 'description',
- fieldtype: 'Text Editor',
- label: 'Description'
- }
+ fieldname: "description",
+ fieldtype: "Text Editor",
+ label: "Description",
+ },
],
links: [],
- modified: '2019-12-10 14:40:53.127615',
- modified_by: 'Administrator',
- module: 'Custom',
- owner: 'Administrator',
+ modified: "2019-12-10 14:40:53.127615",
+ modified_by: "Administrator",
+ module: "Custom",
+ owner: "Administrator",
permissions: [
{
create: 1,
@@ -39,15 +39,15 @@ export default {
email: 1,
print: 1,
read: 1,
- role: 'System Manager',
+ role: "System Manager",
share: 1,
write: 1,
submit: 1,
- cancel: 1
- }
+ cancel: 1,
+ },
],
quick_entry: 1,
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
-};
\ No newline at end of file
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
+};
diff --git a/cypress/fixtures/data_field_validation_doctype.js b/cypress/fixtures/data_field_validation_doctype.js
index da091af7e5..2901630d3f 100644
--- a/cypress/fixtures/data_field_validation_doctype.js
+++ b/cypress/fixtures/data_field_validation_doctype.js
@@ -1,51 +1,51 @@
export default {
- name: 'Validation Test',
+ name: "Validation Test",
custom: 1,
actions: [],
- creation: '2019-03-15 06:29:07.215072',
- doctype: 'DocType',
+ creation: "2019-03-15 06:29:07.215072",
+ doctype: "DocType",
editable_grid: 1,
- engine: 'InnoDB',
+ engine: "InnoDB",
fields: [
{
- fieldname: 'email',
- fieldtype: 'Data',
- label: 'Email',
- options: 'Email'
+ fieldname: "email",
+ fieldtype: "Data",
+ label: "Email",
+ options: "Email",
},
{
- fieldname: 'URL',
- fieldtype: 'Data',
- label: 'URL',
- options: 'URL'
+ fieldname: "URL",
+ fieldtype: "Data",
+ label: "URL",
+ options: "URL",
},
{
- fieldname: 'Phone',
- fieldtype: 'Data',
- label: 'Phone',
- options: 'Phone'
+ fieldname: "Phone",
+ fieldtype: "Data",
+ label: "Phone",
+ options: "Phone",
},
{
- fieldname: 'person_name',
- fieldtype: 'Data',
- label: 'Person Name',
- options: 'Name'
+ fieldname: "person_name",
+ fieldtype: "Data",
+ label: "Person Name",
+ options: "Name",
},
{
- fieldname: 'read_only_url',
- fieldtype: 'Data',
- label: 'Read Only URL',
- options: 'URL',
- read_only: '1',
- default: 'https://frappe.io'
- }
+ fieldname: "read_only_url",
+ fieldtype: "Data",
+ label: "Read Only URL",
+ options: "URL",
+ read_only: "1",
+ default: "https://frappe.io",
+ },
],
issingle: 1,
links: [],
- modified: '2021-04-19 14:40:53.127615',
- modified_by: 'Administrator',
- module: 'Custom',
- owner: 'Administrator',
+ modified: "2021-04-19 14:40:53.127615",
+ modified_by: "Administrator",
+ module: "Custom",
+ owner: "Administrator",
permissions: [
{
create: 1,
@@ -53,13 +53,13 @@ export default {
email: 1,
print: 1,
read: 1,
- role: 'System Manager',
+ role: "System Manager",
share: 1,
- write: 1
- }
+ write: 1,
+ },
],
quick_entry: 1,
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
};
diff --git a/cypress/fixtures/datetime_doctype.js b/cypress/fixtures/datetime_doctype.js
index b8c89ced5c..f1a77ba6bb 100644
--- a/cypress/fixtures/datetime_doctype.js
+++ b/cypress/fixtures/datetime_doctype.js
@@ -1,34 +1,34 @@
export default {
- name: 'DateTime Test',
+ name: "DateTime Test",
custom: 1,
actions: [],
- creation: '2019-03-15 06:29:07.215072',
- doctype: 'DocType',
+ creation: "2019-03-15 06:29:07.215072",
+ doctype: "DocType",
editable_grid: 1,
- engine: 'InnoDB',
+ engine: "InnoDB",
fields: [
{
- fieldname: 'date',
- fieldtype: 'Date',
- label: 'Date'
+ fieldname: "date",
+ fieldtype: "Date",
+ label: "Date",
},
{
- fieldname: 'time',
- fieldtype: 'Time',
- label: 'Time'
+ fieldname: "time",
+ fieldtype: "Time",
+ label: "Time",
},
{
- fieldname: 'datetime',
- fieldtype: 'Datetime',
- label: 'Datetime'
- }
+ fieldname: "datetime",
+ fieldtype: "Datetime",
+ label: "Datetime",
+ },
],
issingle: 1,
links: [],
- modified: '2019-12-09 14:40:53.127615',
- modified_by: 'Administrator',
- module: 'Custom',
- owner: 'Administrator',
+ modified: "2019-12-09 14:40:53.127615",
+ modified_by: "Administrator",
+ module: "Custom",
+ owner: "Administrator",
permissions: [
{
create: 1,
@@ -36,13 +36,13 @@ export default {
email: 1,
print: 1,
read: 1,
- role: 'System Manager',
+ role: "System Manager",
share: 1,
- write: 1
- }
+ write: 1,
+ },
],
quick_entry: 1,
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
};
diff --git a/cypress/fixtures/doctype_to_link.js b/cypress/fixtures/doctype_to_link.js
index f5335b1755..ff5d1b5c68 100644
--- a/cypress/fixtures/doctype_to_link.js
+++ b/cypress/fixtures/doctype_to_link.js
@@ -10,18 +10,18 @@ export default {
engine: "InnoDB",
fields: [
{
- "fieldname": "title",
- "fieldtype": "Data",
- "label": "Title",
- "unique": 1
- }
+ fieldname: "title",
+ fieldtype: "Data",
+ label: "Title",
+ unique: 1,
+ },
],
links: [
{
- "group": "Child Doctype",
- "link_doctype": "Doctype With Child Table",
- "link_fieldname": "title"
- }
+ group: "Child Doctype",
+ link_doctype: "Doctype With Child Table",
+ link_fieldname: "title",
+ },
],
modified: "2022-02-10 12:03:12.603763",
modified_by: "Administrator",
@@ -34,12 +34,12 @@ export default {
email: 1,
print: 1,
read: 1,
- role: 'System Manager',
+ role: "System Manager",
share: 1,
- write: 1
- }
+ write: 1,
+ },
],
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
-};
\ No newline at end of file
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
+};
diff --git a/cypress/fixtures/doctype_with_child_table.js b/cypress/fixtures/doctype_with_child_table.js
index 014074b0b5..7caba516cf 100644
--- a/cypress/fixtures/doctype_with_child_table.js
+++ b/cypress/fixtures/doctype_with_child_table.js
@@ -12,21 +12,21 @@ export default {
fieldname: "title",
fieldtype: "Data",
label: "Title",
- unique: 1
+ unique: 1,
},
{
fieldname: "child_table",
fieldtype: "Table",
label: "Child Table",
options: "Child Table Doctype",
- reqd: 1
+ reqd: 1,
},
{
fieldname: "child_table_1",
fieldtype: "Table",
label: "Child Table 1",
- options: "Child Table Doctype 1"
- }
+ options: "Child Table Doctype 1",
+ },
],
links: [],
modified: "2022-02-10 12:03:12.603763",
@@ -41,12 +41,12 @@ export default {
email: 1,
print: 1,
read: 1,
- role: 'System Manager',
+ role: "System Manager",
share: 1,
- write: 1
- }
+ write: 1,
+ },
],
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
};
diff --git a/cypress/fixtures/doctype_with_phone.js b/cypress/fixtures/doctype_with_phone.js
index c62922ade2..06a24a5be5 100644
--- a/cypress/fixtures/doctype_with_phone.js
+++ b/cypress/fixtures/doctype_with_phone.js
@@ -4,29 +4,28 @@ export default {
custom: 1,
is_submittable: 1,
autoname: "field:title",
- creation: '2022-03-30 06:29:07.215072',
- doctype: 'DocType',
- engine: 'InnoDB',
+ creation: "2022-03-30 06:29:07.215072",
+ doctype: "DocType",
+ engine: "InnoDB",
fields: [
-
{
- fieldname: 'title',
- fieldtype: 'Data',
- label: 'title',
+ fieldname: "title",
+ fieldtype: "Data",
+ label: "title",
unique: 1,
},
{
- fieldname: 'phone',
- fieldtype: 'Phone',
- label: 'Phone'
- }
+ fieldname: "phone",
+ fieldtype: "Phone",
+ label: "Phone",
+ },
],
links: [],
- modified: '2019-03-30 14:40:53.127615',
- modified_by: 'Administrator',
+ modified: "2019-03-30 14:40:53.127615",
+ modified_by: "Administrator",
naming_rule: "By fieldname",
- module: 'Custom',
- owner: 'Administrator',
+ module: "Custom",
+ owner: "Administrator",
permissions: [
{
create: 1,
@@ -34,14 +33,14 @@ export default {
email: 1,
print: 1,
read: 1,
- role: 'System Manager',
+ role: "System Manager",
share: 1,
write: 1,
submit: 1,
- cancel: 1
- }
+ cancel: 1,
+ },
],
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
};
diff --git a/cypress/fixtures/doctype_with_tab_break.js b/cypress/fixtures/doctype_with_tab_break.js
index 74e5e6abba..44d6c16682 100644
--- a/cypress/fixtures/doctype_with_tab_break.js
+++ b/cypress/fixtures/doctype_with_tab_break.js
@@ -1,39 +1,39 @@
export default {
- name: 'Form With Tab Break',
+ name: "Form With Tab Break",
custom: 1,
actions: [],
- doctype: 'DocType',
- engine: 'InnoDB',
+ doctype: "DocType",
+ engine: "InnoDB",
fields: [
{
- fieldname: 'username',
- fieldtype: 'Data',
- label: 'Name',
- options: 'Name'
+ fieldname: "username",
+ fieldtype: "Data",
+ label: "Name",
+ options: "Name",
},
{
- fieldname: 'tab',
- fieldtype: 'Tab Break',
- label: 'Tab 2',
+ fieldname: "tab",
+ fieldtype: "Tab Break",
+ label: "Tab 2",
},
{
- fieldname: 'Phone',
- fieldtype: 'Data',
- label: 'Phone',
- options: 'Phone',
- reqd: 1
+ fieldname: "Phone",
+ fieldtype: "Data",
+ label: "Phone",
+ options: "Phone",
+ reqd: 1,
},
],
links: [
{
- "group": "Profile",
- "link_doctype": "Contact",
- "link_fieldname": "user"
+ group: "Profile",
+ link_doctype: "Contact",
+ link_fieldname: "user",
},
],
- modified_by: 'Administrator',
- module: 'Custom',
- owner: 'Administrator',
+ modified_by: "Administrator",
+ module: "Custom",
+ owner: "Administrator",
permissions: [
{
create: 1,
@@ -41,14 +41,14 @@ export default {
email: 1,
print: 1,
read: 1,
- role: 'System Manager',
+ role: "System Manager",
share: 1,
- write: 1
- }
+ write: 1,
+ },
],
quick_entry: 1,
autoname: "format: Test-{####}",
- sort_field: 'modified',
- sort_order: 'ASC',
- track_changes: 1
+ sort_field: "modified",
+ sort_order: "ASC",
+ track_changes: 1,
};
diff --git a/cypress/integration/api.js b/cypress/integration/api.js
index e8c39e6e25..420cea25fd 100644
--- a/cypress/integration/api.js
+++ b/cypress/integration/api.js
@@ -1,42 +1,43 @@
-context('API Resources', () => {
+context("API Resources", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
- it('Creates two Comments', () => {
- cy.insert_doc('Comment', { comment_type: 'Comment', content: "hello" });
- cy.insert_doc('Comment', { comment_type: 'Comment', content: "world" });
+ it("Creates two Comments", () => {
+ cy.insert_doc("Comment", { comment_type: "Comment", content: "hello" });
+ cy.insert_doc("Comment", { comment_type: "Comment", content: "world" });
});
- it('Lists the Comments', () => {
- cy.get_list('Comment')
- .its('data')
- .then(data => expect(data.length).to.be.at.least(2));
+ it("Lists the Comments", () => {
+ cy.get_list("Comment")
+ .its("data")
+ .then((data) => expect(data.length).to.be.at.least(2));
- cy.get_list('Comment', ['name', 'content'], [['content', '=', 'hello']])
- .then(body => {
- expect(body).to.have.property('data');
- expect(body.data).to.have.lengthOf(1);
- expect(body.data[0]).to.have.property('content');
- expect(body.data[0]).to.have.property('name');
- });
+ cy.get_list("Comment", ["name", "content"], [["content", "=", "hello"]]).then((body) => {
+ expect(body).to.have.property("data");
+ expect(body.data).to.have.lengthOf(1);
+ expect(body.data[0]).to.have.property("content");
+ expect(body.data[0]).to.have.property("name");
+ });
});
- it('Gets each Comment', () => {
- cy.get_list('Comment').then(body => body.data.forEach(comment => {
- cy.get_doc('Comment', comment.name);
- }));
+ it("Gets each Comment", () => {
+ cy.get_list("Comment").then((body) =>
+ body.data.forEach((comment) => {
+ cy.get_doc("Comment", comment.name);
+ })
+ );
});
- it('Removes the Comments', () => {
- cy.get_list('Comment').then(body => {
+ it("Removes the Comments", () => {
+ cy.get_list("Comment").then((body) => {
let comment_names = [];
- body.data.map(comment => comment_names.push(comment.name));
+ body.data.map((comment) => comment_names.push(comment.name));
comment_names = [...new Set(comment_names)]; // remove duplicates
comment_names.forEach((comment_name) => {
- cy.remove_doc('Comment', comment_name);
+ cy.remove_doc("Comment", comment_name);
});
});
});
diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js
index 938034a34a..71e5e498cf 100644
--- a/cypress/integration/awesome_bar.js
+++ b/cypress/integration/awesome_bar.js
@@ -1,48 +1,57 @@
-context('Awesome Bar', () => {
+context("Awesome Bar", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
beforeEach(() => {
- cy.get('.navbar .navbar-home').click();
- cy.findByPlaceholderText('Search or type a command (Ctrl + G)').clear();
+ cy.get(".navbar .navbar-home").click();
+ cy.findByPlaceholderText("Search or type a command (Ctrl + G)").clear();
});
- it('navigates to doctype list', () => {
- cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('todo', { delay: 700 });
- cy.get('.awesomplete').findByRole('listbox').should('be.visible');
- cy.findByPlaceholderText('Search or type a command (Ctrl + G)').type('{enter}', { delay: 700 });
+ it("navigates to doctype list", () => {
+ cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("todo", {
+ delay: 700,
+ });
+ cy.get(".awesomplete").findByRole("listbox").should("be.visible");
+ cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type("{enter}", {
+ delay: 700,
+ });
- cy.get('.title-text').should('contain', 'To Do');
+ cy.get(".title-text").should("contain", "To Do");
- cy.location('pathname').should('eq', '/app/todo');
+ cy.location("pathname").should("eq", "/app/todo");
});
- it('find text in doctype list', () => {
- cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
- .type('test in todo{enter}', { delay: 700 });
+ it("find text in doctype list", () => {
+ cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
+ "test in todo{enter}",
+ { delay: 700 }
+ );
- cy.get('.title-text').should('contain', 'To Do');
+ cy.get(".title-text").should("contain", "To Do");
- cy.findByPlaceholderText('ID')
- .should('have.value', '%test%');
+ cy.findByPlaceholderText("ID").should("have.value", "%test%");
cy.clear_filters();
});
- it('navigates to new form', () => {
- cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
- .type('new blog post{enter}', { delay: 700 });
+ it("navigates to new form", () => {
+ cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
+ "new blog post{enter}",
+ { delay: 700 }
+ );
- cy.get('.title-text:visible').should('have.text', 'New Blog Post');
+ cy.get(".title-text:visible").should("have.text", "New Blog Post");
});
- it('calculates math expressions', () => {
- cy.findByPlaceholderText('Search or type a command (Ctrl + G)')
- .type('55 + 32{downarrow}{enter}', { delay: 700 });
+ it("calculates math expressions", () => {
+ cy.findByPlaceholderText("Search or type a command (Ctrl + G)").type(
+ "55 + 32{downarrow}{enter}",
+ { delay: 700 }
+ );
- cy.get('.modal-title').should('contain', 'Result');
- cy.get('.msgprint').should('contain', '55 + 32 = 87');
+ cy.get(".modal-title").should("contain", "Result");
+ cy.get(".msgprint").should("contain", "55 + 32 = 87");
});
});
diff --git a/cypress/integration/control_attach.js b/cypress/integration/control_attach.js
index 0552780737..96b8c73b6e 100644
--- a/cypress/integration/control_attach.js
+++ b/cypress/integration/control_attach.js
@@ -1,90 +1,95 @@
-context('Attach Control', () => {
+context("Attach Control", () => {
before(() => {
cy.login();
- cy.visit('/app/doctype');
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
- name: 'Test Attach Control',
- fields: [
- {
- "label": "Attach File or Image",
- "fieldname": "attach",
- "fieldtype": "Attach",
- "in_list_view": 1,
- },
- ]
+ cy.visit("/app/doctype");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
+ name: "Test Attach Control",
+ fields: [
+ {
+ label: "Attach File or Image",
+ fieldname: "attach",
+ fieldtype: "Attach",
+ in_list_view: 1,
+ },
+ ],
+ });
});
- });
});
it('Checking functionality for "Link" button in the "Attach" fieldtype', () => {
//Navigating to the new form for the newly created doctype
- cy.new_form('Test Attach Control');
+ cy.new_form("Test Attach Control");
//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
- cy.findByRole('button', {name: 'Attach'}).click();
+ cy.findByRole("button", { name: "Attach" }).click();
//Clicking on "Link" button to attach a file using the "Link" button
- cy.findByRole('button', {name: 'Link'}).click();
- cy.findByPlaceholderText('Attach a web link').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
-
+ cy.findByRole("button", { name: "Link" }).click();
+ cy.findByPlaceholderText("Attach a web link").type(
+ "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg"
+ );
+
//Clicking on the Upload button to upload the file
cy.intercept("POST", "/api/method/upload_file").as("upload_image");
- cy.get('.modal-footer').findByRole("button", {name: "Upload"}).click({delay: 500});
+ cy.get(".modal-footer").findByRole("button", { name: "Upload" }).click({ delay: 500 });
cy.wait("@upload_image");
- cy.findByRole('button', {name: 'Save'}).click();
+ cy.findByRole("button", { name: "Save" }).click();
//Checking if the URL of the attached image is getting displayed in the field of the newly created doctype
- cy.get('.attached-file > .ellipsis > .attached-file-link')
- .should('have.attr', 'href')
- .and('equal', 'https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
+ cy.get(".attached-file > .ellipsis > .attached-file-link")
+ .should("have.attr", "href")
+ .and("equal", "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg");
//Clicking on the "Clear" button
cy.get('[data-action="clear_attachment"]').click();
//Checking if clicking on the clear button clears the field of the doctype form and again displays the attach button
- cy.get('.control-input > .btn-sm').should('contain', 'Attach');
+ cy.get(".control-input > .btn-sm").should("contain", "Attach");
//Deleting the doc
- cy.go_to_list('Test Attach Control');
- cy.get('.list-row-checkbox').eq(0).click();
- cy.get('.actions-btn-group > .btn').contains('Actions').click();
+ cy.go_to_list("Test Attach Control");
+ cy.get(".list-row-checkbox").eq(0).click();
+ cy.get(".actions-btn-group > .btn").contains("Actions").click();
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
- cy.click_modal_primary_button('Yes');
+ cy.click_modal_primary_button("Yes");
});
it('Checking functionality for "Library" button in the "Attach" fieldtype', () => {
//Navigating to the new form for the newly created doctype
- cy.new_form('Test Attach Control');
+ cy.new_form("Test Attach Control");
//Clicking on the attach button which is displayed as part of creating a doctype with "Attach" fieldtype
- cy.findByRole('button', {name: 'Attach'}).click();
+ cy.findByRole("button", { name: "Attach" }).click();
//Clicking on "Library" button to attach a file using the "Library" button
- cy.findByRole('button', {name: 'Library'}).click();
- cy.contains('72402.jpg').click();
+ cy.findByRole("button", { name: "Library" }).click();
+ cy.contains("72402.jpg").click();
//Clicking on the Upload button to upload the file
cy.intercept("POST", "/api/method/upload_file").as("upload_image");
- cy.get('.modal-footer').findByRole("button", {name: "Upload"}).click({delay: 500});
+ cy.get(".modal-footer").findByRole("button", { name: "Upload" }).click({ delay: 500 });
cy.wait("@upload_image");
- cy.findByRole('button', {name: 'Save'}).click();
+ cy.findByRole("button", { name: "Save" }).click();
//Checking if the URL of the attached image is getting displayed in the field of the newly created doctype
- cy.get('.attached-file > .ellipsis > .attached-file-link')
- .should('have.attr', 'href')
- .and('equal', 'https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
+ cy.get(".attached-file > .ellipsis > .attached-file-link")
+ .should("have.attr", "href")
+ .and("equal", "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg");
//Clicking on the "Clear" button
cy.get('[data-action="clear_attachment"]').click();
//Checking if clicking on the clear button clears the field of the doctype form and again displays the attach button
- cy.get('.control-input > .btn-sm').should('contain', 'Attach');
+ cy.get(".control-input > .btn-sm").should("contain", "Attach");
//Deleting the doc
- cy.go_to_list('Test Attach Control');
- cy.get('.list-row-checkbox').eq(0).click();
- cy.get('.actions-btn-group > .btn').contains('Actions').click();
+ cy.go_to_list("Test Attach Control");
+ cy.get(".list-row-checkbox").eq(0).click();
+ cy.get(".actions-btn-group > .btn").contains("Actions").click();
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
- cy.click_modal_primary_button('Yes');
+ cy.click_modal_primary_button("Yes");
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/control_autocomplete.js b/cypress/integration/control_autocomplete.js
index 3bf3e829f9..6dc57fcf43 100644
--- a/cypress/integration/control_autocomplete.js
+++ b/cypress/integration/control_autocomplete.js
@@ -1,57 +1,64 @@
-context('Control Autocomplete', () => {
+context("Control Autocomplete", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
function get_dialog_with_autocomplete(options) {
- cy.visit('/app/website');
+ cy.visit("/app/website");
return cy.dialog({
- title: 'Autocomplete',
+ title: "Autocomplete",
fields: [
{
- 'label': 'Select an option',
- 'fieldname': 'autocomplete',
- 'fieldtype': 'Autocomplete',
- 'options': options || ['Option 1', 'Option 2', 'Option 3'],
- }
- ]
+ label: "Select an option",
+ fieldname: "autocomplete",
+ fieldtype: "Autocomplete",
+ options: options || ["Option 1", "Option 2", "Option 3"],
+ },
+ ],
});
}
- it('should set the valid value', () => {
- get_dialog_with_autocomplete().as('dialog');
+ it("should set the valid value", () => {
+ get_dialog_with_autocomplete().as("dialog");
- cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input');
+ cy.get(".frappe-control[data-fieldname=autocomplete] input").focus().as("input");
cy.wait(1000);
- cy.get('@input').type('2', { delay: 300 });
- cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible');
- cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 });
- cy.get('.frappe-control[data-fieldname=autocomplete] input').blur();
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('autocomplete');
- expect(value).to.eq('Option 2');
+ cy.get("@input").type("2", { delay: 300 });
+ cy.get(".frappe-control[data-fieldname=autocomplete]")
+ .findByRole("listbox")
+ .should("be.visible");
+ cy.get(".frappe-control[data-fieldname=autocomplete] input").type("{enter}", {
+ delay: 300,
+ });
+ cy.get(".frappe-control[data-fieldname=autocomplete] input").blur();
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("autocomplete");
+ expect(value).to.eq("Option 2");
dialog.clear();
});
});
- it('should set the valid value with different label', () => {
+ it("should set the valid value with different label", () => {
const options_with_label = [
{ label: "Option 1", value: "option_1" },
- { label: "Option 2", value: "option_2" }
+ { label: "Option 2", value: "option_2" },
];
- get_dialog_with_autocomplete(options_with_label).as('dialog');
+ get_dialog_with_autocomplete(options_with_label).as("dialog");
- cy.get('.frappe-control[data-fieldname=autocomplete] input').focus().as('input');
- cy.get('.frappe-control[data-fieldname=autocomplete]').findByRole('listbox').should('be.visible');
- cy.get('@input').type('2', { delay: 300 });
- cy.get('.frappe-control[data-fieldname=autocomplete] input').type('{enter}', { delay: 300 });
- cy.get('.frappe-control[data-fieldname=autocomplete] input').blur();
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('autocomplete');
- expect(value).to.eq('option_2');
+ cy.get(".frappe-control[data-fieldname=autocomplete] input").focus().as("input");
+ cy.get(".frappe-control[data-fieldname=autocomplete]")
+ .findByRole("listbox")
+ .should("be.visible");
+ cy.get("@input").type("2", { delay: 300 });
+ cy.get(".frappe-control[data-fieldname=autocomplete] input").type("{enter}", {
+ delay: 300,
+ });
+ cy.get(".frappe-control[data-fieldname=autocomplete] input").blur();
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("autocomplete");
+ expect(value).to.eq("option_2");
dialog.clear();
});
});
-
});
diff --git a/cypress/integration/control_barcode.js b/cypress/integration/control_barcode.js
index 85a3182397..96a1bb43d4 100644
--- a/cypress/integration/control_barcode.js
+++ b/cypress/integration/control_barcode.js
@@ -1,55 +1,57 @@
-context('Control Barcode', () => {
+context("Control Barcode", () => {
beforeEach(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
function get_dialog_with_barcode() {
return cy.dialog({
- title: 'Barcode',
+ title: "Barcode",
fields: [
{
- label: 'Barcode',
- fieldname: 'barcode',
- fieldtype: 'Barcode'
- }
- ]
+ label: "Barcode",
+ fieldname: "barcode",
+ fieldtype: "Barcode",
+ },
+ ],
});
}
- it('should generate barcode on setting a value', () => {
- get_dialog_with_barcode().as('dialog');
+ it("should generate barcode on setting a value", () => {
+ get_dialog_with_barcode().as("dialog");
cy.focused().blur();
- cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
- .type('123456789')
+ cy.get(".frappe-control[data-fieldname=barcode]")
+ .findByRole("textbox")
+ .type("123456789")
.blur();
- cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
- .should('exist');
+ cy.get(
+ '.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]'
+ ).should("exist");
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('barcode');
- expect(value).to.contain(' {
+ let value = dialog.get_value("barcode");
+ expect(value).to.contain(" {
- get_dialog_with_barcode().as('dialog');
+ it("should reset when input is cleared", () => {
+ get_dialog_with_barcode().as("dialog");
cy.focused().blur();
- cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
- .type('123456789')
+ cy.get(".frappe-control[data-fieldname=barcode]")
+ .findByRole("textbox")
+ .type("123456789")
.blur();
- cy.get('.frappe-control[data-fieldname=barcode]').findByRole('textbox')
- .clear()
- .blur();
- cy.get('.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]')
- .should('not.exist');
+ cy.get(".frappe-control[data-fieldname=barcode]").findByRole("textbox").clear().blur();
+ cy.get(
+ '.frappe-control[data-fieldname=barcode] svg[data-barcode-value="123456789"]'
+ ).should("not.exist");
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('barcode');
- expect(value).to.equal('');
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("barcode");
+ expect(value).to.equal("");
});
});
});
diff --git a/cypress/integration/control_color.js b/cypress/integration/control_color.js
index 8d55003618..aa3a45eed8 100644
--- a/cypress/integration/control_color.js
+++ b/cypress/integration/control_color.js
@@ -1,77 +1,80 @@
-context('Control Color', () => {
+context("Control Color", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
function get_dialog_with_color() {
return cy.dialog({
- title: 'Color',
- fields: [{
- label: 'Color',
- fieldname: 'color',
- fieldtype: 'Color'
- }]
+ title: "Color",
+ fields: [
+ {
+ label: "Color",
+ fieldname: "color",
+ fieldtype: "Color",
+ },
+ ],
});
}
- it('Verifying if the color control is selecting correct', () => {
- get_dialog_with_color().as('dialog');
- cy.findByPlaceholderText('Choose a color').click();
+ it("Verifying if the color control is selecting correct", () => {
+ get_dialog_with_color().as("dialog");
+ cy.findByPlaceholderText("Choose a color").click();
///Selecting a color from the color palette
cy.get('[style="background-color: rgb(79, 157, 217);"]').click();
//Checking if the css attribute is correct
- cy.get('.color-map').should('have.css', 'color', 'rgb(79, 157, 217)');
- cy.get('.hue-map').should('have.css', 'color', 'rgb(0, 145, 255)');
+ cy.get(".color-map").should("have.css", "color", "rgb(79, 157, 217)");
+ cy.get(".hue-map").should("have.css", "color", "rgb(0, 145, 255)");
//Checking if the correct color is being selected
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('color');
- expect(value).to.equal('#4F9DD9');
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("color");
+ expect(value).to.equal("#4F9DD9");
});
//Selecting a color
cy.get('[style="background-color: rgb(203, 41, 41);"]').click();
//Checking if the correct css is being selected
- cy.get('.color-map').should('have.css', 'color', 'rgb(203, 41, 41)');
- cy.get('.hue-map').should('have.css', 'color', 'rgb(255, 0, 0)');
+ cy.get(".color-map").should("have.css", "color", "rgb(203, 41, 41)");
+ cy.get(".hue-map").should("have.css", "color", "rgb(255, 0, 0)");
//Checking if the correct color is being selected
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('color');
- expect(value).to.equal('#CB2929');
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("color");
+ expect(value).to.equal("#CB2929");
});
//Selecting color from the palette
- cy.get('.color-map > .color-selector').click(65, 87, {force: true});
- cy.get('.color-map').should('have.css', 'color', 'rgb(56, 0, 0)');
+ cy.get(".color-map > .color-selector").click(65, 87, { force: true });
+ cy.get(".color-map").should("have.css", "color", "rgb(56, 0, 0)");
//Checking if the expected color is selected and getting displayed
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('color');
- expect(value).to.equal('#380000');
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("color");
+ expect(value).to.equal("#380000");
});
//Selecting the color from the hue map
- cy.get('.hue-map > .hue-selector').click(35, -1, {force: true});
- cy.get('.color-map').should('have.css', 'color', 'rgb(56, 45, 0)');
- cy.get('.hue-map').should('have.css', 'color', 'rgb(255, 204, 0)');
- cy.get('.color-map > .color-selector').click(55, 12, {force: true});
- cy.get('.color-map').should('have.css', 'color', 'rgb(46, 37, 0)');
+ cy.get(".hue-map > .hue-selector").click(35, -1, { force: true });
+ cy.get(".color-map").should("have.css", "color", "rgb(56, 45, 0)");
+ cy.get(".hue-map").should("have.css", "color", "rgb(255, 204, 0)");
+ cy.get(".color-map > .color-selector").click(55, 12, { force: true });
+ cy.get(".color-map").should("have.css", "color", "rgb(46, 37, 0)");
//Checking if the correct color is being displayed
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('color');
- expect(value).to.equal('#2e2500');
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("color");
+ expect(value).to.equal("#2e2500");
});
//Clearing the field and checking if the field contains the placeholder "Choose a color"
- cy.get('.input-with-feedback').click({force: true});
- cy.get_field('color', 'Color').type('{selectall}').clear();
- cy.get_field('color', 'Color').invoke('attr', 'placeholder').should('contain', 'Choose a color');
-
+ cy.get(".input-with-feedback").click({ force: true });
+ cy.get_field("color", "Color").type("{selectall}").clear();
+ cy.get_field("color", "Color")
+ .invoke("attr", "placeholder")
+ .should("contain", "Choose a color");
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/control_data.js b/cypress/integration/control_data.js
index 78cece627b..d855df2919 100644
--- a/cypress/integration/control_data.js
+++ b/cypress/integration/control_data.js
@@ -1,134 +1,145 @@
-context('Data Control', () => {
+context("Data Control", () => {
before(() => {
cy.login();
- cy.visit('/app/doctype');
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
- name: 'Test Data Control',
- fields: [
- {
- "label": "Name",
- "fieldname": "name1",
- "fieldtype": "Data",
- "options": "Name",
- "in_list_view": 1,
- "reqd": 1,
- },
- {
- "label": "Email-ID",
- "fieldname": "email",
- "fieldtype": "Data",
- "options": "Email",
- "in_list_view": 1,
- "reqd": 1,
- },
- {
- "label": "Phone No.",
- "fieldname": "phone",
- "fieldtype": "Data",
- "options": "Phone",
- "in_list_view": 1,
- "reqd": 1,
- },
- ]
+ cy.visit("/app/doctype");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
+ name: "Test Data Control",
+ fields: [
+ {
+ label: "Name",
+ fieldname: "name1",
+ fieldtype: "Data",
+ options: "Name",
+ in_list_view: 1,
+ reqd: 1,
+ },
+ {
+ label: "Email-ID",
+ fieldname: "email",
+ fieldtype: "Data",
+ options: "Email",
+ in_list_view: 1,
+ reqd: 1,
+ },
+ {
+ label: "Phone No.",
+ fieldname: "phone",
+ fieldtype: "Data",
+ options: "Phone",
+ in_list_view: 1,
+ reqd: 1,
+ },
+ ],
+ });
});
- });
});
- it('check custom formatters', () => {
+ it("check custom formatters", () => {
cy.visit(`/app/doctype/User`);
- cy.get('[data-fieldname="fields"] .grid-row[data-idx="2"] [data-fieldname="fieldtype"] .static-area').should('have.text', '🔵 Section Break');
+ cy.get(
+ '[data-fieldname="fields"] .grid-row[data-idx="2"] [data-fieldname="fieldtype"] .static-area'
+ ).should("have.text", "🔵 Section Break");
});
it('Verifying data control by inputting different patterns for "Name" field', () => {
- cy.new_form('Test Data Control');
+ cy.new_form("Test Data Control");
//Checking the URL for the new form of the doctype
- cy.location("pathname").should('eq', '/app/test-data-control/new-test-data-control-1');
- cy.get('.title-text').should('have.text', 'New Test Data Control');
- cy.get('.frappe-control[data-fieldname="name1"]').find('label').should('have.class', 'reqd');
- cy.get('.frappe-control[data-fieldname="email"]').find('label').should('have.class', 'reqd');
- cy.get('.frappe-control[data-fieldname="phone"]').find('label').should('have.class', 'reqd');
+ cy.location("pathname").should("eq", "/app/test-data-control/new-test-data-control-1");
+ cy.get(".title-text").should("have.text", "New Test Data Control");
+ cy.get('.frappe-control[data-fieldname="name1"]')
+ .find("label")
+ .should("have.class", "reqd");
+ cy.get('.frappe-control[data-fieldname="email"]')
+ .find("label")
+ .should("have.class", "reqd");
+ cy.get('.frappe-control[data-fieldname="phone"]')
+ .find("label")
+ .should("have.class", "reqd");
//Checking if the status is "Not Saved" initially
- cy.get('.indicator-pill').should('have.text', 'Not Saved');
+ cy.get(".indicator-pill").should("have.text", "Not Saved");
//Inputting data in the field
- cy.fill_field('name1', '@@###', 'Data');
- cy.fill_field('email', 'test@example.com', 'Data');
- cy.fill_field('phone', '9834280031', 'Data');
+ cy.fill_field("name1", "@@###", "Data");
+ cy.fill_field("email", "test@example.com", "Data");
+ cy.fill_field("phone", "9834280031", "Data");
//Checking if the border color of the field changes to red
- cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error');
+ cy.get('.frappe-control[data-fieldname="name1"]').should("have.class", "has-error");
cy.save();
//Checking for the error message
- cy.get('.modal-title').should('have.text', 'Message');
- cy.get('.msgprint').should('have.text', '@@### is not a valid Name');
+ cy.get(".modal-title").should("have.text", "Message");
+ cy.get(".msgprint").should("have.text", "@@### is not a valid Name");
cy.hide_dialog();
- cy.get_field('name1', 'Data').clear({force: true});
- cy.fill_field('name1', 'Komal{}/!', 'Data');
- cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error');
+ cy.get_field("name1", "Data").clear({ force: true });
+ cy.fill_field("name1", "Komal{}/!", "Data");
+ cy.get('.frappe-control[data-fieldname="name1"]').should("have.class", "has-error");
cy.save();
- cy.get('.modal-title').should('have.text', 'Message');
- cy.get('.msgprint').should('have.text', 'Komal{}/! is not a valid Name');
+ cy.get(".modal-title").should("have.text", "Message");
+ cy.get(".msgprint").should("have.text", "Komal{}/! is not a valid Name");
cy.hide_dialog();
});
it('Verifying data control by inputting different patterns for "Email" field', () => {
- cy.get_field('name1', 'Data').clear({force: true});
- cy.fill_field('name1', 'Komal', 'Data');
- cy.get_field('email', 'Data').clear({force: true});
- cy.fill_field('email', 'komal', 'Data');
- cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error');
+ cy.get_field("name1", "Data").clear({ force: true });
+ cy.fill_field("name1", "Komal", "Data");
+ cy.get_field("email", "Data").clear({ force: true });
+ cy.fill_field("email", "komal", "Data");
+ cy.get('.frappe-control[data-fieldname="email"]').should("have.class", "has-error");
cy.save();
- cy.get('.modal-title').should('have.text', 'Message');
- cy.get('.msgprint').should('have.text', 'komal is not a valid Email Address');
+ cy.get(".modal-title").should("have.text", "Message");
+ cy.get(".msgprint").should("have.text", "komal is not a valid Email Address");
cy.hide_dialog();
- cy.get_field('email', 'Data').clear({force: true});
- cy.fill_field('email', 'komal@test', 'Data');
- cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error');
+ cy.get_field("email", "Data").clear({ force: true });
+ cy.fill_field("email", "komal@test", "Data");
+ cy.get('.frappe-control[data-fieldname="email"]').should("have.class", "has-error");
cy.save();
- cy.get('.modal-title').should('have.text', 'Message');
- cy.get('.msgprint').should('have.text', 'komal@test is not a valid Email Address');
+ cy.get(".modal-title").should("have.text", "Message");
+ cy.get(".msgprint").should("have.text", "komal@test is not a valid Email Address");
cy.hide_dialog();
});
it('Verifying data control by inputting different patterns for "Phone" field', () => {
- cy.get_field('email', 'Data').clear({force: true});
- cy.fill_field('email', 'komal@test.com', 'Data');
- cy.get_field('phone', 'Data').clear({force: true});
- cy.fill_field('phone', 'komal', 'Data');
- cy.get('.frappe-control[data-fieldname="phone"]').should('have.class', 'has-error');
- cy.findByRole('button', {name: 'Save'}).click({force: true});
- cy.get('.modal-title').should('have.text', 'Message');
- cy.get('.msgprint').should('have.text', 'komal is not a valid Phone Number');
+ cy.get_field("email", "Data").clear({ force: true });
+ cy.fill_field("email", "komal@test.com", "Data");
+ cy.get_field("phone", "Data").clear({ force: true });
+ cy.fill_field("phone", "komal", "Data");
+ cy.get('.frappe-control[data-fieldname="phone"]').should("have.class", "has-error");
+ cy.findByRole("button", { name: "Save" }).click({ force: true });
+ cy.get(".modal-title").should("have.text", "Message");
+ cy.get(".msgprint").should("have.text", "komal is not a valid Phone Number");
cy.hide_dialog();
});
- it('Inputting correct data and saving the doc', () => {
+ it("Inputting correct data and saving the doc", () => {
//Inputting the data as expected and saving the document
- cy.get_field('name1', 'Data').clear({force: true});
- cy.get_field('email', 'Data').clear({force: true});
- cy.get_field('phone', 'Data').clear({force: true});
- cy.fill_field('name1', 'Komal', 'Data');
- cy.fill_field('email', 'komal@test.com', 'Data');
- cy.fill_field('phone', '9432380001', 'Data');
- cy.findByRole('button', {name: 'Save'}).click({force: true});
+ cy.get_field("name1", "Data").clear({ force: true });
+ cy.get_field("email", "Data").clear({ force: true });
+ cy.get_field("phone", "Data").clear({ force: true });
+ cy.fill_field("name1", "Komal", "Data");
+ cy.fill_field("email", "komal@test.com", "Data");
+ cy.fill_field("phone", "9432380001", "Data");
+ cy.findByRole("button", { name: "Save" }).click({ force: true });
//Checking if the fields contains the data which has been filled in
- cy.location("pathname").should('not.be', '/app/test-data-control/new-test-data-control-1');
- cy.get_field('name1').should('have.value', 'Komal');
- cy.get_field('email').should('have.value', 'komal@test.com');
- cy.get_field('phone').should('have.value', '9432380001');
+ cy.location("pathname").should("not.be", "/app/test-data-control/new-test-data-control-1");
+ cy.get_field("name1").should("have.value", "Komal");
+ cy.get_field("email").should("have.value", "komal@test.com");
+ cy.get_field("phone").should("have.value", "9432380001");
});
- it('Deleting the doc', () => {
+ it("Deleting the doc", () => {
//Deleting the inserted document
- cy.go_to_list('Test Data Control');
- cy.get('.list-row-checkbox').eq(0).click({force: true});
- cy.get('.actions-btn-group > .btn').contains('Actions').click();
+ cy.go_to_list("Test Data Control");
+ cy.get(".list-row-checkbox").eq(0).click({ force: true });
+ cy.get(".actions-btn-group > .btn").contains("Actions").click();
cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click();
- cy.click_modal_primary_button('Yes');
+ cy.click_modal_primary_button("Yes");
});
});
diff --git a/cypress/integration/control_date.js b/cypress/integration/control_date.js
index 6d9f0b9bcc..408f7b819e 100644
--- a/cypress/integration/control_date.js
+++ b/cypress/integration/control_date.js
@@ -1,82 +1,89 @@
-context('Date Control', () => {
+context("Date Control", () => {
before(() => {
cy.login();
- cy.visit('/app');
+ cy.visit("/app");
});
function get_dialog(date_field_options) {
return cy.dialog({
- title: 'Date',
- fields: [{
- "label": "Date",
- "fieldname": "date",
- "fieldtype": "Date",
- "in_list_view": 1,
- ...date_field_options
- }]
+ title: "Date",
+ fields: [
+ {
+ label: "Date",
+ fieldname: "date",
+ fieldtype: "Date",
+ in_list_view: 1,
+ ...date_field_options,
+ },
+ ],
});
}
- it('Selecting a date from the datepicker', () => {
+ it("Selecting a date from the datepicker", () => {
cy.clear_dialogs();
cy.clear_datepickers();
- get_dialog().as('dialog');
- cy.get_field('date', 'Date').click();
- cy.get('.datepicker--nav-title').click();
- cy.get('.datepicker--nav-title').click({force: true});
-
+ get_dialog().as("dialog");
+ cy.get_field("date", "Date").click();
+ cy.get(".datepicker--nav-title").click();
+ cy.get(".datepicker--nav-title").click({ force: true });
//Inputing values in the date field
- cy.get('.datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]').click();
- cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click();
- cy.get('.datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]').click();
+ cy.get(
+ ".datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]"
+ ).click();
+ cy.get(
+ ".datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]"
+ ).click();
+ cy.get(".datepicker--days > .datepicker--cells > .datepicker--cell[data-date=15]").click();
// Verify if the selected date is set the date field
- cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
+ cy.window().its("cur_dialog.fields_dict.date.value").should("be.equal", "2020-01-15");
});
- it('Checking next and previous button', () => {
+ it("Checking next and previous button", () => {
cy.clear_dialogs();
cy.clear_datepickers();
- get_dialog({ default: '2020-01-15' }).as('dialog');
- cy.get_field('date', 'Date').click();
+ get_dialog({ default: "2020-01-15" }).as("dialog");
+ cy.get_field("date", "Date").click();
//Clicking on the next button in the datepicker
- cy.get('.datepicker--nav-action[data-action=next]').click();
+ cy.get(".datepicker--nav-action[data-action=next]").click();
//Selecting a date from the datepicker
- cy.get('.datepicker--cell[data-date=15]').click({force: true});
+ cy.get(".datepicker--cell[data-date=15]").click({ force: true });
//Verifying if the selected date has been displayed in the date field
- cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-02-15');
+ cy.window().its("cur_dialog.fields_dict.date.value").should("be.equal", "2020-02-15");
cy.wait(500);
- cy.get_field('date', 'Date').click();
+ cy.get_field("date", "Date").click();
//Clicking on the previous button in the datepicker
- cy.get('.datepicker--nav-action[data-action=prev]').click();
+ cy.get(".datepicker--nav-action[data-action=prev]").click();
//Selecting a date from the datepicker
- cy.get('.datepicker--cell[data-date=15]').click({force: true});
+ cy.get(".datepicker--cell[data-date=15]").click({ force: true });
//Verifying if the selected date has been displayed in the date field
- cy.window().its('cur_dialog.fields_dict.date.value').should('be.equal', '2020-01-15');
+ cy.window().its("cur_dialog.fields_dict.date.value").should("be.equal", "2020-01-15");
});
it('Clicking on "Today" button gives todays date', () => {
cy.clear_dialogs();
cy.clear_datepickers();
- get_dialog().as('dialog');
- cy.get_field('date', 'Date').click();
+ get_dialog().as("dialog");
+ cy.get_field("date", "Date").click();
//Clicking on "Today" button
- cy.get('.datepicker--button').click();
+ cy.get(".datepicker--button").click();
//Verifying if clicking on "Today" button matches today's date
- cy.window().then(win => {
- expect(win.cur_dialog.fields_dict.date.value).to.be.equal(win.frappe.datetime.get_today());
+ cy.window().then((win) => {
+ expect(win.cur_dialog.fields_dict.date.value).to.be.equal(
+ win.frappe.datetime.get_today()
+ );
});
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/control_date_range.js b/cypress/integration/control_date_range.js
index 6f26b35f84..f95a3825cc 100644
--- a/cypress/integration/control_date_range.js
+++ b/cypress/integration/control_date_range.js
@@ -1,42 +1,48 @@
-context('Date Range Control', () => {
+context("Date Range Control", () => {
before(() => {
cy.login();
- cy.visit('/app');
+ cy.visit("/app");
});
function get_dialog() {
return cy.dialog({
- title: 'Date Range',
- fields: [{
- "label": "Date Range",
- "fieldname": "date_range",
- "fieldtype": "Date Range",
- }]
+ title: "Date Range",
+ fields: [
+ {
+ label: "Date Range",
+ fieldname: "date_range",
+ fieldtype: "Date Range",
+ },
+ ],
});
}
- it('Selecting a date range from the datepicker', () => {
+ it("Selecting a date range from the datepicker", () => {
cy.clear_dialogs();
cy.clear_datepickers();
- get_dialog().as('dialog');
- cy.get_field('date_range', 'Date Range').click();
- cy.get('.datepicker--nav-title').click();
- cy.get('.datepicker--nav-title').click({force: true});
+ get_dialog().as("dialog");
+ cy.get_field("date_range", "Date Range").click();
+ cy.get(".datepicker--nav-title").click();
+ cy.get(".datepicker--nav-title").click({ force: true });
//Inputing date range values in the date range field
- cy.get('.datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]').click();
- cy.get('.datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]').click();
- cy.get('.datepicker--cell[data-date=1]:first').click({force: true});
- cy.get('.datepicker--cell[data-date=15]:first').click({force: true});
+ cy.get(
+ ".datepicker--years > .datepicker--cells > .datepicker--cell[data-year=2020]"
+ ).click();
+ cy.get(
+ ".datepicker--months > .datepicker--cells > .datepicker--cell[data-month=0]"
+ ).click();
+ cy.get(".datepicker--cell[data-date=1]:first").click({ force: true });
+ cy.get(".datepicker--cell[data-date=15]:first").click({ force: true });
// Verify if the selected date range values is set in the date range field
cy.window()
- .its('cur_dialog')
- .then(dialog => {
+ .its("cur_dialog")
+ .then((dialog) => {
let date_range = dialog.get_value("date_range");
- expect(date_range[0]).to.equal('2020-01-01');
- expect(date_range[1]).to.equal('2020-01-15');
+ expect(date_range[0]).to.equal("2020-01-01");
+ expect(date_range[1]).to.equal("2020-01-15");
});
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js
index 09629a344f..a391eec7c1 100644
--- a/cypress/integration/control_duration.js
+++ b/cypress/integration/control_duration.js
@@ -1,46 +1,46 @@
-context('Control Duration', () => {
+context("Control Duration", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
function get_dialog_with_duration(hide_days = 0, hide_seconds = 0) {
return cy.dialog({
- title: 'Duration',
- fields: [{
- 'fieldname': 'duration',
- 'fieldtype': 'Duration',
- 'hide_days': hide_days,
- 'hide_seconds': hide_seconds
- }]
+ title: "Duration",
+ fields: [
+ {
+ fieldname: "duration",
+ fieldtype: "Duration",
+ hide_days: hide_days,
+ hide_seconds: hide_seconds,
+ },
+ ],
});
}
- it('should set duration', () => {
- get_dialog_with_duration().as('dialog');
- cy.get('.frappe-control[data-fieldname=duration] input')
- .first()
- .click();
- cy.get('.duration-input[data-duration=days]')
+ it("should set duration", () => {
+ get_dialog_with_duration().as("dialog");
+ cy.get(".frappe-control[data-fieldname=duration] input").first().click();
+ cy.get(".duration-input[data-duration=days]")
.type(45, { force: true })
.blur({ force: true });
- cy.get('.duration-input[data-duration=minutes]')
- .type(30)
- .blur({ force: true });
- cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m');
- cy.get('.frappe-control[data-fieldname=duration] input').first().blur();
- cy.get('.duration-picker').should('not.be.visible');
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('duration');
+ cy.get(".duration-input[data-duration=minutes]").type(30).blur({ force: true });
+ cy.get(".frappe-control[data-fieldname=duration] input")
+ .first()
+ .should("have.value", "45d 30m");
+ cy.get(".frappe-control[data-fieldname=duration] input").first().blur();
+ cy.get(".duration-picker").should("not.be.visible");
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("duration");
expect(value).to.equal(3889800);
cy.hide_dialog();
});
});
- it('should hide days or seconds according to duration options', () => {
- get_dialog_with_duration(1, 1).as('dialog');
- cy.get('.frappe-control[data-fieldname=duration] input').first();
- cy.get('.duration-input[data-duration=days]').should('not.be.visible');
- cy.get('.duration-input[data-duration=seconds]').should('not.be.visible');
+ it("should hide days or seconds according to duration options", () => {
+ get_dialog_with_duration(1, 1).as("dialog");
+ cy.get(".frappe-control[data-fieldname=duration] input").first();
+ cy.get(".duration-input[data-duration=days]").should("not.be.visible");
+ cy.get(".duration-input[data-duration=seconds]").should("not.be.visible");
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/control_dynamic_link.js b/cypress/integration/control_dynamic_link.js
index 32b2c274a8..7f34f7ad42 100644
--- a/cypress/integration/control_dynamic_link.js
+++ b/cypress/integration/control_dynamic_link.js
@@ -1,133 +1,159 @@
-context('Dynamic Link', () => {
+context("Dynamic Link", () => {
before(() => {
cy.login();
- cy.visit('/app/doctype');
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
- name: 'Test Dynamic Link',
- fields: [
- {
- "label": "Document Type",
- "fieldname": "doc_type",
- "fieldtype": "Link",
- "options": "DocType",
- "in_list_view": 1,
- "in_standard_filter": 1,
- },
- {
- "label": "Document ID",
- "fieldname": "doc_id",
- "fieldtype": "Dynamic Link",
- "options": "doc_type",
- "in_list_view": 1,
- "in_standard_filter": 1,
- },
- ]
+ cy.visit("/app/doctype");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
+ name: "Test Dynamic Link",
+ fields: [
+ {
+ label: "Document Type",
+ fieldname: "doc_type",
+ fieldtype: "Link",
+ options: "DocType",
+ in_list_view: 1,
+ in_standard_filter: 1,
+ },
+ {
+ label: "Document ID",
+ fieldname: "doc_id",
+ fieldtype: "Dynamic Link",
+ options: "doc_type",
+ in_list_view: 1,
+ in_standard_filter: 1,
+ },
+ ],
+ });
});
- });
});
-
function get_dialog_with_dynamic_link() {
return cy.dialog({
- title: 'Dynamic Link',
- fields: [{
- "label": "Document Type",
- "fieldname": "doc_type",
- "fieldtype": "Link",
- "options": "DocType",
- "in_list_view": 1,
- },
- {
- "label": "Document ID",
- "fieldname": "doc_id",
- "fieldtype": "Dynamic Link",
- "options": "doc_type",
- "in_list_view": 1,
- }]
+ title: "Dynamic Link",
+ fields: [
+ {
+ label: "Document Type",
+ fieldname: "doc_type",
+ fieldtype: "Link",
+ options: "DocType",
+ in_list_view: 1,
+ },
+ {
+ label: "Document ID",
+ fieldname: "doc_id",
+ fieldtype: "Dynamic Link",
+ options: "doc_type",
+ in_list_view: 1,
+ },
+ ],
});
}
function get_dialog_with_dynamic_link_option() {
return cy.dialog({
- title: 'Dynamic Link',
- fields: [{
- "label": "Document Type",
- "fieldname": "doc_type",
- "fieldtype": "Link",
- "options": "DocType",
- "in_list_view": 1,
- },
- {
- "label": "Document ID",
- "fieldname": "doc_id",
- "fieldtype": "Dynamic Link",
- "get_options": () => {
- return "User";
+ title: "Dynamic Link",
+ fields: [
+ {
+ label: "Document Type",
+ fieldname: "doc_type",
+ fieldtype: "Link",
+ options: "DocType",
+ in_list_view: 1,
},
- "in_list_view": 1,
- }]
+ {
+ label: "Document ID",
+ fieldname: "doc_id",
+ fieldtype: "Dynamic Link",
+ get_options: () => {
+ return "User";
+ },
+ in_list_view: 1,
+ },
+ ],
});
}
- it('Creating a dynamic link by passing option as function and verifying it in a dialog', () => {
- get_dialog_with_dynamic_link_option().as('dialog');
- cy.get_field('doc_type').clear();
- cy.fill_field('doc_type', 'User', 'Link');
- cy.get_field('doc_id').click();
+ it("Creating a dynamic link by passing option as function and verifying it in a dialog", () => {
+ get_dialog_with_dynamic_link_option().as("dialog");
+ cy.get_field("doc_type").clear();
+ cy.fill_field("doc_type", "User", "Link");
+ cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
- cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
- cy.get('.btn-modal-close').click({force: true});
+ cy.get('[data-fieldname="doc_id"]')
+ .find(".awesomplete")
+ .find("li")
+ .its("length")
+ .should("be.gte", 0);
+ cy.get(".btn-modal-close").click({ force: true });
});
- it('Creating a dynamic link and verifying it in a dialog', () => {
- get_dialog_with_dynamic_link().as('dialog');
- cy.get_field('doc_type').clear();
- cy.fill_field('doc_type', 'User', 'Link');
- cy.get_field('doc_id').click();
+ it("Creating a dynamic link and verifying it in a dialog", () => {
+ get_dialog_with_dynamic_link().as("dialog");
+ cy.get_field("doc_type").clear();
+ cy.fill_field("doc_type", "User", "Link");
+ cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
- cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
- cy.get('.btn-modal-close').click({force: true, multiple: true});
+ cy.get('[data-fieldname="doc_id"]')
+ .find(".awesomplete")
+ .find("li")
+ .its("length")
+ .should("be.gte", 0);
+ cy.get(".btn-modal-close").click({ force: true, multiple: true });
});
- it('Creating a dynamic link and verifying it', () => {
- cy.visit('/app/test-dynamic-link');
+ it("Creating a dynamic link and verifying it", () => {
+ cy.visit("/app/test-dynamic-link");
//Clicking on the Document ID field
- cy.get_field('doc_type').clear();
+ cy.get_field("doc_type").clear();
//Entering User in the Doctype field
- cy.fill_field('doc_type', 'User', 'Link', {delay: 500});
- cy.get_field('doc_id').click();
+ cy.fill_field("doc_type", "User", "Link", { delay: 500 });
+ cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
- cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
+ cy.get('[data-fieldname="doc_id"]')
+ .find(".awesomplete")
+ .find("li")
+ .its("length")
+ .should("be.gte", 0);
//Opening a new form for dynamic link doctype
- cy.new_form('Test Dynamic Link');
- cy.get_field('doc_type').clear();
+ cy.new_form("Test Dynamic Link");
+ cy.get_field("doc_type").clear();
//Entering User in the Doctype field
- cy.fill_field('doc_type', 'User', 'Link', {delay: 500});
- cy.get_field('doc_id').click();
+ cy.fill_field("doc_type", "User", "Link", { delay: 500 });
+ cy.get_field("doc_id").click();
//Checking if the listbox have length greater than 0
- cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0);
- cy.get_field('doc_type').clear();
+ cy.get('[data-fieldname="doc_id"]')
+ .find(".awesomplete")
+ .find("li")
+ .its("length")
+ .should("be.gte", 0);
+ cy.get_field("doc_type").clear();
//Entering System Settings in the Doctype field
- cy.intercept('/api/method/frappe.desk.search.search_link').as('search_query');
- cy.fill_field('doc_type', 'System Settings', 'Link', {delay: 500});
- cy.wait('@search_query');
- cy.get(`[data-fieldname="doc_type"] ul:visible li:first-child`)
- .click({scrollBehavior: false});
+ cy.intercept("/api/method/frappe.desk.search.search_link").as("search_query");
+ cy.fill_field("doc_type", "System Settings", "Link", { delay: 500 });
+ cy.wait("@search_query");
+ cy.get(`[data-fieldname="doc_type"] ul:visible li:first-child`).click({
+ scrollBehavior: false,
+ });
- cy.get_field('doc_id').click();
+ cy.get_field("doc_id").click();
//Checking if the system throws error
- cy.get('.modal-title').should('have.text', 'Error');
- cy.get('.msgprint').should('have.text', 'System Settings is not a valid DocType for Dynamic Link');
+ cy.get(".modal-title").should("have.text", "Error");
+ cy.get(".msgprint").should(
+ "have.text",
+ "System Settings is not a valid DocType for Dynamic Link"
+ );
});
});
diff --git a/cypress/integration/control_float.js b/cypress/integration/control_float.js
index 670d1fe73e..c8261ad043 100644
--- a/cypress/integration/control_float.js
+++ b/cypress/integration/control_float.js
@@ -11,9 +11,9 @@ context("Control Float", () => {
{
fieldname: "float_number",
fieldtype: "Float",
- Label: "Float"
- }
- ]
+ Label: "Float",
+ },
+ ],
});
}
@@ -21,27 +21,21 @@ context("Control Float", () => {
get_dialog_with_float().as("dialog");
let data = get_data();
- data.forEach(x => {
+ data.forEach((x) => {
cy.window()
.its("frappe")
- .then(frappe => {
+ .then((frappe) => {
frappe.boot.sysdefaults.number_format = x.number_format;
});
- x.values.forEach(d => {
+ x.values.forEach((d) => {
cy.get_field("float_number", "Float").clear();
cy.fill_field("float_number", d.input, "Float").blur();
- cy.get_field("float_number", "Float").should(
- "have.value",
- d.blur_expected
- );
+ cy.get_field("float_number", "Float").should("have.value", d.blur_expected);
cy.get_field("float_number", "Float").focus();
cy.get_field("float_number", "Float").blur();
cy.get_field("float_number", "Float").focus();
- cy.get_field("float_number", "Float").should(
- "have.value",
- d.focus_expected
- );
+ cy.get_field("float_number", "Float").should("have.value", d.focus_expected);
});
});
});
@@ -54,19 +48,19 @@ context("Control Float", () => {
{
input: "364.87,334",
blur_expected: "36.487,334",
- focus_expected: "36487.334"
+ focus_expected: "36487.334",
},
{
input: "36487,334",
blur_expected: "36.487,334",
- focus_expected: "36487.334"
+ focus_expected: "36487.334",
},
{
input: "100",
blur_expected: "100,000",
- focus_expected: "100"
- }
- ]
+ focus_expected: "100",
+ },
+ ],
},
{
number_format: "#,###.##",
@@ -74,20 +68,20 @@ context("Control Float", () => {
{
input: "364,87.334",
blur_expected: "36,487.334",
- focus_expected: "36487.334"
+ focus_expected: "36487.334",
},
{
input: "36487.334",
blur_expected: "36,487.334",
- focus_expected: "36487.334"
+ focus_expected: "36487.334",
},
{
input: "100",
blur_expected: "100.000",
- focus_expected: "100"
- }
- ]
- }
+ focus_expected: "100",
+ },
+ ],
+ },
];
}
});
diff --git a/cypress/integration/control_icon.js b/cypress/integration/control_icon.js
index d89eba8840..a965ed0f9e 100644
--- a/cypress/integration/control_icon.js
+++ b/cypress/integration/control_icon.js
@@ -1,50 +1,55 @@
-context('Control Icon', () => {
+context("Control Icon", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
function get_dialog_with_icon() {
return cy.dialog({
- title: 'Icon',
- fields: [{
- label: 'Icon',
- fieldname: 'icon',
- fieldtype: 'Icon'
- }]
+ title: "Icon",
+ fields: [
+ {
+ label: "Icon",
+ fieldname: "icon",
+ fieldtype: "Icon",
+ },
+ ],
});
}
- it('should set icon', () => {
- get_dialog_with_icon().as('dialog');
- cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').click();
+ it("should set icon", () => {
+ get_dialog_with_icon().as("dialog");
+ cy.get(".frappe-control[data-fieldname=icon]").findByRole("textbox").click();
- cy.get('.icon-picker .icon-wrapper[id=heart-active]').first().click();
- cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart-active');
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('icon');
- expect(value).to.equal('heart-active');
+ cy.get(".icon-picker .icon-wrapper[id=heart-active]").first().click();
+ cy.get(".frappe-control[data-fieldname=icon]")
+ .findByRole("textbox")
+ .should("have.value", "heart-active");
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("icon");
+ expect(value).to.equal("heart-active");
});
- cy.get('.icon-picker .icon-wrapper[id=heart]').first().click();
- cy.get('.frappe-control[data-fieldname=icon]').findByRole('textbox').should('have.value', 'heart');
- cy.get('@dialog').then(dialog => {
- let value = dialog.get_value('icon');
- expect(value).to.equal('heart');
+ cy.get(".icon-picker .icon-wrapper[id=heart]").first().click();
+ cy.get(".frappe-control[data-fieldname=icon]")
+ .findByRole("textbox")
+ .should("have.value", "heart");
+ cy.get("@dialog").then((dialog) => {
+ let value = dialog.get_value("icon");
+ expect(value).to.equal("heart");
});
});
- it('search for icon and clear search input', () => {
- let search_text = 'ed';
- cy.get('.icon-picker').findByRole('searchbox').click().type(search_text);
- cy.get('.icon-section .icon-wrapper:not(.hidden)').then(i => {
- cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then(icons => {
+ it("search for icon and clear search input", () => {
+ let search_text = "ed";
+ cy.get(".icon-picker").findByRole("searchbox").click().type(search_text);
+ cy.get(".icon-section .icon-wrapper:not(.hidden)").then((i) => {
+ cy.get(`.icon-section .icon-wrapper[id*='${search_text}']`).then((icons) => {
expect(i.length).to.equal(icons.length);
});
});
- cy.get('.icon-picker').findByRole('searchbox').clear().blur();
- cy.get('.icon-section .icon-wrapper').should('not.have.class', 'hidden');
+ cy.get(".icon-picker").findByRole("searchbox").clear().blur();
+ cy.get(".icon-section .icon-wrapper").should("not.have.class", "hidden");
});
-
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/control_link.js b/cypress/integration/control_link.js
index 44153f7e4a..b34414e5ca 100644
--- a/cypress/integration/control_link.js
+++ b/cypress/integration/control_link.js
@@ -1,93 +1,101 @@
-context('Control Link', () => {
+context("Control Link", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
beforeEach(() => {
- cy.visit('/app/website');
+ cy.visit("/app/website");
cy.create_records({
- doctype: 'ToDo',
- description: 'this is a test todo for link'
- }).as('todos');
+ doctype: "ToDo",
+ description: "this is a test todo for link",
+ }).as("todos");
});
function get_dialog_with_link() {
return cy.dialog({
- title: 'Link',
+ title: "Link",
fields: [
{
- 'label': 'Select ToDo',
- 'fieldname': 'link',
- 'fieldtype': 'Link',
- 'options': 'ToDo',
- }
- ]
+ label: "Select ToDo",
+ fieldname: "link",
+ fieldtype: "Link",
+ options: "ToDo",
+ },
+ ],
});
}
function get_dialog_with_user_link() {
return cy.dialog({
- title: 'Link',
+ title: "Link",
fields: [
{
- 'label': 'Select User',
- 'fieldname': 'link',
- 'fieldtype': 'Link',
- 'options': 'User',
- }
- ]
+ label: "Select User",
+ fieldname: "link",
+ fieldtype: "Link",
+ options: "User",
+ },
+ ],
});
}
- it('should set the valid value', () => {
- get_dialog_with_link().as('dialog');
+ it("should set the valid value", () => {
+ get_dialog_with_link().as("dialog");
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "User",
- "property": "translate_link_fields",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "0"
- }, true);
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "User",
+ property: "translate_link_fields",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "0",
+ },
+ true
+ );
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "ToDo",
- "property": "show_title_field_in_link",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "0"
- }, true);
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "ToDo",
+ property: "show_title_field_in_link",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "0",
+ },
+ true
+ );
- cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
+ cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
- cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
- cy.wait('@search_link');
- cy.get('@input').type('todo for link', { delay: 200 });
- cy.wait('@search_link');
- cy.get('.frappe-control[data-fieldname=link]').findByRole('listbox').should('be.visible');
- cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
- cy.get('.frappe-control[data-fieldname=link] input').blur();
- cy.get('@dialog').then(dialog => {
- cy.get('@todos').then(todos => {
- let value = dialog.get_value('link');
+ cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
+ cy.wait("@search_link");
+ cy.get("@input").type("todo for link", { delay: 200 });
+ cy.wait("@search_link");
+ cy.get(".frappe-control[data-fieldname=link]").findByRole("listbox").should("be.visible");
+ cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
+ cy.get(".frappe-control[data-fieldname=link] input").blur();
+ cy.get("@dialog").then((dialog) => {
+ cy.get("@todos").then((todos) => {
+ let value = dialog.get_value("link");
expect(value).to.eq(todos[0]);
});
});
});
- it('should unset invalid value', () => {
- get_dialog_with_link().as('dialog');
+ it("should unset invalid value", () => {
+ get_dialog_with_link().as("dialog");
- cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
+ cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
- cy.get('.frappe-control[data-fieldname=link] input')
- .type('invalid value', { delay: 100 })
+ cy.get(".frappe-control[data-fieldname=link] input")
+ .type("invalid value", { delay: 100 })
.blur();
- cy.wait('@validate_link');
- cy.get('.frappe-control[data-fieldname=link] input').should('have.value', '');
+ cy.wait("@validate_link");
+ cy.get(".frappe-control[data-fieldname=link] input").should("have.value", "");
});
it("should be possible set empty value explicitly", () => {
@@ -95,295 +103,325 @@ context('Control Link', () => {
cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
- cy.get(".frappe-control[data-fieldname=link] input")
- .type(" ", { delay: 100 })
- .blur();
+ cy.get(".frappe-control[data-fieldname=link] input").type(" ", { delay: 100 }).blur();
cy.wait("@validate_link");
cy.get(".frappe-control[data-fieldname=link] input").should("have.value", "");
cy.window()
.its("cur_dialog")
.then((dialog) => {
- expect(dialog.get_value("link")).to.equal('');
+ expect(dialog.get_value("link")).to.equal("");
});
});
- it('should route to form on arrow click', () => {
- get_dialog_with_link().as('dialog');
+ it("should route to form on arrow click", () => {
+ get_dialog_with_link().as("dialog");
- cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
- cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
+ cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
+ cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
- cy.get('@todos').then(todos => {
- cy.get('.frappe-control[data-fieldname=link] input').as('input');
- cy.get('@input').focus();
- cy.wait('@search_link');
- cy.get('@input').type(todos[0]).blur();
- cy.wait('@validate_link');
- cy.get('@input').focus();
+ cy.get("@todos").then((todos) => {
+ cy.get(".frappe-control[data-fieldname=link] input").as("input");
+ cy.get("@input").focus();
+ cy.wait("@search_link");
+ cy.get("@input").type(todos[0]).blur();
+ cy.wait("@validate_link");
+ cy.get("@input").focus();
cy.wait(500); // wait for arrow to show
- cy.get('.frappe-control[data-fieldname=link] .btn-open')
- .should('be.visible')
- .click();
- cy.location('pathname').should('eq', `/app/todo/${todos[0]}`);
+ cy.get(".frappe-control[data-fieldname=link] .btn-open").should("be.visible").click();
+ cy.location("pathname").should("eq", `/app/todo/${todos[0]}`);
});
});
- it('show title field in link', () => {
+ it("show title field in link", () => {
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "User",
+ property: "translate_link_fields",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "0",
+ },
+ true
+ );
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "User",
- "property": "translate_link_fields",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "0"
- }, true);
-
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "ToDo",
- "property": "show_title_field_in_link",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "1"
- }, true);
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "ToDo",
+ property: "show_title_field_in_link",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "1",
+ },
+ true
+ );
cy.clear_cache();
cy.wait(500);
- get_dialog_with_link().as('dialog');
- cy.window().its('frappe').then(frappe => {
- if (!frappe.boot) {
- frappe.boot = {
- link_title_doctypes: ['ToDo']
- };
- } else {
- frappe.boot.link_title_doctypes = ['ToDo'];
- }
- });
+ get_dialog_with_link().as("dialog");
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ if (!frappe.boot) {
+ frappe.boot = {
+ link_title_doctypes: ["ToDo"],
+ };
+ } else {
+ frappe.boot.link_title_doctypes = ["ToDo"];
+ }
+ });
- cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
+ cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
- cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
- cy.wait('@search_link');
- cy.get('@input').type('todo for link');
- cy.wait('@search_link');
- cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
- cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
- cy.get('.frappe-control[data-fieldname=link] input').blur();
- cy.get('@dialog').then(dialog => {
- cy.get('@todos').then(todos => {
- let field = dialog.get_field('link');
+ cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
+ cy.wait("@search_link");
+ cy.get("@input").type("todo for link");
+ cy.wait("@search_link");
+ cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
+ cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
+ cy.get(".frappe-control[data-fieldname=link] input").blur();
+ cy.get("@dialog").then((dialog) => {
+ cy.get("@todos").then((todos) => {
+ let field = dialog.get_field("link");
let value = field.get_value();
let label = field.get_label_value();
expect(value).to.eq(todos[0]);
- expect(label).to.eq('this is a test todo for link');
+ expect(label).to.eq("this is a test todo for link");
});
});
});
- it('should update dependant fields (via fetch_from)', () => {
- cy.get('@todos').then(todos => {
+ it("should update dependant fields (via fetch_from)", () => {
+ cy.get("@todos").then((todos) => {
cy.visit(`/app/todo/${todos[0]}`);
- cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
- cy.intercept('POST', '/api/method/frappe.client.validate_link').as('validate_link');
+ cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
+ cy.intercept("POST", "/api/method/frappe.client.validate_link").as("validate_link");
- cy.get('.frappe-control[data-fieldname=assigned_by] input').focus().as('input');
- cy.get('@input').type('Administrator', {delay: 100}).blur();
- cy.wait('@validate_link');
- cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
- 'contain', 'Administrator'
+ cy.get(".frappe-control[data-fieldname=assigned_by] input").focus().as("input");
+ cy.get("@input").type("Administrator", { delay: 100 }).blur();
+ cy.wait("@validate_link");
+ cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
+ "contain",
+ "Administrator"
);
- cy.window()
- .its("cur_frm.doc.assigned_by")
- .should("eq", "Administrator");
+ cy.window().its("cur_frm.doc.assigned_by").should("eq", "Administrator");
// invalid input
- cy.get('@input').clear().type('invalid input', {delay: 100}).blur();
- cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
- 'contain', ''
+ cy.get("@input").clear().type("invalid input", { delay: 100 }).blur();
+ cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
+ "contain",
+ ""
);
- cy.window()
- .its("cur_frm.doc.assigned_by")
- .should("eq", null);
+ cy.window().its("cur_frm.doc.assigned_by").should("eq", null);
// set valid value again
- cy.get('@input').clear().focus();
- cy.wait('@search_link');
- cy.get('@input').type('Administrator', {delay: 100}).blur();
- cy.wait('@validate_link');
+ cy.get("@input").clear().focus();
+ cy.wait("@search_link");
+ cy.get("@input").type("Administrator", { delay: 100 }).blur();
+ cy.wait("@validate_link");
- cy.window()
- .its("cur_frm.doc.assigned_by")
- .should("eq", "Administrator");
+ cy.window().its("cur_frm.doc.assigned_by").should("eq", "Administrator");
// clear input
- cy.get('@input').clear().blur();
- cy.get('.frappe-control[data-fieldname=assigned_by_full_name] .control-value').should(
- 'contain', ''
+ cy.get("@input").clear().blur();
+ cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
+ "contain",
+ ""
);
- cy.window()
- .its("cur_frm.doc.assigned_by")
- .should("eq", "");
+ cy.window().its("cur_frm.doc.assigned_by").should("eq", "");
});
});
it("should set default values", () => {
- cy.insert_doc("Property Setter", {
- "doctype_or_field": "DocField",
- "doc_type": "ToDo",
- "field_name": "assigned_by",
- "property": "default",
- "property_type": "Text",
- "value": "Administrator"
- }, true);
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype_or_field: "DocField",
+ doc_type: "ToDo",
+ field_name: "assigned_by",
+ property: "default",
+ property_type: "Text",
+ value: "Administrator",
+ },
+ true
+ );
cy.reload();
cy.new_form("ToDo");
cy.fill_field("description", "new", "Text Editor");
cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form");
- cy.findByRole("button", {name: "Save"}).click();
+ cy.findByRole("button", { name: "Save" }).click();
cy.wait("@save_form");
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
- "contain", "Administrator"
+ "contain",
+ "Administrator"
);
// if user clears default value explicitly, system should not reset default again
cy.get_field("assigned_by").clear().blur();
cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form");
- cy.findByRole("button", {name: "Save"}).click();
+ cy.findByRole("button", { name: "Save" }).click();
cy.wait("@save_form");
cy.get_field("assigned_by").should("have.value", "");
cy.get(".frappe-control[data-fieldname=assigned_by_full_name] .control-value").should(
- "contain", ""
+ "contain",
+ ""
);
});
- it('show translated text for link with show_title_field_in_link enabled', () => {
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "ToDo",
- "property": "translate_link_fields",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "1"
- }, true);
+ it("show translated text for link with show_title_field_in_link enabled", () => {
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "ToDo",
+ property: "translate_link_fields",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "1",
+ },
+ true
+ );
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "ToDo",
- "property": "show_title_field_in_link",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "1"
- }, true);
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "ToDo",
+ property: "show_title_field_in_link",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "1",
+ },
+ true
+ );
- cy.window().its('frappe').then(frappe => {
- cy.insert_doc("Translation", {
- doctype: "Translation",
- language: frappe.boot.lang,
- source_text: "this is a test todo for link",
- translated_text: "this is a translated test todo for link",
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ cy.insert_doc("Translation", {
+ doctype: "Translation",
+ language: frappe.boot.lang,
+ source_text: "this is a test todo for link",
+ translated_text: "this is a translated test todo for link",
+ });
});
- });
cy.clear_cache();
cy.wait(500);
- cy.window().its('frappe').then(frappe => {
- if (!frappe.boot) {
- frappe.boot = {
- link_title_doctypes: ['ToDo'],
- translatable_doctypes: ['ToDo']
- };
- } else {
- frappe.boot.link_title_doctypes = ['ToDo'];
- frappe.boot.translatable_doctypes = ['ToDo'];
- }
- });
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ if (!frappe.boot) {
+ frappe.boot = {
+ link_title_doctypes: ["ToDo"],
+ translatable_doctypes: ["ToDo"],
+ };
+ } else {
+ frappe.boot.link_title_doctypes = ["ToDo"];
+ frappe.boot.translatable_doctypes = ["ToDo"];
+ }
+ });
- get_dialog_with_link().as('dialog');
- cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
+ get_dialog_with_link().as("dialog");
+ cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
- cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
- cy.wait('@search_link');
- cy.get('@input').type('todo for link', { delay: 100 });
- cy.wait('@search_link');
- cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
- cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
- cy.get('.frappe-control[data-fieldname=link] input').blur();
- cy.get('@dialog').then(dialog => {
- cy.get('@todos').then(todos => {
- let field = dialog.get_field('link');
+ cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
+ cy.wait("@search_link");
+ cy.get("@input").type("todo for link", { delay: 100 });
+ cy.wait("@search_link");
+ cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
+ cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
+ cy.get(".frappe-control[data-fieldname=link] input").blur();
+ cy.get("@dialog").then((dialog) => {
+ cy.get("@todos").then((todos) => {
+ let field = dialog.get_field("link");
let value = field.get_value();
let label = field.get_label_value();
expect(value).to.eq(todos[0]);
- expect(label).to.eq('this is a translated test todo for link');
+ expect(label).to.eq("this is a translated test todo for link");
});
});
});
- it('show translated text for link with show_title_field_in_link disabled', () => {
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "User",
- "property": "translate_link_fields",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "1"
- }, true);
+ it("show translated text for link with show_title_field_in_link disabled", () => {
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "User",
+ property: "translate_link_fields",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "1",
+ },
+ true
+ );
- cy.insert_doc("Property Setter", {
- "doctype": "Property Setter",
- "doc_type": "ToDo",
- "property": "show_title_field_in_link",
- "property_type": "Check",
- "doctype_or_field": "DocType",
- "value": "0"
- }, true);
+ cy.insert_doc(
+ "Property Setter",
+ {
+ doctype: "Property Setter",
+ doc_type: "ToDo",
+ property: "show_title_field_in_link",
+ property_type: "Check",
+ doctype_or_field: "DocType",
+ value: "0",
+ },
+ true
+ );
- cy.window().its('frappe').then(frappe => {
- cy.insert_doc("Translation", {
- doctype: "Translation",
- language: frappe.boot.lang,
- source_text: "test@erpnext.com",
- translated_text: "translatedtest@erpnext.com",
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ cy.insert_doc("Translation", {
+ doctype: "Translation",
+ language: frappe.boot.lang,
+ source_text: "test@erpnext.com",
+ translated_text: "translatedtest@erpnext.com",
+ });
});
- });
cy.clear_cache();
cy.wait(500);
- cy.window().its('frappe').then(frappe => {
- if (!frappe.boot) {
- frappe.boot = {
- translatable_doctypes: ['User']
- };
- } else {
- frappe.boot.translatable_doctypes = ['User'];
- }
- });
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ if (!frappe.boot) {
+ frappe.boot = {
+ translatable_doctypes: ["User"],
+ };
+ } else {
+ frappe.boot.translatable_doctypes = ["User"];
+ }
+ });
- get_dialog_with_user_link().as('dialog');
- cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
+ get_dialog_with_user_link().as("dialog");
+ cy.intercept("POST", "/api/method/frappe.desk.search.search_link").as("search_link");
- cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
- cy.wait('@search_link');
- cy.get('@input').type('test@erpnext.com', { delay: 100 });
- cy.wait('@search_link');
- cy.get('.frappe-control[data-fieldname=link] ul').should('be.visible');
- cy.get('.frappe-control[data-fieldname=link] input').type('{enter}', { delay: 100 });
- cy.get('.frappe-control[data-fieldname=link] input').blur();
- cy.get('@dialog').then(dialog => {
- let field = dialog.get_field('link');
+ cy.get(".frappe-control[data-fieldname=link] input").focus().as("input");
+ cy.wait("@search_link");
+ cy.get("@input").type("test@erpnext.com", { delay: 100 });
+ cy.wait("@search_link");
+ cy.get(".frappe-control[data-fieldname=link] ul").should("be.visible");
+ cy.get(".frappe-control[data-fieldname=link] input").type("{enter}", { delay: 100 });
+ cy.get(".frappe-control[data-fieldname=link] input").blur();
+ cy.get("@dialog").then((dialog) => {
+ let field = dialog.get_field("link");
let value = field.get_value();
let label = field.get_label_value();
- expect(value).to.eq('test@erpnext.com');
- expect(label).to.eq('translatedtest@erpnext.com');
+ expect(value).to.eq("test@erpnext.com");
+ expect(label).to.eq("translatedtest@erpnext.com");
});
});
});
diff --git a/cypress/integration/control_markdown_editor.js b/cypress/integration/control_markdown_editor.js
index 34f94f13bf..16c3dac51f 100644
--- a/cypress/integration/control_markdown_editor.js
+++ b/cypress/integration/control_markdown_editor.js
@@ -7,12 +7,9 @@ context("Control Markdown Editor", () => {
it("should allow inserting images by drag and drop", () => {
cy.visit("/app/web-page/new");
cy.fill_field("content_type", "Markdown", "Select");
- cy.get_field("main_section_md", "Markdown Editor").attachFile(
- "sample_image.jpg",
- {
- subjectType: "drag-n-drop"
- }
- );
+ cy.get_field("main_section_md", "Markdown Editor").attachFile("sample_image.jpg", {
+ subjectType: "drag-n-drop",
+ });
cy.click_modal_primary_button("Upload");
cy.get_field("main_section_md", "Markdown Editor").should(
"contain",
diff --git a/cypress/integration/control_phone.js b/cypress/integration/control_phone.js
index 5a26decdee..b56343c2d8 100644
--- a/cypress/integration/control_phone.js
+++ b/cypress/integration/control_phone.js
@@ -1,4 +1,4 @@
-import doctype_with_phone from '../fixtures/doctype_with_phone';
+import doctype_with_phone from "../fixtures/doctype_with_phone";
context("Control Phone", () => {
before(() => {
@@ -9,10 +9,12 @@ context("Control Phone", () => {
function get_dialog_with_phone() {
return cy.dialog({
title: "Phone",
- fields: [{
- "fieldname": "phone",
- "fieldtype": "Phone",
- }]
+ fields: [
+ {
+ fieldname: "phone",
+ fieldtype: "Phone",
+ },
+ ],
});
}
@@ -27,18 +29,16 @@ context("Control Phone", () => {
let phone_number = "9312672712";
cy.get(".selected-phone > img").click().first();
- cy.get_field("phone")
- .first()
- .click({multiple: true});
+ cy.get_field("phone").first().click({ multiple: true });
cy.get(".frappe-control[data-fieldname=phone]")
.findByRole("textbox")
.first()
- .type(phone_number, {force: true});
+ .type(phone_number, { force: true });
cy.get_field("phone").first().should("have.value", phone_number);
- cy.get_field("phone").first().blur({force: true});
+ cy.get_field("phone").first().blur({ force: true });
cy.wait(100);
- cy.get("@dialog").then(dialog => {
+ cy.get("@dialog").then((dialog) => {
let value = dialog.get_value("phone");
expect(value).to.equal("+91-" + phone_number);
});
@@ -48,10 +48,12 @@ context("Control Phone", () => {
let search_text = "india";
cy.get(".selected-phone").click().first();
cy.get(".phone-picker").findByRole("searchbox").click().type(search_text);
- cy.get(".phone-section .phone-wrapper:not(.hidden)").then(i => {
- cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(countries => {
- expect(i.length).to.equal(countries.length);
- });
+ cy.get(".phone-section .phone-wrapper:not(.hidden)").then((i) => {
+ cy.get(`.phone-section .phone-wrapper[id*="${search_text.toLowerCase()}"]`).then(
+ (countries) => {
+ expect(i.length).to.equal(countries.length);
+ }
+ );
});
cy.get(".phone-picker").findByRole("searchbox").clear().blur();
diff --git a/cypress/integration/control_rating.js b/cypress/integration/control_rating.js
index 15c11b352b..613a6e9f92 100644
--- a/cypress/integration/control_rating.js
+++ b/cypress/integration/control_rating.js
@@ -1,56 +1,54 @@
-context('Control Rating', () => {
+context("Control Rating", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
function get_dialog_with_rating() {
return cy.dialog({
- title: 'Rating',
- fields: [{
- 'fieldname': 'rate',
- 'fieldtype': 'Rating',
- 'options': 7
- }]
+ title: "Rating",
+ fields: [
+ {
+ fieldname: "rate",
+ fieldtype: "Rating",
+ options: 7,
+ },
+ ],
});
}
- it('click on the star rating to record value', () => {
- get_dialog_with_rating().as('dialog');
+ it("click on the star rating to record value", () => {
+ get_dialog_with_rating().as("dialog");
- cy.get('div.rating')
- .children('svg')
- .find('.right-half')
+ cy.get("div.rating")
+ .children("svg")
+ .find(".right-half")
.first()
.click()
- .should('have.class', 'star-click');
- cy.get('@dialog').then(dialog => {
- var value = dialog.get_value('rate');
- expect(value).to.equal(1/7);
+ .should("have.class", "star-click");
+ cy.get("@dialog").then((dialog) => {
+ var value = dialog.get_value("rate");
+ expect(value).to.equal(1 / 7);
dialog.hide();
});
});
- it('hover on the star', () => {
+ it("hover on the star", () => {
get_dialog_with_rating();
- cy.get('div.rating')
- .children('svg')
- .find('.right-half')
+ cy.get("div.rating")
+ .children("svg")
+ .find(".right-half")
.first()
- .invoke('trigger', 'mouseenter')
- .should('have.class', 'star-hover')
- .invoke('trigger', 'mouseleave')
- .should('not.have.class', 'star-hover');
+ .invoke("trigger", "mouseenter")
+ .should("have.class", "star-hover")
+ .invoke("trigger", "mouseleave")
+ .should("not.have.class", "star-hover");
});
- it('check number of stars in rating', () => {
+ it("check number of stars in rating", () => {
get_dialog_with_rating();
- cy.get('div.rating')
- .first()
- .children('svg')
- .should('have.length', 7);
+ cy.get("div.rating").first().children("svg").should("have.length", 7);
});
-
});
diff --git a/cypress/integration/control_select.js b/cypress/integration/control_select.js
index 8e18d21260..5f7a07e0c4 100644
--- a/cypress/integration/control_select.js
+++ b/cypress/integration/control_select.js
@@ -1,37 +1,40 @@
-context('Control Select', () => {
+context("Control Select", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
function get_dialog_with_select() {
return cy.dialog({
- title: 'Select',
- fields: [{
- 'fieldname': 'select_control',
- 'fieldtype': 'Select',
- 'placeholder': 'Select an Option',
- 'options': ['', 'Option 1', 'Option 2', 'Option 2'],
- }]
+ title: "Select",
+ fields: [
+ {
+ fieldname: "select_control",
+ fieldtype: "Select",
+ placeholder: "Select an Option",
+ options: ["", "Option 1", "Option 2", "Option 2"],
+ },
+ ],
});
}
- it('toggles placholder on clicking an option', () => {
- get_dialog_with_select().as('dialog');
+ it("toggles placholder on clicking an option", () => {
+ get_dialog_with_select().as("dialog");
- cy.get('.frappe-control[data-fieldname=select_control] .control-input').as('control');
- cy.get('.frappe-control[data-fieldname=select_control] .control-input select').as('select');
- cy.get('@control').get('.select-icon').should('exist');
- cy.get('@control').get('.placeholder').should('have.css', 'display', 'block');
- cy.get('@select').select('Option 1');
- cy.findByDisplayValue('Option 1').should('exist');
- cy.get('@control').get('.placeholder').should('have.css', 'display', 'none');
- cy.get('@select').invoke('val', '');
- cy.findByDisplayValue('Option 1').should('not.exist');
- cy.get('@control').get('.placeholder').should('have.css', 'display', 'block');
+ cy.get(".frappe-control[data-fieldname=select_control] .control-input").as("control");
+ cy.get(".frappe-control[data-fieldname=select_control] .control-input select").as(
+ "select"
+ );
+ cy.get("@control").get(".select-icon").should("exist");
+ cy.get("@control").get(".placeholder").should("have.css", "display", "block");
+ cy.get("@select").select("Option 1");
+ cy.findByDisplayValue("Option 1").should("exist");
+ cy.get("@control").get(".placeholder").should("have.css", "display", "none");
+ cy.get("@select").invoke("val", "");
+ cy.findByDisplayValue("Option 1").should("not.exist");
+ cy.get("@control").get(".placeholder").should("have.css", "display", "block");
-
- cy.get('@dialog').then(dialog => {
+ cy.get("@dialog").then((dialog) => {
dialog.hide();
});
});
diff --git a/cypress/integration/custom_buttons.js b/cypress/integration/custom_buttons.js
index 6045d009c2..ddbd19731a 100644
--- a/cypress/integration/custom_buttons.js
+++ b/cypress/integration/custom_buttons.js
@@ -31,10 +31,7 @@ const check_button_count = (label, group = "TestGroup") => {
.should("be.visible");
//reset viewport
- cy.viewport(
- Cypress.config("viewportWidth"),
- Cypress.config("viewportHeight")
- );
+ cy.viewport(Cypress.config("viewportWidth"), Cypress.config("viewportHeight"));
};
describe(
diff --git a/cypress/integration/customize_form.js b/cypress/integration/customize_form.js
index 3857d7ccd8..cd03f7b54c 100644
--- a/cypress/integration/customize_form.js
+++ b/cypress/integration/customize_form.js
@@ -1,19 +1,19 @@
-context('Customize Form', () => {
+context("Customize Form", () => {
before(() => {
cy.login();
- cy.visit('/app/customize-form');
+ cy.visit("/app/customize-form");
});
- it('Changing to naming rule should update autoname', () => {
+ it("Changing to naming rule should update autoname", () => {
cy.fill_field("doc_type", "ToDo", "Link").blur();
cy.click_form_section("Naming");
const naming_rule_default_autoname_map = {
"Set by user": "prompt",
"By fieldname": "field:",
'By "Naming Series" field': "naming_series:",
- "Expression": "format:",
+ Expression: "format:",
"Expression (old style)": "",
- "Random": "hash",
- "By script": ""
+ Random: "hash",
+ "By script": "",
};
Cypress._.forOwn(naming_rule_default_autoname_map, (value, naming_rule) => {
cy.fill_field("naming_rule", naming_rule, "Select");
diff --git a/cypress/integration/dashboard_chart.js b/cypress/integration/dashboard_chart.js
index ae71fcda3a..6023a50abe 100644
--- a/cypress/integration/dashboard_chart.js
+++ b/cypress/integration/dashboard_chart.js
@@ -1,22 +1,22 @@
-context('Dashboard Chart', () => {
+context("Dashboard Chart", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
- it('Check filter populate for child table doctype', () => {
- cy.visit('/app/dashboard-chart/new-dashboard-chart-1');
- cy.get('[data-fieldname="parent_document_type"]').should('have.css', 'display', 'none');
+ it("Check filter populate for child table doctype", () => {
+ cy.visit("/app/dashboard-chart/new-dashboard-chart-1");
+ cy.get('[data-fieldname="parent_document_type"]').should("have.css", "display", "none");
- cy.get_field('document_type', 'Link');
- cy.fill_field('document_type', 'Workspace Link', 'Link').focus().blur();
- cy.get_field('document_type', 'Link').should('have.value', 'Workspace Link');
+ cy.get_field("document_type", "Link");
+ cy.fill_field("document_type", "Workspace Link", "Link").focus().blur();
+ cy.get_field("document_type", "Link").should("have.value", "Workspace Link");
- cy.fill_field('chart_name', 'Test Chart', 'Data');
+ cy.fill_field("chart_name", "Test Chart", "Data");
cy.get('[data-fieldname="filters_json"]').click().wait(200);
- cy.get('.modal-body .filter-action-buttons .add-filter').click();
- cy.get('.modal-body .fieldname-select-area').click();
- cy.get('.modal-actions .btn-modal-close').click();
+ cy.get(".modal-body .filter-action-buttons .add-filter").click();
+ cy.get(".modal-body .fieldname-select-area").click();
+ cy.get(".modal-actions .btn-modal-close").click();
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/dashboard_links.js b/cypress/integration/dashboard_links.js
index 019de1991d..31572b7976 100644
--- a/cypress/integration/dashboard_links.js
+++ b/cypress/integration/dashboard_links.js
@@ -1,91 +1,94 @@
-import doctype_with_child_table from '../fixtures/doctype_with_child_table';
-import child_table_doctype from '../fixtures/child_table_doctype';
-import child_table_doctype_1 from '../fixtures/child_table_doctype_1';
-import doctype_to_link from '../fixtures/doctype_to_link';
+import doctype_with_child_table from "../fixtures/doctype_with_child_table";
+import child_table_doctype from "../fixtures/child_table_doctype";
+import child_table_doctype_1 from "../fixtures/child_table_doctype_1";
+import doctype_to_link from "../fixtures/doctype_to_link";
const doctype_to_link_name = doctype_to_link.name;
const child_table_doctype_name = child_table_doctype.name;
-context('Dashboard links', () => {
+context("Dashboard links", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
- cy.insert_doc('DocType', child_table_doctype, true);
- cy.insert_doc('DocType', child_table_doctype_1, true);
- cy.insert_doc('DocType', doctype_with_child_table, true);
- cy.insert_doc('DocType', doctype_to_link, true);
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall("frappe.tests.ui_test_helpers.update_child_table", {
- name: child_table_doctype_name
+ cy.insert_doc("DocType", child_table_doctype, true);
+ cy.insert_doc("DocType", child_table_doctype_1, true);
+ cy.insert_doc("DocType", doctype_with_child_table, true);
+ cy.insert_doc("DocType", doctype_to_link, true);
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.update_child_table", {
+ name: child_table_doctype_name,
+ });
});
- });
});
- it('Adding a new contact, checking for the counter on the dashboard and deleting the created contact', () => {
- cy.visit('/app/contact');
+ it("Adding a new contact, checking for the counter on the dashboard and deleting the created contact", () => {
+ cy.visit("/app/contact");
cy.clear_filters();
- cy.visit('/app/user');
- cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true });
+ cy.visit("/app/user");
+ cy.get(".list-row-col > .level-item > .ellipsis").eq(0).click({ force: true });
//To check if initially the dashboard contains only the "Contact" link and there is no counter
- cy.get('[data-doctype="Contact"]').should('contain', 'Contact');
+ cy.get('[data-doctype="Contact"]').should("contain", "Contact");
//Adding a new contact
cy.get('.document-link-badge[data-doctype="Contact"]').click();
cy.wait(300);
- cy.findByRole('button', {name: 'Add Contact'}).should('be.visible');
- cy.findByRole('button', {name: 'Add Contact'}).click();
- cy.get('[data-doctype="Contact"][data-fieldname="first_name"]').type('Admin');
- cy.findByRole('button', {name: 'Save'}).click();
- cy.visit('/app/user');
- cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true });
+ cy.findByRole("button", { name: "Add Contact" }).should("be.visible");
+ cy.findByRole("button", { name: "Add Contact" }).click();
+ cy.get('[data-doctype="Contact"][data-fieldname="first_name"]').type("Admin");
+ cy.findByRole("button", { name: "Save" }).click();
+ cy.visit("/app/user");
+ cy.get(".list-row-col > .level-item > .ellipsis").eq(0).click({ force: true });
//To check if the counter for contact doc is "1" after adding the contact
- cy.get('[data-doctype="Contact"] > .count').should('contain', '1');
- cy.get('[data-doctype="Contact"]').contains('Contact').click();
+ cy.get('[data-doctype="Contact"] > .count').should("contain", "1");
+ cy.get('[data-doctype="Contact"]').contains("Contact").click();
//Deleting the newly created contact
- cy.visit('/app/contact');
- cy.get('.list-subject > .select-like > .list-row-checkbox').eq(0).click({ force: true });
- cy.findByRole('button', {name: 'Actions'}).click();
+ cy.visit("/app/contact");
+ cy.get(".list-subject > .select-like > .list-row-checkbox").eq(0).click({ force: true });
+ cy.findByRole("button", { name: "Actions" }).click();
cy.get('.actions-btn-group [data-label="Delete"]').click();
- cy.findByRole('button', {name: 'Yes'}).click({delay: 700});
-
+ cy.findByRole("button", { name: "Yes" }).click({ delay: 700 });
//To check if the counter from the "Contact" doc link is removed
cy.wait(700);
- cy.visit('/app/user');
- cy.get('.list-row-col > .level-item > .ellipsis').eq(0).click({ force: true });
- cy.get('[data-doctype="Contact"]').should('contain', 'Contact');
+ cy.visit("/app/user");
+ cy.get(".list-row-col > .level-item > .ellipsis").eq(0).click({ force: true });
+ cy.get('[data-doctype="Contact"]').should("contain", "Contact");
});
- it('Report link in dashboard', () => {
- cy.visit('/app/user');
- cy.visit('/app/user/Administrator');
- cy.get('[data-doctype="Contact"]').should('contain', 'Contact');
- cy.findByText('Connections');
+ it("Report link in dashboard", () => {
+ cy.visit("/app/user");
+ cy.visit("/app/user/Administrator");
+ cy.get('[data-doctype="Contact"]').should("contain", "Contact");
+ cy.findByText("Connections");
cy.window()
- .its('cur_frm')
- .then(cur_frm => {
+ .its("cur_frm")
+ .then((cur_frm) => {
cur_frm.dashboard.data.reports = [
{
- 'label': 'Reports',
- 'items': ['Website Analytics']
- }
+ label: "Reports",
+ items: ["Website Analytics"],
+ },
];
cur_frm.dashboard.render_report_links();
- cy.get('[data-report="Website Analytics"]').contains('Website Analytics').click();
- cy.findByText('Website Analytics');
+ cy.get('[data-report="Website Analytics"]').contains("Website Analytics").click();
+ cy.findByText("Website Analytics");
});
});
- it('check if child table is populated with linked field on creation from dashboard link', () => {
+ it("check if child table is populated with linked field on creation from dashboard link", () => {
cy.new_form(doctype_to_link_name);
cy.fill_field("title", "Test Linking");
- cy.findByRole("button", {name: "Save"}).click();
+ cy.findByRole("button", { name: "Save" }).click();
- cy.get('.document-link .btn-new').click();
- cy.get('.frappe-control[data-fieldname="child_table"] .rows .data-row .col[data-fieldname="doctype_to_link"]')
- .should('contain.text', 'Test Linking');
+ cy.get(".document-link .btn-new").click();
+ cy.get(
+ '.frappe-control[data-fieldname="child_table"] .rows .data-row .col[data-fieldname="doctype_to_link"]'
+ ).should("contain.text", "Test Linking");
});
});
diff --git a/cypress/integration/data_field_form_validation.js b/cypress/integration/data_field_form_validation.js
index c6feea5550..49513e72fb 100644
--- a/cypress/integration/data_field_form_validation.js
+++ b/cypress/integration/data_field_form_validation.js
@@ -1,43 +1,45 @@
-import data_field_validation_doctype from '../fixtures/data_field_validation_doctype';
+import data_field_validation_doctype from "../fixtures/data_field_validation_doctype";
const doctype_name = data_field_validation_doctype.name;
-
-context('Data Field Input Validation in New Form', () => {
+context("Data Field Input Validation in New Form", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.insert_doc('DocType', data_field_validation_doctype, true);
+ cy.visit("/app/website");
+ return cy.insert_doc("DocType", data_field_validation_doctype, true);
});
function validateField(fieldname, invalid_value, valid_value) {
// Invalid, should have has-error class
cy.get_field(fieldname).clear().type(invalid_value).blur();
- cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should('have.class', 'has-error');
+ cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should("have.class", "has-error");
// Valid value, should not have has-error class
cy.get_field(fieldname).clear().type(valid_value);
- cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should('not.have.class', 'has-error');
+ cy.get(`.frappe-control[data-fieldname="${fieldname}"]`).should(
+ "not.have.class",
+ "has-error"
+ );
}
- describe('Data Field Options', () => {
- it('should validate email address', () => {
+ describe("Data Field Options", () => {
+ it("should validate email address", () => {
cy.new_form(doctype_name);
- validateField('email', 'captian', 'hello@test.com');
+ validateField("email", "captian", "hello@test.com");
});
- it('should validate URL', () => {
- validateField('url', 'jkl', 'https://frappe.io');
- validateField('url', 'abcd.com', 'http://google.com/home');
- validateField('url', '&&http://google.uae', 'gopher://frappe.io');
- validateField('url', 'ftt2:://google.in?q=news', 'ftps2://frappe.io/__/#home');
- validateField('url', 'ftt2://', 'ntps://localhost'); // For intranet URLs
+ it("should validate URL", () => {
+ validateField("url", "jkl", "https://frappe.io");
+ validateField("url", "abcd.com", "http://google.com/home");
+ validateField("url", "&&http://google.uae", "gopher://frappe.io");
+ validateField("url", "ftt2:://google.in?q=news", "ftps2://frappe.io/__/#home");
+ validateField("url", "ftt2://", "ntps://localhost"); // For intranet URLs
});
- it('should validate phone number', () => {
- validateField('phone', 'america', '89787878');
+ it("should validate phone number", () => {
+ validateField("phone", "america", "89787878");
});
- it('should validate name', () => {
- validateField('person_name', ' 777Hello', 'James Bond');
+ it("should validate name", () => {
+ validateField("person_name", " 777Hello", "James Bond");
});
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/datetime.js b/cypress/integration/datetime.js
index 4a24faf40b..7a8a68c1d9 100644
--- a/cypress/integration/datetime.js
+++ b/cypress/integration/datetime.js
@@ -1,53 +1,52 @@
-import datetime_doctype from '../fixtures/datetime_doctype';
+import datetime_doctype from "../fixtures/datetime_doctype";
const doctype_name = datetime_doctype.name;
-context('Control Date, Time and DateTime', () => {
+context("Control Date, Time and DateTime", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.insert_doc('DocType', datetime_doctype, true);
+ cy.visit("/app/website");
+ return cy.insert_doc("DocType", datetime_doctype, true);
});
- describe('Date formats', () => {
+ describe("Date formats", () => {
let date_formats = [
{
- date_format: 'dd-mm-yyyy',
+ date_format: "dd-mm-yyyy",
part: 2,
length: 4,
- separator: '-'
+ separator: "-",
},
{
- date_format: 'mm/dd/yyyy',
+ date_format: "mm/dd/yyyy",
part: 0,
length: 2,
- separator: '/'
- }
+ separator: "/",
+ },
];
- date_formats.forEach(d => {
- it('test date format ' + d.date_format, () => {
- cy.set_value('System Settings', 'System Settings', {
- date_format: d.date_format
+ date_formats.forEach((d) => {
+ it("test date format " + d.date_format, () => {
+ cy.set_value("System Settings", "System Settings", {
+ date_format: d.date_format,
});
cy.window()
- .its('frappe')
- .then(frappe => {
+ .its("frappe")
+ .then((frappe) => {
// update sys_defaults value to avoid a reload
frappe.sys_defaults.date_format = d.date_format;
});
cy.new_form(doctype_name);
- cy.get('.form-control[data-fieldname=date]').focus();
- cy.get('.datepickers-container .datepicker.active')
- .should('be.visible');
+ cy.get(".form-control[data-fieldname=date]").focus();
+ cy.get(".datepickers-container .datepicker.active").should("be.visible");
cy.get(
- '.datepickers-container .datepicker.active .datepicker--cell-day.-current-'
+ ".datepickers-container .datepicker.active .datepicker--cell-day.-current-"
).click({ force: true });
cy.window()
- .its('cur_frm')
- .then(cur_frm => {
- let formatted_value = cur_frm.get_field('date').input.value;
+ .its("cur_frm")
+ .then((cur_frm) => {
+ let formatted_value = cur_frm.get_field("date").input.value;
let parts = formatted_value.split(d.separator);
expect(parts[d.part].length).to.equal(d.length);
});
@@ -55,74 +54,72 @@ context('Control Date, Time and DateTime', () => {
});
});
- describe('Time formats', () => {
+ describe("Time formats", () => {
let time_formats = [
{
- time_format: 'HH:mm:ss',
- value: ' 11:00:12',
- match_value: '11:00:12'
+ time_format: "HH:mm:ss",
+ value: " 11:00:12",
+ match_value: "11:00:12",
},
{
- time_format: 'HH:mm',
- value: ' 11:00:12',
- match_value: '11:00'
- }
+ time_format: "HH:mm",
+ value: " 11:00:12",
+ match_value: "11:00",
+ },
];
- time_formats.forEach(d => {
- it('test time format ' + d.time_format, () => {
- cy.set_value('System Settings', 'System Settings', {
- time_format: d.time_format
+ time_formats.forEach((d) => {
+ it("test time format " + d.time_format, () => {
+ cy.set_value("System Settings", "System Settings", {
+ time_format: d.time_format,
});
cy.window()
- .its('frappe')
- .then(frappe => {
+ .its("frappe")
+ .then((frappe) => {
frappe.sys_defaults.time_format = d.time_format;
});
cy.new_form(doctype_name);
- cy.fill_field('time', d.value, 'Time').blur();
- cy.get_field('time').should('have.value', d.match_value);
+ cy.fill_field("time", d.value, "Time").blur();
+ cy.get_field("time").should("have.value", d.match_value);
});
});
});
- describe('DateTime formats', () => {
+ describe("DateTime formats", () => {
let datetime_formats = [
{
- date_format: 'dd.mm.yyyy',
- time_format: 'HH:mm:ss',
- value: ' 02.12.2019 11:00:12',
- doc_value: '2019-12-02 00:30:12', // system timezone (America/New_York)
- input_value: '02.12.2019 11:00:12' // admin timezone (Asia/Kolkata)
+ date_format: "dd.mm.yyyy",
+ time_format: "HH:mm:ss",
+ value: " 02.12.2019 11:00:12",
+ doc_value: "2019-12-02 00:30:12", // system timezone (America/New_York)
+ input_value: "02.12.2019 11:00:12", // admin timezone (Asia/Kolkata)
},
{
- date_format: 'mm-dd-yyyy',
- time_format: 'HH:mm',
- value: ' 12-02-2019 11:00:00',
- doc_value: '2019-12-02 00:30:00', // system timezone (America/New_York)
- input_value: '12-02-2019 11:00' // admin timezone (Asia/Kolkata)
- }
+ date_format: "mm-dd-yyyy",
+ time_format: "HH:mm",
+ value: " 12-02-2019 11:00:00",
+ doc_value: "2019-12-02 00:30:00", // system timezone (America/New_York)
+ input_value: "12-02-2019 11:00", // admin timezone (Asia/Kolkata)
+ },
];
- datetime_formats.forEach(d => {
+ datetime_formats.forEach((d) => {
it(`test datetime format ${d.date_format} ${d.time_format}`, () => {
- cy.set_value('System Settings', 'System Settings', {
+ cy.set_value("System Settings", "System Settings", {
date_format: d.date_format,
- time_format: d.time_format
+ time_format: d.time_format,
});
cy.window()
- .its('frappe')
- .then(frappe => {
+ .its("frappe")
+ .then((frappe) => {
frappe.sys_defaults.date_format = d.date_format;
frappe.sys_defaults.time_format = d.time_format;
});
cy.new_form(doctype_name);
- cy.fill_field('datetime', d.value, 'Datetime').blur();
- cy.get_field('datetime').should('have.value', d.input_value);
+ cy.fill_field("datetime", d.value, "Datetime").blur();
+ cy.get_field("datetime").should("have.value", d.input_value);
- cy.window()
- .its('cur_frm.doc.datetime')
- .should('eq', d.doc_value);
+ cy.window().its("cur_frm.doc.datetime").should("eq", d.doc_value);
});
});
});
diff --git a/cypress/integration/datetime_field_form_validation.js b/cypress/integration/datetime_field_form_validation.js
index ef47a0fbf7..1a549d8a1d 100644
--- a/cypress/integration/datetime_field_form_validation.js
+++ b/cypress/integration/datetime_field_form_validation.js
@@ -16,4 +16,4 @@
// cy.get('.indicator-pill').should('contain', 'Open').should('have.class', 'red');
// });
// });
-// });
\ No newline at end of file
+// });
diff --git a/cypress/integration/depends_on.js b/cypress/integration/depends_on.js
index 12f54f2b6e..6419809466 100644
--- a/cypress/integration/depends_on.js
+++ b/cypress/integration/depends_on.js
@@ -1,135 +1,152 @@
-context('Depends On', () => {
+context("Depends On", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall('frappe.tests.ui_test_helpers.create_child_doctype', {
- name: 'Child Test Depends On',
- fields: [
- {
- "label": "Child Test Field",
- "fieldname": "child_test_field",
- "fieldtype": "Data",
- "in_list_view": 1,
- },
- {
- "label": "Child Dependant Field",
- "fieldname": "child_dependant_field",
- "fieldtype": "Data",
- "in_list_view": 1,
- },
- {
- "label": "Child Display Dependant Field",
- "fieldname": "child_display_dependant_field",
- "fieldtype": "Data",
- "in_list_view": 1,
- },
- ]
+ cy.visit("/app/website");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.create_child_doctype", {
+ name: "Child Test Depends On",
+ fields: [
+ {
+ label: "Child Test Field",
+ fieldname: "child_test_field",
+ fieldtype: "Data",
+ in_list_view: 1,
+ },
+ {
+ label: "Child Dependant Field",
+ fieldname: "child_dependant_field",
+ fieldtype: "Data",
+ in_list_view: 1,
+ },
+ {
+ label: "Child Display Dependant Field",
+ fieldname: "child_display_dependant_field",
+ fieldtype: "Data",
+ in_list_view: 1,
+ },
+ ],
+ });
+ })
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.create_doctype", {
+ name: "Test Depends On",
+ fields: [
+ {
+ label: "Test Field",
+ fieldname: "test_field",
+ fieldtype: "Data",
+ },
+ {
+ label: "Dependant Field",
+ fieldname: "dependant_field",
+ fieldtype: "Data",
+ mandatory_depends_on: "eval:doc.test_field=='Some Value'",
+ read_only_depends_on: "eval:doc.test_field=='Some Other Value'",
+ },
+ {
+ label: "Display Dependant Field",
+ fieldname: "display_dependant_field",
+ fieldtype: "Data",
+ depends_on: "eval:doc.test_field=='Value'",
+ },
+ {
+ label: "Child Test Depends On Field",
+ fieldname: "child_test_depends_on_field",
+ fieldtype: "Table",
+ read_only_depends_on: "eval:doc.test_field=='Some Other Value'",
+ options: "Child Test Depends On",
+ },
+ {
+ label: "Dependent Tab",
+ fieldname: "dependent_tab",
+ fieldtype: "Tab Break",
+ depends_on: "eval:doc.test_field=='Show Tab'",
+ },
+ {
+ fieldname: "tab_section",
+ fieldtype: "Section Break",
+ },
+ {
+ label: "Field in Tab",
+ fieldname: "field_in_tab",
+ fieldtype: "Data",
+ },
+ ],
+ });
});
- }).then(frappe => {
- return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', {
- name: 'Test Depends On',
- fields: [
- {
- "label": "Test Field",
- "fieldname": "test_field",
- "fieldtype": "Data",
- },
- {
- "label": "Dependant Field",
- "fieldname": "dependant_field",
- "fieldtype": "Data",
- "mandatory_depends_on": "eval:doc.test_field=='Some Value'",
- "read_only_depends_on": "eval:doc.test_field=='Some Other Value'",
- },
- {
- "label": "Display Dependant Field",
- "fieldname": "display_dependant_field",
- "fieldtype": "Data",
- 'depends_on': "eval:doc.test_field=='Value'"
- },
- {
- "label": "Child Test Depends On Field",
- "fieldname": "child_test_depends_on_field",
- "fieldtype": "Table",
- 'read_only_depends_on': "eval:doc.test_field=='Some Other Value'",
- 'options': "Child Test Depends On"
- },
- {
- "label": "Dependent Tab",
- "fieldname": "dependent_tab",
- "fieldtype": "Tab Break",
- "depends_on": "eval:doc.test_field=='Show Tab'"
- },
- {
- "fieldname": "tab_section",
- "fieldtype": "Section Break",
- },
- {
- "label": "Field in Tab",
- "fieldname": "field_in_tab",
- "fieldtype": "Data",
- }
- ]
- });
- });
});
- it('should show the tab on other setting field value', () => {
- cy.new_form('Test Depends On');
- cy.fill_field('test_field', 'Show Tab');
- cy.get('body').click();
- cy.findByRole("tab", {name: "Dependent Tab"}).should('be.visible');
+ it("should show the tab on other setting field value", () => {
+ cy.new_form("Test Depends On");
+ cy.fill_field("test_field", "Show Tab");
+ cy.get("body").click();
+ cy.findByRole("tab", { name: "Dependent Tab" }).should("be.visible");
});
- it('should set the field as mandatory depending on other fields value', () => {
- cy.new_form('Test Depends On');
- cy.fill_field('test_field', 'Some Value');
- cy.findByRole('button', {name: 'Save'}).click();
- cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible');
+ it("should set the field as mandatory depending on other fields value", () => {
+ cy.new_form("Test Depends On");
+ cy.fill_field("test_field", "Some Value");
+ cy.findByRole("button", { name: "Save" }).click();
+ cy.get(".msgprint-dialog .modal-title").contains("Missing Fields").should("be.visible");
cy.hide_dialog();
- cy.fill_field('test_field', 'Random value');
- cy.findByRole('button', {name: 'Save'}).click();
- cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible');
+ cy.fill_field("test_field", "Random value");
+ cy.findByRole("button", { name: "Save" }).click();
+ cy.get(".msgprint-dialog .modal-title")
+ .contains("Missing Fields")
+ .should("not.be.visible");
});
- it('should set the field as read only depending on other fields value', () => {
- cy.new_form('Test Depends On');
- cy.fill_field('dependant_field', 'Some Value');
- cy.fill_field('test_field', 'Some Other Value');
- cy.get('body').click();
- cy.get('.control-input [data-fieldname="dependant_field"]').should('be.disabled');
- cy.fill_field('test_field', 'Random Value');
- cy.get('body').click();
- cy.get('.control-input [data-fieldname="dependant_field"]').should('not.be.disabled');
+ it("should set the field as read only depending on other fields value", () => {
+ cy.new_form("Test Depends On");
+ cy.fill_field("dependant_field", "Some Value");
+ cy.fill_field("test_field", "Some Other Value");
+ cy.get("body").click();
+ cy.get('.control-input [data-fieldname="dependant_field"]').should("be.disabled");
+ cy.fill_field("test_field", "Random Value");
+ cy.get("body").click();
+ cy.get('.control-input [data-fieldname="dependant_field"]').should("not.be.disabled");
});
- it('should set the table and its fields as read only depending on other fields value', () => {
- cy.new_form('Test Depends On');
- cy.fill_field('dependant_field', 'Some Value');
+ it("should set the table and its fields as read only depending on other fields value", () => {
+ cy.new_form("Test Depends On");
+ cy.fill_field("dependant_field", "Some Value");
//cy.fill_field('test_field', 'Some Other Value');
- cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as('table');
- cy.get('@table').findByRole('button', {name: 'Add Row'}).click();
- cy.get('@table').find('[data-idx="1"]').as('row1');
- cy.get('@row1').find('.btn-open-row').click();
- cy.get('@row1').find('.form-in-grid').as('row1-form_in_grid');
+ cy.get('.frappe-control[data-fieldname="child_test_depends_on_field"]').as("table");
+ cy.get("@table").findByRole("button", { name: "Add Row" }).click();
+ cy.get("@table").find('[data-idx="1"]').as("row1");
+ cy.get("@row1").find(".btn-open-row").click();
+ cy.get("@row1").find(".form-in-grid").as("row1-form_in_grid");
//cy.get('@row1-form_in_grid').find('')
- cy.fill_table_field('child_test_depends_on_field', '1', 'child_test_field', 'Some Value');
- cy.fill_table_field('child_test_depends_on_field', '1', 'child_dependant_field', 'Some Other Value');
+ cy.fill_table_field("child_test_depends_on_field", "1", "child_test_field", "Some Value");
+ cy.fill_table_field(
+ "child_test_depends_on_field",
+ "1",
+ "child_dependant_field",
+ "Some Other Value"
+ );
- cy.get('@row1-form_in_grid').find('.grid-collapse-row').click();
+ cy.get("@row1-form_in_grid").find(".grid-collapse-row").click();
// set the table to read-only
- cy.fill_field('test_field', 'Some Other Value');
+ cy.fill_field("test_field", "Some Other Value");
// grid row form fields should be read-only
- cy.get('@row1').find('.btn-open-row').click();
+ cy.get("@row1").find(".btn-open-row").click();
- cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_test_field"]').should('be.disabled');
- cy.get('@row1-form_in_grid').find('.control-input [data-fieldname="child_dependant_field"]').should('be.disabled');
+ cy.get("@row1-form_in_grid")
+ .find('.control-input [data-fieldname="child_test_field"]')
+ .should("be.disabled");
+ cy.get("@row1-form_in_grid")
+ .find('.control-input [data-fieldname="child_dependant_field"]')
+ .should("be.disabled");
});
- it('should display the field depending on other fields value', () => {
- cy.new_form('Test Depends On');
- cy.get('.control-input [data-fieldname="display_dependant_field"]').should('not.be.visible');
+ it("should display the field depending on other fields value", () => {
+ cy.new_form("Test Depends On");
+ cy.get('.control-input [data-fieldname="display_dependant_field"]').should(
+ "not.be.visible"
+ );
cy.get('.control-input [data-fieldname="test_field"]').clear();
- cy.fill_field('test_field', 'Value');
- cy.get('body').click();
- cy.get('.control-input [data-fieldname="display_dependant_field"]').should('be.visible');
+ cy.fill_field("test_field", "Value");
+ cy.get("body").click();
+ cy.get('.control-input [data-fieldname="display_dependant_field"]').should("be.visible");
});
});
diff --git a/cypress/integration/discussions.js b/cypress/integration/discussions.js
index caf7d6c3f9..55bcabce19 100644
--- a/cypress/integration/discussions.js
+++ b/cypress/integration/discussions.js
@@ -1,79 +1,101 @@
-context('Discussions', () => {
+context("Discussions", () => {
before(() => {
cy.login();
- cy.visit('/app');
- return cy.window().its('frappe').then(frappe => {
- return frappe.call('frappe.tests.ui_test_helpers.create_data_for_discussions');
- });
+ cy.visit("/app");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.call("frappe.tests.ui_test_helpers.create_data_for_discussions");
+ });
});
const reply_through_modal = () => {
- cy.visit('/test-page-discussions');
+ cy.visit("/test-page-discussions");
// Open the modal
- cy.get('.reply').click();
+ cy.get(".reply").click();
cy.wait(500);
- cy.get('.discussion-modal').should('be.visible');
+ cy.get(".discussion-modal").should("be.visible");
// Enter title
- cy.get('.modal .topic-title').type('Discussion from tests')
- .should('have.value', 'Discussion from tests');
+ cy.get(".modal .topic-title")
+ .type("Discussion from tests")
+ .should("have.value", "Discussion from tests");
// Enter comment
- cy.get('.modal .comment-field')
- .type('This is a discussion from the cypress ui tests.')
- .should('have.value', 'This is a discussion from the cypress ui tests.');
+ cy.get(".modal .comment-field")
+ .type("This is a discussion from the cypress ui tests.")
+ .should("have.value", "This is a discussion from the cypress ui tests.");
// Submit
- cy.get('.modal .submit-discussion').click();
+ cy.get(".modal .submit-discussion").click();
cy.wait(2000);
// Check if discussion is added to page and content is visible
- cy.get('.sidebar-parent:first .discussion-topic-title').should('have.text', 'Discussion from tests');
- cy.get('.discussion-on-page:visible').should('have.class', 'show');
- cy.get('.discussion-on-page:visible .reply-card .reply-text')
- .should('have.text', 'This is a discussion from the cypress ui tests.\n');
-
+ cy.get(".sidebar-parent:first .discussion-topic-title").should(
+ "have.text",
+ "Discussion from tests"
+ );
+ cy.get(".discussion-on-page:visible").should("have.class", "show");
+ cy.get(".discussion-on-page:visible .reply-card .reply-text").should(
+ "have.text",
+ "This is a discussion from the cypress ui tests.\n"
+ );
};
const reply_through_comment_box = () => {
- cy.get('.discussion-form:visible .comment-field')
- .type('This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.')
- .should('have.value', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.');
+ cy.get(".discussion-form:visible .comment-field")
+ .type(
+ "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page."
+ )
+ .should(
+ "have.value",
+ "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page."
+ );
- cy.get('.discussion-form:visible .submit-discussion').click();
+ cy.get(".discussion-form:visible .submit-discussion").click();
cy.wait(3000);
- cy.get('.discussion-on-page:visible').should('have.class', 'show');
- cy.get('.discussion-on-page:visible').children(".reply-card").eq(1).find(".reply-text")
- .should('have.text', 'This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.\n');
+ cy.get(".discussion-on-page:visible").should("have.class", "show");
+ cy.get(".discussion-on-page:visible")
+ .children(".reply-card")
+ .eq(1)
+ .find(".reply-text")
+ .should(
+ "have.text",
+ "This is a discussion from the cypress ui tests. \n\nThis comment was entered through the commentbox on the page.\n"
+ );
};
const cancel_and_clear_comment_box = () => {
- cy.get('.discussion-form:visible .comment-field')
- .type('This is a discussion from the cypress ui tests.')
- .should('have.value', 'This is a discussion from the cypress ui tests.');
+ cy.get(".discussion-form:visible .comment-field")
+ .type("This is a discussion from the cypress ui tests.")
+ .should("have.value", "This is a discussion from the cypress ui tests.");
- cy.get('.discussion-form:visible .cancel-comment').click();
- cy.get('.discussion-form:visible .comment-field').should('have.value', '');
+ cy.get(".discussion-form:visible .cancel-comment").click();
+ cy.get(".discussion-form:visible .comment-field").should("have.value", "");
};
const single_thread_discussion = () => {
- cy.visit('/test-single-thread');
- cy.get('.discussions-sidebar').should('have.length', 0);
- cy.get('.reply').should('have.length', 0);
+ cy.visit("/test-single-thread");
+ cy.get(".discussions-sidebar").should("have.length", 0);
+ cy.get(".reply").should("have.length", 0);
- cy.get('.discussion-form:visible .comment-field')
- .type('This comment is being made on a single thread discussion.')
- .should('have.value', 'This comment is being made on a single thread discussion.');
+ cy.get(".discussion-form:visible .comment-field")
+ .type("This comment is being made on a single thread discussion.")
+ .should("have.value", "This comment is being made on a single thread discussion.");
- cy.get('.discussion-form:visible .submit-discussion').click();
+ cy.get(".discussion-form:visible .submit-discussion").click();
cy.wait(3000);
- cy.get('.discussion-on-page').children(".reply-card").eq(-1).find(".reply-text")
- .should('have.text', 'This comment is being made on a single thread discussion.\n');
+ cy.get(".discussion-on-page")
+ .children(".reply-card")
+ .eq(-1)
+ .find(".reply-text")
+ .should("have.text", "This comment is being made on a single thread discussion.\n");
};
- it('reply through modal', reply_through_modal);
- it('reply through comment box', reply_through_comment_box);
- it('cancel and clear comment box', cancel_and_clear_comment_box);
- it('single thread discussion', single_thread_discussion);
+ it("reply through modal", reply_through_modal);
+ it("reply through comment box", reply_through_comment_box);
+ it("cancel and clear comment box", cancel_and_clear_comment_box);
+ it("single thread discussion", single_thread_discussion);
});
diff --git a/cypress/integration/file_uploader.js b/cypress/integration/file_uploader.js
index 3d4f92df3c..669f9ba385 100644
--- a/cypress/integration/file_uploader.js
+++ b/cypress/integration/file_uploader.js
@@ -1,78 +1,82 @@
-context('FileUploader', () => {
+context("FileUploader", () => {
before(() => {
cy.login();
- cy.visit('/app');
+ cy.visit("/app");
});
function open_upload_dialog() {
- cy.window().its('frappe').then(frappe => {
- new frappe.ui.FileUploader();
- });
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ new frappe.ui.FileUploader();
+ });
}
- it('upload dialog api works', () => {
+ it("upload dialog api works", () => {
open_upload_dialog();
- cy.get_open_dialog().should('contain', 'Drag and drop files');
+ cy.get_open_dialog().should("contain", "Drag and drop files");
cy.hide_dialog();
});
- it('should accept dropped files', () => {
+ it("should accept dropped files", () => {
open_upload_dialog();
- cy.get_open_dialog().find('.file-upload-area').attachFile('example.json', {
- subjectType: 'drag-n-drop',
+ cy.get_open_dialog().find(".file-upload-area").attachFile("example.json", {
+ subjectType: "drag-n-drop",
});
- cy.get_open_dialog().find('.file-name').should('contain', 'example.json');
- cy.intercept('POST', '/api/method/upload_file').as('upload_file');
- cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
- cy.wait('@upload_file').its('response.statusCode').should('eq', 200);
- cy.get('.modal:visible').should('not.exist');
+ cy.get_open_dialog().find(".file-name").should("contain", "example.json");
+ cy.intercept("POST", "/api/method/upload_file").as("upload_file");
+ cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
+ cy.wait("@upload_file").its("response.statusCode").should("eq", 200);
+ cy.get(".modal:visible").should("not.exist");
});
- it('should accept uploaded files', () => {
+ it("should accept uploaded files", () => {
open_upload_dialog();
- cy.get_open_dialog().findByRole('button', {name: 'Library'}).click();
- cy.findByPlaceholderText('Search by filename or extension').type('example.json');
- cy.get_open_dialog().findAllByText('example.json').first().click();
- cy.intercept('POST', '/api/method/upload_file').as('upload_file');
- cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
- cy.wait('@upload_file').its('response.body.message')
- .should('have.property', 'file_name', 'example.json');
- cy.get('.modal:visible').should('not.exist');
+ cy.get_open_dialog().findByRole("button", { name: "Library" }).click();
+ cy.findByPlaceholderText("Search by filename or extension").type("example.json");
+ cy.get_open_dialog().findAllByText("example.json").first().click();
+ cy.intercept("POST", "/api/method/upload_file").as("upload_file");
+ cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
+ cy.wait("@upload_file")
+ .its("response.body.message")
+ .should("have.property", "file_name", "example.json");
+ cy.get(".modal:visible").should("not.exist");
});
- it('should accept web links', () => {
+ it("should accept web links", () => {
open_upload_dialog();
- cy.get_open_dialog().findByRole('button', {name: 'Link'}).click();
+ cy.get_open_dialog().findByRole("button", { name: "Link" }).click();
cy.get_open_dialog()
- .findByPlaceholderText('Attach a web link')
- .type('https://github.com', { delay: 100, force: true });
- cy.intercept('POST', '/api/method/upload_file').as('upload_file');
- cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
- cy.wait('@upload_file').its('response.body.message')
- .should('have.property', 'file_url', 'https://github.com');
- cy.get('.modal:visible').should('not.exist');
+ .findByPlaceholderText("Attach a web link")
+ .type("https://github.com", { delay: 100, force: true });
+ cy.intercept("POST", "/api/method/upload_file").as("upload_file");
+ cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
+ cy.wait("@upload_file")
+ .its("response.body.message")
+ .should("have.property", "file_url", "https://github.com");
+ cy.get(".modal:visible").should("not.exist");
});
- it('should allow cropping and optimization for valid images', () => {
+ it("should allow cropping and optimization for valid images", () => {
open_upload_dialog();
- cy.get_open_dialog().find('.file-upload-area').attachFile('sample_image.jpg', {
- subjectType: 'drag-n-drop',
+ cy.get_open_dialog().find(".file-upload-area").attachFile("sample_image.jpg", {
+ subjectType: "drag-n-drop",
});
- cy.get_open_dialog().findAllByText('sample_image.jpg').should('exist');
- cy.get_open_dialog().find('.btn-crop').first().click();
- cy.get_open_dialog().findByRole('button', {name: 'Crop'}).click();
- cy.get_open_dialog().findAllByRole('checkbox', {name: 'Optimize'}).should('exist');
- cy.get_open_dialog().findAllByLabelText('Optimize').first().click();
+ cy.get_open_dialog().findAllByText("sample_image.jpg").should("exist");
+ cy.get_open_dialog().find(".btn-crop").first().click();
+ cy.get_open_dialog().findByRole("button", { name: "Crop" }).click();
+ cy.get_open_dialog().findAllByRole("checkbox", { name: "Optimize" }).should("exist");
+ cy.get_open_dialog().findAllByLabelText("Optimize").first().click();
- cy.intercept('POST', '/api/method/upload_file').as('upload_file');
- cy.get_open_dialog().findByRole('button', {name: 'Upload'}).click();
- cy.wait('@upload_file').its('response.statusCode').should('eq', 200);
- cy.get('.modal:visible').should('not.exist');
+ cy.intercept("POST", "/api/method/upload_file").as("upload_file");
+ cy.get_open_dialog().findByRole("button", { name: "Upload" }).click();
+ cy.wait("@upload_file").its("response.statusCode").should("eq", 200);
+ cy.get(".modal:visible").should("not.exist");
});
});
diff --git a/cypress/integration/first_day_of_the_week.js b/cypress/integration/first_day_of_the_week.js
index 1e65b78990..784c068f01 100644
--- a/cypress/integration/first_day_of_the_week.js
+++ b/cypress/integration/first_day_of_the_week.js
@@ -4,42 +4,48 @@ context("First Day of the Week", () => {
});
beforeEach(() => {
- cy.visit('/app/system-settings');
- cy.findByText('Date and Number Format').click();
+ cy.visit("/app/system-settings");
+ cy.findByText("Date and Number Format").click();
});
it("Date control starts with same day as selected in System Settings", () => {
- cy.intercept('POST', '/api/method/frappe.core.doctype.system_settings.system_settings.load').as("load_settings");
- cy.fill_field('first_day_of_the_week', 'Tuesday', 'Select');
- cy.findByRole('button', {name: 'Save'}).click();
+ cy.intercept(
+ "POST",
+ "/api/method/frappe.core.doctype.system_settings.system_settings.load"
+ ).as("load_settings");
+ cy.fill_field("first_day_of_the_week", "Tuesday", "Select");
+ cy.findByRole("button", { name: "Save" }).click();
cy.wait("@load_settings");
cy.dialog({
- title: 'Date',
+ title: "Date",
fields: [
{
- label: 'Date',
- fieldname: 'date',
- fieldtype: 'Date'
- }
- ]
+ label: "Date",
+ fieldname: "date",
+ fieldtype: "Date",
+ },
+ ],
});
- cy.get_field('date').click();
- cy.get('.datepicker--day-name').eq(0).should('have.text', 'Tu');
+ cy.get_field("date").click();
+ cy.get(".datepicker--day-name").eq(0).should("have.text", "Tu");
});
it("Calendar view starts with same day as selected in System Settings", () => {
- cy.intercept('POST', '/api/method/frappe.core.doctype.system_settings.system_settings.load').as("load_settings");
- cy.fill_field('first_day_of_the_week', 'Monday', 'Select');
- cy.findByRole('button', {name: 'Save'}).click();
+ cy.intercept(
+ "POST",
+ "/api/method/frappe.core.doctype.system_settings.system_settings.load"
+ ).as("load_settings");
+ cy.fill_field("first_day_of_the_week", "Monday", "Select");
+ cy.findByRole("button", { name: "Save" }).click();
cy.wait("@load_settings");
cy.visit("app/todo/view/calendar/default");
- cy.get('.fc-day-header > span').eq(0).should('have.text', 'Mon');
+ cy.get(".fc-day-header > span").eq(0).should("have.text", "Mon");
});
after(() => {
- cy.visit('/app/system-settings');
- cy.findByText('Date and Number Format').click();
- cy.fill_field('first_day_of_the_week', 'Sunday', 'Select');
- cy.findByRole('button', {name: 'Save'}).click();
+ cy.visit("/app/system-settings");
+ cy.findByText("Date and Number Format").click();
+ cy.fill_field("first_day_of_the_week", "Sunday", "Select");
+ cy.findByRole("button", { name: "Save" }).click();
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js
index 484419b4aa..e15a354de0 100644
--- a/cypress/integration/folder_navigation.js
+++ b/cypress/integration/folder_navigation.js
@@ -1,79 +1,85 @@
-context('Folder Navigation', () => {
+context("Folder Navigation", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
- cy.visit('/app/file');
+ cy.visit("/app/file");
});
- it('Adding Folders', () => {
+ it("Adding Folders", () => {
//Adding filter to go into the home folder
- cy.get('.filter-selector > .btn').findByText('1 filter').click();
- cy.findByRole('button', {name: 'Clear Filters'}).click();
- cy.get('.filter-action-buttons > .text-muted').findByText('+ Add a Filter').click();
- cy.get('.fieldname-select-area > .awesomplete > .form-control').type('Fol{enter}');
- cy.get('.filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback').type('Home{enter}');
- cy.get('.filter-action-buttons > div > .btn-primary').findByText('Apply Filters').click();
+ cy.get(".filter-selector > .btn").findByText("1 filter").click();
+ cy.findByRole("button", { name: "Clear Filters" }).click();
+ cy.get(".filter-action-buttons > .text-muted").findByText("+ Add a Filter").click();
+ cy.get(".fieldname-select-area > .awesomplete > .form-control").type("Fol{enter}");
+ cy.get(
+ ".filter-field > .form-group > .link-field > .awesomplete > .input-with-feedback"
+ ).type("Home{enter}");
+ cy.get(".filter-action-buttons > div > .btn-primary").findByText("Apply Filters").click();
//Adding folder (Test Folder)
cy.click_menu_button("New Folder");
- cy.fill_field('value', 'Test Folder');
- cy.click_modal_primary_button('Create');
+ cy.fill_field("value", "Test Folder");
+ cy.click_modal_primary_button("Create");
});
- it('Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct', () => {
+ it("Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct", () => {
//Navigating inside the Attachments folder
cy.get('[title="Attachments"] > span').click();
//To check if the URL formed after visiting the attachments folder is correct
- cy.location('pathname').should('eq', '/app/file/view/home/Attachments');
- cy.visit('/app/file/view/home/Attachments');
+ cy.location("pathname").should("eq", "/app/file/view/home/Attachments");
+ cy.visit("/app/file/view/home/Attachments");
//Adding folder inside the attachments folder
cy.click_menu_button("New Folder");
- cy.fill_field('value', 'Test Folder');
- cy.click_modal_primary_button('Create');
+ cy.fill_field("value", "Test Folder");
+ cy.click_modal_primary_button("Create");
//Navigating inside the added folder in the Attachments folder
cy.get('[title="Test Folder"] > span').click();
//To check if the URL is correct after visiting the Test Folder
- cy.location('pathname').should('eq', '/app/file/view/home/Attachments/Test%20Folder');
- cy.visit('/app/file/view/home/Attachments/Test%20Folder');
+ cy.location("pathname").should("eq", "/app/file/view/home/Attachments/Test%20Folder");
+ cy.visit("/app/file/view/home/Attachments/Test%20Folder");
//Adding a file inside the Test Folder
- cy.findByRole('button', {name: 'Add File'}).eq(0).click({force: true});
- cy.get('.file-uploader').findByText('Link').click();
- cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
- cy.click_modal_primary_button('Upload');
+ cy.findByRole("button", { name: "Add File" }).eq(0).click({ force: true });
+ cy.get(".file-uploader").findByText("Link").click();
+ cy.get(".input-group > .form-control").type(
+ "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg"
+ );
+ cy.click_modal_primary_button("Upload");
//To check if the added file is present in the Test Folder
- cy.get('span.level-item > span').should('contain', 'Test Folder');
- cy.get('.list-row-container').eq(0).should('contain.text', '72402.jpg');
- cy.get('.list-row-checkbox').eq(0).click();
+ cy.get("span.level-item > span").should("contain", "Test Folder");
+ cy.get(".list-row-container").eq(0).should("contain.text", "72402.jpg");
+ cy.get(".list-row-checkbox").eq(0).click();
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.reportview.delete_items'
- }).as('file_deleted');
+ method: "POST",
+ url: "api/method/frappe.desk.reportview.delete_items",
+ }).as("file_deleted");
//Deleting the added file from the Test folder
cy.click_action_button("Delete");
- cy.click_modal_primary_button('Yes');
- cy.wait('@file_deleted');
+ cy.click_modal_primary_button("Yes");
+ cy.wait("@file_deleted");
//Deleting the Test Folder
- cy.visit('/app/file/view/home/Attachments');
- cy.get('.list-row-checkbox').eq(0).click();
+ cy.visit("/app/file/view/home/Attachments");
+ cy.get(".list-row-checkbox").eq(0).click();
cy.click_action_button("Delete");
- cy.click_modal_primary_button('Yes');
- cy.wait('@file_deleted');
+ cy.click_modal_primary_button("Yes");
+ cy.wait("@file_deleted");
});
- it('Deleting Test Folder from the home', () => {
- //Deleting the Test Folder added in the home directory
- cy.visit('/app/file/view/home');
- cy.get('.level-left > .list-subject > .file-select >.list-row-checkbox').eq(0).click({force: true, delay: 500});
+ it("Deleting Test Folder from the home", () => {
+ //Deleting the Test Folder added in the home directory
+ cy.visit("/app/file/view/home");
+ cy.get(".level-left > .list-subject > .file-select >.list-row-checkbox")
+ .eq(0)
+ .click({ force: true, delay: 500 });
cy.click_action_button("Delete");
- cy.click_modal_primary_button('Yes');
+ cy.click_modal_primary_button("Yes");
});
});
diff --git a/cypress/integration/form.js b/cypress/integration/form.js
index 53b87994d7..43ab5350b7 100644
--- a/cypress/integration/form.js
+++ b/cypress/integration/form.js
@@ -1,107 +1,114 @@
-context('Form', () => {
+context("Form", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.window().its('frappe').then(frappe => {
- return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
- });
+ cy.visit("/app/website");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
+ });
});
- it('create a new form', () => {
- cy.visit('/app/todo/new');
- cy.get_field('description', 'Text Editor').type('this is a test todo', {force: true}).wait(200);
- cy.get('.page-title').should('contain', 'Not Saved');
+ it("create a new form", () => {
+ cy.visit("/app/todo/new");
+ cy.get_field("description", "Text Editor")
+ .type("this is a test todo", { force: true })
+ .wait(200);
+ cy.get(".page-title").should("contain", "Not Saved");
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.form.save.savedocs'
- }).as('form_save');
- cy.get('.primary-action').click();
- cy.wait('@form_save').its('response.statusCode').should('eq', 200);
+ method: "POST",
+ url: "api/method/frappe.desk.form.save.savedocs",
+ }).as("form_save");
+ cy.get(".primary-action").click();
+ cy.wait("@form_save").its("response.statusCode").should("eq", 200);
- cy.go_to_list('ToDo');
- cy.clear_filters()
- cy.get('.page-head').findByTitle('To Do').should('exist');
- cy.get('.list-row').should('contain', 'this is a test todo');
+ cy.go_to_list("ToDo");
+ cy.clear_filters();
+ cy.get(".page-head").findByTitle("To Do").should("exist");
+ cy.get(".list-row").should("contain", "this is a test todo");
});
- it('navigates between documents with child table list filters applied', () => {
- cy.visit('/app/contact');
+ it("navigates between documents with child table list filters applied", () => {
+ cy.visit("/app/contact");
cy.clear_filters();
- cy.get('.standard-filter-section [data-fieldname="name"] input').type('Test Form Contact 3').blur();
- cy.click_listview_row_item_with_text('Test Form Contact 3');
+ cy.get('.standard-filter-section [data-fieldname="name"] input')
+ .type("Test Form Contact 3")
+ .blur();
+ cy.click_listview_row_item_with_text("Test Form Contact 3");
- cy.get('#page-Contact .page-head').findByTitle('Test Form Contact 3').should('exist');
- cy.get('.prev-doc').should('be.visible').click();
- cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
+ cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
+ cy.get(".prev-doc").should("be.visible").click();
+ cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible");
cy.hide_dialog();
- cy.get('#page-Contact .page-head').findByTitle('Test Form Contact 3').should('exist');
- cy.get('.next-doc').should('be.visible').click();
- cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
+ cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
+ cy.get(".next-doc").should("be.visible").click();
+ cy.get(".msgprint-dialog .modal-body").contains("No further records").should("be.visible");
cy.hide_dialog();
- cy.get('#page-Contact .page-head').findByTitle('Test Form Contact 3').should('exist');
+ cy.get("#page-Contact .page-head").findByTitle("Test Form Contact 3").should("exist");
// clear filters
- cy.visit('/app/contact');
+ cy.visit("/app/contact");
cy.clear_filters();
});
- it('validates behaviour of Data options validations in child table', () => {
+ it("validates behaviour of Data options validations in child table", () => {
// test email validations for set_invalid controller
- let website_input = 'website.in';
- let valid_email = 'user@email.com';
- let expectBackgroundColor = 'rgb(255, 245, 245)';
+ let website_input = "website.in";
+ let valid_email = "user@email.com";
+ let expectBackgroundColor = "rgb(255, 245, 245)";
- cy.visit('/app/contact/new');
- cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
- cy.get('@table').find('button.grid-add-row').click();
- cy.get('@table').find('button.grid-add-row').click();
- cy.get('@table').find('[data-idx="1"]').as('row1');
- cy.get('@table').find('[data-idx="2"]').as('row2');
- cy.get('@row1').click();
- cy.get('@row1').find('input.input-with-feedback.form-control').as('email_input1');
+ cy.visit("/app/contact/new");
+ cy.get('.frappe-control[data-fieldname="email_ids"]').as("table");
+ cy.get("@table").find("button.grid-add-row").click();
+ cy.get("@table").find("button.grid-add-row").click();
+ cy.get("@table").find('[data-idx="1"]').as("row1");
+ cy.get("@table").find('[data-idx="2"]').as("row2");
+ cy.get("@row1").click();
+ cy.get("@row1").find("input.input-with-feedback.form-control").as("email_input1");
- cy.get('@email_input1').type(website_input, { waitForAnimations: false });
- cy.fill_field('company_name', 'Test Company');
+ cy.get("@email_input1").type(website_input, { waitForAnimations: false });
+ cy.fill_field("company_name", "Test Company");
- cy.get('@row2').click();
- cy.get('@row2').find('input.input-with-feedback.form-control').as('email_input2');
- cy.get('@email_input2').type(valid_email, { waitForAnimations: false });
+ cy.get("@row2").click();
+ cy.get("@row2").find("input.input-with-feedback.form-control").as("email_input2");
+ cy.get("@email_input2").type(valid_email, { waitForAnimations: false });
- cy.get('@row1').click();
- cy.get('@email_input1').should($div => {
+ cy.get("@row1").click();
+ cy.get("@email_input1").should(($div) => {
const style = window.getComputedStyle($div[0]);
expect(style.backgroundColor).to.equal(expectBackgroundColor);
});
- cy.get('@email_input1').should('have.class', 'invalid');
+ cy.get("@email_input1").should("have.class", "invalid");
- cy.get('@row2').click();
- cy.get('@email_input2').should('not.have.class', 'invalid');
+ cy.get("@row2").click();
+ cy.get("@email_input2").should("not.have.class", "invalid");
});
- it('Shows version conflict warning', { scrollBehavior: false }, () => {
- cy.visit('/app/todo');
+ it("Shows version conflict warning", { scrollBehavior: false }, () => {
+ cy.visit("/app/todo");
- cy.insert_doc("ToDo", {"description": "old"}).then(doc => {
+ cy.insert_doc("ToDo", { description: "old" }).then((doc) => {
cy.visit(`/app/todo/${doc.name}`);
// make form dirty
cy.fill_field("status", "Cancelled", "Select");
// update doc using api - simulating parallel change by another user
- cy.update_doc("ToDo", doc.name, {"status": "Closed"}).then(() => {
- cy.findByRole("button", {name: "Refresh"}).click();
+ cy.update_doc("ToDo", doc.name, { status: "Closed" }).then(() => {
+ cy.findByRole("button", { name: "Refresh" }).click();
cy.get_field("status", "Select").should("have.value", "Closed");
- })
- })
+ });
+ });
});
- it('let user undo/redo field value changes', { scrollBehavior: false }, () => {
+ it("let user undo/redo field value changes", { scrollBehavior: false }, () => {
const jump_to_field = (field_label) => {
cy.get("body")
- .type("{esc}") // lose focus if any
- .type("{ctrl+j}") // jump to field
+ .type("{esc}") // lose focus if any
+ .type("{ctrl+j}") // jump to field
.type(field_label)
.wait(500)
.type("{enter}")
@@ -111,16 +118,13 @@ context('Form', () => {
};
const type_value = (value) => {
- cy.focused()
- .clear()
- .type(value)
- .type("{esc}");
+ cy.focused().clear().type(value).type("{esc}");
};
const undo = () => cy.get("body").type("{esc}").type("{ctrl+z}").wait(500);
const redo = () => cy.get("body").type("{esc}").type("{ctrl+y}").wait(500);
- cy.new_form('User');
+ cy.new_form("User");
jump_to_field("Email");
type_value("admin@example.com");
@@ -132,7 +136,7 @@ context('Form', () => {
type_value("12-31-01");
jump_to_field("Send Welcome Email");
- cy.focused().uncheck()
+ cy.focused().uncheck();
// make a mistake
jump_to_field("Username");
@@ -140,19 +144,27 @@ context('Form', () => {
// undo behaviour
undo();
- cy.get_field("username").should('have.value', 'admin42');
+ cy.get_field("username").should("have.value", "admin42");
// redo behaviour
redo();
- cy.get_field("username").should('have.value', 'admin24');
+ cy.get_field("username").should("have.value", "admin24");
// undo everything & redo everything, ensure same values at the end
- undo(); undo(); undo(); undo(); undo();
- redo(); redo(); redo(); redo(); redo();
+ undo();
+ undo();
+ undo();
+ undo();
+ undo();
+ redo();
+ redo();
+ redo();
+ redo();
+ redo();
- cy.get_field("username").should('have.value', 'admin24');
- cy.get_field("email").should('have.value', 'admin@example.com');
- cy.get_field("birth_date").should('have.value', '12-31-2001'); // parsed value
- cy.get_field("send_welcome_email").should('not.be.checked');
+ cy.get_field("username").should("have.value", "admin24");
+ cy.get_field("email").should("have.value", "admin@example.com");
+ cy.get_field("birth_date").should("have.value", "12-31-2001"); // parsed value
+ cy.get_field("send_welcome_email").should("not.be.checked");
});
});
diff --git a/cypress/integration/form_tab_break.js b/cypress/integration/form_tab_break.js
index 45c3c92084..91695cb143 100644
--- a/cypress/integration/form_tab_break.js
+++ b/cypress/integration/form_tab_break.js
@@ -1,31 +1,30 @@
-import doctype_with_tab_break from '../fixtures/doctype_with_tab_break';
+import doctype_with_tab_break from "../fixtures/doctype_with_tab_break";
const doctype_name = doctype_with_tab_break.name;
context("Form Tab Break", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.insert_doc('DocType', doctype_with_tab_break, true);
+ cy.visit("/app/website");
+ return cy.insert_doc("DocType", doctype_with_tab_break, true);
});
it("Should switch tab and open correct tabs on validation error", () => {
cy.new_form(doctype_name);
// test tab switch
- cy.findByRole("tab", {name: "Tab 2"}).click();
+ cy.findByRole("tab", { name: "Tab 2" }).click();
cy.findByText("Phone");
- cy.findByRole("tab", {name: "Details"}).click();
+ cy.findByRole("tab", { name: "Details" }).click();
cy.findByText("Name");
// form should switch to the tab with un-filled mandatory field
cy.fill_field("username", "Test");
- cy.findByRole("button", {name: "Save"}).click();
+ cy.findByRole("button", { name: "Save" }).click();
cy.findByText("Missing Fields");
cy.hide_dialog();
cy.findByText("Phone");
cy.fill_field("phone", "12345678");
- cy.findByRole("button", {name: "Save"}).click();
+ cy.findByRole("button", { name: "Save" }).click();
// After save, first tab should have dashboard
cy.get(".form-tabs > .nav-item").eq(0).click();
cy.findByText("Connections");
-
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/form_tour.js b/cypress/integration/form_tour.js
index 507a07ab1a..f4ae0dbb6d 100644
--- a/cypress/integration/form_tour.js
+++ b/cypress/integration/form_tour.js
@@ -1,88 +1,94 @@
-context.skip('Form Tour', () => {
+context.skip("Form Tour", () => {
before(() => {
cy.login();
- cy.visit('/app');
- return cy.window().its('frappe').then(frappe => {
- return frappe.call("frappe.tests.ui_test_helpers.create_form_tour");
- });
+ cy.visit("/app");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.call("frappe.tests.ui_test_helpers.create_form_tour");
+ });
});
const open_test_form_tour = () => {
- cy.visit('/app/form-tour/Test Form Tour');
- cy.findByRole('button', {name: 'Show Tour'}).should('be.visible').as('show_tour');
- cy.get('@show_tour').click();
+ cy.visit("/app/form-tour/Test Form Tour");
+ cy.findByRole("button", { name: "Show Tour" }).should("be.visible").as("show_tour");
+ cy.get("@show_tour").click();
cy.wait(500);
- cy.url().should('include', '/app/contact');
+ cy.url().should("include", "/app/contact");
};
- it('jump to a form tour', open_test_form_tour);
+ it("jump to a form tour", open_test_form_tour);
- it('navigates a form tour', () => {
+ it("navigates a form tour", () => {
open_test_form_tour();
- cy.get('.frappe-driver').should('be.visible');
- cy.get('.frappe-control[data-fieldname="first_name"]').as('first_name');
- cy.get('@first_name').should('have.class', 'driver-highlighted-element');
- cy.get('.frappe-driver').findByRole('button', {name: 'Next'}).as('next_btn');
+ cy.get(".frappe-driver").should("be.visible");
+ cy.get('.frappe-control[data-fieldname="first_name"]').as("first_name");
+ cy.get("@first_name").should("have.class", "driver-highlighted-element");
+ cy.get(".frappe-driver").findByRole("button", { name: "Next" }).as("next_btn");
// next btn shouldn't move to next step, if first name is not entered
- cy.get('@next_btn').click();
+ cy.get("@next_btn").click();
cy.wait(500);
- cy.get('@first_name').should('have.class', 'driver-highlighted-element');
+ cy.get("@first_name").should("have.class", "driver-highlighted-element");
// after filling the field, next step should be highlighted
- cy.fill_field('first_name', 'Test Name', 'Data');
+ cy.fill_field("first_name", "Test Name", "Data");
cy.wait(500);
- cy.get('@next_btn').click();
+ cy.get("@next_btn").click();
cy.wait(500);
// assert field is highlighted
- cy.get('.frappe-control[data-fieldname="last_name"]').as('last_name');
- cy.get('@last_name').should('have.class', 'driver-highlighted-element');
+ cy.get('.frappe-control[data-fieldname="last_name"]').as("last_name");
+ cy.get("@last_name").should("have.class", "driver-highlighted-element");
// after filling the field, next step should be highlighted
- cy.fill_field('last_name', 'Test Last Name', 'Data');
+ cy.fill_field("last_name", "Test Last Name", "Data");
cy.wait(500);
- cy.get('@next_btn').click();
+ cy.get("@next_btn").click();
cy.wait(500);
// assert field is highlighted
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('phone_nos');
- cy.get('@phone_nos').should('have.class', 'driver-highlighted-element');
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("phone_nos");
+ cy.get("@phone_nos").should("have.class", "driver-highlighted-element");
// move to next step
cy.wait(500);
- cy.get('@next_btn').click();
+ cy.get("@next_btn").click();
cy.wait(500);
// assert add row btn is highlighted
- cy.get('@phone_nos').find('.grid-add-row').as('add_row');
- cy.get('@add_row').should('have.class', 'driver-highlighted-element');
+ cy.get("@phone_nos").find(".grid-add-row").as("add_row");
+ cy.get("@add_row").should("have.class", "driver-highlighted-element");
// add a row & move to next step
cy.wait(500);
- cy.get('@add_row').click();
+ cy.get("@add_row").click();
cy.wait(500);
// assert table field is highlighted
- cy.get('.grid-row-open .frappe-control[data-fieldname="phone"]').as('phone');
- cy.get('@phone').should('have.class', 'driver-highlighted-element');
+ cy.get('.grid-row-open .frappe-control[data-fieldname="phone"]').as("phone");
+ cy.get("@phone").should("have.class", "driver-highlighted-element");
// enter value in a table field
- let field = cy.fill_table_field('phone_nos', '1', 'phone', '1234567890');
+ let field = cy.fill_table_field("phone_nos", "1", "phone", "1234567890");
field.blur();
// move to collapse row step
cy.wait(500);
- cy.get('.driver-popover-title').contains('Test Title 4').siblings().get('@next_btn').click();
+ cy.get(".driver-popover-title")
+ .contains("Test Title 4")
+ .siblings()
+ .get("@next_btn")
+ .click();
cy.wait(500);
// collapse row
- cy.get('.grid-row-open .grid-collapse-row').click();
+ cy.get(".grid-row-open .grid-collapse-row").click();
cy.wait(500);
// assert save btn is highlighted
- cy.get('.primary-action').should('have.class', 'driver-highlighted-element');
+ cy.get(".primary-action").should("have.class", "driver-highlighted-element");
cy.wait(500);
- cy.get('.frappe-driver').findByRole('button', {name: 'Save'}).should('be.visible');
-
+ cy.get(".frappe-driver").findByRole("button", { name: "Save" }).should("be.visible");
});
});
diff --git a/cypress/integration/grid.js b/cypress/integration/grid.js
index 4fa52712cf..6cf9e8cdc7 100644
--- a/cypress/integration/grid.js
+++ b/cypress/integration/grid.js
@@ -1,92 +1,114 @@
-context('Grid', () => {
+context("Grid", () => {
beforeEach(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.window().its('frappe').then(frappe => {
- return frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
- });
+ cy.visit("/app/website");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.call(
+ "frappe.tests.ui_test_helpers.create_contact_phone_nos_records"
+ );
+ });
});
- it('update docfield property using update_docfield_property', () => {
- cy.visit('/app/contact/Test Contact');
- cy.window().its("cur_frm").then(frm => {
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- let field = frm.get_field("phone_nos");
- field.grid.update_docfield_property("is_primary_phone", "hidden", true);
+ it("update docfield property using update_docfield_property", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.window()
+ .its("cur_frm")
+ .then((frm) => {
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ let field = frm.get_field("phone_nos");
+ field.grid.update_docfield_property("is_primary_phone", "hidden", true);
- cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_phone"]').should("be.hidden");
- cy.get('@table-form').find('.grid-footer-toolbar').click();
+ cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get("@table-form")
+ .find('.frappe-control[data-fieldname="is_primary_phone"]')
+ .should("be.hidden");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
- cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_phone"]').should("be.hidden");
- cy.get('@table-form').find('.grid-footer-toolbar').click();
- });
+ cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get("@table-form")
+ .find('.frappe-control[data-fieldname="is_primary_phone"]')
+ .should("be.hidden");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
+ });
});
- it('update docfield property using toggle_display', () => {
- cy.visit('/app/contact/Test Contact');
- cy.window().its("cur_frm").then(frm => {
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- let field = frm.get_field("phone_nos");
- field.grid.toggle_display("is_primary_mobile_no", false);
+ it("update docfield property using toggle_display", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.window()
+ .its("cur_frm")
+ .then((frm) => {
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ let field = frm.get_field("phone_nos");
+ field.grid.toggle_display("is_primary_mobile_no", false);
- cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_mobile_no"]').should("be.hidden");
- cy.get('@table-form').find('.grid-footer-toolbar').click();
+ cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get("@table-form")
+ .find('.frappe-control[data-fieldname="is_primary_mobile_no"]')
+ .should("be.hidden");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
- cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get('@table-form').find('.frappe-control[data-fieldname="is_primary_mobile_no"]').should("be.hidden");
- cy.get('@table-form').find('.grid-footer-toolbar').click();
- });
+ cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get("@table-form")
+ .find('.frappe-control[data-fieldname="is_primary_mobile_no"]')
+ .should("be.hidden");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
+ });
});
- it('update docfield property using toggle_enable', () => {
- cy.visit('/app/contact/Test Contact');
- cy.window().its("cur_frm").then(frm => {
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- let field = frm.get_field("phone_nos");
- field.grid.toggle_enable("phone", false);
+ it("update docfield property using toggle_enable", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.window()
+ .its("cur_frm")
+ .then((frm) => {
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ let field = frm.get_field("phone_nos");
+ field.grid.toggle_enable("phone", false);
+ cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get("@table-form")
+ .find('.frappe-control[data-fieldname="phone"] .control-value')
+ .should("have.class", "like-disabled-input");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
- cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get('@table-form').find('.frappe-control[data-fieldname="phone"] .control-value').should('have.class', 'like-disabled-input');
- cy.get('@table-form').find('.grid-footer-toolbar').click();
-
- cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get('@table-form').find('.frappe-control[data-fieldname="phone"] .control-value').should('have.class', 'like-disabled-input');
- cy.get('@table-form').find('.grid-footer-toolbar').click();
- });
+ cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get("@table-form")
+ .find('.frappe-control[data-fieldname="phone"] .control-value')
+ .should("have.class", "like-disabled-input");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
+ });
});
- it('update docfield property using toggle_reqd', () => {
- cy.visit('/app/contact/Test Contact');
- cy.window().its("cur_frm").then(frm => {
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- let field = frm.get_field("phone_nos");
- field.grid.toggle_reqd("phone", false);
+ it("update docfield property using toggle_reqd", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.window()
+ .its("cur_frm")
+ .then((frm) => {
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ let field = frm.get_field("phone_nos");
+ field.grid.toggle_reqd("phone", false);
- cy.get('@table').find('[data-idx="1"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get_field("phone").as('phone-field');
- cy.get('@phone-field').focus().clear().wait(500).blur();
- cy.get('@phone-field').should("not.have.class", "has-error");
- cy.get('@table-form').find('.grid-footer-toolbar').click();
+ cy.get("@table").find('[data-idx="1"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get_field("phone").as("phone-field");
+ cy.get("@phone-field").focus().clear().wait(500).blur();
+ cy.get("@phone-field").should("not.have.class", "has-error");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
- cy.get('@table').find('[data-idx="2"] .edit-grid-row').click();
- cy.get('.grid-row-open').as('table-form');
- cy.get_field("phone").as('phone-field');
- cy.get('@phone-field').focus().clear().wait(500).blur();
- cy.get('@phone-field').should("not.have.class", "has-error");
- cy.get('@table-form').find('.grid-footer-toolbar').click();
-
- });
+ cy.get("@table").find('[data-idx="2"] .edit-grid-row').click();
+ cy.get(".grid-row-open").as("table-form");
+ cy.get_field("phone").as("phone-field");
+ cy.get("@phone-field").focus().clear().wait(500).blur();
+ cy.get("@phone-field").should("not.have.class", "has-error");
+ cy.get("@table-form").find(".grid-footer-toolbar").click();
+ });
});
});
-
diff --git a/cypress/integration/grid_configuration.js b/cypress/integration/grid_configuration.js
index 7193d804c2..9112d7023e 100644
--- a/cypress/integration/grid_configuration.js
+++ b/cypress/integration/grid_configuration.js
@@ -1,23 +1,23 @@
-context('Grid Configuration', () => {
+context("Grid Configuration", () => {
beforeEach(() => {
cy.login();
- cy.visit('/app/doctype/User');
+ cy.visit("/app/doctype/User");
});
- it('Set user wise grid settings', () => {
+ it("Set user wise grid settings", () => {
cy.wait(100);
- cy.get('.frappe-control[data-fieldname="fields"]').as('table');
- cy.get('@table').find('.icon-sm').click();
+ cy.get('.frappe-control[data-fieldname="fields"]').as("table");
+ cy.get("@table").find(".icon-sm").click();
cy.wait(100);
- cy.get('.frappe-control[data-fieldname="fields_html"]').as('modal');
- cy.get('@modal').find('.add-new-fields').click();
+ cy.get('.frappe-control[data-fieldname="fields_html"]').as("modal");
+ cy.get("@modal").find(".add-new-fields").click();
cy.wait(100);
cy.get('[type="checkbox"][data-unit="read_only"]').check();
- cy.findByRole('button', {name: 'Add'}).click();
+ cy.findByRole("button", { name: "Add" }).click();
cy.wait(100);
- cy.get('[data-fieldname="options"]').invoke('attr', 'value', '1');
- cy.get('.form-control.column-width[data-fieldname="options"]').trigger('change');
- cy.findByRole('button', {name: 'Update'}).click();
+ cy.get('[data-fieldname="options"]').invoke("attr", "value", "1");
+ cy.get('.form-control.column-width[data-fieldname="options"]').trigger("change");
+ cy.findByRole("button", { name: "Update" }).click();
cy.wait(200);
- cy.get('[title="Read Only"').should('be.visible');
+ cy.get('[title="Read Only"').should("be.visible");
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/grid_keyboard_shortcut.js b/cypress/integration/grid_keyboard_shortcut.js
index 9cf39165ad..414e822516 100644
--- a/cypress/integration/grid_keyboard_shortcut.js
+++ b/cypress/integration/grid_keyboard_shortcut.js
@@ -1,40 +1,47 @@
-context('Grid Keyboard Shortcut', () => {
+context("Grid Keyboard Shortcut", () => {
let total_count = 0;
before(() => {
cy.login();
});
beforeEach(() => {
cy.reload();
- cy.visit('/app/contact/new-contact-1');
+ cy.visit("/app/contact/new-contact-1");
cy.get('.frappe-control[data-fieldname="email_ids"]').find(".grid-add-row").click();
});
- it('Insert new row at the end', () => {
- cy.add_new_row_in_grid('{ctrl}{shift}{downarrow}', (cy, total_count) => {
- cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', `${total_count+1}`);
- }, total_count);
+ it("Insert new row at the end", () => {
+ cy.add_new_row_in_grid(
+ "{ctrl}{shift}{downarrow}",
+ (cy, total_count) => {
+ cy.get('[data-name="new-contact-email-1"]').should(
+ "have.attr",
+ "data-idx",
+ `${total_count + 1}`
+ );
+ },
+ total_count
+ );
});
- it('Insert new row at the top', () => {
- cy.add_new_row_in_grid('{ctrl}{shift}{uparrow}', (cy) => {
- cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', '2');
+ it("Insert new row at the top", () => {
+ cy.add_new_row_in_grid("{ctrl}{shift}{uparrow}", (cy) => {
+ cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "2");
});
});
- it('Insert new row below', () => {
- cy.add_new_row_in_grid('{ctrl}{downarrow}', (cy) => {
- cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', '1');
+ it("Insert new row below", () => {
+ cy.add_new_row_in_grid("{ctrl}{downarrow}", (cy) => {
+ cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "1");
});
});
- it('Insert new row above', () => {
- cy.add_new_row_in_grid('{ctrl}{uparrow}', (cy) => {
- cy.get('[data-name="new-contact-email-1"]').should('have.attr', 'data-idx', '2');
+ it("Insert new row above", () => {
+ cy.add_new_row_in_grid("{ctrl}{uparrow}", (cy) => {
+ cy.get('[data-name="new-contact-email-1"]').should("have.attr", "data-idx", "2");
});
});
});
-Cypress.Commands.add('add_new_row_in_grid', (shortcut_keys, callbackFn, total_count) => {
- cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
- cy.get('@table').find('.grid-body [data-fieldname="email_id"]').first().click();
- cy.get('@table').find('.grid-body [data-fieldname="email_id"]')
- .first().type(shortcut_keys);
+Cypress.Commands.add("add_new_row_in_grid", (shortcut_keys, callbackFn, total_count) => {
+ cy.get('.frappe-control[data-fieldname="email_ids"]').as("table");
+ cy.get("@table").find('.grid-body [data-fieldname="email_id"]').first().click();
+ cy.get("@table").find('.grid-body [data-fieldname="email_id"]').first().type(shortcut_keys);
callbackFn(cy, total_count);
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/grid_pagination.js b/cypress/integration/grid_pagination.js
index 84b3320282..097f2a5cdc 100644
--- a/cypress/integration/grid_pagination.js
+++ b/cypress/integration/grid_pagination.js
@@ -1,65 +1,73 @@
-context('Grid Pagination', () => {
+context("Grid Pagination", () => {
beforeEach(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.window().its('frappe').then(frappe => {
- return frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records");
- });
+ cy.visit("/app/website");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.call(
+ "frappe.tests.ui_test_helpers.create_contact_phone_nos_records"
+ );
+ });
});
- it('creates pages for child table', () => {
- cy.visit('/app/contact/Test Contact');
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- cy.get('@table').find('.current-page-number').should('have.value', '1');
- cy.get('@table').find('.total-page-number').should('contain', '20');
- cy.get('@table').find('.grid-body .grid-row').should('have.length', 50);
+ it("creates pages for child table", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ cy.get("@table").find(".current-page-number").should("have.value", "1");
+ cy.get("@table").find(".total-page-number").should("contain", "20");
+ cy.get("@table").find(".grid-body .grid-row").should("have.length", 50);
});
- it('goes to the next and previous page', () => {
- cy.visit('/app/contact/Test Contact');
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- cy.get('@table').find('.next-page').click();
- cy.get('@table').find('.current-page-number').should('have.value', '2');
- cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '51');
- cy.get('@table').find('.prev-page').click();
- cy.get('@table').find('.current-page-number').should('have.value', '1');
- cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1');
+ it("goes to the next and previous page", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ cy.get("@table").find(".next-page").click();
+ cy.get("@table").find(".current-page-number").should("have.value", "2");
+ cy.get("@table")
+ .find(".grid-body .grid-row")
+ .first()
+ .should("have.attr", "data-idx", "51");
+ cy.get("@table").find(".prev-page").click();
+ cy.get("@table").find(".current-page-number").should("have.value", "1");
+ cy.get("@table").find(".grid-body .grid-row").first().should("have.attr", "data-idx", "1");
});
- it('adds and deletes rows and changes page', () => {
- cy.visit('/app/contact/Test Contact');
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- cy.get('@table').findByRole('button', {name: 'Add Row'}).click();
- cy.get('@table').find('.grid-body .row-index').should('contain', 1001);
- cy.get('@table').find('.current-page-number').should('have.value', '21');
- cy.get('@table').find('.total-page-number').should('contain', '21');
- cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true });
- cy.get('@table').findByRole('button', {name: 'Delete'}).click();
- cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000);
- cy.get('@table').find('.current-page-number').should('have.value', '20');
- cy.get('@table').find('.total-page-number').should('contain', '20');
+ it("adds and deletes rows and changes page", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ cy.get("@table").findByRole("button", { name: "Add Row" }).click();
+ cy.get("@table").find(".grid-body .row-index").should("contain", 1001);
+ cy.get("@table").find(".current-page-number").should("have.value", "21");
+ cy.get("@table").find(".total-page-number").should("contain", "21");
+ cy.get("@table").find(".grid-body .grid-row .grid-row-check").click({ force: true });
+ cy.get("@table").findByRole("button", { name: "Delete" }).click();
+ cy.get("@table").find(".grid-body .row-index").last().should("contain", 1000);
+ cy.get("@table").find(".current-page-number").should("have.value", "20");
+ cy.get("@table").find(".total-page-number").should("contain", "20");
});
- it('go to specific page, use up and down arrow, type characters, 0 page and more than existing page', () => {
- cy.visit('/app/contact/Test Contact');
- cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
- cy.get('@table').find('.current-page-number').focus().clear().type('17').blur();
- cy.get('@table').find('.grid-body .row-index').should('contain', 801);
+ it("go to specific page, use up and down arrow, type characters, 0 page and more than existing page", () => {
+ cy.visit("/app/contact/Test Contact");
+ cy.get('.frappe-control[data-fieldname="phone_nos"]').as("table");
+ cy.get("@table").find(".current-page-number").focus().clear().type("17").blur();
+ cy.get("@table").find(".grid-body .row-index").should("contain", 801);
- cy.get('@table').find('.current-page-number').focus().type('{uparrow}{uparrow}');
- cy.get('@table').find('.current-page-number').should('have.value', '19');
+ cy.get("@table").find(".current-page-number").focus().type("{uparrow}{uparrow}");
+ cy.get("@table").find(".current-page-number").should("have.value", "19");
- cy.get('@table').find('.current-page-number').focus().type('{downarrow}{downarrow}');
- cy.get('@table').find('.current-page-number').should('have.value', '17');
+ cy.get("@table").find(".current-page-number").focus().type("{downarrow}{downarrow}");
+ cy.get("@table").find(".current-page-number").should("have.value", "17");
- cy.get('@table').find('.current-page-number').focus().clear().type('700').blur();
- cy.get('@table').find('.current-page-number').should('have.value', '20');
+ cy.get("@table").find(".current-page-number").focus().clear().type("700").blur();
+ cy.get("@table").find(".current-page-number").should("have.value", "20");
- cy.get('@table').find('.current-page-number').focus().clear().type('0').blur();
- cy.get('@table').find('.current-page-number').should('have.value', '1');
+ cy.get("@table").find(".current-page-number").focus().clear().type("0").blur();
+ cy.get("@table").find(".current-page-number").should("have.value", "1");
- cy.get('@table').find('.current-page-number').focus().clear().type('abc').blur();
- cy.get('@table').find('.current-page-number').should('have.value', '1');
+ cy.get("@table").find(".current-page-number").focus().clear().type("abc").blur();
+ cy.get("@table").find(".current-page-number").should("have.value", "1");
});
// it('deletes all rows', ()=> {
// cy.visit('/app/contact/Test Contact');
@@ -69,4 +77,4 @@ context('Grid Pagination', () => {
// cy.get('.modal-dialog .btn-primary').contains('Yes').click();
// cy.get('@table').find('.grid-body .grid-row').should('have.length', 0);
// });
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/grid_search.js b/cypress/integration/grid_search.js
index d30545a2e1..3d43412313 100644
--- a/cypress/integration/grid_search.js
+++ b/cypress/integration/grid_search.js
@@ -1,107 +1,133 @@
-import doctype_with_child_table from '../fixtures/doctype_with_child_table';
-import child_table_doctype from '../fixtures/child_table_doctype';
-import child_table_doctype_1 from '../fixtures/child_table_doctype_1';
+import doctype_with_child_table from "../fixtures/doctype_with_child_table";
+import child_table_doctype from "../fixtures/child_table_doctype";
+import child_table_doctype_1 from "../fixtures/child_table_doctype_1";
const doctype_with_child_table_name = doctype_with_child_table.name;
-context('Grid Search', () => {
+context("Grid Search", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
- cy.visit('/app/website');
- cy.insert_doc('DocType', child_table_doctype, true);
- cy.insert_doc('DocType', child_table_doctype_1, true);
- cy.insert_doc('DocType', doctype_with_child_table, true);
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall("frappe.tests.ui_test_helpers.insert_doctype_with_child_table_record", {
- name: doctype_with_child_table_name
+ cy.visit("/app/website");
+ cy.insert_doc("DocType", child_table_doctype, true);
+ cy.insert_doc("DocType", child_table_doctype_1, true);
+ cy.insert_doc("DocType", doctype_with_child_table, true);
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall(
+ "frappe.tests.ui_test_helpers.insert_doctype_with_child_table_record",
+ {
+ name: doctype_with_child_table_name,
+ }
+ );
});
- });
});
- it('Test search row visibility', () => {
- cy.window().its('frappe').then(frappe => {
- frappe.model.user_settings.save('Doctype With Child Table', 'GridView', {
- 'Child Table Doctype 1': [
- {'fieldname': 'data', 'columns': 2},
- {'fieldname': 'barcode', 'columns': 1},
- {'fieldname': 'check', 'columns': 1},
- {'fieldname': 'rating', 'columns': 2},
- {'fieldname': 'duration', 'columns': 2},
- {'fieldname': 'date', 'columns': 2}
- ]
+ it("Test search row visibility", () => {
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ frappe.model.user_settings.save("Doctype With Child Table", "GridView", {
+ "Child Table Doctype 1": [
+ { fieldname: "data", columns: 2 },
+ { fieldname: "barcode", columns: 1 },
+ { fieldname: "check", columns: 1 },
+ { fieldname: "rating", columns: 2 },
+ { fieldname: "duration", columns: 2 },
+ { fieldname: "date", columns: 2 },
+ ],
+ });
});
- });
cy.visit(`/app/doctype-with-child-table/Test Grid Search`);
- cy.get('.frappe-control[data-fieldname="child_table_1"]').as('table');
- cy.get('@table').find('.grid-row-check:last').click();
- cy.get('@table').find('.grid-footer').contains('Delete').click();
- cy.get('.grid-heading-row .grid-row .search').should('not.exist');
+ cy.get('.frappe-control[data-fieldname="child_table_1"]').as("table");
+ cy.get("@table").find(".grid-row-check:last").click();
+ cy.get("@table").find(".grid-footer").contains("Delete").click();
+ cy.get(".grid-heading-row .grid-row .search").should("not.exist");
});
- it('test search field for different fieldtypes', () => {
+ it("test search field for different fieldtypes", () => {
cy.visit(`/app/doctype-with-child-table/Test Grid Search`);
- cy.get('.frappe-control[data-fieldname="child_table_1"]').as('table');
+ cy.get('.frappe-control[data-fieldname="child_table_1"]').as("table");
// Index Column
- cy.get('@table').find('.grid-heading-row .row-index.search input').type('3');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 2);
- cy.get('@table').find('.grid-heading-row .row-index.search input').clear();
+ cy.get("@table").find(".grid-heading-row .row-index.search input").type("3");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 2);
+ cy.get("@table").find(".grid-heading-row .row-index.search input").clear();
// Data Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Data"]').type('Data');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 1);
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Data"]').clear();
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Data"]')
+ .type("Data");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 1);
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Data"]').clear();
// Barcode Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Barcode"]').type('092');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 4);
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Barcode"]').clear();
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Barcode"]')
+ .type("092");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 4);
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Barcode"]').clear();
// Check Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').type('1');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 9);
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').type("1");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 9);
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').type('0');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 11);
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').type("0");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 11);
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Check"]').clear();
// Rating Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Rating"]').type('3');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 3);
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Rating"]').clear();
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Rating"]')
+ .type("3");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 3);
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Rating"]').clear();
// Duration Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Duration"]').type('3d');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 3);
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Duration"]').clear();
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Duration"]')
+ .type("3d");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 3);
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Duration"]')
+ .clear();
// Date Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Date"]').type('2022');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 4);
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Date"]').clear();
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Date"]')
+ .type("2022");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 4);
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Date"]').clear();
});
- it('test with multiple filter', () => {
- cy.get('.frappe-control[data-fieldname="child_table_1"]').as('table');
+ it("test with multiple filter", () => {
+ cy.get('.frappe-control[data-fieldname="child_table_1"]').as("table");
// Data Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Data"]').type('a');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 10);
+ cy.get("@table").find('.grid-heading-row .search input[data-fieldtype="Data"]').type("a");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 10);
// Barcode Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Barcode"]').type('0');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 8);
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Barcode"]')
+ .type("0");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 8);
// Duration Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Duration"]').type('d');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 5);
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Duration"]')
+ .type("d");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 5);
// Date Column
- cy.get('@table').find('.grid-heading-row .search input[data-fieldtype="Date"]').type('02-');
- cy.get('@table').find('.grid-body .rows .grid-row').should('have.length', 2);
+ cy.get("@table")
+ .find('.grid-heading-row .search input[data-fieldtype="Date"]')
+ .type("02-");
+ cy.get("@table").find(".grid-body .rows .grid-row").should("have.length", 2);
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/kanban.js b/cypress/integration/kanban.js
index 6ebab5d008..7296a12666 100644
--- a/cypress/integration/kanban.js
+++ b/cypress/integration/kanban.js
@@ -1,75 +1,100 @@
-context('Kanban Board', () => {
+context("Kanban Board", () => {
before(() => {
cy.login();
- cy.visit('/app');
+ cy.visit("/app");
});
- it('Create ToDo Kanban', () => {
- cy.visit('/app/todo');
+ it("Create ToDo Kanban", () => {
+ cy.visit("/app/todo");
- cy.get('.page-actions .custom-btn-group button').click();
- cy.get('.page-actions .custom-btn-group ul.dropdown-menu li').contains('Kanban').click();
+ cy.get(".page-actions .custom-btn-group button").click();
+ cy.get(".page-actions .custom-btn-group ul.dropdown-menu li").contains("Kanban").click();
cy.focused().blur();
- cy.fill_field('board_name', 'ToDo Kanban', 'Data');
- cy.fill_field('field_name', 'Status', 'Select');
- cy.click_modal_primary_button('Save');
+ cy.fill_field("board_name", "ToDo Kanban", "Data");
+ cy.fill_field("field_name", "Status", "Select");
+ cy.click_modal_primary_button("Save");
- cy.get('.title-text').should('contain', 'ToDo Kanban');
+ cy.get(".title-text").should("contain", "ToDo Kanban");
});
- it('Create ToDo from kanban', () => {
+ it("Create ToDo from kanban", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.client.save'
- }).as('save-todo');
+ method: "POST",
+ url: "api/method/frappe.client.save",
+ }).as("save-todo");
- cy.click_listview_primary_button('Add ToDo');
+ cy.click_listview_primary_button("Add ToDo");
- cy.fill_field('description', 'Test Kanban ToDo', 'Text Editor').wait(300);
- cy.get('.modal-footer .btn-primary').last().click();
+ cy.fill_field("description", "Test Kanban ToDo", "Text Editor").wait(300);
+ cy.get(".modal-footer .btn-primary").last().click();
- cy.wait('@save-todo');
+ cy.wait("@save-todo");
});
- it('Add and Remove fields', () => {
- cy.visit('/app/todo/view/kanban/ToDo Kanban');
+ it("Add and Remove fields", () => {
+ cy.visit("/app/todo/view/kanban/ToDo Kanban");
- cy.intercept('POST', '/api/method/frappe.desk.doctype.kanban_board.kanban_board.save_settings').as('save-kanban');
- cy.intercept('POST', '/api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order').as('update-order');
+ cy.intercept(
+ "POST",
+ "/api/method/frappe.desk.doctype.kanban_board.kanban_board.save_settings"
+ ).as("save-kanban");
+ cy.intercept(
+ "POST",
+ "/api/method/frappe.desk.doctype.kanban_board.kanban_board.update_order"
+ ).as("update-order");
- cy.get('.page-actions .menu-btn-group > .btn').click();
- cy.get('.page-actions .menu-btn-group .dropdown-menu li').contains('Kanban Settings').click();
- cy.get('.add-new-fields').click();
+ cy.get(".page-actions .menu-btn-group > .btn").click();
+ cy.get(".page-actions .menu-btn-group .dropdown-menu li")
+ .contains("Kanban Settings")
+ .click();
+ cy.get(".add-new-fields").click();
- cy.get('.checkbox-options .checkbox').contains('ID').click();
- cy.get('.checkbox-options .checkbox').contains('Status').first().click();
- cy.get('.checkbox-options .checkbox').contains('Priority').click();
+ cy.get(".checkbox-options .checkbox").contains("ID").click();
+ cy.get(".checkbox-options .checkbox").contains("Status").first().click();
+ cy.get(".checkbox-options .checkbox").contains("Priority").click();
- cy.get('.modal-footer .btn-primary').last().click();
+ cy.get(".modal-footer .btn-primary").last().click();
- cy.get('.frappe-control .label-area').contains('Show Labels').click();
- cy.click_modal_primary_button('Save');
+ cy.get(".frappe-control .label-area").contains("Show Labels").click();
+ cy.click_modal_primary_button("Save");
- cy.wait('@save-kanban');
+ cy.wait("@save-kanban");
- cy.get('.kanban-column[data-column-value="Open"] .kanban-cards').as('open-cards');
- cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'ID:');
- cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'Status:');
- cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('contain', 'Priority:');
+ cy.get('.kanban-column[data-column-value="Open"] .kanban-cards').as("open-cards");
+ cy.get("@open-cards")
+ .find(".kanban-card .kanban-card-doc")
+ .first()
+ .should("contain", "ID:");
+ cy.get("@open-cards")
+ .find(".kanban-card .kanban-card-doc")
+ .first()
+ .should("contain", "Status:");
+ cy.get("@open-cards")
+ .find(".kanban-card .kanban-card-doc")
+ .first()
+ .should("contain", "Priority:");
- cy.get('.page-actions .menu-btn-group > .btn').click();
- cy.get('.page-actions .menu-btn-group .dropdown-menu li').contains('Kanban Settings').click();
- cy.get_open_dialog().find('.frappe-control[data-fieldname="fields_html"] div[data-label="ID"] .remove-field').click();
+ cy.get(".page-actions .menu-btn-group > .btn").click();
+ cy.get(".page-actions .menu-btn-group .dropdown-menu li")
+ .contains("Kanban Settings")
+ .click();
+ cy.get_open_dialog()
+ .find(
+ '.frappe-control[data-fieldname="fields_html"] div[data-label="ID"] .remove-field'
+ )
+ .click();
- cy.wait('@update-order');
- cy.get_open_dialog().find('.frappe-control .label-area').contains('Show Labels').click();
- cy.get('.modal-footer .btn-primary').last().click();
+ cy.wait("@update-order");
+ cy.get_open_dialog().find(".frappe-control .label-area").contains("Show Labels").click();
+ cy.get(".modal-footer .btn-primary").last().click();
- cy.wait('@save-kanban');
-
- cy.get('@open-cards').find('.kanban-card .kanban-card-doc').first().should('not.contain', 'ID:');
+ cy.wait("@save-kanban");
+ cy.get("@open-cards")
+ .find(".kanban-card .kanban-card-doc")
+ .first()
+ .should("not.contain", "ID:");
});
// it('Drag todo', () => {
@@ -84,4 +109,4 @@ context('Kanban Board', () => {
// cy.wait('@drag-completed');
// });
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/list_paging.js b/cypress/integration/list_paging.js
index 0cf6f2e565..3071950260 100644
--- a/cypress/integration/list_paging.js
+++ b/cypress/integration/list_paging.js
@@ -1,39 +1,42 @@
-context('List Paging', () => {
+context("List Paging", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.window().its('frappe').then(frappe => {
- return frappe.call("frappe.tests.ui_test_helpers.create_multiple_todo_records");
- });
+ cy.visit("/app/website");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.call("frappe.tests.ui_test_helpers.create_multiple_todo_records");
+ });
});
- it('test load more with count selection buttons', () => {
- cy.visit('/app/todo/view/report');
- cy.clear_filters()
+ it("test load more with count selection buttons", () => {
+ cy.visit("/app/todo/view/report");
+ cy.clear_filters();
- cy.get('.list-paging-area .list-count').should('contain.text', '20 of');
- cy.get('.list-paging-area .btn-more').click();
- cy.get('.list-paging-area .list-count').should('contain.text', '40 of');
- cy.get('.list-paging-area .btn-more').click();
- cy.get('.list-paging-area .list-count').should('contain.text', '60 of');
+ cy.get(".list-paging-area .list-count").should("contain.text", "20 of");
+ cy.get(".list-paging-area .btn-more").click();
+ cy.get(".list-paging-area .list-count").should("contain.text", "40 of");
+ cy.get(".list-paging-area .btn-more").click();
+ cy.get(".list-paging-area .list-count").should("contain.text", "60 of");
cy.get('.list-paging-area .btn-group .btn-paging[data-value="100"]').click();
- cy.get('.list-paging-area .list-count').should('contain.text', '100 of');
- cy.get('.list-paging-area .btn-more').click();
- cy.get('.list-paging-area .list-count').should('contain.text', '200 of');
- cy.get('.list-paging-area .btn-more').click();
- cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
+ cy.get(".list-paging-area .list-count").should("contain.text", "100 of");
+ cy.get(".list-paging-area .btn-more").click();
+ cy.get(".list-paging-area .list-count").should("contain.text", "200 of");
+ cy.get(".list-paging-area .btn-more").click();
+ cy.get(".list-paging-area .list-count").should("contain.text", "300 of");
// check if refresh works after load more
cy.get('.page-head .standard-actions [data-original-title="Refresh"]').click();
- cy.get('.list-paging-area .list-count').should('contain.text', '300 of');
+ cy.get(".list-paging-area .list-count").should("contain.text", "300 of");
cy.get('.list-paging-area .btn-group .btn-paging[data-value="500"]').click();
- cy.get('.list-paging-area .list-count').should('contain.text', '500 of');
- cy.get('.list-paging-area .btn-more').click();
+ cy.get(".list-paging-area .list-count").should("contain.text", "500 of");
+ cy.get(".list-paging-area .btn-more").click();
- cy.get('.list-paging-area .list-count').should('contain.text', '1000 of');
+ cy.get(".list-paging-area .list-count").should("contain.text", "1000 of");
});
});
diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js
index ee12b37638..7fb0ef445c 100644
--- a/cypress/integration/list_view.js
+++ b/cypress/integration/list_view.js
@@ -1,48 +1,67 @@
-context('List View', () => {
+context("List View", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
- });
+ cy.visit("/app/website");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
+ });
});
- it('Keep checkbox checked after Refresh', () => {
- cy.go_to_list('ToDo');
- cy.clear_filters()
- cy.get('.list-row-container .list-row-checkbox').click({ multiple: true, force: true });
- cy.get('.actions-btn-group button').contains('Actions').should('be.visible');
- cy.intercept('/api/method/frappe.desk.reportview.get').as('list-refresh');
+ it("Keep checkbox checked after Refresh", () => {
+ cy.go_to_list("ToDo");
+ cy.clear_filters();
+ cy.get(".list-row-container .list-row-checkbox").click({ multiple: true, force: true });
+ cy.get(".actions-btn-group button").contains("Actions").should("be.visible");
+ cy.intercept("/api/method/frappe.desk.reportview.get").as("list-refresh");
cy.wait(3000); // wait before you hit another refresh
cy.get('button[data-original-title="Refresh"]').click();
- cy.wait('@list-refresh');
- cy.get('.list-row-container .list-row-checkbox:checked').should('be.visible');
+ cy.wait("@list-refresh");
+ cy.get(".list-row-container .list-row-checkbox:checked").should("be.visible");
});
it('enables "Actions" button', () => {
- const actions = ['Approve', 'Reject', 'Edit', 'Export', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete'];
- cy.go_to_list('ToDo');
- cy.clear_filters()
- cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true });
- cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
- cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 9).each((el, index) => {
- cy.wrap(el).contains(actions[index]);
- }).then((elements) => {
- cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.model.workflow.bulk_workflow_approval'
- }).as('bulk-approval');
- cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.reportview.get'
- }).as('real-time-update');
- cy.wrap(elements).contains('Approve').click();
- cy.wait(['@bulk-approval', '@real-time-update']);
- cy.wait(300);
- cy.get_open_dialog().find('.btn-modal-close').click();
- cy.reload();
- cy.clear_filters();
- cy.get('.list-row-container:visible').should('contain', 'Approved');
+ const actions = [
+ "Approve",
+ "Reject",
+ "Edit",
+ "Export",
+ "Assign To",
+ "Apply Assignment Rule",
+ "Add Tags",
+ "Print",
+ "Delete",
+ ];
+ cy.go_to_list("ToDo");
+ cy.clear_filters();
+ cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({
+ multiple: true,
+ force: true,
});
+ cy.get(".actions-btn-group button").contains("Actions").should("be.visible").click();
+ cy.get(".dropdown-menu li:visible .dropdown-item")
+ .should("have.length", 9)
+ .each((el, index) => {
+ cy.wrap(el).contains(actions[index]);
+ })
+ .then((elements) => {
+ cy.intercept({
+ method: "POST",
+ url: "api/method/frappe.model.workflow.bulk_workflow_approval",
+ }).as("bulk-approval");
+ cy.intercept({
+ method: "POST",
+ url: "api/method/frappe.desk.reportview.get",
+ }).as("real-time-update");
+ cy.wrap(elements).contains("Approve").click();
+ cy.wait(["@bulk-approval", "@real-time-update"]);
+ cy.wait(300);
+ cy.get_open_dialog().find(".btn-modal-close").click();
+ cy.reload();
+ cy.clear_filters();
+ cy.get(".list-row-container:visible").should("contain", "Approved");
+ });
});
});
diff --git a/cypress/integration/list_view_settings.js b/cypress/integration/list_view_settings.js
index 61d4b8aae5..5e66ee43f5 100644
--- a/cypress/integration/list_view_settings.js
+++ b/cypress/integration/list_view_settings.js
@@ -1,36 +1,36 @@
-context('List View Settings', () => {
+context("List View Settings", () => {
beforeEach(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
- it('Default settings', () => {
- cy.visit('/app/List/DocType/List');
- cy.get('.list-count').should('contain', "20 of");
- cy.get('.list-stats').should('contain', "Tags");
+ it("Default settings", () => {
+ cy.visit("/app/List/DocType/List");
+ cy.get(".list-count").should("contain", "20 of");
+ cy.get(".list-stats").should("contain", "Tags");
});
- it('disable count and sidebar stats then verify', () => {
+ it("disable count and sidebar stats then verify", () => {
cy.wait(300);
- cy.visit('/app/List/DocType/List');
+ cy.visit("/app/List/DocType/List");
cy.wait(300);
- cy.get('.list-count').should('contain', "20 of");
- cy.get('.menu-btn-group button').click();
- cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click();
- cy.get('.modal-dialog').should('contain', 'DocType Settings');
+ cy.get(".list-count").should("contain", "20 of");
+ cy.get(".menu-btn-group button").click();
+ cy.get(".dropdown-menu li").filter(":visible").contains("List Settings").click();
+ cy.get(".modal-dialog").should("contain", "DocType Settings");
- cy.findByLabelText('Disable Count').check({ force: true });
- cy.findByLabelText('Disable Sidebar Stats').check({ force: true });
- cy.findByRole('button', {name: 'Save'}).click();
+ cy.findByLabelText("Disable Count").check({ force: true });
+ cy.findByLabelText("Disable Sidebar Stats").check({ force: true });
+ cy.findByRole("button", { name: "Save" }).click();
cy.reload({ force: true });
- cy.get('.list-count').should('be.empty');
- cy.get('.list-sidebar .list-tags').should('not.exist');
+ cy.get(".list-count").should("be.empty");
+ cy.get(".list-sidebar .list-tags").should("not.exist");
- cy.get('.menu-btn-group button').click({ force: true });
- cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click();
- cy.get('.modal-dialog').should('contain', 'DocType Settings');
- cy.findByLabelText('Disable Count').uncheck({ force: true });
- cy.findByLabelText('Disable Sidebar Stats').uncheck({ force: true });
- cy.findByRole('button', {name: 'Save'}).click();
+ cy.get(".menu-btn-group button").click({ force: true });
+ cy.get(".dropdown-menu li").filter(":visible").contains("List Settings").click();
+ cy.get(".modal-dialog").should("contain", "DocType Settings");
+ cy.findByLabelText("Disable Count").uncheck({ force: true });
+ cy.findByLabelText("Disable Sidebar Stats").uncheck({ force: true });
+ cy.findByRole("button", { name: "Save" }).click();
});
});
diff --git a/cypress/integration/login.js b/cypress/integration/login.js
index 98739bb4c9..2db4b1fdcd 100644
--- a/cypress/integration/login.js
+++ b/cypress/integration/login.js
@@ -1,68 +1,66 @@
-context('Login', () => {
+context("Login", () => {
beforeEach(() => {
- cy.request('/api/method/logout');
- cy.visit('/login');
- cy.location('pathname').should('eq', '/login');
+ cy.request("/api/method/logout");
+ cy.visit("/login");
+ cy.location("pathname").should("eq", "/login");
});
- it('greets with login screen', () => {
- cy.get('.page-card-head').contains('Login');
+ it("greets with login screen", () => {
+ cy.get(".page-card-head").contains("Login");
});
- it('validates password', () => {
- cy.get('#login_email').type('Administrator');
- cy.findByRole('button', {name: 'Login'}).click();
- cy.location('pathname').should('eq', '/login');
+ it("validates password", () => {
+ cy.get("#login_email").type("Administrator");
+ cy.findByRole("button", { name: "Login" }).click();
+ cy.location("pathname").should("eq", "/login");
});
- it('validates email', () => {
- cy.get('#login_password').type('qwe');
- cy.findByRole('button', {name: 'Login'}).click();
- cy.location('pathname').should('eq', '/login');
+ it("validates email", () => {
+ cy.get("#login_password").type("qwe");
+ cy.findByRole("button", { name: "Login" }).click();
+ cy.location("pathname").should("eq", "/login");
});
- it('shows invalid login if incorrect credentials', () => {
- cy.get('#login_email').type('Administrator');
- cy.get('#login_password').type('qwer');
+ it("shows invalid login if incorrect credentials", () => {
+ cy.get("#login_email").type("Administrator");
+ cy.get("#login_password").type("qwer");
- cy.findByRole('button', {name: 'Login'}).click();
- cy.findByRole('button', {name: 'Invalid Login. Try again.'}).should('exist');
- cy.location('pathname').should('eq', '/login');
+ cy.findByRole("button", { name: "Login" }).click();
+ cy.findByRole("button", { name: "Invalid Login. Try again." }).should("exist");
+ cy.location("pathname").should("eq", "/login");
});
- it('logs in using correct credentials', () => {
- cy.get('#login_email').type('Administrator');
- cy.get('#login_password').type(Cypress.config('adminPassword'));
+ it("logs in using correct credentials", () => {
+ cy.get("#login_email").type("Administrator");
+ cy.get("#login_password").type(Cypress.config("adminPassword"));
- cy.findByRole('button', {name: 'Login'}).click();
- cy.location('pathname').should('eq', '/app');
- cy.window().its('frappe.session.user').should('eq', 'Administrator');
+ cy.findByRole("button", { name: "Login" }).click();
+ cy.location("pathname").should("eq", "/app");
+ cy.window().its("frappe.session.user").should("eq", "Administrator");
});
- it('check redirect after login', () => {
-
+ it("check redirect after login", () => {
// mock for OAuth 2.0 client_id, redirect_uri, scope and state
const payload = new URLSearchParams({
- uuid: '6fed1519-cfd8-4a2d-84a6-9a1799c7c741',
- encoded_string: 'hello all',
- encoded_url: 'http://test.localhost/callback',
- base64_string: 'aGVsbG8gYWxs'
+ uuid: "6fed1519-cfd8-4a2d-84a6-9a1799c7c741",
+ encoded_string: "hello all",
+ encoded_url: "http://test.localhost/callback",
+ base64_string: "aGVsbG8gYWxs",
});
- cy.request('/api/method/logout');
+ cy.request("/api/method/logout");
// redirect-to /me page with params to mock OAuth 2.0 like request
cy.visit(
- '/login?redirect-to=/me?' +
- encodeURIComponent(payload.toString().replace("+", " "))
+ "/login?redirect-to=/me?" + encodeURIComponent(payload.toString().replace("+", " "))
);
- cy.get('#login_email').type('Administrator');
- cy.get('#login_password').type(Cypress.config('adminPassword'));
+ cy.get("#login_email").type("Administrator");
+ cy.get("#login_password").type(Cypress.config("adminPassword"));
- cy.findByRole('button', {name: 'Login'}).click();
+ cy.findByRole("button", { name: "Login" }).click();
// verify redirected location and url params after login
- cy.url().should('include', '/me?' + payload.toString().replace('+', '%20'));
+ cy.url().should("include", "/me?" + payload.toString().replace("+", "%20"));
});
});
diff --git a/cypress/integration/multi_select_dialog.js b/cypress/integration/multi_select_dialog.js
index 607db506c7..1be56d3b3d 100644
--- a/cypress/integration/multi_select_dialog.js
+++ b/cypress/integration/multi_select_dialog.js
@@ -1,99 +1,102 @@
-context('MultiSelectDialog', () => {
+context("MultiSelectDialog", () => {
before(() => {
cy.login();
- cy.visit('/app');
+ cy.visit("/app");
const contact_template = {
- "doctype": "Contact",
- "first_name": "Test",
- "status": "Passive",
- "email_ids": [
+ doctype: "Contact",
+ first_name: "Test",
+ status: "Passive",
+ email_ids: [
{
- "doctype": "Contact Email",
- "email_id": "test@example.com",
- "is_primary": 0
- }
- ]
+ doctype: "Contact Email",
+ email_id: "test@example.com",
+ is_primary: 0,
+ },
+ ],
};
- const promises = Array.from({length: 25})
- .map(() => cy.insert_doc('Contact', contact_template, true));
+ const promises = Array.from({ length: 25 }).map(() =>
+ cy.insert_doc("Contact", contact_template, true)
+ );
Promise.all(promises);
});
function open_multi_select_dialog() {
- cy.window().its('frappe').then(frappe => {
- new frappe.ui.form.MultiSelectDialog({
- doctype: "Contact",
- target: {},
- setters: {
- status: null,
- gender: null
- },
- add_filters_group: 1,
- allow_child_item_selection: 1,
- child_fieldname: "email_ids",
- child_columns: ["email_id", "is_primary"]
+ cy.window()
+ .its("frappe")
+ .then((frappe) => {
+ new frappe.ui.form.MultiSelectDialog({
+ doctype: "Contact",
+ target: {},
+ setters: {
+ status: null,
+ gender: null,
+ },
+ add_filters_group: 1,
+ allow_child_item_selection: 1,
+ child_fieldname: "email_ids",
+ child_columns: ["email_id", "is_primary"],
+ });
});
- });
}
- it('checks multi select dialog api works', () => {
+ it("checks multi select dialog api works", () => {
open_multi_select_dialog();
- cy.get_open_dialog().should('contain', 'Select Contacts');
+ cy.get_open_dialog().should("contain", "Select Contacts");
});
- it('checks for filters', () => {
- ['search_term', 'status', 'gender'].forEach(fieldname => {
- cy.get_open_dialog().get(`.frappe-control[data-fieldname="${fieldname}"]`).should('exist');
+ it("checks for filters", () => {
+ ["search_term", "status", "gender"].forEach((fieldname) => {
+ cy.get_open_dialog()
+ .get(`.frappe-control[data-fieldname="${fieldname}"]`)
+ .should("exist");
});
// add_filters_group: 1 should add a filter group
- cy.get_open_dialog().get(`.frappe-control[data-fieldname="filter_area"]`).should('exist');
-
+ cy.get_open_dialog().get(`.frappe-control[data-fieldname="filter_area"]`).should("exist");
});
- it('checks for child item selection', () => {
- cy.get_open_dialog()
- .get(`.dt-row-header`).should('not.exist');
+ it("checks for child item selection", () => {
+ cy.get_open_dialog().get(`.dt-row-header`).should("not.exist");
cy.get_open_dialog()
.get(`.frappe-control[data-fieldname="allow_child_item_selection"]`)
.find('input[data-fieldname="allow_child_item_selection"]')
- .should('exist')
- .click({force: true});
+ .should("exist")
+ .click({ force: true });
cy.get_open_dialog()
.get(`.frappe-control[data-fieldname="child_selection_area"]`)
- .should('exist');
+ .should("exist");
- cy.get_open_dialog()
- .get(`.dt-row-header`).should('contain', 'Contact');
+ cy.get_open_dialog().get(`.dt-row-header`).should("contain", "Contact");
- cy.get_open_dialog()
- .get(`.dt-row-header`).should('contain', 'Email Id');
+ cy.get_open_dialog().get(`.dt-row-header`).should("contain", "Email Id");
- cy.get_open_dialog()
- .get(`.dt-row-header`).should('contain', 'Is Primary');
+ cy.get_open_dialog().get(`.dt-row-header`).should("contain", "Is Primary");
});
- it('tests more button', () => {
+ it("tests more button", () => {
cy.get_open_dialog()
.get(`.frappe-control[data-fieldname="more_child_btn"]`)
- .should('exist')
- .as('more-btn');
-
- cy.get_open_dialog().get('.datatable .dt-scrollable .dt-row').should(($rows) => {
- expect($rows).to.have.length(20);
- });
+ .should("exist")
+ .as("more-btn");
- cy.intercept('POST', 'api/method/frappe.client.get_list').as('get-more-records');
- cy.get('@more-btn').find('button').click({force: true});
- cy.wait('@get-more-records');
+ cy.get_open_dialog()
+ .get(".datatable .dt-scrollable .dt-row")
+ .should(($rows) => {
+ expect($rows).to.have.length(20);
+ });
- cy.get_open_dialog().get('.datatable .dt-scrollable .dt-row').should(($rows) => {
- if ($rows.length <= 20) {
- throw new Error("More button doesn't work");
- }
- });
+ cy.intercept("POST", "api/method/frappe.client.get_list").as("get-more-records");
+ cy.get("@more-btn").find("button").click({ force: true });
+ cy.wait("@get-more-records");
+ cy.get_open_dialog()
+ .get(".datatable .dt-scrollable .dt-row")
+ .should(($rows) => {
+ if ($rows.length <= 20) {
+ throw new Error("More button doesn't work");
+ }
+ });
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/navigation.js b/cypress/integration/navigation.js
index b4e023c53e..2302296f23 100644
--- a/cypress/integration/navigation.js
+++ b/cypress/integration/navigation.js
@@ -1,25 +1,29 @@
-context('Navigation', () => {
+context("Navigation", () => {
before(() => {
cy.login();
});
- it('Navigate to route with hash in document name', () => {
- cy.insert_doc('ToDo', {'__newname': 'ABC#123', 'description': 'Test this', 'ignore_duplicate': true});
- cy.visit('/app/todo/ABC#123');
- cy.title().should('eq', 'Test this - ABC#123');
- cy.get_field('description', 'Text Editor').contains('Test this');
- cy.go('back');
- cy.title().should('eq', 'Website');
+ it("Navigate to route with hash in document name", () => {
+ cy.insert_doc("ToDo", {
+ __newname: "ABC#123",
+ description: "Test this",
+ ignore_duplicate: true,
+ });
+ cy.visit("/app/todo/ABC#123");
+ cy.title().should("eq", "Test this - ABC#123");
+ cy.get_field("description", "Text Editor").contains("Test this");
+ cy.go("back");
+ cy.title().should("eq", "Website");
});
- it.only('Navigate to previous page after login', () => {
- cy.visit('/app/todo');
- cy.get('.page-head').findByTitle('To Do').should('be.visible');
- cy.request('/api/method/logout');
- cy.reload().as('reload');
- cy.get('@reload').get('.page-card .btn-primary').contains('Login').click();
- cy.location('pathname').should('eq', '/login');
+ it.only("Navigate to previous page after login", () => {
+ cy.visit("/app/todo");
+ cy.get(".page-head").findByTitle("To Do").should("be.visible");
+ cy.request("/api/method/logout");
+ cy.reload().as("reload");
+ cy.get("@reload").get(".page-card .btn-primary").contains("Login").click();
+ cy.location("pathname").should("eq", "/login");
cy.login();
- cy.visit('/app');
- cy.location('pathname').should('eq', '/app/todo');
+ cy.visit("/app");
+ cy.location("pathname").should("eq", "/app/todo");
});
});
diff --git a/cypress/integration/number_card.js b/cypress/integration/number_card.js
index a01ff1152d..eb0f19be26 100644
--- a/cypress/integration/number_card.js
+++ b/cypress/integration/number_card.js
@@ -1,22 +1,22 @@
-context('Number Card', () => {
+context("Number Card", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
+ cy.visit("/app/website");
});
- it('Check filter populate for child table doctype', () => {
- cy.visit('/app/number-card/new-number-card-1');
- cy.get('[data-fieldname="parent_document_type"]').should('have.css', 'display', 'none');
+ it("Check filter populate for child table doctype", () => {
+ cy.visit("/app/number-card/new-number-card-1");
+ cy.get('[data-fieldname="parent_document_type"]').should("have.css", "display", "none");
- cy.get_field('document_type', 'Link');
- cy.fill_field('document_type', 'Workspace Link', 'Link').focus().blur();
- cy.get_field('document_type', 'Link').should('have.value', 'Workspace Link');
+ cy.get_field("document_type", "Link");
+ cy.fill_field("document_type", "Workspace Link", "Link").focus().blur();
+ cy.get_field("document_type", "Link").should("have.value", "Workspace Link");
- cy.fill_field('label', 'Test Number Card', 'Data');
+ cy.fill_field("label", "Test Number Card", "Data");
cy.get('[data-fieldname="filters_json"]').click().wait(200);
- cy.get('.modal-body .filter-action-buttons .add-filter').click();
- cy.get('.modal-body .fieldname-select-area').click();
- cy.get('.modal-actions .btn-modal-close').click();
+ cy.get(".modal-body .filter-action-buttons .add-filter").click();
+ cy.get(".modal-body .fieldname-select-area").click();
+ cy.get(".modal-actions .btn-modal-close").click();
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/query_report.js b/cypress/integration/query_report.js
index 43f26f8b50..5dd0ab2d53 100644
--- a/cypress/integration/query_report.js
+++ b/cypress/integration/query_report.js
@@ -1,63 +1,91 @@
-context('Query Report', () => {
+context("Query Report", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- cy.insert_doc('Report', {
- 'report_name': 'Test ToDo Report',
- 'ref_doctype': 'ToDo',
- 'report_type': 'Query Report',
- 'query': 'select * from tabToDo'
- }, true).as('doc');
+ cy.visit("/app/website");
+ cy.insert_doc(
+ "Report",
+ {
+ report_name: "Test ToDo Report",
+ ref_doctype: "ToDo",
+ report_type: "Query Report",
+ query: "select * from tabToDo",
+ },
+ true
+ ).as("doc");
cy.create_records({
- doctype: 'ToDo',
- description: 'this is a test todo for query report'
- }).as('todos');
+ doctype: "ToDo",
+ description: "this is a test todo for query report",
+ }).as("todos");
});
- it('add custom column in report', () => {
- cy.visit('/app/query-report/Permitted Documents For User');
+ it("add custom column in report", () => {
+ cy.visit("/app/query-report/Permitted Documents For User");
- cy.get('.page-form.flex', { timeout: 60000 }).should('have.length', 1).then(() => {
- cy.get('#page-query-report input[data-fieldname="user"]').as('input-user');
- cy.get('@input-user').focus().type('test@erpnext.com', { delay: 100 }).blur();
- cy.wait(300);
- cy.get('#page-query-report input[data-fieldname="doctype"]').as('input-role');
- cy.get('@input-role').focus().type('Role', { delay: 100 }).blur();
+ cy.get(".page-form.flex", { timeout: 60000 })
+ .should("have.length", 1)
+ .then(() => {
+ cy.get('#page-query-report input[data-fieldname="user"]').as("input-user");
+ cy.get("@input-user").focus().type("test@erpnext.com", { delay: 100 }).blur();
+ cy.wait(300);
+ cy.get('#page-query-report input[data-fieldname="doctype"]').as("input-role");
+ cy.get("@input-role").focus().type("Role", { delay: 100 }).blur();
- cy.get('.datatable').should('exist');
- cy.get('#page-query-report .page-actions .menu-btn-group button').click({ force: true });
- cy.get('#page-query-report .menu-btn-group .dropdown-menu').contains('Add Column').click({ force: true });
- cy.get_open_dialog().get('.modal-title').should('contain', 'Add Column');
- cy.get('select[data-fieldname="doctype"]').select("Role", { force: true });
- cy.get('select[data-fieldname="field"]').select("Role Name", { force: true });
- cy.get('select[data-fieldname="insert_after"]').select("Name", { force: true });
- cy.get_open_dialog().findByRole('button', {name: 'Submit'}).click({ force: true });
- cy.get('#page-query-report .page-actions .menu-btn-group button').click({ force: true });
- cy.get('#page-query-report .menu-btn-group .dropdown-menu').contains('Save').click({ timeout: 100, force: true });
- cy.get_open_dialog().get('.modal-title').should('contain', 'Save Report');
+ cy.get(".datatable").should("exist");
+ cy.get("#page-query-report .page-actions .menu-btn-group button").click({
+ force: true,
+ });
+ cy.get("#page-query-report .menu-btn-group .dropdown-menu")
+ .contains("Add Column")
+ .click({ force: true });
+ cy.get_open_dialog().get(".modal-title").should("contain", "Add Column");
+ cy.get('select[data-fieldname="doctype"]').select("Role", { force: true });
+ cy.get('select[data-fieldname="field"]').select("Role Name", { force: true });
+ cy.get('select[data-fieldname="insert_after"]').select("Name", { force: true });
+ cy.get_open_dialog()
+ .findByRole("button", { name: "Submit" })
+ .click({ force: true });
+ cy.get("#page-query-report .page-actions .menu-btn-group button").click({
+ force: true,
+ });
+ cy.get("#page-query-report .menu-btn-group .dropdown-menu")
+ .contains("Save")
+ .click({ timeout: 100, force: true });
+ cy.get_open_dialog().get(".modal-title").should("contain", "Save Report");
- cy.get('input[data-fieldname="report_name"]').type("Test Report", { delay: 100, force: true });
- cy.get_open_dialog().findByRole('button', {name: 'Submit'}).click({ timeout: 1000, force: true });
- });
+ cy.get('input[data-fieldname="report_name"]').type("Test Report", {
+ delay: 100,
+ force: true,
+ });
+ cy.get_open_dialog()
+ .findByRole("button", { name: "Submit" })
+ .click({ timeout: 1000, force: true });
+ });
});
let save_report_and_open = (report, update_name) => {
- cy.get('#page-query-report .page-actions .menu-btn-group button').click({ force: true });
- cy.get('#page-query-report .menu-btn-group .dropdown-menu').contains('Save').click({ timeout: 100, force: true });
- cy.get_open_dialog().get('.modal-title').should('contain', 'Save Report');
+ cy.get("#page-query-report .page-actions .menu-btn-group button").click({ force: true });
+ cy.get("#page-query-report .menu-btn-group .dropdown-menu")
+ .contains("Save")
+ .click({ timeout: 100, force: true });
+ cy.get_open_dialog().get(".modal-title").should("contain", "Save Report");
- cy.get('input[data-fieldname="report_name"]').type(update_name, { delay: 100, force: true });
- cy.get_open_dialog().findByRole('button', {name: 'Submit'}).click({ timeout: 1000, force: true });
+ cy.get('input[data-fieldname="report_name"]').type(update_name, {
+ delay: 100,
+ force: true,
+ });
+ cy.get_open_dialog()
+ .findByRole("button", { name: "Submit" })
+ .click({ timeout: 1000, force: true });
- cy.visit('/app/query-report/'+report);
- cy.get('.datatable').should('exist');
+ cy.visit("/app/query-report/" + report);
+ cy.get(".datatable").should("exist");
};
- it('test multi level query report', () => {
- cy.visit('/app/query-report/Test ToDo Report');
- cy.get('.datatable').should('exist');
+ it("test multi level query report", () => {
+ cy.visit("/app/query-report/Test ToDo Report");
+ cy.get(".datatable").should("exist");
- save_report_and_open('Test ToDo Report 1', ' 1');
- save_report_and_open('Test ToDo Report 11', '1');
+ save_report_and_open("Test ToDo Report 1", " 1");
+ save_report_and_open("Test ToDo Report 11", "1");
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js
index 57d3c01356..de95a852fc 100644
--- a/cypress/integration/recorder.js
+++ b/cypress/integration/recorder.js
@@ -1,66 +1,72 @@
-context.skip('Recorder', () => {
+context.skip("Recorder", () => {
before(() => {
cy.login();
});
beforeEach(() => {
- cy.visit('/app/recorder');
- return cy.window().its('frappe').then(frappe => {
- // reset recorder
- return frappe.xcall("frappe.recorder.stop").then(() => {
- return frappe.xcall("frappe.recorder.delete");
+ cy.visit("/app/recorder");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ // reset recorder
+ return frappe.xcall("frappe.recorder.stop").then(() => {
+ return frappe.xcall("frappe.recorder.delete");
+ });
});
- });
});
- it('Recorder Empty State', () => {
- cy.get('.page-head').findByTitle('Recorder').should('exist');
+ it("Recorder Empty State", () => {
+ cy.get(".page-head").findByTitle("Recorder").should("exist");
- cy.get('.indicator-pill').should('contain', 'Inactive').should('have.class', 'red');
+ cy.get(".indicator-pill").should("contain", "Inactive").should("have.class", "red");
- cy.get('.page-actions').findByRole('button', {name: 'Start'}).should('exist');
- cy.get('.page-actions').findByRole('button', {name: 'Clear'}).should('exist');
+ cy.get(".page-actions").findByRole("button", { name: "Start" }).should("exist");
+ cy.get(".page-actions").findByRole("button", { name: "Clear" }).should("exist");
- cy.get('.msg-box').should('contain', 'Recorder is Inactive');
- cy.get('.msg-box').findByRole('button', {name: 'Start Recording'}).should('exist');
+ cy.get(".msg-box").should("contain", "Recorder is Inactive");
+ cy.get(".msg-box").findByRole("button", { name: "Start Recording" }).should("exist");
});
- it('Recorder Start', () => {
- cy.get('.page-actions').findByRole('button', {name: 'Start'}).click();
- cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green');
+ it("Recorder Start", () => {
+ cy.get(".page-actions").findByRole("button", { name: "Start" }).click();
+ cy.get(".indicator-pill").should("contain", "Active").should("have.class", "green");
- cy.get('.msg-box').should('contain', 'No Requests found');
+ cy.get(".msg-box").should("contain", "No Requests found");
- cy.visit('/app/List/DocType/List');
- cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
- cy.wait('@list_refresh');
+ cy.visit("/app/List/DocType/List");
+ cy.intercept("POST", "/api/method/frappe.desk.reportview.get").as("list_refresh");
+ cy.wait("@list_refresh");
- cy.get('.page-head').findByTitle('DocType').should('exist');
- cy.get('.list-count').should('contain', '20 of ');
+ cy.get(".page-head").findByTitle("DocType").should("exist");
+ cy.get(".list-count").should("contain", "20 of ");
- cy.visit('/app/recorder');
- cy.get('.page-head').findByTitle('Recorder').should('exist');
- cy.get('.frappe-list .result-list').should('contain', '/api/method/frappe.desk.reportview.get');
+ cy.visit("/app/recorder");
+ cy.get(".page-head").findByTitle("Recorder").should("exist");
+ cy.get(".frappe-list .result-list").should(
+ "contain",
+ "/api/method/frappe.desk.reportview.get"
+ );
});
- it('Recorder View Request', () => {
- cy.get('.page-actions').findByRole('button', {name: 'Start'}).click();
+ it("Recorder View Request", () => {
+ cy.get(".page-actions").findByRole("button", { name: "Start" }).click();
- cy.visit('/app/List/DocType/List');
- cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
- cy.wait('@list_refresh');
+ cy.visit("/app/List/DocType/List");
+ cy.intercept("POST", "/api/method/frappe.desk.reportview.get").as("list_refresh");
+ cy.wait("@list_refresh");
- cy.get('.page-head').findByTitle('DocType').should('exist');
- cy.get('.list-count').should('contain', '20 of ');
+ cy.get(".page-head").findByTitle("DocType").should("exist");
+ cy.get(".list-count").should("contain", "20 of ");
- cy.visit('/app/recorder');
+ cy.visit("/app/recorder");
- cy.get('.frappe-list .list-row-container span')
- .contains('/api/method/frappe')
- .should('be.visible')
- .click({force: true});
+ cy.get(".frappe-list .list-row-container span")
+ .contains("/api/method/frappe")
+ .should("be.visible")
+ .click({ force: true });
- cy.url().should('include', '/recorder/request');
- cy.get('form').should('contain', '/api/method/frappe');
+ cy.url().should("include", "/recorder/request");
+ cy.get("form").should("contain", "/api/method/frappe");
});
});
diff --git a/cypress/integration/report_view.js b/cypress/integration/report_view.js
index bacbf9c172..27fe840450 100644
--- a/cypress/integration/report_view.js
+++ b/cypress/integration/report_view.js
@@ -1,42 +1,46 @@
-import custom_submittable_doctype from '../fixtures/custom_submittable_doctype';
+import custom_submittable_doctype from "../fixtures/custom_submittable_doctype";
const doctype_name = custom_submittable_doctype.name;
-context('Report View', () => {
+context("Report View", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- cy.insert_doc('DocType', custom_submittable_doctype, true);
+ cy.visit("/app/website");
+ cy.insert_doc("DocType", custom_submittable_doctype, true);
cy.clear_cache();
- cy.insert_doc(doctype_name, {
- 'title': 'Doc 1',
- 'description': 'Random Text',
- 'enabled': 0,
- 'docstatus': 1 // submit document
- }, true);
+ cy.insert_doc(
+ doctype_name,
+ {
+ title: "Doc 1",
+ description: "Random Text",
+ enabled: 0,
+ docstatus: 1, // submit document
+ },
+ true
+ );
});
- it('Field with enabled allow_on_submit should be editable.', () => {
- cy.intercept('POST', 'api/method/frappe.client.set_value').as('value-update');
+ it("Field with enabled allow_on_submit should be editable.", () => {
+ cy.intercept("POST", "api/method/frappe.client.set_value").as("value-update");
cy.visit(`/app/List/${doctype_name}/Report`);
// check status column added from docstatus
- cy.get('.dt-row-0 > .dt-cell--col-3').should('contain', 'Submitted');
- let cell = cy.get('.dt-row-0 > .dt-cell--col-4');
+ cy.get(".dt-row-0 > .dt-cell--col-3").should("contain", "Submitted");
+ let cell = cy.get(".dt-row-0 > .dt-cell--col-4");
// select the cell
cell.dblclick();
- cell.get('.dt-cell__edit--col-4').findByRole('checkbox').check({ force: true });
- cy.get('.dt-row-0 > .dt-cell--col-3').click(); // click outside
+ cell.get(".dt-cell__edit--col-4").findByRole("checkbox").check({ force: true });
+ cy.get(".dt-row-0 > .dt-cell--col-3").click(); // click outside
- cy.wait('@value-update');
+ cy.wait("@value-update");
- cy.call('frappe.client.get_value', {
+ cy.call("frappe.client.get_value", {
doctype: doctype_name,
filters: {
- title: 'Doc 1',
+ title: "Doc 1",
},
- fieldname: 'enabled'
- }).then(r => {
+ fieldname: "enabled",
+ }).then((r) => {
expect(r.message.enabled).to.equals(1);
});
});
diff --git a/cypress/integration/sidebar.js b/cypress/integration/sidebar.js
index 2831c9bad5..6ed2d4022c 100644
--- a/cypress/integration/sidebar.js
+++ b/cypress/integration/sidebar.js
@@ -1,55 +1,64 @@
-context('Sidebar', () => {
+context("Sidebar", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
- cy.visit('/app/doctype');
+ cy.visit("/app/doctype");
});
it('Test for checking "Assigned To" counter value, adding filter and adding & removing an assignment', () => {
cy.click_sidebar_button("Assigned To");
//To check if no filter is available in "Assigned To" dropdown
- cy.get('.empty-state').should('contain', 'No filters found');
+ cy.get(".empty-state").should("contain", "No filters found");
cy.click_sidebar_button("Created By");
//To check if "Created By" dropdown contains filter
- cy.get('.group-by-item > .dropdown-item').should('contain', 'Me');
+ cy.get(".group-by-item > .dropdown-item").should("contain", "Me");
//Assigning a doctype to a user
- cy.visit('/app/doctype/ToDo');
- cy.get('.form-assignments > .flex > .text-muted').click();
- cy.get_field('assign_to_me', 'Check').click();
- cy.get('.modal-footer > .standard-actions > .btn-primary').click();
- cy.visit('/app/doctype');
+ cy.visit("/app/doctype/ToDo");
+ cy.get(".form-assignments > .flex > .text-muted").click();
+ cy.get_field("assign_to_me", "Check").click();
+ cy.get(".modal-footer > .standard-actions > .btn-primary").click();
+ cy.visit("/app/doctype");
cy.click_sidebar_button("Assigned To");
//To check if filter is added in "Assigned To" dropdown after assignment
- cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').should('contain', '1');
+ cy.get(".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item").should(
+ "contain",
+ "1"
+ );
//To check if there is no filter added to the listview
- cy.get('.filter-selector > .btn').should('contain', 'Filter');
+ cy.get(".filter-selector > .btn").should("contain", "Filter");
//To add a filter to display data into the listview
- cy.get('.group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item').click();
+ cy.get(".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item").click();
//To check if filter is applied
- cy.click_filter_button().should('contain', '1 filter');
- cy.get('.fieldname-select-area > .awesomplete > .form-control').should('have.value', 'Assigned To');
- cy.get('.condition').should('have.value', 'like');
- cy.get('.filter-field > .form-group > .input-with-feedback').should('have.value', '%Administrator%');
+ cy.click_filter_button().should("contain", "1 filter");
+ cy.get(".fieldname-select-area > .awesomplete > .form-control").should(
+ "have.value",
+ "Assigned To"
+ );
+ cy.get(".condition").should("have.value", "like");
+ cy.get(".filter-field > .form-group > .input-with-feedback").should(
+ "have.value",
+ "%Administrator%"
+ );
cy.click_filter_button();
//To remove the applied filter
cy.clear_filters();
//To remove the assignment
- cy.visit('/app/doctype/ToDo');
- cy.get('.assignments > .avatar-group > .avatar > .avatar-frame').click();
- cy.get('.remove-btn').click({force: true});
+ cy.visit("/app/doctype/ToDo");
+ cy.get(".assignments > .avatar-group > .avatar > .avatar-frame").click();
+ cy.get(".remove-btn").click({ force: true });
cy.hide_dialog();
- cy.visit('/app/doctype');
+ cy.visit("/app/doctype");
cy.click_sidebar_button("Assigned To");
- cy.get('.empty-state').should('contain', 'No filters found');
+ cy.get(".empty-state").should("contain", "No filters found");
});
});
diff --git a/cypress/integration/table_multiselect.js b/cypress/integration/table_multiselect.js
index ae93354964..133af44d51 100644
--- a/cypress/integration/table_multiselect.js
+++ b/cypress/integration/table_multiselect.js
@@ -1,51 +1,57 @@
-context('Table MultiSelect', () => {
+context("Table MultiSelect", () => {
before(() => {
cy.login();
});
- let name = 'table multiselect' + Math.random().toString().slice(2, 8);
+ let name = "table multiselect" + Math.random().toString().slice(2, 8);
- it('select value from multiselect dropdown', () => {
- cy.new_form('Assignment Rule');
- cy.fill_field('__newname', name);
- cy.fill_field('document_type', 'Blog Post');
- cy.get('.section-head').contains('Assignment Rules').scrollIntoView();
- cy.fill_field('assign_condition', 'status=="Open"', 'Code');
- cy.get('input[data-fieldname="users"]').focus().as('input');
- cy.get('input[data-fieldname="users"] + ul').should('be.visible');
- cy.get('@input').type('test{enter}', { delay: 100 });
- cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value .btn-link-to-form')
- .as('selected-value');
- cy.get('@selected-value').should('contain', 'test@erpnext.com');
+ it("select value from multiselect dropdown", () => {
+ cy.new_form("Assignment Rule");
+ cy.fill_field("__newname", name);
+ cy.fill_field("document_type", "Blog Post");
+ cy.get(".section-head").contains("Assignment Rules").scrollIntoView();
+ cy.fill_field("assign_condition", 'status=="Open"', "Code");
+ cy.get('input[data-fieldname="users"]').focus().as("input");
+ cy.get('input[data-fieldname="users"] + ul').should("be.visible");
+ cy.get("@input").type("test{enter}", { delay: 100 });
+ cy.get(
+ '.frappe-control[data-fieldname="users"] .form-control .tb-selected-value .btn-link-to-form'
+ ).as("selected-value");
+ cy.get("@selected-value").should("contain", "test@erpnext.com");
- cy.intercept('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
+ cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form");
// trigger save
- cy.get('.primary-action').click();
- cy.wait('@save_form').its('response.statusCode').should('eq', 200);
- cy.get('@selected-value').should('contain', 'test@erpnext.com');
+ cy.get(".primary-action").click();
+ cy.wait("@save_form").its("response.statusCode").should("eq", 200);
+ cy.get("@selected-value").should("contain", "test@erpnext.com");
});
- it('delete value using backspace', () => {
- cy.go_to_list('Assignment Rule');
- cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
- cy.get('input[data-fieldname="users"]').focus().type('{backspace}');
- cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value')
- .should('not.exist');
+ it("delete value using backspace", () => {
+ cy.go_to_list("Assignment Rule");
+ cy.get(`.list-subject:contains("table multiselect")`).last().find("a").click();
+ cy.get('input[data-fieldname="users"]').focus().type("{backspace}");
+ cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value').should(
+ "not.exist"
+ );
});
- it('delete value using x', () => {
- cy.go_to_list('Assignment Rule');
- cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
- cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value').as('existing_value');
- cy.get('@existing_value').find('.btn-remove').click();
- cy.get('@existing_value').should('not.exist');
+ it("delete value using x", () => {
+ cy.go_to_list("Assignment Rule");
+ cy.get(`.list-subject:contains("table multiselect")`).last().find("a").click();
+ cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value').as(
+ "existing_value"
+ );
+ cy.get("@existing_value").find(".btn-remove").click();
+ cy.get("@existing_value").should("not.exist");
});
- it('navigate to selected value', () => {
- cy.go_to_list('Assignment Rule');
- cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
- cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value').as('existing_value');
- cy.get('@existing_value').find('.btn-link-to-form').click();
- cy.location('pathname').should('contain', '/user/test@erpnext.com');
+ it("navigate to selected value", () => {
+ cy.go_to_list("Assignment Rule");
+ cy.get(`.list-subject:contains("table multiselect")`).last().find("a").click();
+ cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value').as(
+ "existing_value"
+ );
+ cy.get("@existing_value").find(".btn-link-to-form").click();
+ cy.location("pathname").should("contain", "/user/test@erpnext.com");
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/theme_switcher_dialog.js b/cypress/integration/theme_switcher_dialog.js
index b4297e5674..158ff3e244 100644
--- a/cypress/integration/theme_switcher_dialog.js
+++ b/cypress/integration/theme_switcher_dialog.js
@@ -1,30 +1,29 @@
-context('Theme Switcher Shortcut', () => {
+context("Theme Switcher Shortcut", () => {
before(() => {
cy.login();
- cy.visit('/app');
+ cy.visit("/app");
});
beforeEach(() => {
cy.reload();
});
- it('Check Toggle', () => {
- cy.open_theme_dialog('{ctrl+shift+g}');
- cy.get('.modal-backdrop').should('exist');
- cy.get('.theme-grid > div').first().click();
- cy.close_theme('{ctrl+shift+g}');
- cy.get('.modal-backdrop').should('not.exist');
+ it("Check Toggle", () => {
+ cy.open_theme_dialog("{ctrl+shift+g}");
+ cy.get(".modal-backdrop").should("exist");
+ cy.get(".theme-grid > div").first().click();
+ cy.close_theme("{ctrl+shift+g}");
+ cy.get(".modal-backdrop").should("not.exist");
});
- it('Check Enter', () => {
- cy.open_theme_dialog('{ctrl+shift+g}');
- cy.get('.theme-grid > div').first().click();
- cy.close_theme('{enter}');
- cy.get('.modal-backdrop').should('not.exist');
+ it("Check Enter", () => {
+ cy.open_theme_dialog("{ctrl+shift+g}");
+ cy.get(".theme-grid > div").first().click();
+ cy.close_theme("{enter}");
+ cy.get(".modal-backdrop").should("not.exist");
});
-
});
-Cypress.Commands.add('open_theme_dialog', (shortcut_keys) => {
- cy.get('body').type(shortcut_keys);
+Cypress.Commands.add("open_theme_dialog", (shortcut_keys) => {
+ cy.get("body").type(shortcut_keys);
+});
+Cypress.Commands.add("close_theme", (shortcut_keys) => {
+ cy.get(".modal-header").type(shortcut_keys);
});
-Cypress.Commands.add('close_theme', (shortcut_keys) => {
- cy.get('.modal-header').type(shortcut_keys);
-});
\ No newline at end of file
diff --git a/cypress/integration/timeline.js b/cypress/integration/timeline.js
index e7308fbaa7..5841891af6 100644
--- a/cypress/integration/timeline.js
+++ b/cypress/integration/timeline.js
@@ -1,83 +1,91 @@
-import custom_submittable_doctype from '../fixtures/custom_submittable_doctype';
+import custom_submittable_doctype from "../fixtures/custom_submittable_doctype";
-context('Timeline', () => {
+context("Timeline", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
});
- it('Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo', () => {
+ it("Adding new ToDo, adding new comment, verifying comment addition & deletion and deleting ToDo", () => {
//Adding new ToDo
- cy.visit('/app/todo/new-todo-1');
- cy.get('[data-fieldname="description"] .ql-editor.ql-blank').type('Test ToDo', {force: true}).wait(200);
- cy.get('.page-head .page-actions').findByRole('button', {name: 'Save'}).click();
+ cy.visit("/app/todo/new-todo-1");
+ cy.get('[data-fieldname="description"] .ql-editor.ql-blank')
+ .type("Test ToDo", { force: true })
+ .wait(200);
+ cy.get(".page-head .page-actions").findByRole("button", { name: "Save" }).click();
- cy.go_to_list('ToDo');
- cy.clear_filters()
+ cy.go_to_list("ToDo");
+ cy.clear_filters();
cy.click_listview_row_item(0);
//To check if the comment box is initially empty and tying some text into it
- cy.get('[data-fieldname="comment"] .ql-editor').should('contain', '').type('Testing Timeline');
+ cy.get('[data-fieldname="comment"] .ql-editor')
+ .should("contain", "")
+ .type("Testing Timeline");
//Adding new comment
- cy.get('.comment-box').findByRole('button', {name: 'Comment'}).click();
+ cy.get(".comment-box").findByRole("button", { name: "Comment" }).click();
//To check if the commented text is visible in the timeline content
- cy.get('.timeline-content').should('contain', 'Testing Timeline');
+ cy.get(".timeline-content").should("contain", "Testing Timeline");
//Editing comment
cy.click_timeline_action_btn("Edit");
- cy.get('.timeline-content [data-fieldname="comment"] .ql-editor').first().type(' 123');
+ cy.get('.timeline-content [data-fieldname="comment"] .ql-editor').first().type(" 123");
cy.click_timeline_action_btn("Save");
//To check if the edited comment text is visible in timeline content
- cy.get('.timeline-content').should('contain', 'Testing Timeline 123');
+ cy.get(".timeline-content").should("contain", "Testing Timeline 123");
//Discarding comment
cy.click_timeline_action_btn("Edit");
cy.click_timeline_action_btn("Dismiss");
//To check if after discarding the timeline content is same as previous
- cy.get('.timeline-content').should('contain', 'Testing Timeline 123');
+ cy.get(".timeline-content").should("contain", "Testing Timeline 123");
//Deleting the added comment
- cy.get('.timeline-message-box .more-actions > .action-btn').click(); //Menu button in timeline item
- cy.get('.timeline-message-box .more-actions .dropdown-item').contains('Delete').click({ force: true });
- cy.get_open_dialog().findByRole('button', {name: 'Yes'}).click({ force: true });
+ cy.get(".timeline-message-box .more-actions > .action-btn").click(); //Menu button in timeline item
+ cy.get(".timeline-message-box .more-actions .dropdown-item")
+ .contains("Delete")
+ .click({ force: true });
+ cy.get_open_dialog().findByRole("button", { name: "Yes" }).click({ force: true });
- cy.get('.timeline-content').should('not.contain', 'Testing Timeline 123');
+ cy.get(".timeline-content").should("not.contain", "Testing Timeline 123");
});
- it('Timeline should have submit and cancel activity information', () => {
- cy.visit('/app/doctype');
+ it("Timeline should have submit and cancel activity information", () => {
+ cy.visit("/app/doctype");
//Creating custom doctype
- cy.insert_doc('DocType', custom_submittable_doctype, true);
+ cy.insert_doc("DocType", custom_submittable_doctype, true);
- cy.visit('/app/custom-submittable-doctype');
- cy.click_listview_primary_button('Add Custom Submittable DocType');
+ cy.visit("/app/custom-submittable-doctype");
+ cy.click_listview_primary_button("Add Custom Submittable DocType");
//Adding a new entry for the created custom doctype
- cy.fill_field('title', 'Test');
- cy.click_modal_primary_button('Save');
- cy.click_modal_primary_button('Submit');
+ cy.fill_field("title", "Test");
+ cy.click_modal_primary_button("Save");
+ cy.click_modal_primary_button("Submit");
- cy.visit('/app/custom-submittable-doctype');
+ cy.visit("/app/custom-submittable-doctype");
cy.click_listview_row_item(0);
//To check if the submission of the documemt is visible in the timeline content
- cy.get('.timeline-content').should('contain', 'Administrator submitted this document');
- cy.get('[id="page-Custom Submittable DocType"] .page-actions').findByRole('button', {name: 'Cancel'}).click();
- cy.get_open_dialog().findByRole('button', {name: 'Yes'}).click();
+ cy.get(".timeline-content").should("contain", "Administrator submitted this document");
+ cy.get('[id="page-Custom Submittable DocType"] .page-actions')
+ .findByRole("button", { name: "Cancel" })
+ .click();
+ cy.get_open_dialog().findByRole("button", { name: "Yes" }).click();
//To check if the cancellation of the documemt is visible in the timeline content
- cy.get('.timeline-content').should('contain', 'Administrator cancelled this document');
+ cy.get(".timeline-content").should("contain", "Administrator cancelled this document");
//Deleting the document
- cy.visit('/app/custom-submittable-doctype');
+ cy.visit("/app/custom-submittable-doctype");
cy.select_listview_row_checkbox(0);
- cy.get('.page-actions').findByRole('button', {name: 'Actions'}).click();
+ cy.get(".page-actions").findByRole("button", { name: "Actions" }).click();
cy.get('.page-actions .actions-btn-group [data-label="Delete"]').click();
- cy.click_modal_primary_button('Yes');
+ cy.click_modal_primary_button("Yes");
});
});
diff --git a/cypress/integration/timeline_email.js b/cypress/integration/timeline_email.js
index 993847bcb8..3a22f49bfa 100644
--- a/cypress/integration/timeline_email.js
+++ b/cypress/integration/timeline_email.js
@@ -1,76 +1,93 @@
-context('Timeline Email', () => {
+context("Timeline Email", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
- cy.visit('/app/todo');
+ cy.visit("/app/todo");
});
- it('Adding new ToDo', () => {
- cy.click_listview_primary_button('Add ToDo');
- cy.get('.custom-actions:visible > .btn').contains("Edit Full Form").click({delay: 500});
- cy.fill_field("description", "Test ToDo", "Text Editor");
+ it("Adding new ToDo", () => {
+ cy.click_listview_primary_button("Add ToDo");
+ cy.get(".custom-actions:visible > .btn").contains("Edit Full Form").click({ delay: 500 });
+ cy.fill_field("description", "Test ToDo", "Text Editor");
cy.wait(500);
- cy.get('.primary-action').contains('Save').click({force: true});
+ cy.get(".primary-action").contains("Save").click({ force: true });
cy.wait(700);
});
- it('Adding email and verifying timeline content for email attachment', () => {
- cy.visit('/app/todo');
- cy.click_listview_row_item_with_text('Test ToDo');
+ it("Adding email and verifying timeline content for email attachment", () => {
+ cy.visit("/app/todo");
+ cy.click_listview_row_item_with_text("Test ToDo");
//Creating a new email
- cy.get('.timeline-actions > .timeline-item > .action-buttons > .action-btn').click();
- cy.fill_field('recipients', 'test@example.com', 'MultiSelect');
- cy.get('.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor').type('Test Mail');
+ cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click();
+ cy.fill_field("recipients", "test@example.com", "MultiSelect");
+ cy.get(
+ '.modal.show > .modal-dialog > .modal-content > .modal-body > :nth-child(1) > .form-layout > .form-page > :nth-child(3) > .section-body > .form-column > form > [data-fieldtype="Text Editor"] > .form-group > .control-input-wrapper > .control-input > .ql-container > .ql-editor'
+ ).type("Test Mail");
//Adding attachment to the email
- cy.get('.add-more-attachments > .btn').click();
- cy.get('.mt-2 > .btn > .mt-1').eq(2).click();
- cy.get('.input-group > .form-control').type('https://wallpaperplay.com/walls/full/8/2/b/72402.jpg');
- cy.get('.btn-primary').contains('Upload').click();
+ cy.get(".add-more-attachments > .btn").click();
+ cy.get(".mt-2 > .btn > .mt-1").eq(2).click();
+ cy.get(".input-group > .form-control").type(
+ "https://wallpaperplay.com/walls/full/8/2/b/72402.jpg"
+ );
+ cy.get(".btn-primary").contains("Upload").click();
//Sending the email
- cy.click_modal_primary_button('Send', {delay: 500});
+ cy.click_modal_primary_button("Send", { delay: 500 });
//To check if the sent mail content is shown in the timeline content
- cy.get('[data-doctype="Communication"] > .timeline-content').should('contain', 'Test Mail');
+ cy.get('[data-doctype="Communication"] > .timeline-content').should(
+ "contain",
+ "Test Mail"
+ );
//To check if the attachment of email is shown in the timeline content
- cy.get('.timeline-content').should('contain', 'Added 72402.jpg');
+ cy.get(".timeline-content").should("contain", "Added 72402.jpg");
//Deleting the sent email
- cy.get('[title="Open Communication"] > .icon').first().click({force: true});
- cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn').click();
- cy.get('#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link').eq(9).click();
- cy.get('.modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary').click();
+ cy.get('[title="Open Communication"] > .icon').first().click({ force: true });
+ cy.get(
+ "#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .btn"
+ ).click();
+ cy.get(
+ "#page-Communication > .page-head > .container > .row > .col > .standard-actions > .menu-btn-group > .dropdown-menu > li > .grey-link"
+ )
+ .eq(9)
+ .click();
+ cy.get(
+ ".modal.show > .modal-dialog > .modal-content > .modal-footer > .standard-actions > .btn-primary"
+ ).click();
});
- it('Deleting attachment and ToDo', () => {
- cy.visit('/app/todo');
- cy.click_listview_row_item_with_text('Test ToDo');
+ it("Deleting attachment and ToDo", () => {
+ cy.visit("/app/todo");
+ cy.click_listview_row_item_with_text("Test ToDo");
//Removing the added attachment
- cy.get('.attachment-row > .data-pill > .remove-btn > .icon').click();
+ cy.get(".attachment-row > .data-pill > .remove-btn > .icon").click();
cy.wait(500);
- cy.get('.modal-footer:visible > .standard-actions > .btn-primary').contains('Yes').click();
+ cy.get(".modal-footer:visible > .standard-actions > .btn-primary").contains("Yes").click();
//To check if the removed attachment is shown in the timeline content
- cy.get('.timeline-content').should('contain', 'Removed 72402.jpg');
+ cy.get(".timeline-content").should("contain", "Removed 72402.jpg");
cy.wait(500);
//To check if the discard button functionality in email is working correctly
- cy.get('.timeline-actions > .timeline-item > .action-buttons > .action-btn').click();
- cy.fill_field('recipients', 'test@example.com', 'MultiSelect');
- cy.get('.modal-footer > .standard-actions > .btn-secondary').contains('Discard').click();
+ cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click();
+ cy.fill_field("recipients", "test@example.com", "MultiSelect");
+ cy.get(".modal-footer > .standard-actions > .btn-secondary").contains("Discard").click();
cy.wait(500);
- cy.get('.timeline-actions > .timeline-item > .action-buttons > .action-btn').click();
+ cy.get(".timeline-actions > .timeline-item > .action-buttons > .action-btn").click();
cy.wait(500);
- cy.get_field('recipients', 'MultiSelect').should('have.text', '');
- cy.get('.modal-header:visible > .modal-actions > .btn-modal-close > .icon').click();
+ cy.get_field("recipients", "MultiSelect").should("have.text", "");
+ cy.get(".modal-header:visible > .modal-actions > .btn-modal-close > .icon").click();
//Deleting the added ToDo
- cy.get('.menu-btn-group:visible > .btn').click();
- cy.get('.menu-btn-group:visible > .dropdown-menu > li > .dropdown-item').contains('Delete').click();
- cy.get('.modal-footer:visible > .standard-actions > .btn-primary').click();
+ cy.get(".menu-btn-group:visible > .btn").click();
+ cy.get(".menu-btn-group:visible > .dropdown-menu > li > .dropdown-item")
+ .contains("Delete")
+ .click();
+ cy.get(".modal-footer:visible > .standard-actions > .btn-primary").click();
});
});
diff --git a/cypress/integration/url_data_field.js b/cypress/integration/url_data_field.js
index cf22c62363..c74bc8b1a2 100644
--- a/cypress/integration/url_data_field.js
+++ b/cypress/integration/url_data_field.js
@@ -1,43 +1,42 @@
-import data_field_validation_doctype from '../fixtures/data_field_validation_doctype';
+import data_field_validation_doctype from "../fixtures/data_field_validation_doctype";
const doctype_name = data_field_validation_doctype.name;
-context('URL Data Field Input', () => {
+context("URL Data Field Input", () => {
before(() => {
cy.login();
- cy.visit('/app/website');
- return cy.insert_doc('DocType', data_field_validation_doctype, true);
+ cy.visit("/app/website");
+ return cy.insert_doc("DocType", data_field_validation_doctype, true);
});
-
- describe('URL Data Field Input ', () => {
- it('should not show URL link button without focus', () => {
+ describe("URL Data Field Input ", () => {
+ it("should not show URL link button without focus", () => {
cy.new_form(doctype_name);
- cy.get_field('url').clear().type('https://frappe.io');
- cy.get_field('url').blur().wait(500);
- cy.get('.link-btn').should('not.be.visible');
+ cy.get_field("url").clear().type("https://frappe.io");
+ cy.get_field("url").blur().wait(500);
+ cy.get(".link-btn").should("not.be.visible");
});
- it('should show URL link button on focus', () => {
- cy.get_field('url').focus().wait(500);
- cy.get('.link-btn').should('be.visible');
+ it("should show URL link button on focus", () => {
+ cy.get_field("url").focus().wait(500);
+ cy.get(".link-btn").should("be.visible");
});
- it('should not show URL link button for invalid URL', () => {
- cy.get_field('url').clear().type('fuzzbuzz');
- cy.get('.link-btn').should('not.be.visible');
+ it("should not show URL link button for invalid URL", () => {
+ cy.get_field("url").clear().type("fuzzbuzz");
+ cy.get(".link-btn").should("not.be.visible");
});
- it('should have valid URL link with target _blank', () => {
- cy.get_field('url').clear().type('https://frappe.io');
- cy.get('.link-btn .btn-open').should('have.attr', 'href', 'https://frappe.io');
- cy.get('.link-btn .btn-open').should('have.attr', 'target', '_blank');
+ it("should have valid URL link with target _blank", () => {
+ cy.get_field("url").clear().type("https://frappe.io");
+ cy.get(".link-btn .btn-open").should("have.attr", "href", "https://frappe.io");
+ cy.get(".link-btn .btn-open").should("have.attr", "target", "_blank");
});
- it('should inject anchor tag in read-only URL data field', () => {
+ it("should inject anchor tag in read-only URL data field", () => {
cy.get('[data-fieldname="read_only_url"]')
- .find('a')
- .should('have.attr', 'target', '_blank');
+ .find("a")
+ .should("have.attr", "target", "_blank");
});
});
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/web_form.js b/cypress/integration/web_form.js
index 74edee0eb9..d69d7ec576 100644
--- a/cypress/integration/web_form.js
+++ b/cypress/integration/web_form.js
@@ -1,256 +1,270 @@
-context('Web Form', () => {
+context("Web Form", () => {
before(() => {
cy.login();
});
- it('Create Web Form', () => {
- cy.visit('/app/web-form/new');
+ it("Create Web Form", () => {
+ cy.visit("/app/web-form/new");
- cy.intercept('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
+ cy.intercept("POST", "/api/method/frappe.desk.form.save.savedocs").as("save_form");
- cy.fill_field('title', 'Note');
- cy.fill_field('doc_type', 'Note', 'Link');
- cy.fill_field('module', 'Website', 'Link');
- cy.click_custom_action_button('Get Fields');
- cy.click_custom_action_button('Publish');
+ cy.fill_field("title", "Note");
+ cy.fill_field("doc_type", "Note", "Link");
+ cy.fill_field("module", "Website", "Link");
+ cy.click_custom_action_button("Get Fields");
+ cy.click_custom_action_button("Publish");
- cy.wait('@save_form');
+ cy.wait("@save_form");
- cy.get_field('route').should('have.value', 'note');
- cy.get('.title-area .indicator-pill').contains('Published');
+ cy.get_field("route").should("have.value", "note");
+ cy.get(".title-area .indicator-pill").contains("Published");
});
- it('Open Web Form (Logged in User)', () => {
- cy.visit('/note');
+ it("Open Web Form (Logged in User)", () => {
+ cy.visit("/note");
- cy.fill_field('title', 'Note 1');
- cy.get('.web-form-actions button').contains('Save').click();
+ cy.fill_field("title", "Note 1");
+ cy.get(".web-form-actions button").contains("Save").click();
- cy.url().should('include', '/note/Note%201');
+ cy.url().should("include", "/note/Note%201");
- cy.visit('/note');
- cy.url().should('include', '/note/Note%201');
+ cy.visit("/note");
+ cy.url().should("include", "/note/Note%201");
});
- it('Open Web Form (Guest)', () => {
- cy.request('/api/method/logout');
- cy.visit('/note');
+ it("Open Web Form (Guest)", () => {
+ cy.request("/api/method/logout");
+ cy.visit("/note");
- cy.url().should('include', '/note/new');
+ cy.url().should("include", "/note/new");
- cy.fill_field('title', 'Guest Note 1');
- cy.get('.web-form-actions button').contains('Save').click();
+ cy.fill_field("title", "Guest Note 1");
+ cy.get(".web-form-actions button").contains("Save").click();
- cy.url().should('include', '/note/new');
+ cy.url().should("include", "/note/new");
- cy.visit('/note');
- cy.url().should('include', '/note/new');
+ cy.visit("/note");
+ cy.url().should("include", "/note/new");
});
- it('Login Required', () => {
+ it("Login Required", () => {
cy.login();
- cy.visit('/app/web-form/note');
+ cy.visit("/app/web-form/note");
- cy.findByRole("tab", {name: "Form Settings"}).click();
- cy.get('input[data-fieldname="login_required"]').check({force: true});
+ cy.findByRole("tab", { name: "Form Settings" }).click();
+ cy.get('input[data-fieldname="login_required"]').check({ force: true });
cy.save();
- cy.visit('/note');
- cy.url().should('include', '/note/Note%201');
+ cy.visit("/note");
+ cy.url().should("include", "/note/Note%201");
- cy.call('logout');
+ cy.call("logout");
- cy.visit('/note');
+ cy.visit("/note");
cy.get_open_dialog()
- .get('.modal-message')
- .contains('You are not permitted to access this page without login.');
+ .get(".modal-message")
+ .contains("You are not permitted to access this page without login.");
});
- it('Show List', () => {
+ it("Show List", () => {
cy.login();
- cy.visit('/app/web-form/note');
+ cy.visit("/app/web-form/note");
- cy.findByRole("tab", {name: "List Settings"}).click();
+ cy.findByRole("tab", { name: "List Settings" }).click();
cy.get('input[data-fieldname="show_list"]').check();
cy.save();
- cy.visit('/note');
- cy.url().should('include', '/note/list');
- cy.get('.web-list-table').should('be.visible');
+ cy.visit("/note");
+ cy.url().should("include", "/note/list");
+ cy.get(".web-list-table").should("be.visible");
});
- it('Show Custom List Title', () => {
- cy.visit('/app/web-form/note');
+ it("Show Custom List Title", () => {
+ cy.visit("/app/web-form/note");
- cy.findByRole("tab", {name: "List Settings"}).click();
- cy.fill_field('list_title', 'Note List');
+ cy.findByRole("tab", { name: "List Settings" }).click();
+ cy.fill_field("list_title", "Note List");
cy.save();
- cy.visit('/note');
- cy.url().should('include', '/note/list');
- cy.get('.web-list-header h1').should('contain.text', 'Note List');
+ cy.visit("/note");
+ cy.url().should("include", "/note/list");
+ cy.get(".web-list-header h1").should("contain.text", "Note List");
});
- it('Show Custom List Columns', () => {
- cy.visit('/note');
- cy.url().should('include', '/note/list');
+ it("Show Custom List Columns", () => {
+ cy.visit("/note");
+ cy.url().should("include", "/note/list");
- cy.get('.web-list-table thead th').contains('Name');
- cy.get('.web-list-table thead th').contains('Title');
+ cy.get(".web-list-table thead th").contains("Name");
+ cy.get(".web-list-table thead th").contains("Title");
- cy.visit('/app/web-form/note');
+ cy.visit("/app/web-form/note");
- cy.findByRole("tab", {name: "List Settings"}).click();
+ cy.findByRole("tab", { name: "List Settings" }).click();
- cy.get('[data-fieldname="list_columns"] .grid-footer button').contains('Add Row').as('add-row');
+ cy.get('[data-fieldname="list_columns"] .grid-footer button')
+ .contains("Add Row")
+ .as("add-row");
- cy.get('@add-row').click();
- cy.get('[data-fieldname="list_columns"] .grid-body .rows').as('grid-rows');
- cy.get('@grid-rows').find('.grid-row:first [data-fieldname="fieldname"]').click();
- cy.get('@grid-rows').find('.grid-row:first select[data-fieldname="fieldname"]').select('Title (Data)');
+ cy.get("@add-row").click();
+ cy.get('[data-fieldname="list_columns"] .grid-body .rows').as("grid-rows");
+ cy.get("@grid-rows").find('.grid-row:first [data-fieldname="fieldname"]').click();
+ cy.get("@grid-rows")
+ .find('.grid-row:first select[data-fieldname="fieldname"]')
+ .select("Title (Data)");
- cy.get('@add-row').click();
- cy.get('@grid-rows').find('.grid-row[data-idx="2"] [data-fieldname="fieldname"]').click();
- cy.get('@grid-rows').find('.grid-row[data-idx="2"] select[data-fieldname="fieldname"]').select('Public (Check)');
+ cy.get("@add-row").click();
+ cy.get("@grid-rows").find('.grid-row[data-idx="2"] [data-fieldname="fieldname"]').click();
+ cy.get("@grid-rows")
+ .find('.grid-row[data-idx="2"] select[data-fieldname="fieldname"]')
+ .select("Public (Check)");
- cy.get('@add-row').click();
- cy.get('@grid-rows').find('.grid-row:last [data-fieldname="fieldname"]').click();
- cy.get('@grid-rows').find('.grid-row:last select[data-fieldname="fieldname"]').select('Content (Text Editor)');
+ cy.get("@add-row").click();
+ cy.get("@grid-rows").find('.grid-row:last [data-fieldname="fieldname"]').click();
+ cy.get("@grid-rows")
+ .find('.grid-row:last select[data-fieldname="fieldname"]')
+ .select("Content (Text Editor)");
cy.save();
- cy.visit('/note');
- cy.url().should('include', '/note/list');
- cy.get('.web-list-table thead th').contains('Title');
- cy.get('.web-list-table thead th').contains('Public');
- cy.get('.web-list-table thead th').contains('Content');
+ cy.visit("/note");
+ cy.url().should("include", "/note/list");
+ cy.get(".web-list-table thead th").contains("Title");
+ cy.get(".web-list-table thead th").contains("Public");
+ cy.get(".web-list-table thead th").contains("Content");
});
- it('Breadcrumbs', () => {
- cy.visit('/note/Note 1');
- cy.get('.breadcrumb-container .breadcrumb .breadcrumb-item:first a')
- .should('contain.text', 'Note').click();
- cy.url().should('include', '/note/list');
+ it("Breadcrumbs", () => {
+ cy.visit("/note/Note 1");
+ cy.get(".breadcrumb-container .breadcrumb .breadcrumb-item:first a")
+ .should("contain.text", "Note")
+ .click();
+ cy.url().should("include", "/note/list");
});
- it('Custom Breadcrumbs', () => {
- cy.visit('/app/web-form/note');
-
- cy.findByRole("tab", {name: "Form Settings"}).click();
- cy.get('.form-section .section-head').contains('Customization').click();
- cy.fill_field('breadcrumbs', '[{"label": _("Notes"), "route":"note"}]', 'Code');
- cy.get('.form-section .section-head').contains('Customization').click();
+ it("Custom Breadcrumbs", () => {
+ cy.visit("/app/web-form/note");
+
+ cy.findByRole("tab", { name: "Form Settings" }).click();
+ cy.get(".form-section .section-head").contains("Customization").click();
+ cy.fill_field("breadcrumbs", '[{"label": _("Notes"), "route":"note"}]', "Code");
+ cy.get(".form-section .section-head").contains("Customization").click();
cy.save();
- cy.visit('/note/Note 1');
- cy.get('.breadcrumb-container .breadcrumb .breadcrumb-item:first a')
- .should('contain.text', 'Notes');
+ cy.visit("/note/Note 1");
+ cy.get(".breadcrumb-container .breadcrumb .breadcrumb-item:first a").should(
+ "contain.text",
+ "Notes"
+ );
});
- it('Read Only', () => {
+ it("Read Only", () => {
cy.login();
- cy.visit('/note');
- cy.url().should('include', '/note/list');
+ cy.visit("/note");
+ cy.url().should("include", "/note/list");
// Read Only Field
cy.get('.web-list-table tbody tr[id="Note 1"]').click();
- cy.get('.frappe-control[data-fieldname="title"] .control-input')
- .should('have.css', 'display', 'none');
+ cy.get('.frappe-control[data-fieldname="title"] .control-input').should(
+ "have.css",
+ "display",
+ "none"
+ );
});
- it('Edit Mode', () => {
- cy.visit('/app/web-form/note');
+ it("Edit Mode", () => {
+ cy.visit("/app/web-form/note");
- cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.findByRole("tab", { name: "Form Settings" }).click();
cy.get('input[data-fieldname="allow_edit"]').check();
cy.save();
- cy.visit('/note/Note 1');
- cy.url().should('include', '/note/Note%201');
+ cy.visit("/note/Note 1");
+ cy.url().should("include", "/note/Note%201");
- cy.get('.web-form-actions a').contains('Edit').click();
- cy.url().should('include', '/note/Note%201/edit');
+ cy.get(".web-form-actions a").contains("Edit").click();
+ cy.url().should("include", "/note/Note%201/edit");
// Editable Field
- cy.get_field('title').should('have.value', 'Note 1');
-
- cy.fill_field('title', ' Edited');
- cy.get('.web-form-actions button').contains('Save').click();
- cy.get_field('title').should('have.value', 'Note 1 Edited');
+ cy.get_field("title").should("have.value", "Note 1");
+
+ cy.fill_field("title", " Edited");
+ cy.get(".web-form-actions button").contains("Save").click();
+ cy.get_field("title").should("have.value", "Note 1 Edited");
});
- it('Allow Multiple Response', () => {
- cy.visit('/app/web-form/note');
+ it("Allow Multiple Response", () => {
+ cy.visit("/app/web-form/note");
- cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.findByRole("tab", { name: "Form Settings" }).click();
cy.get('input[data-fieldname="allow_multiple"]').check();
cy.save();
- cy.visit('/note');
- cy.url().should('include', '/note/list');
+ cy.visit("/note");
+ cy.url().should("include", "/note/list");
- cy.get('.web-list-actions a:visible').contains('New').click();
- cy.url().should('include', '/note/new');
+ cy.get(".web-list-actions a:visible").contains("New").click();
+ cy.url().should("include", "/note/new");
- cy.fill_field('title', 'Note 2');
- cy.get('.web-form-actions button').contains('Save').click();
+ cy.fill_field("title", "Note 2");
+ cy.get(".web-form-actions button").contains("Save").click();
});
- it('Allow Delete', () => {
- cy.visit('/app/web-form/note');
+ it("Allow Delete", () => {
+ cy.visit("/app/web-form/note");
- cy.findByRole("tab", {name: "Form Settings"}).click();
+ cy.findByRole("tab", { name: "Form Settings" }).click();
cy.get('input[data-fieldname="allow_delete"]').check();
cy.save();
- cy.visit('/note');
- cy.url().should('include', '/note/list');
+ cy.visit("/note");
+ cy.url().should("include", "/note/list");
cy.get('.web-list-table tbody tr[id="Note 1"] .list-col-checkbox').click();
cy.get('.web-list-table tbody tr[id="Note 2"] .list-col-checkbox').click();
- cy.get('.web-list-actions button:visible').contains('Delete').click({force: true});
+ cy.get(".web-list-actions button:visible").contains("Delete").click({ force: true });
- cy.get('.web-list-actions button').contains('Delete').should('not.be.visible');
+ cy.get(".web-list-actions button").contains("Delete").should("not.be.visible");
- cy.visit('/note');
- cy.get('.web-list-table tbody tr[id="Note 1"]').should('not.exist');
- cy.get('.web-list-table tbody tr[id="Note 2"]').should('not.exist');
- cy.get('.web-list-table tbody tr[id="Guest Note 1"]').should('exist');
+ cy.visit("/note");
+ cy.get('.web-list-table tbody tr[id="Note 1"]').should("not.exist");
+ cy.get('.web-list-table tbody tr[id="Note 2"]').should("not.exist");
+ cy.get('.web-list-table tbody tr[id="Guest Note 1"]').should("exist");
});
- it('Navigate and Submit a WebForm', () => {
- cy.visit('/update-profile');
+ it("Navigate and Submit a WebForm", () => {
+ cy.visit("/update-profile");
- cy.get('.web-form-actions a').contains('Edit').click();
+ cy.get(".web-form-actions a").contains("Edit").click();
- cy.fill_field('last_name', '_Test User');
+ cy.fill_field("last_name", "_Test User");
- cy.get('.web-form-actions .btn-primary').click();
- cy.url().should('include', '/me');
+ cy.get(".web-form-actions .btn-primary").click();
+ cy.url().should("include", "/me");
});
- it('Navigate and Submit a MultiStep WebForm', () => {
- cy.call('frappe.tests.ui_test_helpers.update_webform_to_multistep').then(() => {
- cy.visit('/update-profile-duplicate');
+ it("Navigate and Submit a MultiStep WebForm", () => {
+ cy.call("frappe.tests.ui_test_helpers.update_webform_to_multistep").then(() => {
+ cy.visit("/update-profile-duplicate");
- cy.get('.web-form-actions a').contains('Edit').click();
+ cy.get(".web-form-actions a").contains("Edit").click();
- cy.fill_field('last_name', '_Test User');
+ cy.fill_field("last_name", "_Test User");
- cy.get('.btn-next').should('be.visible');
- cy.get('.btn-next').click();
+ cy.get(".btn-next").should("be.visible");
+ cy.get(".btn-next").click();
- cy.get('.btn-previous').should('be.visible');
- cy.get('.btn-next').should('not.be.visible');
+ cy.get(".btn-previous").should("be.visible");
+ cy.get(".btn-next").should("not.be.visible");
- cy.get('.web-form-actions .btn-primary').click();
- cy.url().should('include', '/me');
+ cy.get(".web-form-actions .btn-primary").click();
+ cy.url().should("include", "/me");
});
});
});
diff --git a/cypress/integration/workspace.js b/cypress/integration/workspace.js
index a12d86b3d6..e7d97c705b 100644
--- a/cypress/integration/workspace.js
+++ b/cypress/integration/workspace.js
@@ -1,185 +1,214 @@
-context('Workspace 2.0', () => {
+context("Workspace 2.0", () => {
before(() => {
- cy.visit('/login');
+ cy.visit("/login");
cy.login();
});
- it('Navigate to page from sidebar', () => {
- cy.visit('/app/build');
- cy.get('.codex-editor__redactor .ce-block');
+ it("Navigate to page from sidebar", () => {
+ cy.visit("/app/build");
+ cy.get(".codex-editor__redactor .ce-block");
cy.get('.sidebar-item-container[item-name="Settings"]').first().click();
- cy.location('pathname').should('eq', '/app/settings');
+ cy.location("pathname").should("eq", "/app/settings");
});
- it('Create Private Page', () => {
+ it("Create Private Page", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.doctype.workspace.workspace.new_page'
- }).as('new_page');
+ method: "POST",
+ url: "api/method/frappe.desk.doctype.workspace.workspace.new_page",
+ }).as("new_page");
- cy.get('.codex-editor__redactor .ce-block');
+ cy.get(".codex-editor__redactor .ce-block");
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click();
- cy.fill_field('title', 'Test Private Page', 'Data');
- cy.fill_field('icon', 'edit', 'Icon');
- cy.get_open_dialog().find('.modal-header').click();
- cy.get_open_dialog().find('.btn-primary').click();
+ cy.fill_field("title", "Test Private Page", "Data");
+ cy.fill_field("icon", "edit", "Icon");
+ cy.get_open_dialog().find(".modal-header").click();
+ cy.get_open_dialog().find(".btn-primary").click();
// check if sidebar item is added in pubic section
- cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0');
+ cy.get('.sidebar-item-container[item-name="Test Private Page"]').should(
+ "have.attr",
+ "item-public",
+ "0"
+ );
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
cy.wait(300);
- cy.get('.sidebar-item-container[item-name="Test Private Page"]').should('have.attr', 'item-public', '0');
+ cy.get('.sidebar-item-container[item-name="Test Private Page"]').should(
+ "have.attr",
+ "item-public",
+ "0"
+ );
- cy.wait('@new_page');
+ cy.wait("@new_page");
});
- it('Create Child Page', () => {
+ it("Create Child Page", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.doctype.workspace.workspace.new_page'
- }).as('new_page');
+ method: "POST",
+ url: "api/method/frappe.desk.doctype.workspace.workspace.new_page",
+ }).as("new_page");
- cy.get('.codex-editor__redactor .ce-block');
+ cy.get(".codex-editor__redactor .ce-block");
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click();
- cy.fill_field('title', 'Test Child Page', 'Data');
- cy.fill_field('parent', 'Test Private Page', 'Select');
- cy.fill_field('icon', 'edit', 'Icon');
- cy.get_open_dialog().find('.modal-header').click();
- cy.get_open_dialog().find('.btn-primary').click();
+ cy.fill_field("title", "Test Child Page", "Data");
+ cy.fill_field("parent", "Test Private Page", "Select");
+ cy.fill_field("icon", "edit", "Icon");
+ cy.get_open_dialog().find(".modal-header").click();
+ cy.get_open_dialog().find(".btn-primary").click();
// check if sidebar item is added in pubic section
- cy.get('.sidebar-item-container[item-name="Test Child Page"]').should('have.attr', 'item-public', '0');
+ cy.get('.sidebar-item-container[item-name="Test Child Page"]').should(
+ "have.attr",
+ "item-public",
+ "0"
+ );
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
cy.wait(300);
- cy.get('.sidebar-item-container[item-name="Test Child Page"]').should('have.attr', 'item-public', '0');
+ cy.get('.sidebar-item-container[item-name="Test Child Page"]').should(
+ "have.attr",
+ "item-public",
+ "0"
+ );
- cy.wait('@new_page');
+ cy.wait("@new_page");
});
- it('Duplicate Page', () => {
+ it("Duplicate Page", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.doctype.workspace.workspace.duplicate_page'
- }).as('page_duplicated');
+ method: "POST",
+ url: "api/method/frappe.desk.doctype.workspace.workspace.duplicate_page",
+ }).as("page_duplicated");
- cy.get('.codex-editor__redactor .ce-block');
- cy.get('.standard-actions .btn-secondary[data-label=Edit]').click();
+ cy.get(".codex-editor__redactor .ce-block");
+ cy.get(".standard-actions .btn-secondary[data-label=Edit]").click();
- cy.get('.sidebar-item-container[item-name="Test Private Page"]').as('sidebar-item');
+ cy.get('.sidebar-item-container[item-name="Test Private Page"]').as("sidebar-item");
- cy.get('@sidebar-item').find('.standard-sidebar-item').first().click();
- cy.get('@sidebar-item').find('.dropdown-btn').first().click();
- cy.get('@sidebar-item').find('.dropdown-list .dropdown-item').contains('Duplicate').first().click({force: true});
+ cy.get("@sidebar-item").find(".standard-sidebar-item").first().click();
+ cy.get("@sidebar-item").find(".dropdown-btn").first().click();
+ cy.get("@sidebar-item")
+ .find(".dropdown-list .dropdown-item")
+ .contains("Duplicate")
+ .first()
+ .click({ force: true });
- cy.get_open_dialog().fill_field('title', 'Duplicate Page', 'Data');
- cy.click_modal_primary_button('Duplicate');
+ cy.get_open_dialog().fill_field("title", "Duplicate Page", "Data");
+ cy.click_modal_primary_button("Duplicate");
- cy.wait('@page_duplicated');
+ cy.wait("@page_duplicated");
});
- it('Drag Sidebar Item', () => {
+ it("Drag Sidebar Item", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.doctype.workspace.workspace.sort_pages'
- }).as('page_sorted');
+ method: "POST",
+ url: "api/method/frappe.desk.doctype.workspace.workspace.sort_pages",
+ }).as("page_sorted");
- cy.get('.sidebar-item-container[item-name="Duplicate Page"]').as('sidebar-item');
+ cy.get('.sidebar-item-container[item-name="Duplicate Page"]').as("sidebar-item");
- cy.get('@sidebar-item').find('.standard-sidebar-item').first().click();
- cy.get('@sidebar-item').find('.drag-handle').first().move({ deltaX: 0, deltaY: 100 });
+ cy.get("@sidebar-item").find(".standard-sidebar-item").first().click();
+ cy.get("@sidebar-item").find(".drag-handle").first().move({ deltaX: 0, deltaY: 100 });
- cy.get('.sidebar-item-container[item-name="Build"]').as('sidebar-item');
+ cy.get('.sidebar-item-container[item-name="Build"]').as("sidebar-item");
- cy.get('@sidebar-item').find('.standard-sidebar-item').first().click();
- cy.get('@sidebar-item').find('.drag-handle').first().move({ deltaX: 0, deltaY: 100 });
+ cy.get("@sidebar-item").find(".standard-sidebar-item").first().click();
+ cy.get("@sidebar-item").find(".drag-handle").first().move({ deltaX: 0, deltaY: 100 });
- cy.wait('@page_sorted');
+ cy.wait("@page_sorted");
});
- it('Edit Page Detail', () => {
+ it("Edit Page Detail", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.doctype.workspace.workspace.update_page'
- }).as('page_updated');
+ method: "POST",
+ url: "api/method/frappe.desk.doctype.workspace.workspace.update_page",
+ }).as("page_updated");
- cy.get('.sidebar-item-container[item-name="Test Private Page"]').as('sidebar-item');
+ cy.get('.sidebar-item-container[item-name="Test Private Page"]').as("sidebar-item");
- cy.get('@sidebar-item').find('.standard-sidebar-item').first().click();
- cy.get('@sidebar-item').find('.dropdown-btn').first().click();
- cy.get('@sidebar-item').find('.dropdown-list .dropdown-item').contains('Edit').first().click({force: true});
+ cy.get("@sidebar-item").find(".standard-sidebar-item").first().click();
+ cy.get("@sidebar-item").find(".dropdown-btn").first().click();
+ cy.get("@sidebar-item")
+ .find(".dropdown-list .dropdown-item")
+ .contains("Edit")
+ .first()
+ .click({ force: true });
- cy.get_open_dialog().fill_field('title', ' 1', 'Data');
+ cy.get_open_dialog().fill_field("title", " 1", "Data");
cy.get_open_dialog().find('input[data-fieldname="is_public"]').check();
- cy.click_modal_primary_button('Update');
+ cy.click_modal_primary_button("Update");
- cy.get('.standard-sidebar-section:first .sidebar-item-container[item-name="Test Private Page"]').should('not.exist');
- cy.get('.standard-sidebar-section:last .sidebar-item-container[item-name="Test Private Page 1"]').should('exist');
+ cy.get(
+ '.standard-sidebar-section:first .sidebar-item-container[item-name="Test Private Page"]'
+ ).should("not.exist");
+ cy.get(
+ '.standard-sidebar-section:last .sidebar-item-container[item-name="Test Private Page 1"]'
+ ).should("exist");
- cy.wait('@page_updated');
+ cy.wait("@page_updated");
});
- it('Add New Block', () => {
- cy.get('.sidebar-item-container[item-name="Duplicate Page"]').as('sidebar-item');
+ it("Add New Block", () => {
+ cy.get('.sidebar-item-container[item-name="Duplicate Page"]').as("sidebar-item");
- cy.get('@sidebar-item').find('.standard-sidebar-item').first().click();
+ cy.get("@sidebar-item").find(".standard-sidebar-item").first().click();
- cy.get('.ce-block').click().type('{enter}');
- cy.get('.block-list-container .block-list-item').contains('Heading').click();
- cy.get(":focus").type('Header');
- cy.get(".ce-block:last").find('.ce-header').should('exist');
+ cy.get(".ce-block").click().type("{enter}");
+ cy.get(".block-list-container .block-list-item").contains("Heading").click();
+ cy.get(":focus").type("Header");
+ cy.get(".ce-block:last").find(".ce-header").should("exist");
- cy.get('.ce-block:last').click().type('{enter}');
- cy.get('.block-list-container .block-list-item').contains('Text').click();
- cy.get(":focus").type('Paragraph text');
- cy.get(".ce-block:last").find('.ce-paragraph').should('exist');
+ cy.get(".ce-block:last").click().type("{enter}");
+ cy.get(".block-list-container .block-list-item").contains("Text").click();
+ cy.get(":focus").type("Paragraph text");
+ cy.get(".ce-block:last").find(".ce-paragraph").should("exist");
});
- it('Delete A Block', () => {
+ it("Delete A Block", () => {
cy.get(":focus").click();
- cy.get('.paragraph-control .setting-btn').click();
- cy.get('.paragraph-control .dropdown-item').contains('Delete').click();
- cy.get(".ce-block:last").find('.ce-paragraph').should('not.exist');
+ cy.get(".paragraph-control .setting-btn").click();
+ cy.get(".paragraph-control .dropdown-item").contains("Delete").click();
+ cy.get(".ce-block:last").find(".ce-paragraph").should("not.exist");
});
- it('Shrink and Expand A Block', () => {
+ it("Shrink and Expand A Block", () => {
cy.get(":focus").click();
- cy.get('.ce-block:last .setting-btn').click();
- cy.get('.ce-block:last .dropdown-item').contains('Shrink').click();
- cy.get(".ce-block:last").should('have.class', 'col-xs-11');
- cy.get('.ce-block:last .dropdown-item').contains('Shrink').click();
- cy.get(".ce-block:last").should('have.class', 'col-xs-10');
- cy.get('.ce-block:last .dropdown-item').contains('Shrink').click();
- cy.get(".ce-block:last").should('have.class', 'col-xs-9');
- cy.get('.ce-block:last .dropdown-item').contains('Expand').click();
- cy.get(".ce-block:last").should('have.class', 'col-xs-10');
- cy.get('.ce-block:last .dropdown-item').contains('Expand').click();
- cy.get(".ce-block:last").should('have.class', 'col-xs-11');
- cy.get('.ce-block:last .dropdown-item').contains('Expand').click();
- cy.get(".ce-block:last").should('have.class', 'col-xs-12');
+ cy.get(".ce-block:last .setting-btn").click();
+ cy.get(".ce-block:last .dropdown-item").contains("Shrink").click();
+ cy.get(".ce-block:last").should("have.class", "col-xs-11");
+ cy.get(".ce-block:last .dropdown-item").contains("Shrink").click();
+ cy.get(".ce-block:last").should("have.class", "col-xs-10");
+ cy.get(".ce-block:last .dropdown-item").contains("Shrink").click();
+ cy.get(".ce-block:last").should("have.class", "col-xs-9");
+ cy.get(".ce-block:last .dropdown-item").contains("Expand").click();
+ cy.get(".ce-block:last").should("have.class", "col-xs-10");
+ cy.get(".ce-block:last .dropdown-item").contains("Expand").click();
+ cy.get(".ce-block:last").should("have.class", "col-xs-11");
+ cy.get(".ce-block:last .dropdown-item").contains("Expand").click();
+ cy.get(".ce-block:last").should("have.class", "col-xs-12");
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
});
- it('Delete Duplicate Page', () => {
+ it("Delete Duplicate Page", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.doctype.workspace.workspace.delete_page'
- }).as('page_deleted');
+ method: "POST",
+ url: "api/method/frappe.desk.doctype.workspace.workspace.delete_page",
+ }).as("page_deleted");
- cy.get('.codex-editor__redactor .ce-block');
- cy.get('.standard-actions .btn-secondary[data-label=Edit]').click();
+ cy.get(".codex-editor__redactor .ce-block");
+ cy.get(".standard-actions .btn-secondary[data-label=Edit]").click();
cy.get('.sidebar-item-container[item-name="Duplicate Page"]')
- .find('.sidebar-item-control .setting-btn').click();
+ .find(".sidebar-item-control .setting-btn")
+ .click();
cy.get('.sidebar-item-container[item-name="Duplicate Page"]')
- .find('.dropdown-item[title="Delete Workspace"]').click({force: true});
+ .find('.dropdown-item[title="Delete Workspace"]')
+ .click({ force: true });
cy.wait(300);
- cy.get('.modal-footer > .standard-actions > .btn-modal-primary:visible').first().click();
- cy.get('.sidebar-item-container[item-name="Duplicate Page"]').should('not.exist');
+ cy.get(".modal-footer > .standard-actions > .btn-modal-primary:visible").first().click();
+ cy.get('.sidebar-item-container[item-name="Duplicate Page"]').should("not.exist");
- cy.wait('@page_deleted');
+ cy.wait("@page_deleted");
});
-
-});
\ No newline at end of file
+});
diff --git a/cypress/integration/workspace_blocks.js b/cypress/integration/workspace_blocks.js
index 527cacab93..5b3167b3ac 100644
--- a/cypress/integration/workspace_blocks.js
+++ b/cypress/integration/workspace_blocks.js
@@ -1,140 +1,152 @@
-context('Workspace Blocks', () => {
+context("Workspace Blocks", () => {
before(() => {
cy.login();
- cy.visit('/app');
- return cy.window().its('frappe').then(frappe => {
- return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
- });
+ cy.visit("/app");
+ return cy
+ .window()
+ .its("frappe")
+ .then((frappe) => {
+ return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
+ });
});
- it('Create Test Page', () => {
+ it("Create Test Page", () => {
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.doctype.workspace.workspace.new_page'
- }).as('new_page');
+ method: "POST",
+ url: "api/method/frappe.desk.doctype.workspace.workspace.new_page",
+ }).as("new_page");
- cy.visit('/app/website');
- cy.get('.codex-editor__redactor .ce-block');
+ cy.visit("/app/website");
+ cy.get(".codex-editor__redactor .ce-block");
cy.get('.custom-actions button[data-label="Create%20Workspace"]').click();
- cy.fill_field('title', 'Test Block Page', 'Data');
- cy.fill_field('icon', 'edit', 'Icon');
- cy.get_open_dialog().find('.modal-header').click();
- cy.get_open_dialog().find('.btn-primary').click();
+ cy.fill_field("title", "Test Block Page", "Data");
+ cy.fill_field("icon", "edit", "Icon");
+ cy.get_open_dialog().find(".modal-header").click();
+ cy.get_open_dialog().find(".btn-primary").click();
// check if sidebar item is added in private section
- cy.get('.sidebar-item-container[item-name="Test Block Page"]').should('have.attr', 'item-public', '0');
+ cy.get('.sidebar-item-container[item-name="Test Block Page"]').should(
+ "have.attr",
+ "item-public",
+ "0"
+ );
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
cy.wait(300);
- cy.get('.sidebar-item-container[item-name="Test Block Page"]').should('have.attr', 'item-public', '0');
+ cy.get('.sidebar-item-container[item-name="Test Block Page"]').should(
+ "have.attr",
+ "item-public",
+ "0"
+ );
- cy.wait('@new_page');
+ cy.wait("@new_page");
});
- it('Quick List Block', () => {
+ it("Quick List Block", () => {
cy.create_records([
{
- doctype: 'ToDo',
- description: 'Quick List ToDo 1',
- status: 'Open'
+ doctype: "ToDo",
+ description: "Quick List ToDo 1",
+ status: "Open",
},
{
- doctype: 'ToDo',
- description: 'Quick List ToDo 2',
- status: 'Open'
+ doctype: "ToDo",
+ description: "Quick List ToDo 2",
+ status: "Open",
},
{
- doctype: 'ToDo',
- description: 'Quick List ToDo 3',
- status: 'Open'
+ doctype: "ToDo",
+ description: "Quick List ToDo 3",
+ status: "Open",
},
{
- doctype: 'ToDo',
- description: 'Quick List ToDo 4',
- status: 'Open'
- }
+ doctype: "ToDo",
+ description: "Quick List ToDo 4",
+ status: "Open",
+ },
]);
cy.intercept({
- method: 'GET',
- url: 'api/method/frappe.desk.form.load.getdoctype'
- }).as('get_doctype');
+ method: "GET",
+ url: "api/method/frappe.desk.form.load.getdoctype",
+ }).as("get_doctype");
- cy.get('.codex-editor__redactor .ce-block');
- cy.get('.standard-actions .btn-secondary[data-label=Edit]').click();
+ cy.get(".codex-editor__redactor .ce-block");
+ cy.get(".standard-actions .btn-secondary[data-label=Edit]").click();
// test quick list creation
- cy.get('.ce-block').first().click({force: true}).type('{enter}');
- cy.get('.block-list-container .block-list-item').contains('Quick List').click();
+ cy.get(".ce-block").first().click({ force: true }).type("{enter}");
+ cy.get(".block-list-container .block-list-item").contains("Quick List").click();
- cy.get_open_dialog().find('.modal-header').click();
+ cy.get_open_dialog().find(".modal-header").click();
- cy.fill_field('document_type', 'ToDo', 'Link').blur();
- cy.fill_field('label', 'ToDo', 'Data').blur();
- cy.wait('@get_doctype');
+ cy.fill_field("document_type", "ToDo", "Link").blur();
+ cy.fill_field("label", "ToDo", "Data").blur();
+ cy.wait("@get_doctype");
- cy.get_open_dialog().find('.filter-edit-area').should('contain', 'No filters selected');
- cy.get_open_dialog().find('.filter-area .add-filter').click();
+ cy.get_open_dialog().find(".filter-edit-area").should("contain", "No filters selected");
+ cy.get_open_dialog().find(".filter-area .add-filter").click();
- cy.get_open_dialog().find('.fieldname-select-area input').type('Workflow State{enter}').blur();
- cy.get_open_dialog().find('.filter-field .input-with-feedback').type('Pending');
+ cy.get_open_dialog()
+ .find(".fieldname-select-area input")
+ .type("Workflow State{enter}")
+ .blur();
+ cy.get_open_dialog().find(".filter-field .input-with-feedback").type("Pending");
- cy.get_open_dialog().find('.modal-header').click();
- cy.get_open_dialog().find('.btn-primary').click();
+ cy.get_open_dialog().find(".modal-header").click();
+ cy.get_open_dialog().find(".btn-primary").click();
cy.get('.standard-actions .btn-primary[data-label="Save"]').click();
+ cy.get(".codex-editor__redactor .ce-block");
- cy.get('.codex-editor__redactor .ce-block');
+ cy.get(".ce-block .quick-list-widget-box").first().as("todo-quick-list");
- cy.get('.ce-block .quick-list-widget-box').first().as('todo-quick-list');
-
- cy.get('@todo-quick-list').find('.quick-list-item .status').should('contain', 'Pending');
+ cy.get("@todo-quick-list").find(".quick-list-item .status").should("contain", "Pending");
// test quick-list-item
- cy.get('@todo-quick-list').find('.quick-list-item .title')
+ cy.get("@todo-quick-list")
+ .find(".quick-list-item .title")
.first()
- .invoke('attr', 'title')
- .then(title => {
- cy.get('@todo-quick-list').find('.quick-list-item').contains(title).click();
- cy.get_field('description', 'Text Editor').should('contain', title);
- cy.click_action_button('Approve');
+ .invoke("attr", "title")
+ .then((title) => {
+ cy.get("@todo-quick-list").find(".quick-list-item").contains(title).click();
+ cy.get_field("description", "Text Editor").should("contain", title);
+ cy.click_action_button("Approve");
});
- cy.go('back');
+ cy.go("back");
// test filter-list
- cy.get('@todo-quick-list').realHover().find('.widget-control .filter-list').click();
+ cy.get("@todo-quick-list").realHover().find(".widget-control .filter-list").click();
- cy.get_open_dialog().find('.filter-field .input-with-feedback').type('{selectall}Approved');
- cy.get_open_dialog().find('.modal-header').click();
- cy.get_open_dialog().find('.btn-primary').click();
-
- cy.get('@todo-quick-list').find('.quick-list-item .status').should('contain', 'Approved');
+ cy.get_open_dialog()
+ .find(".filter-field .input-with-feedback")
+ .type("{selectall}Approved");
+ cy.get_open_dialog().find(".modal-header").click();
+ cy.get_open_dialog().find(".btn-primary").click();
+ cy.get("@todo-quick-list").find(".quick-list-item .status").should("contain", "Approved");
// test refresh-list
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.desk.reportview.get'
- }).as('refresh-list');
-
- cy.get('@todo-quick-list').realHover().find('.widget-control .refresh-list').click();
- cy.wait('@refresh-list');
+ method: "POST",
+ url: "api/method/frappe.desk.reportview.get",
+ }).as("refresh-list");
+ cy.get("@todo-quick-list").realHover().find(".widget-control .refresh-list").click();
+ cy.wait("@refresh-list");
// test add-new
- cy.get('@todo-quick-list').realHover().find('.widget-control .add-new').click();
- cy.url().should('include', `/todo/new-todo-1`);
- cy.go('back');
-
+ cy.get("@todo-quick-list").realHover().find(".widget-control .add-new").click();
+ cy.url().should("include", `/todo/new-todo-1`);
+ cy.go("back");
// test see-all
- cy.get('@todo-quick-list').find('.widget-footer .see-all').click();
+ cy.get("@todo-quick-list").find(".widget-footer .see-all").click();
cy.open_list_filter();
cy.get('.filter-field input[data-fieldname="workflow_state"]')
- .invoke('val')
- .should('eq', 'Pending');
- cy.go('back');
+ .invoke("val")
+ .should("eq", "Pending");
+ cy.go("back");
});
-
-});
\ No newline at end of file
+});
diff --git a/cypress/plugins/index.js b/cypress/plugins/index.js
index 9720faa666..b13275373c 100644
--- a/cypress/plugins/index.js
+++ b/cypress/plugins/index.js
@@ -12,6 +12,6 @@
// the project's config changing)
module.exports = (on, config) => {
- require('@cypress/code-coverage/task')(on, config);
+ require("@cypress/code-coverage/task")(on, config);
return config;
-};
\ No newline at end of file
+};
diff --git a/cypress/support/commands.js b/cypress/support/commands.js
index 5424e8c6e4..cbb88cb8cb 100644
--- a/cypress/support/commands.js
+++ b/cypress/support/commands.js
@@ -1,6 +1,6 @@
-import 'cypress-file-upload';
-import '@testing-library/cypress/add-commands';
-import '@4tw/cypress-drag-drop';
+import "cypress-file-upload";
+import "@testing-library/cypress/add-commands";
+import "@4tw/cypress-drag-drop";
import "cypress-real-events/support";
// ***********************************************
// This example commands.js shows you how to
@@ -28,296 +28,304 @@ import "cypress-real-events/support";
// -- This is will overwrite an existing command --
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... });
-Cypress.Commands.add('login', (email, password) => {
+Cypress.Commands.add("login", (email, password) => {
if (!email) {
- email = 'Administrator';
+ email = "Administrator";
}
if (!password) {
- password = Cypress.env('adminPassword');
+ password = Cypress.env("adminPassword");
}
cy.request({
- url: '/api/method/login',
- method: 'POST',
+ url: "/api/method/login",
+ method: "POST",
body: {
usr: email,
- pwd: password
- }
+ pwd: password,
+ },
});
});
-Cypress.Commands.add('call', (method, args) => {
+Cypress.Commands.add("call", (method, args) => {
return cy
.window()
- .its('frappe.csrf_token')
- .then(csrf_token => {
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
return cy
.request({
url: `/api/method/${method}`,
- method: 'POST',
+ method: "POST",
body: args,
headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'X-Frappe-CSRF-Token': csrf_token
- }
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
})
- .then(res => {
+ .then((res) => {
expect(res.status).eq(200);
return res.body;
});
});
});
-Cypress.Commands.add('get_list', (doctype, fields = [], filters = []) => {
+Cypress.Commands.add("get_list", (doctype, fields = [], filters = []) => {
filters = JSON.stringify(filters);
fields = JSON.stringify(fields);
let url = `/api/resource/${doctype}?fields=${fields}&filters=${filters}`;
return cy
.window()
- .its('frappe.csrf_token')
- .then(csrf_token => {
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
return cy
.request({
- method: 'GET',
+ method: "GET",
url,
headers: {
- Accept: 'application/json',
- 'X-Frappe-CSRF-Token': csrf_token
- }
+ Accept: "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
})
- .then(res => {
+ .then((res) => {
expect(res.status).eq(200);
return res.body;
});
});
});
-Cypress.Commands.add('get_doc', (doctype, name) => {
+Cypress.Commands.add("get_doc", (doctype, name) => {
return cy
.window()
- .its('frappe.csrf_token')
- .then(csrf_token => {
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
return cy
.request({
- method: 'GET',
+ method: "GET",
url: `/api/resource/${doctype}/${name}`,
headers: {
- Accept: 'application/json',
- 'X-Frappe-CSRF-Token': csrf_token
- }
+ Accept: "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
})
- .then(res => {
+ .then((res) => {
expect(res.status).eq(200);
return res.body;
});
});
});
-Cypress.Commands.add('remove_doc', (doctype, name) => {
+Cypress.Commands.add("remove_doc", (doctype, name) => {
return cy
.window()
- .its('frappe.csrf_token')
- .then(csrf_token => {
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
return cy
.request({
- method: 'DELETE',
+ method: "DELETE",
url: `/api/resource/${doctype}/${name}`,
headers: {
- Accept: 'application/json',
- 'X-Frappe-CSRF-Token': csrf_token
- }
+ Accept: "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
+ },
})
- .then(res => {
+ .then((res) => {
expect(res.status).eq(202);
return res.body;
});
});
});
-Cypress.Commands.add('create_records', doc => {
+Cypress.Commands.add("create_records", (doc) => {
return cy
- .call('frappe.tests.ui_test_helpers.create_if_not_exists', {doc: JSON.stringify(doc)})
- .then(r => r.message);
+ .call("frappe.tests.ui_test_helpers.create_if_not_exists", { doc: JSON.stringify(doc) })
+ .then((r) => r.message);
});
-Cypress.Commands.add('set_value', (doctype, name, obj) => {
- return cy.call('frappe.client.set_value', {
+Cypress.Commands.add("set_value", (doctype, name, obj) => {
+ return cy.call("frappe.client.set_value", {
doctype,
name,
- fieldname: obj
+ fieldname: obj,
});
});
-Cypress.Commands.add('fill_field', (fieldname, value, fieldtype = 'Data') => {
- cy.get_field(fieldname, fieldtype).as('input');
+Cypress.Commands.add("fill_field", (fieldname, value, fieldtype = "Data") => {
+ cy.get_field(fieldname, fieldtype).as("input");
- if (['Date', 'Time', 'Datetime'].includes(fieldtype)) {
- cy.get('@input').click().wait(200);
- cy.get('.datepickers-container .datepicker.active').should('exist');
+ if (["Date", "Time", "Datetime"].includes(fieldtype)) {
+ cy.get("@input").click().wait(200);
+ cy.get(".datepickers-container .datepicker.active").should("exist");
}
- if (fieldtype === 'Time') {
- cy.get('@input').clear().wait(200);
+ if (fieldtype === "Time") {
+ cy.get("@input").clear().wait(200);
}
- if (fieldtype === 'Select') {
- cy.get('@input').select(value);
+ if (fieldtype === "Select") {
+ cy.get("@input").select(value);
} else {
- cy.get('@input').type(value, {
+ cy.get("@input").type(value, {
waitForAnimations: false,
parseSpecialCharSequences: false,
force: true,
- delay: 100
+ delay: 100,
});
}
- return cy.get('@input');
+ return cy.get("@input");
});
-Cypress.Commands.add('get_field', (fieldname, fieldtype = 'Data') => {
- let field_element = fieldtype === 'Select' ? 'select': 'input';
+Cypress.Commands.add("get_field", (fieldname, fieldtype = "Data") => {
+ let field_element = fieldtype === "Select" ? "select" : "input";
let selector = `[data-fieldname="${fieldname}"] ${field_element}:visible`;
- if (fieldtype === 'Text Editor') {
+ if (fieldtype === "Text Editor") {
selector = `[data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]:visible`;
}
- if (fieldtype === 'Code') {
+ if (fieldtype === "Code") {
selector = `[data-fieldname="${fieldname}"] .ace_text-input`;
}
- if (fieldtype === 'Markdown Editor') {
+ if (fieldtype === "Markdown Editor") {
selector = `[data-fieldname="${fieldname}"] .ace-editor-target`;
}
return cy.get(selector).first();
});
-Cypress.Commands.add('fill_table_field', (tablefieldname, row_idx, fieldname, value, fieldtype = 'Data') => {
- cy.get_table_field(tablefieldname, row_idx, fieldname, fieldtype).as('input');
+Cypress.Commands.add(
+ "fill_table_field",
+ (tablefieldname, row_idx, fieldname, value, fieldtype = "Data") => {
+ cy.get_table_field(tablefieldname, row_idx, fieldname, fieldtype).as("input");
- if (['Date', 'Time', 'Datetime'].includes(fieldtype)) {
- cy.get('@input').click().wait(200);
- cy.get('.datepickers-container .datepicker.active').should('exist');
- }
- if (fieldtype === 'Time') {
- cy.get('@input').clear().wait(200);
- }
+ if (["Date", "Time", "Datetime"].includes(fieldtype)) {
+ cy.get("@input").click().wait(200);
+ cy.get(".datepickers-container .datepicker.active").should("exist");
+ }
+ if (fieldtype === "Time") {
+ cy.get("@input").clear().wait(200);
+ }
- if (fieldtype === 'Select') {
- cy.get('@input').select(value);
- } else {
- cy.get('@input').type(value, {waitForAnimations: false, force: true});
+ if (fieldtype === "Select") {
+ cy.get("@input").select(value);
+ } else {
+ cy.get("@input").type(value, { waitForAnimations: false, force: true });
+ }
+ return cy.get("@input");
}
- return cy.get('@input');
+);
+
+Cypress.Commands.add(
+ "get_table_field",
+ (tablefieldname, row_idx, fieldname, fieldtype = "Data") => {
+ let selector = `.frappe-control[data-fieldname="${tablefieldname}"]`;
+ selector += ` [data-idx="${row_idx}"]`;
+
+ if (fieldtype === "Text Editor") {
+ selector += ` [data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
+ } else if (fieldtype === "Code") {
+ selector += ` [data-fieldname="${fieldname}"] .ace_text-input`;
+ } else {
+ selector += ` [data-fieldname="${fieldname}"]`;
+ return cy.get(selector).find(".form-control:visible, .static-area:visible").first();
+ }
+ return cy.get(selector);
+ }
+);
+
+Cypress.Commands.add("awesomebar", (text) => {
+ cy.get("#navbar-search").type(`${text}{downarrow}{enter}`, { delay: 700 });
});
-Cypress.Commands.add('get_table_field', (tablefieldname, row_idx, fieldname, fieldtype = 'Data') => {
- let selector = `.frappe-control[data-fieldname="${tablefieldname}"]`;
- selector += ` [data-idx="${row_idx}"]`;
-
- if (fieldtype === 'Text Editor') {
- selector += ` [data-fieldname="${fieldname}"] .ql-editor[contenteditable=true]`;
- } else if (fieldtype === 'Code') {
- selector += ` [data-fieldname="${fieldname}"] .ace_text-input`;
- } else {
- selector += ` [data-fieldname="${fieldname}"]`;
- return cy.get(selector).find('.form-control:visible, .static-area:visible').first();
- }
- return cy.get(selector);
-});
-
-Cypress.Commands.add('awesomebar', text => {
- cy.get('#navbar-search').type(`${text}{downarrow}{enter}`, {delay: 700});
-});
-
-Cypress.Commands.add('new_form', doctype => {
- let dt_in_route = doctype.toLowerCase().replace(/ /g, '-');
+Cypress.Commands.add("new_form", (doctype) => {
+ let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
cy.visit(`/app/${dt_in_route}/new`);
- cy.get('body').should('have.attr', 'data-route', `Form/${doctype}/new-${dt_in_route}-1`);
- cy.get('body').should('have.attr', 'data-ajax-state', 'complete');
+ cy.get("body").should("have.attr", "data-route", `Form/${doctype}/new-${dt_in_route}-1`);
+ cy.get("body").should("have.attr", "data-ajax-state", "complete");
});
-Cypress.Commands.add('go_to_list', doctype => {
- let dt_in_route = doctype.toLowerCase().replace(/ /g, '-');
+Cypress.Commands.add("go_to_list", (doctype) => {
+ let dt_in_route = doctype.toLowerCase().replace(/ /g, "-");
cy.visit(`/app/${dt_in_route}`);
});
-Cypress.Commands.add('clear_cache', () => {
+Cypress.Commands.add("clear_cache", () => {
cy.window()
- .its('frappe')
- .then(frappe => {
+ .its("frappe")
+ .then((frappe) => {
frappe.ui.toolbar.clear_cache();
});
});
-Cypress.Commands.add('dialog', opts => {
- return cy.window({ log: false }).its('frappe', { log: false }).then(frappe => {
- Cypress.log({
- name: "dialog",
- displayName: "dialog",
- message: 'frappe.ui.Dialog',
- consoleProps: () => {
- return {
- options: opts,
- dialog: d
- }
- }
+Cypress.Commands.add("dialog", (opts) => {
+ return cy
+ .window({ log: false })
+ .its("frappe", { log: false })
+ .then((frappe) => {
+ Cypress.log({
+ name: "dialog",
+ displayName: "dialog",
+ message: "frappe.ui.Dialog",
+ consoleProps: () => {
+ return {
+ options: opts,
+ dialog: d,
+ };
+ },
+ });
+
+ var d = new frappe.ui.Dialog(opts);
+ d.show();
+ return d;
});
-
- var d = new frappe.ui.Dialog(opts);
- d.show();
- return d;
- });
});
-Cypress.Commands.add('get_open_dialog', () => {
- return cy.get('.modal:visible').last();
+Cypress.Commands.add("get_open_dialog", () => {
+ return cy.get(".modal:visible").last();
});
-Cypress.Commands.add('save', () => {
- cy.intercept('/api').as('api');
- cy.get(`button[data-label="Save"]:visible`).click({scrollBehavior: false, force: true});
- cy.wait('@api');
+Cypress.Commands.add("save", () => {
+ cy.intercept("/api").as("api");
+ cy.get(`button[data-label="Save"]:visible`).click({ scrollBehavior: false, force: true });
+ cy.wait("@api");
});
-Cypress.Commands.add('hide_dialog', () => {
+Cypress.Commands.add("hide_dialog", () => {
cy.wait(300);
- cy.get_open_dialog().focus().find('.btn-modal-close').click();
- cy.get('.modal:visible').should('not.exist');
+ cy.get_open_dialog().focus().find(".btn-modal-close").click();
+ cy.get(".modal:visible").should("not.exist");
});
-Cypress.Commands.add('clear_dialogs', () => {
+Cypress.Commands.add("clear_dialogs", () => {
cy.window().then((win) => {
- win.$('.modal, .modal-backdrop').remove();
+ win.$(".modal, .modal-backdrop").remove();
});
- cy.get('.modal').should('not.exist');
+ cy.get(".modal").should("not.exist");
});
-Cypress.Commands.add('clear_datepickers', () => {
+Cypress.Commands.add("clear_datepickers", () => {
cy.window().then((win) => {
- win.$('.datepicker').remove();
+ win.$(".datepicker").remove();
});
- cy.get('.datepicker').should('not.exist');
+ cy.get(".datepicker").should("not.exist");
});
-
-Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => {
+Cypress.Commands.add("insert_doc", (doctype, args, ignore_duplicate) => {
if (!args.doctype) {
args.doctype = doctype;
}
return cy
.window()
- .its('frappe.csrf_token')
- .then(csrf_token => {
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
return cy
.request({
- method: 'POST',
+ method: "POST",
url: `/api/resource/${doctype}`,
body: args,
headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'X-Frappe-CSRF-Token': csrf_token
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
},
- failOnStatusCode: !ignore_duplicate
+ failOnStatusCode: !ignore_duplicate,
})
- .then(res => {
+ .then((res) => {
let status_codes = [200];
if (ignore_duplicate) {
status_codes.push(409);
@@ -325,7 +333,11 @@ Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => {
let message = null;
if (ignore_duplicate && !status_codes.includes(res.status)) {
- message = `Document insert failed, response: ${JSON.stringify(res, null, '\t')}`;
+ message = `Document insert failed, response: ${JSON.stringify(
+ res,
+ null,
+ "\t"
+ )}`;
}
expect(res.status).to.be.oneOf(status_codes, message);
return res.body.data;
@@ -333,112 +345,117 @@ Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => {
});
});
-Cypress.Commands.add('update_doc', (doctype, docname, args) => {
+Cypress.Commands.add("update_doc", (doctype, docname, args) => {
return cy
.window()
- .its('frappe.csrf_token')
- .then(csrf_token => {
+ .its("frappe.csrf_token")
+ .then((csrf_token) => {
return cy
.request({
- method: 'PUT',
+ method: "PUT",
url: `/api/resource/${doctype}/${docname}`,
body: args,
headers: {
- Accept: 'application/json',
- 'Content-Type': 'application/json',
- 'X-Frappe-CSRF-Token': csrf_token
+ Accept: "application/json",
+ "Content-Type": "application/json",
+ "X-Frappe-CSRF-Token": csrf_token,
},
})
- .then(res => {
+ .then((res) => {
expect(res.status).to.eq(200);
return res.body.data;
});
});
});
-
-Cypress.Commands.add('open_list_filter', () => {
- cy.get('.filter-section .filter-button').click();
+Cypress.Commands.add("open_list_filter", () => {
+ cy.get(".filter-section .filter-button").click();
cy.wait(300);
- cy.get('.filter-popover').should('exist');
+ cy.get(".filter-popover").should("exist");
});
-Cypress.Commands.add('click_custom_action_button', (name) => {
+Cypress.Commands.add("click_custom_action_button", (name) => {
cy.get(`.custom-actions [data-label="${encodeURIComponent(name)}"]`).click();
});
-Cypress.Commands.add('click_action_button', (name) => {
- cy.findByRole('button', {name: 'Actions'}).click();
+Cypress.Commands.add("click_action_button", (name) => {
+ cy.findByRole("button", { name: "Actions" }).click();
cy.get(`.actions-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
});
-Cypress.Commands.add('click_menu_button', (name) => {
- cy.get('.standard-actions .menu-btn-group > .btn').click();
+Cypress.Commands.add("click_menu_button", (name) => {
+ cy.get(".standard-actions .menu-btn-group > .btn").click();
cy.get(`.menu-btn-group [data-label="${encodeURIComponent(name)}"]`).click();
});
-Cypress.Commands.add('clear_filters', () => {
+Cypress.Commands.add("clear_filters", () => {
let has_filter = false;
cy.intercept({
- method: 'POST',
- url: 'api/method/frappe.model.utils.user_settings.save'
- }).as('filter-saved');
- cy.get('.filter-section .filter-button').click({force: true});
+ method: "POST",
+ url: "api/method/frappe.model.utils.user_settings.save",
+ }).as("filter-saved");
+ cy.get(".filter-section .filter-button").click({ force: true });
cy.wait(300);
- cy.get('.filter-popover').should('exist');
- cy.get('.filter-popover').then(popover => {
- if (popover.find('input.input-with-feedback')[0].value != '') {
+ cy.get(".filter-popover").should("exist");
+ cy.get(".filter-popover").then((popover) => {
+ if (popover.find("input.input-with-feedback")[0].value != "") {
has_filter = true;
}
});
- cy.get('.filter-popover').find('.clear-filters').click();
- cy.get('.filter-section .filter-button').click();
- cy.window().its('cur_list').then(cur_list => {
- cur_list && cur_list.filter_area && cur_list.filter_area.clear();
- has_filter && cy.wait('@filter-saved');
- });
+ cy.get(".filter-popover").find(".clear-filters").click();
+ cy.get(".filter-section .filter-button").click();
+ cy.window()
+ .its("cur_list")
+ .then((cur_list) => {
+ cur_list && cur_list.filter_area && cur_list.filter_area.clear();
+ has_filter && cy.wait("@filter-saved");
+ });
});
-Cypress.Commands.add('click_modal_primary_button', (btn_name) => {
+Cypress.Commands.add("click_modal_primary_button", (btn_name) => {
cy.wait(400);
- cy.get('.modal-footer > .standard-actions > .btn-primary').contains(btn_name).click({force: true});
+ cy.get(".modal-footer > .standard-actions > .btn-primary")
+ .contains(btn_name)
+ .click({ force: true });
});
-Cypress.Commands.add('click_sidebar_button', (btn_name) => {
- cy.get('.list-group-by-fields .list-link > a').contains(btn_name).click({force: true});
+Cypress.Commands.add("click_sidebar_button", (btn_name) => {
+ cy.get(".list-group-by-fields .list-link > a").contains(btn_name).click({ force: true });
});
-Cypress.Commands.add('click_listview_row_item', (row_no) => {
- cy.get('.list-row > .level-left > .list-subject > .level-item > .ellipsis').eq(row_no).click({force: true});
+Cypress.Commands.add("click_listview_row_item", (row_no) => {
+ cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
+ .eq(row_no)
+ .click({ force: true });
});
-Cypress.Commands.add('click_listview_row_item_with_text', (text) => {
- cy.get('.list-row > .level-left > .list-subject > .level-item > .ellipsis')
+Cypress.Commands.add("click_listview_row_item_with_text", (text) => {
+ cy.get(".list-row > .level-left > .list-subject > .level-item > .ellipsis")
.contains(text)
.first()
- .click({force: true});
+ .click({ force: true });
});
-Cypress.Commands.add('click_filter_button', () => {
- cy.get('.filter-selector > .btn').click();
+Cypress.Commands.add("click_filter_button", () => {
+ cy.get(".filter-selector > .btn").click();
});
-Cypress.Commands.add('click_listview_primary_button', (btn_name) => {
- cy.get('.primary-action').contains(btn_name).click({force: true});
+Cypress.Commands.add("click_listview_primary_button", (btn_name) => {
+ cy.get(".primary-action").contains(btn_name).click({ force: true });
});
-Cypress.Commands.add('click_doc_primary_button', (btn_name) => {
- cy.get('.primary-action').contains(btn_name).click({force: true});
+Cypress.Commands.add("click_doc_primary_button", (btn_name) => {
+ cy.get(".primary-action").contains(btn_name).click({ force: true });
});
-Cypress.Commands.add('click_timeline_action_btn', (btn_name) => {
- cy.get('.timeline-message-box .actions .action-btn').contains(btn_name).click();
+Cypress.Commands.add("click_timeline_action_btn", (btn_name) => {
+ cy.get(".timeline-message-box .actions .action-btn").contains(btn_name).click();
});
-Cypress.Commands.add('select_listview_row_checkbox', (row_no) => {
- cy.get('.frappe-list .select-like > .list-row-checkbox').eq(row_no).click();
+Cypress.Commands.add("select_listview_row_checkbox", (row_no) => {
+ cy.get(".frappe-list .select-like > .list-row-checkbox").eq(row_no).click();
});
-Cypress.Commands.add('click_form_section', (section_name) => {
- cy.get('.section-head').contains(section_name).click();
+Cypress.Commands.add("click_form_section", (section_name) => {
+ cy.get(".section-head").contains(section_name).click();
});
diff --git a/cypress/support/index.js b/cypress/support/index.js
index 5980e96677..8ce8317a2f 100644
--- a/cypress/support/index.js
+++ b/cypress/support/index.js
@@ -14,10 +14,10 @@
// ***********************************************************
// Import commands.js using ES2015 syntax:
-import './commands';
-import '@cypress/code-coverage/support';
+import "./commands";
+import "@cypress/code-coverage/support";
-Cypress.on('uncaught:exception', (err, runnable) => {
+Cypress.on("uncaught:exception", (err, runnable) => {
return false;
});
@@ -25,5 +25,5 @@ Cypress.on('uncaught:exception', (err, runnable) => {
// require('./commands')
Cypress.Cookies.defaults({
- preserve: 'sid'
-});
\ No newline at end of file
+ preserve: "sid",
+});
diff --git a/esbuild/build-cleanup.js b/esbuild/build-cleanup.js
index cf03606a34..023fce08c5 100644
--- a/esbuild/build-cleanup.js
+++ b/esbuild/build-cleanup.js
@@ -4,9 +4,9 @@ const fs = require("fs");
const glob = require("fast-glob");
module.exports = {
- name: 'build_cleanup',
+ name: "build_cleanup",
setup(build) {
- build.onEnd(result => {
+ build.onEnd((result) => {
if (result.errors.length) return;
clean_dist_files(Object.keys(result.metafile.outputs));
});
@@ -14,25 +14,18 @@ module.exports = {
};
function clean_dist_files(new_files) {
- new_files.forEach(
- file => {
- if (file.endsWith(".map")) return;
+ new_files.forEach((file) => {
+ if (file.endsWith(".map")) return;
- const pattern = file.split(".").slice(0, -2).join(".") + "*";
- glob.sync(pattern).forEach(
- file_to_delete => {
- if (file_to_delete.startsWith(file)) return;
+ const pattern = file.split(".").slice(0, -2).join(".") + "*";
+ glob.sync(pattern).forEach((file_to_delete) => {
+ if (file_to_delete.startsWith(file)) return;
- fs.unlink(path.resolve(file_to_delete), err => {
- if (!err) return;
+ fs.unlink(path.resolve(file_to_delete), (err) => {
+ if (!err) return;
- console.error(
- `Error deleting ${file.split(path.sep).pop()}`
- );
- });
- }
-
- );
- }
- );
-}
\ No newline at end of file
+ console.error(`Error deleting ${file.split(path.sep).pop()}`);
+ });
+ });
+ });
+}
diff --git a/esbuild/esbuild.js b/esbuild/esbuild.js
index 4aa1ebc824..56910cbcac 100644
--- a/esbuild/esbuild.js
+++ b/esbuild/esbuild.js
@@ -8,7 +8,7 @@ const yargs = require("yargs");
const cliui = require("cliui")();
const chalk = require("chalk");
const html_plugin = require("./frappe-html");
-const rtlcss = require('rtlcss');
+const rtlcss = require("rtlcss");
const postCssPlugin = require("@frappe/esbuild-plugin-postcss2").default;
const ignore_assets = require("./ignore-assets");
const sass_options = require("./sass_options");
@@ -25,44 +25,41 @@ const {
log_warn,
log_error,
bench_path,
- get_redis_subscriber
+ get_redis_subscriber,
} = require("./utils");
const argv = yargs
.usage("Usage: node esbuild [options]")
.option("apps", {
type: "string",
- description: "Run build for specific apps"
+ description: "Run build for specific apps",
})
.option("skip_frappe", {
type: "boolean",
- description: "Skip building frappe assets"
+ description: "Skip building frappe assets",
})
.option("files", {
type: "string",
- description: "Run build for specified bundles"
+ description: "Run build for specified bundles",
})
.option("watch", {
type: "boolean",
- description: "Run in watch mode and rebuild on file changes"
+ description: "Run in watch mode and rebuild on file changes",
})
.option("live-reload", {
type: "boolean",
description: `Automatically reload Desk when assets are rebuilt.
- Can only be used with the --watch flag.`
+ Can only be used with the --watch flag.`,
})
.option("production", {
type: "boolean",
- description: "Run build in production mode"
+ description: "Run build in production mode",
})
.option("run-build-command", {
type: "boolean",
- description: "Run build command for apps"
+ description: "Run build command for apps",
})
- .example(
- "node esbuild --apps frappe,erpnext",
- "Run build only for frappe and erpnext"
- )
+ .example("node esbuild --apps frappe,erpnext", "Run build only for frappe and erpnext")
.example(
"node esbuild --files frappe/website.bundle.js,frappe/desk.bundle.js",
"Run build only for specified bundles"
@@ -70,7 +67,7 @@ const argv = yargs
.version(false).argv;
const APPS = (!argv.apps ? app_list : argv.apps.split(",")).filter(
- app => !(argv.skip_frappe && app == "frappe")
+ (app) => !(argv.skip_frappe && app == "frappe")
);
const FILES_TO_BUILD = argv.files ? argv.files.split(",") : [];
const WATCH_MODE = Boolean(argv.watch);
@@ -81,17 +78,15 @@ const TOTAL_BUILD_TIME = `${chalk.black.bgGreen(" DONE ")} Total Build Time`;
const NODE_PATHS = [].concat(
// node_modules of apps directly importable
app_list
- .map(app => path.resolve(get_app_path(app), "../node_modules"))
+ .map((app) => path.resolve(get_app_path(app), "../node_modules"))
.filter(fs.existsSync),
// import js file of any app if you provide the full path
- app_list
- .map(app => path.resolve(get_app_path(app), ".."))
- .filter(fs.existsSync)
+ app_list.map((app) => path.resolve(get_app_path(app), "..")).filter(fs.existsSync)
);
execute()
.then(() => RUN_BUILD_COMMAND && run_build_command_for_apps(APPS))
- .catch(e => console.error(e));
+ .catch((e) => console.error(e));
if (WATCH_MODE) {
// listen for open files in editor event
@@ -131,7 +126,7 @@ function build_assets_for_apps(apps, files) {
? get_files_to_build(files)
: get_all_files_to_build(apps);
- return glob(include_patterns, { ignore: ignore_patterns }).then(files => {
+ return glob(include_patterns, { ignore: ignore_patterns }).then((files) => {
let output_path = assets_path;
let file_map = {};
@@ -143,39 +138,38 @@ function build_assets_for_apps(apps, files) {
let extension = path.extname(file);
let output_name = path.basename(file, extension);
- if (
- [".css", ".scss", ".less", ".sass", ".styl"].includes(extension)
- ) {
+ if ([".css", ".scss", ".less", ".sass", ".styl"].includes(extension)) {
output_name = path.join("css", output_name);
} else if ([".js", ".ts"].includes(extension)) {
output_name = path.join("js", output_name);
}
output_name = path.join(app, "dist", output_name);
- if (Object.keys(file_map).includes(output_name) || Object.keys(style_file_map).includes(output_name)) {
- log_warn(
- `Duplicate output file ${output_name} generated from ${file}`
- );
+ if (
+ Object.keys(file_map).includes(output_name) ||
+ Object.keys(style_file_map).includes(output_name)
+ ) {
+ log_warn(`Duplicate output file ${output_name} generated from ${file}`);
}
if ([".css", ".scss", ".less", ".sass", ".styl"].includes(extension)) {
style_file_map[output_name] = file;
- rtl_style_file_map[output_name.replace('/css/', '/css-rtl/')] = file;
+ rtl_style_file_map[output_name.replace("/css/", "/css-rtl/")] = file;
} else {
file_map[output_name] = file;
}
}
let build = build_files({
files: file_map,
- outdir: output_path
+ outdir: output_path,
});
let style_build = build_style_files({
files: style_file_map,
- outdir: output_path
+ outdir: output_path,
});
let rtl_style_build = build_style_files({
files: rtl_style_file_map,
outdir: output_path,
- rtl_style: true
+ rtl_style: true,
});
return Promise.all([build, style_build, rtl_style_build]);
});
@@ -188,11 +182,7 @@ function get_all_files_to_build(apps) {
for (let app of apps) {
let public_path = get_public_path(app);
include_patterns.push(
- path.resolve(
- public_path,
- "**",
- "*.bundle.{js,ts,css,sass,scss,less,styl}"
- )
+ path.resolve(public_path, "**", "*.bundle.{js,ts,css,sass,scss,less,styl}")
);
ignore_patterns.push(
path.resolve(public_path, "node_modules"),
@@ -202,7 +192,7 @@ function get_all_files_to_build(apps) {
return {
include_patterns,
- ignore_patterns
+ ignore_patterns,
};
}
@@ -223,16 +213,12 @@ function get_files_to_build(files) {
return {
include_patterns,
- ignore_patterns
+ ignore_patterns,
};
}
function build_files({ files, outdir }) {
- let build_plugins = [
- html_plugin,
- build_cleanup_plugin,
- vue(),
- ];
+ let build_plugins = [html_plugin, build_cleanup_plugin, vue()];
return esbuild.build(get_build_options(files, outdir, build_plugins));
}
@@ -247,8 +233,8 @@ function build_style_files({ files, outdir, rtl_style = false }) {
build_cleanup_plugin,
postCssPlugin({
plugins: plugins,
- sassOptions: sass_options
- })
+ sassOptions: sass_options,
+ }),
];
plugins.push(require("autoprefixer"));
@@ -259,7 +245,7 @@ function get_build_options(files, outdir, plugins) {
return {
entryPoints: files,
entryNames: "[dir]/[name].[hash]",
- target: ['es2017'],
+ target: ["es2017"],
outdir,
sourcemap: true,
bundle: true,
@@ -267,12 +253,10 @@ function get_build_options(files, outdir, plugins) {
minify: PRODUCTION,
nodePaths: NODE_PATHS,
define: {
- "process.env.NODE_ENV": JSON.stringify(
- PRODUCTION ? "production" : "development"
- )
+ "process.env.NODE_ENV": JSON.stringify(PRODUCTION ? "production" : "development"),
},
plugins: plugins,
- watch: get_watch_config()
+ watch: get_watch_config(),
};
}
@@ -286,17 +270,13 @@ function get_watch_config() {
log(chalk.dim(error.stack));
notify_redis({ error });
} else {
- let {
- new_assets_json,
- prev_assets_json
- } = await write_assets_json(result.metafile);
+ let { new_assets_json, prev_assets_json } = await write_assets_json(
+ result.metafile
+ );
let changed_files;
if (prev_assets_json) {
- changed_files = get_rebuilt_assets(
- prev_assets_json,
- new_assets_json
- );
+ changed_files = get_rebuilt_assets(prev_assets_json, new_assets_json);
let timestamp = new Date().toLocaleTimeString();
let message = `${timestamp}: Compiled ${changed_files.length} files...`;
@@ -309,7 +289,7 @@ function get_watch_config() {
}
notify_redis({ success: true, changed_files });
}
- }
+ },
};
}
return null;
@@ -324,11 +304,11 @@ function log_built_assets(results) {
cliui.div(
{
text: chalk.cyan.bold("File"),
- width: column_widths[0]
+ width: column_widths[0],
},
{
text: chalk.cyan.bold("Size"),
- width: column_widths[1]
+ width: column_widths[1],
}
);
cliui.div("");
@@ -344,7 +324,7 @@ function log_built_assets(results) {
output_by_dist_path[dist_path] = output_by_dist_path[dist_path] || [];
output_by_dist_path[dist_path].push({
name: filename,
- size: (data.bytes / 1000).toFixed(2) + " Kb"
+ size: (data.bytes / 1000).toFixed(2) + " Kb",
});
}
@@ -352,7 +332,7 @@ function log_built_assets(results) {
let files = output_by_dist_path[dist_path];
cliui.div({
text: dist_path,
- width: column_widths[0]
+ width: column_widths[0],
});
for (let i in files) {
@@ -367,11 +347,11 @@ function log_built_assets(results) {
cliui.div(
{
text: branch + chalk[color]("" + file.name),
- width: column_widths[0]
+ width: column_widths[0],
},
{
text: file.size,
- width: column_widths[1]
+ width: column_widths[1],
}
);
}
@@ -393,7 +373,7 @@ async function write_assets_json(metafile) {
let asset_path = "/" + path.relative(sites_path, output);
if (info.entryPoint) {
let key = path.basename(info.entryPoint);
- if (key.endsWith('.css') && asset_path.includes('/css-rtl/')) {
+ if (key.endsWith(".css") && asset_path.includes("/css-rtl/")) {
rtl = true;
key = `rtl_${key}`;
}
@@ -401,7 +381,7 @@ async function write_assets_json(metafile) {
}
}
- let assets_json_path = path.resolve(assets_path, `assets${rtl?'-rtl':''}.json`);
+ let assets_json_path = path.resolve(assets_path, `assets${rtl ? "-rtl" : ""}.json`);
let assets_json;
try {
assets_json = await fs.promises.readFile(assets_json_path, "utf-8");
@@ -413,26 +393,23 @@ async function write_assets_json(metafile) {
let new_assets_json = Object.assign({}, assets_json, out);
curr_assets_json = new_assets_json;
- await fs.promises.writeFile(
- assets_json_path,
- JSON.stringify(new_assets_json, null, 4)
- );
+ await fs.promises.writeFile(assets_json_path, JSON.stringify(new_assets_json, null, 4));
await update_assets_json_in_cache();
return {
new_assets_json,
- prev_assets_json
+ prev_assets_json,
};
}
function update_assets_json_in_cache() {
// update assets_json cache in redis, so that it can be read directly by python
- return new Promise(resolve => {
+ return new Promise((resolve) => {
let client = get_redis_subscriber("redis_cache");
// handle error event to avoid printing stack traces
- client.on("error", _ => {
+ client.on("error", (_) => {
log_warn("Cannot connect to redis_cache to update assets_json");
});
- client.del("assets_json", err => {
+ client.del("assets_json", (err) => {
client.unref();
resolve();
});
@@ -464,7 +441,7 @@ function run_build_command_for_apps(apps) {
async function notify_redis({ error, success, changed_files }) {
// notify redis which in turns tells socketio to publish this to browser
let subscriber = get_redis_subscriber("redis_socketio");
- subscriber.on("error", _ => {
+ subscriber.on("error", (_) => {
log_warn("Cannot connect to redis_socketio for browser events");
});
@@ -472,20 +449,20 @@ async function notify_redis({ error, success, changed_files }) {
if (error) {
let formatted = await esbuild.formatMessages(error.errors, {
kind: "error",
- terminalWidth: 100
+ terminalWidth: 100,
});
let stack = error.stack.replace(new RegExp(bench_path, "g"), "");
payload = {
error,
formatted,
- stack
+ stack,
};
}
if (success) {
payload = {
success: true,
changed_files,
- live_reload: argv["live-reload"]
+ live_reload: argv["live-reload"],
};
}
@@ -493,14 +470,14 @@ async function notify_redis({ error, success, changed_files }) {
"events",
JSON.stringify({
event: "build_event",
- message: payload
+ message: payload,
})
);
}
function open_in_editor() {
let subscriber = get_redis_subscriber("redis_socketio");
- subscriber.on("error", _ => {
+ subscriber.on("error", (_) => {
log_warn("Cannot connect to redis_socketio for open_in_editor events");
});
subscriber.on("message", (event, file) => {
diff --git a/esbuild/frappe-html.js b/esbuild/frappe-html.js
index 9a7edb144d..d38a0c23cb 100644
--- a/esbuild/frappe-html.js
+++ b/esbuild/frappe-html.js
@@ -4,24 +4,24 @@ module.exports = {
let path = require("path");
let fs = require("fs/promises");
- build.onResolve({ filter: /\.html$/ }, args => {
+ build.onResolve({ filter: /\.html$/ }, (args) => {
return {
path: path.join(args.resolveDir, args.path),
- namespace: "frappe-html"
+ namespace: "frappe-html",
};
});
- build.onLoad({ filter: /.*/, namespace: "frappe-html" }, args => {
+ build.onLoad({ filter: /.*/, namespace: "frappe-html" }, (args) => {
let filepath = args.path;
let filename = path.basename(filepath).split(".")[0];
return fs
.readFile(filepath, "utf-8")
- .then(content => {
+ .then((content) => {
content = scrub_html_template(content);
return {
contents: `\n\tfrappe.templates['${filename}'] = \`${content}\`;\n`,
- watchFiles: [filepath]
+ watchFiles: [filepath],
};
})
.catch(() => {
@@ -29,13 +29,13 @@ module.exports = {
contents: "",
warnings: [
{
- text: `There was an error importing ${filepath}`
- }
- ]
+ text: `There was an error importing ${filepath}`,
+ },
+ ],
};
});
});
- }
+ },
};
function scrub_html_template(content) {
diff --git a/esbuild/ignore-assets.js b/esbuild/ignore-assets.js
index 5edfef2110..fad7a95e0d 100644
--- a/esbuild/ignore-assets.js
+++ b/esbuild/ignore-assets.js
@@ -1,11 +1,11 @@
module.exports = {
name: "frappe-ignore-asset",
setup(build) {
- build.onResolve({ filter: /^\/assets\// }, args => {
+ build.onResolve({ filter: /^\/assets\// }, (args) => {
return {
path: args.path,
- external: true
+ external: true,
};
});
- }
+ },
};
diff --git a/esbuild/sass_options.js b/esbuild/sass_options.js
index 0c1189e90c..92b691cb46 100644
--- a/esbuild/sass_options.js
+++ b/esbuild/sass_options.js
@@ -1,19 +1,13 @@
let path = require("path");
let { get_app_path, app_list } = require("./utils");
-let node_modules_path = path.resolve(
- get_app_path("frappe"),
- "..",
- "node_modules"
-);
-let app_paths = app_list
- .map(get_app_path)
- .map(app_path => path.resolve(app_path, ".."));
+let node_modules_path = path.resolve(get_app_path("frappe"), "..", "node_modules");
+let app_paths = app_list.map(get_app_path).map((app_path) => path.resolve(app_path, ".."));
module.exports = {
includePaths: [node_modules_path, ...app_paths],
quietDeps: true,
- importer: function(url) {
+ importer: function (url) {
if (url.startsWith("~")) {
// strip ~ so that it can resolve from node_modules
url = url.slice(1);
@@ -24,7 +18,7 @@ module.exports = {
}
// normal file, let it go
return {
- file: url
+ file: url,
};
- }
+ },
};
diff --git a/esbuild/utils.js b/esbuild/utils.js
index b2a4e98d54..db58b89e8b 100644
--- a/esbuild/utils.js
+++ b/esbuild/utils.js
@@ -26,24 +26,20 @@ const bundle_map = app_list.reduce((out, app) => {
const public_js_path = public_js_paths[app];
if (fs.existsSync(public_js_path)) {
const all_files = fs.readdirSync(public_js_path);
- const js_files = all_files.filter(file => file.endsWith(".js"));
+ const js_files = all_files.filter((file) => file.endsWith(".js"));
for (let js_file of js_files) {
const filename = path.basename(js_file).split(".")[0];
- out[path.join(app, "js", filename)] = path.resolve(
- public_js_path,
- js_file
- );
+ out[path.join(app, "js", filename)] = path.resolve(public_js_path, js_file);
}
}
return out;
}, {});
-const get_public_path = app => public_paths[app];
+const get_public_path = (app) => public_paths[app];
-const get_build_json_path = app =>
- path.resolve(get_public_path(app), "build.json");
+const get_build_json_path = (app) => path.resolve(get_public_path(app), "build.json");
function get_build_json(app) {
try {
@@ -62,7 +58,7 @@ function delete_file(path) {
function run_serially(tasks) {
let result = Promise.resolve();
- tasks.forEach(task => {
+ tasks.forEach((task) => {
if (task) {
result = result.then ? result.then(task) : Promise.resolve();
}
@@ -70,12 +66,12 @@ function run_serially(tasks) {
return result;
}
-const get_app_path = app => app_paths[app];
+const get_app_path = (app) => app_paths[app];
function get_apps_list() {
return fs
.readFileSync(path.resolve(sites_path, "apps.txt"), {
- encoding: "utf-8"
+ encoding: "utf-8",
})
.split("\n")
.filter(Boolean);
@@ -116,7 +112,7 @@ function get_redis_subscriber(kind) {
let { get_redis_subscriber: get_redis, get_conf } = require("../node_utils");
if (process.env.CI == 1 || get_conf().developer_mode == 1) {
- retry_strategy = () => { }
+ retry_strategy = () => {};
} else {
retry_strategy = function (options) {
// abort after 10 connection attempts
@@ -124,7 +120,7 @@ function get_redis_subscriber(kind) {
return undefined;
}
return Math.min(options.attempt * 100, 2000);
- }
+ };
}
return get_redis(kind, { retry_strategy });
}
@@ -146,5 +142,5 @@ module.exports = {
log,
log_warn,
log_error,
- get_redis_subscriber
+ get_redis_subscriber,
};
diff --git a/frappe/automation/doctype/assignment_rule/assignment_rule.js b/frappe/automation/doctype/assignment_rule/assignment_rule.js
index 97bed4f8f3..3e029e8444 100644
--- a/frappe/automation/doctype/assignment_rule/assignment_rule.js
+++ b/frappe/automation/doctype/assignment_rule/assignment_rule.js
@@ -1,82 +1,77 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Assignment Rule', {
- refresh: function(frm) {
- frm.trigger('setup_assignment_days_buttons');
- frm.trigger('set_options');
+frappe.ui.form.on("Assignment Rule", {
+ refresh: function (frm) {
+ frm.trigger("setup_assignment_days_buttons");
+ frm.trigger("set_options");
// refresh description
frm.events.rule(frm);
},
- setup: function(frm) {
+ setup: function (frm) {
frm.set_query("document_type", () => {
return {
filters: {
- name: ["!=", "ToDo"]
- }
+ name: ["!=", "ToDo"],
+ },
};
});
},
- document_type: function(frm) {
- frm.trigger('set_options');
+ document_type: function (frm) {
+ frm.trigger("set_options");
},
- setup_assignment_days_buttons: function(frm) {
- const labels = ['Weekends', 'Weekdays', 'All Days'];
+ setup_assignment_days_buttons: function (frm) {
+ const labels = ["Weekends", "Weekdays", "All Days"];
let get_days = (label) => {
- const weekdays = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday'];
- const weekends = ['Saturday', 'Sunday'];
+ const weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"];
+ const weekends = ["Saturday", "Sunday"];
return {
- 'All Days': weekdays.concat(weekends),
- 'Weekdays': weekdays,
- 'Weekends': weekends,
+ "All Days": weekdays.concat(weekends),
+ Weekdays: weekdays,
+ Weekends: weekends,
}[label];
};
let set_days = (e) => {
- frm.clear_table('assignment_days');
+ frm.clear_table("assignment_days");
const label = $(e.currentTarget).text();
- get_days(label).forEach((day) =>
- frm.add_child('assignment_days', { day: day })
- );
- frm.refresh_field('assignment_days');
+ get_days(label).forEach((day) => frm.add_child("assignment_days", { day: day }));
+ frm.refresh_field("assignment_days");
};
- labels.forEach(label =>
- frm.fields_dict['assignment_days'].grid.add_custom_button(
- label,
- set_days,
- 'top'
- )
+ labels.forEach((label) =>
+ frm.fields_dict["assignment_days"].grid.add_custom_button(label, set_days, "top")
);
},
- rule: function(frm) {
+ rule: function (frm) {
const description_map = {
- 'Round Robin': __('Assign one by one, in sequence'),
- 'Load Balancing': __('Assign to the one who has the least assignments'),
- 'Based on Field': __('Assign to the user set in this field'),
+ "Round Robin": __("Assign one by one, in sequence"),
+ "Load Balancing": __("Assign to the one who has the least assignments"),
+ "Based on Field": __("Assign to the user set in this field"),
};
- frm.get_field('rule').set_description(description_map[frm.doc.rule]);
+ frm.get_field("rule").set_description(description_map[frm.doc.rule]);
},
set_options(frm) {
const doctype = frm.doc.document_type;
frm.set_fields_as_options(
- 'field',
+ "field",
doctype,
- (df) => ['Dynamic Link', 'Data'].includes(df.fieldtype)
- || (df.fieldtype == 'Link' && df.options == 'User'),
- [{ label: 'Owner', value: 'owner' }]
+ (df) =>
+ ["Dynamic Link", "Data"].includes(df.fieldtype) ||
+ (df.fieldtype == "Link" && df.options == "User"),
+ [{ label: "Owner", value: "owner" }]
);
if (doctype) {
- frm.set_fields_as_options(
- 'due_date_based_on',
- doctype,
- (df) => ['Date', 'Datetime'].includes(df.fieldtype)
- ).then(options => frm.set_df_property('due_date_based_on', 'hidden', !options.length));
+ frm.set_fields_as_options("due_date_based_on", doctype, (df) =>
+ ["Date", "Datetime"].includes(df.fieldtype)
+ ).then((options) =>
+ frm.set_df_property("due_date_based_on", "hidden", !options.length)
+ );
}
},
});
diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.js b/frappe/automation/doctype/auto_repeat/auto_repeat.js
index 80f2255f47..c0fa2696be 100644
--- a/frappe/automation/doctype/auto_repeat/auto_repeat.js
+++ b/frappe/automation/doctype/auto_repeat/auto_repeat.js
@@ -2,41 +2,45 @@
// For license information, please see license.txt
frappe.provide("frappe.auto_repeat");
-frappe.ui.form.on('Auto Repeat', {
- setup: function(frm) {
- frm.fields_dict['reference_doctype'].get_query = function() {
+frappe.ui.form.on("Auto Repeat", {
+ setup: function (frm) {
+ frm.fields_dict["reference_doctype"].get_query = function () {
return {
- query: "frappe.automation.doctype.auto_repeat.auto_repeat.get_auto_repeat_doctypes"
+ query: "frappe.automation.doctype.auto_repeat.auto_repeat.get_auto_repeat_doctypes",
};
};
- frm.fields_dict['reference_document'].get_query = function() {
+ frm.fields_dict["reference_document"].get_query = function () {
return {
filters: {
- "auto_repeat": ''
- }
+ auto_repeat: "",
+ },
};
};
- frm.fields_dict['print_format'].get_query = function() {
+ frm.fields_dict["print_format"].get_query = function () {
return {
filters: {
- "doc_type": frm.doc.reference_doctype
- }
+ doc_type: frm.doc.reference_doctype,
+ },
};
};
},
- refresh: function(frm) {
+ refresh: function (frm) {
// auto repeat message
if (frm.is_new()) {
- let customize_form_link = `${__('Customize Form')} `;
- frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link]));
+ let customize_form_link = `${__("Customize Form")} `;
+ frm.dashboard.set_headline(
+ __('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [
+ customize_form_link,
+ ])
+ );
}
// view document button
if (!frm.is_dirty()) {
- let label = __('View {0}', [__(frm.doc.reference_doctype)]);
+ let label = __("View {0}", [__(frm.doc.reference_doctype)]);
frm.add_custom_button(label, () =>
frappe.set_route("List", frm.doc.reference_doctype, { auto_repeat: frm.doc.name })
);
@@ -45,24 +49,24 @@ frappe.ui.form.on('Auto Repeat', {
// auto repeat schedule
frappe.auto_repeat.render_schedule(frm);
- frm.trigger('toggle_submit_on_creation');
+ frm.trigger("toggle_submit_on_creation");
},
- reference_doctype: function(frm) {
- frm.trigger('toggle_submit_on_creation');
+ reference_doctype: function (frm) {
+ frm.trigger("toggle_submit_on_creation");
},
- toggle_submit_on_creation: function(frm) {
+ toggle_submit_on_creation: function (frm) {
// submit on creation checkbox
if (frm.doc.reference_doctype) {
frappe.model.with_doctype(frm.doc.reference_doctype, () => {
let meta = frappe.get_meta(frm.doc.reference_doctype);
- frm.toggle_display('submit_on_creation', meta.is_submittable);
+ frm.toggle_display("submit_on_creation", meta.is_submittable);
});
}
},
- template: function(frm) {
+ template: function (frm) {
if (frm.doc.template) {
frappe.model.with_doc("Email Template", frm.doc.template, () => {
let email_template = frappe.get_doc("Email Template", frm.doc.template);
@@ -74,11 +78,11 @@ frappe.ui.form.on('Auto Repeat', {
}
},
- get_contacts: function(frm) {
- frm.call('fetch_linked_contacts');
+ get_contacts: function (frm) {
+ frm.call("fetch_linked_contacts");
},
- preview_message: function(frm) {
+ preview_message: function (frm) {
if (frm.doc.message) {
frappe.call({
method: "frappe.automation.doctype.auto_repeat.auto_repeat.generate_message_preview",
@@ -86,29 +90,29 @@ frappe.ui.form.on('Auto Repeat', {
reference_dt: frm.doc.reference_doctype,
reference_doc: frm.doc.reference_document,
subject: frm.doc.subject,
- message: frm.doc.message
+ message: frm.doc.message,
},
- callback: function(r) {
+ callback: function (r) {
if (r.message) {
- frappe.msgprint(r.message.message, r.message.subject)
+ frappe.msgprint(r.message.message, r.message.subject);
}
- }
+ },
});
} else {
- frappe.msgprint(__("Please setup a message first"), __("Message not setup"))
+ frappe.msgprint(__("Please setup a message first"), __("Message not setup"));
}
- }
+ },
});
-frappe.auto_repeat.render_schedule = function(frm) {
- if (!frm.is_dirty() && frm.doc.status !== 'Disabled') {
- frm.call("get_auto_repeat_schedule").then(r => {
+frappe.auto_repeat.render_schedule = function (frm) {
+ if (!frm.is_dirty() && frm.doc.status !== "Disabled") {
+ frm.call("get_auto_repeat_schedule").then((r) => {
frm.dashboard.reset();
frm.dashboard.add_section(
frappe.render_template("auto_repeat_schedule", {
- schedule_details: r.message || []
+ schedule_details: r.message || [],
}),
- __('Auto Repeat Schedule')
+ __("Auto Repeat Schedule")
);
frm.dashboard.show();
});
diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat_list.js b/frappe/automation/doctype/auto_repeat/auto_repeat_list.js
index f906580f7e..f970341fa3 100644
--- a/frappe/automation/doctype/auto_repeat/auto_repeat_list.js
+++ b/frappe/automation/doctype/auto_repeat/auto_repeat_list.js
@@ -1,11 +1,11 @@
-frappe.listview_settings['Auto Repeat'] = {
+frappe.listview_settings["Auto Repeat"] = {
add_fields: ["next_schedule_date"],
- get_indicator: function(doc) {
+ get_indicator: function (doc) {
var colors = {
- "Active": "green",
- "Disabled": "red",
- "Completed": "blue",
+ Active: "green",
+ Disabled: "red",
+ Completed: "blue",
};
return [__(doc.status), colors[doc.status], "status,=," + doc.status];
- }
+ },
};
diff --git a/frappe/automation/doctype/milestone/milestone.js b/frappe/automation/doctype/milestone/milestone.js
index 9a1cf577ff..2a5ab04135 100644
--- a/frappe/automation/doctype/milestone/milestone.js
+++ b/frappe/automation/doctype/milestone/milestone.js
@@ -1,8 +1,7 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Milestone', {
+frappe.ui.form.on("Milestone", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/automation/doctype/milestone_tracker/milestone_tracker.js b/frappe/automation/doctype/milestone_tracker/milestone_tracker.js
index 2a74bfb070..bf5be880a0 100644
--- a/frappe/automation/doctype/milestone_tracker/milestone_tracker.js
+++ b/frappe/automation/doctype/milestone_tracker/milestone_tracker.js
@@ -1,14 +1,14 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Milestone Tracker', {
- refresh: function(frm) {
- frm.trigger('update_options');
+frappe.ui.form.on("Milestone Tracker", {
+ refresh: function (frm) {
+ frm.trigger("update_options");
},
- document_type: function(frm) {
- frm.trigger('update_options');
+ document_type: function (frm) {
+ frm.trigger("update_options");
},
- update_options: function(frm) {
+ update_options: function (frm) {
// update select options for `track_field`
let doctype = frm.doc.document_type;
let track_fields = [];
@@ -16,18 +16,16 @@ frappe.ui.form.on('Milestone Tracker', {
if (doctype) {
frappe.model.with_doctype(doctype, () => {
// get all date and datetime fields
- frappe.get_meta(doctype).fields.map(df => {
- if (['Link', 'Select'].includes(df.fieldtype)) {
- track_fields.push({label: df.label, value: df.fieldname});
+ frappe.get_meta(doctype).fields.map((df) => {
+ if (["Link", "Select"].includes(df.fieldtype)) {
+ track_fields.push({ label: df.label, value: df.fieldname });
}
});
- frm.set_df_property('track_field', 'options', track_fields);
+ frm.set_df_property("track_field", "options", track_fields);
});
} else {
// update select options
- frm.set_df_property('track_field', 'options', []);
+ frm.set_df_property("track_field", "options", []);
}
-
},
-
});
diff --git a/frappe/contacts/doctype/address/address.js b/frappe/contacts/doctype/address/address.js
index 63574622c0..548dd40060 100644
--- a/frappe/contacts/doctype/address/address.js
+++ b/frappe/contacts/doctype/address/address.js
@@ -2,60 +2,74 @@
// For license information, please see license.txt
frappe.ui.form.on("Address", {
- refresh: function(frm) {
- if(frm.doc.__islocal) {
+ refresh: function (frm) {
+ if (frm.doc.__islocal) {
const last_doc = frappe.contacts.get_last_doc(frm);
- if(frappe.dynamic_link && frappe.dynamic_link.doc
- && frappe.dynamic_link.doc.name == last_doc.docname) {
- frm.set_value('links', '');
- frm.add_child('links', {
+ if (
+ frappe.dynamic_link &&
+ frappe.dynamic_link.doc &&
+ frappe.dynamic_link.doc.name == last_doc.docname
+ ) {
+ frm.set_value("links", "");
+ frm.add_child("links", {
link_doctype: frappe.dynamic_link.doctype,
- link_name: frappe.dynamic_link.doc[frappe.dynamic_link.fieldname]
+ link_name: frappe.dynamic_link.doc[frappe.dynamic_link.fieldname],
});
}
}
- frm.set_query('link_doctype', "links", function() {
+ frm.set_query("link_doctype", "links", function () {
return {
query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
filters: {
fieldtype: "HTML",
fieldname: "address_html",
- }
- }
+ },
+ };
});
frm.refresh_field("links");
if (frm.doc.links) {
for (let i in frm.doc.links) {
let link = frm.doc.links[i];
- frm.add_custom_button(__("{0}: {1}", [__(link.link_doctype), __(link.link_name)]), function() {
- frappe.set_route("Form", link.link_doctype, link.link_name);
- }, __("Links"));
+ frm.add_custom_button(
+ __("{0}: {1}", [__(link.link_doctype), __(link.link_name)]),
+ function () {
+ frappe.set_route("Form", link.link_doctype, link.link_name);
+ },
+ __("Links")
+ );
}
}
},
- validate: function(frm) {
+ validate: function (frm) {
// clear linked customer / supplier / sales partner on saving...
- if(frm.doc.links) {
- frm.doc.links.forEach(function(d) {
+ if (frm.doc.links) {
+ frm.doc.links.forEach(function (d) {
frappe.model.remove_from_locals(d.link_doctype, d.link_name);
});
}
},
- after_save: function(frm) {
+ after_save: function (frm) {
frappe.run_serially([
() => frappe.timeout(1),
() => {
const last_doc = frappe.contacts.get_last_doc(frm);
- if (frappe.dynamic_link && frappe.dynamic_link.doc && frappe.dynamic_link.doc.name == last_doc.docname) {
+ if (
+ frappe.dynamic_link &&
+ frappe.dynamic_link.doc &&
+ frappe.dynamic_link.doc.name == last_doc.docname
+ ) {
for (let i in frm.doc.links) {
let link = frm.doc.links[i];
- if (last_doc.doctype == link.link_doctype && last_doc.docname == link.link_name) {
- frappe.set_route('Form', last_doc.doctype, last_doc.docname);
+ if (
+ last_doc.doctype == link.link_doctype &&
+ last_doc.docname == link.link_name
+ ) {
+ frappe.set_route("Form", last_doc.doctype, last_doc.docname);
}
}
}
- }
+ },
]);
- }
+ },
});
diff --git a/frappe/contacts/doctype/address_template/address_template.js b/frappe/contacts/doctype/address_template/address_template.js
index 502d02e7f9..bfe139bce8 100644
--- a/frappe/contacts/doctype/address_template/address_template.js
+++ b/frappe/contacts/doctype/address_template/address_template.js
@@ -1,16 +1,16 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Address Template', {
- refresh: function(frm) {
- if(frm.is_new() && !frm.doc.template) {
+frappe.ui.form.on("Address Template", {
+ refresh: function (frm) {
+ if (frm.is_new() && !frm.doc.template) {
// set default template via js so that it is translated
frappe.call({
- method: 'frappe.contacts.doctype.address_template.address_template.get_default_address_template',
- callback: function(r) {
- frm.set_value('template', r.message);
- }
+ method: "frappe.contacts.doctype.address_template.address_template.get_default_address_template",
+ callback: function (r) {
+ frm.set_value("template", r.message);
+ },
});
}
- }
+ },
});
diff --git a/frappe/contacts/doctype/contact/contact.js b/frappe/contacts/doctype/contact/contact.js
index fae6e6515e..d4ae9379fa 100644
--- a/frappe/contacts/doctype/contact/contact.js
+++ b/frappe/contacts/doctype/contact/contact.js
@@ -5,49 +5,52 @@ frappe.ui.form.on("Contact", {
onload(frm) {
frm.email_field = "email_id";
},
- refresh: function(frm) {
- if(frm.doc.__islocal) {
+ refresh: function (frm) {
+ if (frm.doc.__islocal) {
const last_doc = frappe.contacts.get_last_doc(frm);
- if(frappe.dynamic_link && frappe.dynamic_link.doc
- && frappe.dynamic_link.doc.name == last_doc.docname) {
- frm.set_value('links', '');
- frm.add_child('links', {
+ if (
+ frappe.dynamic_link &&
+ frappe.dynamic_link.doc &&
+ frappe.dynamic_link.doc.name == last_doc.docname
+ ) {
+ frm.set_value("links", "");
+ frm.add_child("links", {
link_doctype: frappe.dynamic_link.doctype,
- link_name: frappe.dynamic_link.doc[frappe.dynamic_link.fieldname]
+ link_name: frappe.dynamic_link.doc[frappe.dynamic_link.fieldname],
});
}
}
- if(!frm.doc.user && !frm.is_new() && frm.perm[0].write) {
- frm.add_custom_button(__("Invite as User"), function() {
+ if (!frm.doc.user && !frm.is_new() && frm.perm[0].write) {
+ frm.add_custom_button(__("Invite as User"), function () {
return frappe.call({
method: "frappe.contacts.doctype.contact.contact.invite_user",
args: {
- contact: frm.doc.name
+ contact: frm.doc.name,
},
- callback: function(r) {
+ callback: function (r) {
frm.set_value("user", r.message);
- }
+ },
});
});
}
- frm.set_query('link_doctype', "links", function() {
+ frm.set_query("link_doctype", "links", function () {
return {
query: "frappe.contacts.address_and_contact.filter_dynamic_link_doctypes",
filters: {
fieldtype: "HTML",
fieldname: "contact_html",
- }
- }
+ },
+ };
});
frm.refresh_field("links");
let numbers = frm.doc.phone_nos;
if (numbers && numbers.length && frappe.phone_call.handler) {
- frm.add_custom_button(__('Call'), () => {
+ frm.add_custom_button(__("Call"), () => {
numbers = frm.doc.phone_nos
.sort((prev, next) => next.is_primary_mobile_no - prev.is_primary_mobile_no)
- .map(d => d.phone);
+ .map((d) => d.phone);
frappe.phone_call.handler(numbers);
});
}
@@ -55,73 +58,94 @@ frappe.ui.form.on("Contact", {
if (frm.doc.links) {
frappe.call({
method: "frappe.contacts.doctype.contact.contact.address_query",
- args: {links: frm.doc.links},
- callback: function(r) {
+ args: { links: frm.doc.links },
+ callback: function (r) {
if (r && r.message) {
frm.set_query("address", function () {
return {
filters: {
name: ["in", r.message],
- }
- }
+ },
+ };
});
}
- }
+ },
});
for (let i in frm.doc.links) {
let link = frm.doc.links[i];
- frm.add_custom_button(__("{0}: {1}", [__(link.link_doctype), __(link.link_name)]), function() {
- frappe.set_route("Form", link.link_doctype, link.link_name);
- }, __("Links"));
+ frm.add_custom_button(
+ __("{0}: {1}", [__(link.link_doctype), __(link.link_name)]),
+ function () {
+ frappe.set_route("Form", link.link_doctype, link.link_name);
+ },
+ __("Links")
+ );
}
}
},
- validate: function(frm) {
+ validate: function (frm) {
// clear linked customer / supplier / sales partner on saving...
- if(frm.doc.links) {
- frm.doc.links.forEach(function(d) {
+ if (frm.doc.links) {
+ frm.doc.links.forEach(function (d) {
frappe.model.remove_from_locals(d.link_doctype, d.link_name);
});
}
},
- after_save: function(frm) {
+ after_save: function (frm) {
frappe.run_serially([
() => frappe.timeout(1),
() => {
const last_doc = frappe.contacts.get_last_doc(frm);
- if (frappe.dynamic_link && frappe.dynamic_link.doc && frappe.dynamic_link.doc.name == last_doc.docname) {
+ if (
+ frappe.dynamic_link &&
+ frappe.dynamic_link.doc &&
+ frappe.dynamic_link.doc.name == last_doc.docname
+ ) {
for (let i in frm.doc.links) {
let link = frm.doc.links[i];
- if (last_doc.doctype == link.link_doctype && last_doc.docname == link.link_name) {
- frappe.set_route('Form', last_doc.doctype, last_doc.docname);
+ if (
+ last_doc.doctype == link.link_doctype &&
+ last_doc.docname == link.link_name
+ ) {
+ frappe.set_route("Form", last_doc.doctype, last_doc.docname);
}
}
}
- }
+ },
]);
},
- sync_with_google_contacts: function(frm) {
+ sync_with_google_contacts: function (frm) {
if (frm.doc.sync_with_google_contacts) {
- frappe.db.get_value("Google Contacts", {"email_id": frappe.session.user}, "name", (r) => {
- if (r && r.name) {
- frm.set_value("google_contacts", r.name);
+ frappe.db.get_value(
+ "Google Contacts",
+ { email_id: frappe.session.user },
+ "name",
+ (r) => {
+ if (r && r.name) {
+ frm.set_value("google_contacts", r.name);
+ }
}
- })
+ );
}
- }
+ },
});
frappe.ui.form.on("Dynamic Link", {
- link_name:function(frm, cdt, cdn){
+ link_name: function (frm, cdt, cdn) {
var child = locals[cdt][cdn];
- if(child.link_name) {
+ if (child.link_name) {
frappe.model.with_doctype(child.link_doctype, function () {
- var title_field = frappe.get_meta(child.link_doctype).title_field || "name"
- frappe.model.get_value(child.link_doctype, child.link_name, title_field, function (r) {
- frappe.model.set_value(cdt, cdn, "link_title", r[title_field])
- })
- })
+ var title_field = frappe.get_meta(child.link_doctype).title_field || "name";
+ frappe.model.get_value(
+ child.link_doctype,
+ child.link_name,
+ title_field,
+ function (r) {
+ frappe.model.set_value(cdt, cdn, "link_title", r[title_field]);
+ }
+ );
+ });
}
- }
-})
+ },
+});
diff --git a/frappe/contacts/doctype/contact/contact_list.js b/frappe/contacts/doctype/contact/contact_list.js
index a93b3f0d73..2b3cd8a062 100644
--- a/frappe/contacts/doctype/contact/contact_list.js
+++ b/frappe/contacts/doctype/contact/contact_list.js
@@ -1,3 +1,3 @@
-frappe.listview_settings['Contact'] = {
+frappe.listview_settings["Contact"] = {
add_fields: ["image"],
-};
\ No newline at end of file
+};
diff --git a/frappe/contacts/doctype/gender/gender.js b/frappe/contacts/doctype/gender/gender.js
index e2fd2f18eb..3b34b1584e 100644
--- a/frappe/contacts/doctype/gender/gender.js
+++ b/frappe/contacts/doctype/gender/gender.js
@@ -1,8 +1,6 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Gender', {
- refresh: function() {
-
- }
+frappe.ui.form.on("Gender", {
+ refresh: function () {},
});
diff --git a/frappe/contacts/doctype/salutation/salutation.js b/frappe/contacts/doctype/salutation/salutation.js
index 856b72e04c..e7da1f389b 100644
--- a/frappe/contacts/doctype/salutation/salutation.js
+++ b/frappe/contacts/doctype/salutation/salutation.js
@@ -1,8 +1,6 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Salutation', {
- refresh: function() {
-
- }
+frappe.ui.form.on("Salutation", {
+ refresh: function () {},
});
diff --git a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js
index 10137e80d5..9870ee611b 100644
--- a/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js
+++ b/frappe/contacts/report/addresses_and_contacts/addresses_and_contacts.js
@@ -2,32 +2,32 @@
// For license information, please see license.txt
frappe.query_reports["Addresses And Contacts"] = {
- "filters": [
+ filters: [
{
- "reqd": 1,
- "fieldname":"reference_doctype",
- "label": __("Entity Type"),
- "fieldtype": "Link",
- "options": "DocType",
- "get_query": function() {
+ reqd: 1,
+ fieldname: "reference_doctype",
+ label: __("Entity Type"),
+ fieldtype: "Link",
+ options: "DocType",
+ get_query: function () {
return {
- "filters": {
- "name": ["in", "Contact, Address"],
- }
- }
- }
+ filters: {
+ name: ["in", "Contact, Address"],
+ },
+ };
+ },
},
{
- "fieldname":"reference_name",
- "label": __("Entity Name"),
- "fieldtype": "Dynamic Link",
- "get_options": function() {
- let reference_doctype = frappe.query_report.get_filter_value('reference_doctype');
- if(!reference_doctype) {
+ fieldname: "reference_name",
+ label: __("Entity Name"),
+ fieldtype: "Dynamic Link",
+ get_options: function () {
+ let reference_doctype = frappe.query_report.get_filter_value("reference_doctype");
+ if (!reference_doctype) {
frappe.throw(__("Please select Entity Type first"));
}
return reference_doctype;
- }
- }
- ]
-}
+ },
+ },
+ ],
+};
diff --git a/frappe/core/doctype/access_log/access_log.js b/frappe/core/doctype/access_log/access_log.js
index d36d10768b..94f1bf732d 100644
--- a/frappe/core/doctype/access_log/access_log.js
+++ b/frappe/core/doctype/access_log/access_log.js
@@ -1,17 +1,17 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Access Log', {
+frappe.ui.form.on("Access Log", {
show_document: function (frm) {
- frappe.set_route('Form', frm.doc.export_from, frm.doc.reference_document);
+ frappe.set_route("Form", frm.doc.export_from, frm.doc.reference_document);
},
show_report: function (frm) {
- if (frm.doc.report_name.includes('/')) {
+ if (frm.doc.report_name.includes("/")) {
frappe.set_route(frm.doc.report_name);
} else {
let filters = frm.doc.filters ? JSON.parse(frm.doc.filters) : {};
- frappe.set_route('query-report', frm.doc.report_name, filters);
+ frappe.set_route("query-report", frm.doc.report_name, filters);
}
- }
+ },
});
diff --git a/frappe/core/doctype/activity_log/activity_log.js b/frappe/core/doctype/activity_log/activity_log.js
index 97e49e4b34..7df644a86a 100644
--- a/frappe/core/doctype/activity_log/activity_log.js
+++ b/frappe/core/doctype/activity_log/activity_log.js
@@ -1,8 +1,6 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Activity Log', {
- refresh: function() {
-
- }
+frappe.ui.form.on("Activity Log", {
+ refresh: function () {},
});
diff --git a/frappe/core/doctype/activity_log/activity_log_list.js b/frappe/core/doctype/activity_log/activity_log_list.js
index e3a75a1941..53758a7b62 100644
--- a/frappe/core/doctype/activity_log/activity_log_list.js
+++ b/frappe/core/doctype/activity_log/activity_log_list.js
@@ -1,13 +1,12 @@
-frappe.listview_settings['Activity Log'] = {
- get_indicator: function(doc) {
- if(doc.operation == "Login" && doc.status == "Success")
- return [__(doc.status), "green"];
- else if(doc.operation == "Login" && doc.status == "Failed")
+frappe.listview_settings["Activity Log"] = {
+ get_indicator: function (doc) {
+ if (doc.operation == "Login" && doc.status == "Success") return [__(doc.status), "green"];
+ else if (doc.operation == "Login" && doc.status == "Failed")
return [__(doc.status), "red"];
},
- onload: function(listview) {
+ onload: function (listview) {
frappe.require("logtypes.bundle.js", () => {
frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
- })
+ });
},
};
diff --git a/frappe/core/doctype/comment/comment.js b/frappe/core/doctype/comment/comment.js
index a793f766cb..4d227f6f5f 100644
--- a/frappe/core/doctype/comment/comment.js
+++ b/frappe/core/doctype/comment/comment.js
@@ -1,8 +1,7 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Comment', {
+frappe.ui.form.on("Comment", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/communication/communication.js b/frappe/core/doctype/communication/communication.js
index 07674d16ae..a36af705a7 100644
--- a/frappe/core/doctype/communication/communication.js
+++ b/frappe/core/doctype/communication/communication.js
@@ -1,120 +1,158 @@
frappe.ui.form.on("Communication", {
- onload: function(frm) {
- if(frm.doc.content) {
+ onload: function (frm) {
+ if (frm.doc.content) {
frm.doc.content = frappe.dom.remove_script_and_style(frm.doc.content);
}
- frm.set_query("reference_doctype", function() {
+ frm.set_query("reference_doctype", function () {
return {
filters: {
- "issingle": 0,
- "istable": 0
- }
- }
+ issingle: 0,
+ istable: 0,
+ },
+ };
});
},
- refresh: function(frm) {
- if(frm.is_new()) return;
+ refresh: function (frm) {
+ if (frm.is_new()) return;
frm.convert_to_click && frm.set_convert_button();
frm.subject_field = "subject";
// content field contains weird table html that does not render well in Quill
// this field is not to be edited directly anyway, so setting it as read only
- frm.set_df_property('content', 'read_only', 1);
+ frm.set_df_property("content", "read_only", 1);
- if(frm.doc.reference_doctype && frm.doc.reference_name) {
- frm.add_custom_button(__(frm.doc.reference_name), function() {
+ if (frm.doc.reference_doctype && frm.doc.reference_name) {
+ frm.add_custom_button(__(frm.doc.reference_name), function () {
frappe.set_route("Form", frm.doc.reference_doctype, frm.doc.reference_name);
});
} else {
// if an unlinked communication, set email field
- if (frm.doc.sent_or_received==="Received") {
+ if (frm.doc.sent_or_received === "Received") {
frm.email_field = "sender";
} else {
frm.email_field = "recipients";
}
}
- if(frm.doc.status==="Open") {
- frm.add_custom_button(__("Close"), function() {
- frm.trigger('mark_as_closed_open');
+ if (frm.doc.status === "Open") {
+ frm.add_custom_button(__("Close"), function () {
+ frm.trigger("mark_as_closed_open");
});
} else if (frm.doc.status !== "Linked") {
- frm.add_custom_button(__("Reopen"), function() {
- frm.trigger('mark_as_closed_open');
+ frm.add_custom_button(__("Reopen"), function () {
+ frm.trigger("mark_as_closed_open");
});
}
- frm.add_custom_button(__("Relink"), function() {
- frm.trigger('show_relink_dialog');
+ frm.add_custom_button(__("Relink"), function () {
+ frm.trigger("show_relink_dialog");
});
- if(frm.doc.communication_type=="Communication"
- && frm.doc.communication_medium == "Email"
- && frm.doc.sent_or_received == "Received") {
-
- frm.add_custom_button(__("Reply"), function() {
- frm.trigger('reply');
+ if (
+ frm.doc.communication_type == "Communication" &&
+ frm.doc.communication_medium == "Email" &&
+ frm.doc.sent_or_received == "Received"
+ ) {
+ frm.add_custom_button(__("Reply"), function () {
+ frm.trigger("reply");
});
- frm.add_custom_button(__("Reply All"), function() {
- frm.trigger('reply_all');
- }, __("Actions"));
+ frm.add_custom_button(
+ __("Reply All"),
+ function () {
+ frm.trigger("reply_all");
+ },
+ __("Actions")
+ );
- frm.add_custom_button(__("Forward"), function() {
- frm.trigger('forward_mail');
- }, __("Actions"));
+ frm.add_custom_button(
+ __("Forward"),
+ function () {
+ frm.trigger("forward_mail");
+ },
+ __("Actions")
+ );
- frm.add_custom_button(frm.doc.seen ? __("Mark as Unread") : __("Mark as Read"), function() {
- frm.trigger('mark_as_read_unread');
- }, __("Actions"));
+ frm.add_custom_button(
+ frm.doc.seen ? __("Mark as Unread") : __("Mark as Read"),
+ function () {
+ frm.trigger("mark_as_read_unread");
+ },
+ __("Actions")
+ );
- frm.add_custom_button(__("Move"), function() {
- frm.trigger('show_move_dialog');
- }, __("Actions"));
+ frm.add_custom_button(
+ __("Move"),
+ function () {
+ frm.trigger("show_move_dialog");
+ },
+ __("Actions")
+ );
- if(frm.doc.email_status != "Spam")
- frm.add_custom_button(__("Mark as Spam"), function() {
- frm.trigger('mark_as_spam');
- }, __("Actions"));
+ if (frm.doc.email_status != "Spam")
+ frm.add_custom_button(
+ __("Mark as Spam"),
+ function () {
+ frm.trigger("mark_as_spam");
+ },
+ __("Actions")
+ );
- if(frm.doc.email_status != "Trash") {
- frm.add_custom_button(__("Move To Trash"), function() {
- frm.trigger('move_to_trash');
- }, __("Actions"));
+ if (frm.doc.email_status != "Trash") {
+ frm.add_custom_button(
+ __("Move To Trash"),
+ function () {
+ frm.trigger("move_to_trash");
+ },
+ __("Actions")
+ );
}
- frm.add_custom_button(__("Contact"), function() {
- frm.trigger('add_to_contact');
- }, __('Create'));
+ frm.add_custom_button(
+ __("Contact"),
+ function () {
+ frm.trigger("add_to_contact");
+ },
+ __("Create")
+ );
}
- if(frm.doc.communication_type=="Communication"
- && frm.doc.communication_medium == "Phone"
- && frm.doc.sent_or_received == "Received"){
-
- frm.add_custom_button(__("Add Contact"), function() {
- frm.trigger('add_to_contact');
- }, __("Actions"));
+ if (
+ frm.doc.communication_type == "Communication" &&
+ frm.doc.communication_medium == "Phone" &&
+ frm.doc.sent_or_received == "Received"
+ ) {
+ frm.add_custom_button(
+ __("Add Contact"),
+ function () {
+ frm.trigger("add_to_contact");
+ },
+ __("Actions")
+ );
}
},
- show_relink_dialog: function(frm) {
- var d = new frappe.ui.Dialog ({
+ show_relink_dialog: function (frm) {
+ var d = new frappe.ui.Dialog({
title: __("Relink Communication"),
- fields: [{
- "fieldtype": "Link",
- "options": "DocType",
- "label": __("Reference Doctype"),
- "fieldname": "reference_doctype",
- "get_query": function() {return {"query": "frappe.email.get_communication_doctype"}}
- },
- {
- "fieldtype": "Dynamic Link",
- "options": "reference_doctype",
- "label": __("Reference Name"),
- "fieldname": "reference_name"
- }]
+ fields: [
+ {
+ fieldtype: "Link",
+ options: "DocType",
+ label: __("Reference Doctype"),
+ fieldname: "reference_doctype",
+ get_query: function () {
+ return { query: "frappe.email.get_communication_doctype" };
+ },
+ },
+ {
+ fieldtype: "Dynamic Link",
+ options: "reference_doctype",
+ label: __("Reference Name"),
+ fieldname: "reference_name",
+ },
+ ],
});
d.set_value("reference_doctype", frm.doc.reference_doctype);
d.set_value("reference_name", frm.doc.reference_name);
@@ -122,24 +160,27 @@ frappe.ui.form.on("Communication", {
var values = d.get_values();
if (values) {
frappe.confirm(
- __('Are you sure you want to relink this communication to {0}?', [values["reference_name"]]),
+ __("Are you sure you want to relink this communication to {0}?", [
+ values["reference_name"],
+ ]),
function () {
d.hide();
frappe.call({
method: "frappe.email.relink",
args: {
- "name": frm.doc.name,
- "reference_doctype": values["reference_doctype"],
- "reference_name": values["reference_name"]
+ name: frm.doc.name,
+ reference_doctype: values["reference_doctype"],
+ reference_name: values["reference_name"],
},
callback: function () {
frm.refresh();
- }
+ },
});
},
- function() {
+ function () {
frappe.show_alert({
- message: __('Document not Relinked'), 'indicator': 'info'
+ message: __("Document not Relinked"),
+ indicator: "info",
});
}
);
@@ -148,24 +189,26 @@ frappe.ui.form.on("Communication", {
d.show();
},
- show_move_dialog: function(frm) {
- var d = new frappe.ui.Dialog ({
+ show_move_dialog: function (frm) {
+ var d = new frappe.ui.Dialog({
title: __("Move"),
- fields: [{
- "fieldtype": "Link",
- "options": "Email Account",
- "label": __("Email Account"),
- "fieldname": "email_account",
- "reqd": 1,
- "get_query": function() {
- return {
- "filters": {
- "name": ["!=", frm.doc.email_account],
- "enable_incoming": ["=", 1]
- }
- };
- }
- }],
+ fields: [
+ {
+ fieldtype: "Link",
+ options: "Email Account",
+ label: __("Email Account"),
+ fieldname: "email_account",
+ reqd: 1,
+ get_query: function () {
+ return {
+ filters: {
+ name: ["!=", frm.doc.email_account],
+ enable_incoming: ["=", 1],
+ },
+ };
+ },
+ },
+ ],
primary_action_label: __("Move"),
primary_action(values) {
d.hide();
@@ -173,88 +216,88 @@ frappe.ui.form.on("Communication", {
method: "frappe.email.inbox.move_email",
args: {
communication: frm.doc.name,
- email_account: values.email_account
+ email_account: values.email_account,
},
freeze: true,
- callback: function() {
+ callback: function () {
window.history.back();
- }
+ },
});
- }
+ },
});
d.show();
},
- mark_as_read_unread: function(frm) {
- var action = frm.doc.seen? "Unread": "Read";
+ mark_as_read_unread: function (frm) {
+ var action = frm.doc.seen ? "Unread" : "Read";
var flag = "(\\SEEN)";
return frappe.call({
method: "frappe.email.inbox.create_email_flag_queue",
args: {
- 'names': [frm.doc.name],
- 'action': action,
- 'flag': flag
+ names: [frm.doc.name],
+ action: action,
+ flag: flag,
},
freeze: true,
- callback: function() {
+ callback: function () {
frm.reload_doc();
- }
+ },
});
},
- mark_as_closed_open: function(frm) {
+ mark_as_closed_open: function (frm) {
var status = frm.doc.status == "Open" ? "Closed" : "Open";
return frappe.call({
method: "frappe.email.inbox.mark_as_closed_open",
args: {
communication: frm.doc.name,
- status: status
+ status: status,
},
freeze: true,
- callback: function() {
+ callback: function () {
frm.reload_doc();
- }
+ },
});
},
- reply: function(frm) {
+ reply: function (frm) {
var args = frm.events.get_mail_args(frm);
$.extend(args, {
subject: __("Re: {0}", [frm.doc.subject]),
- recipients: frm.doc.sender
- })
+ recipients: frm.doc.sender,
+ });
new frappe.views.CommunicationComposer(args);
},
- reply_all: function(frm) {
- var args = frm.events.get_mail_args(frm)
+ reply_all: function (frm) {
+ var args = frm.events.get_mail_args(frm);
$.extend(args, {
subject: __("Res: {0}", [frm.doc.subject]),
recipients: frm.doc.sender,
- cc: frm.doc.cc
- })
+ cc: frm.doc.cc,
+ });
new frappe.views.CommunicationComposer(args);
},
- forward_mail: function(frm) {
- var args = frm.events.get_mail_args(frm)
+ forward_mail: function (frm) {
+ var args = frm.events.get_mail_args(frm);
$.extend(args, {
forward: true,
subject: __("Fw: {0}", [frm.doc.subject]),
- })
+ });
new frappe.views.CommunicationComposer(args);
},
- get_mail_args: function(frm) {
- var sender_email_id = ""
- $.each(frappe.boot.email_accounts, function(idx, account) {
- if(account.email_account == frm.doc.email_account) {
- sender_email_id = account.email_id
- return
+ get_mail_args: function (frm) {
+ var sender_email_id = "";
+ $.each(frappe.boot.email_accounts, function (idx, account) {
+ if (account.email_account == frm.doc.email_account) {
+ sender_email_id = account.email_id;
+ return;
}
});
@@ -263,51 +306,51 @@ frappe.ui.form.on("Communication", {
doc: frm.doc,
last_email: frm.doc,
sender: sender_email_id,
- attachments: frm.doc.attachments
- }
+ attachments: frm.doc.attachments,
+ };
},
- add_to_contact: function(frm) {
+ add_to_contact: function (frm) {
var me = this;
- var fullname = frm.doc.sender_full_name || ""
+ var fullname = frm.doc.sender_full_name || "";
- var names = fullname.split(" ")
- var first_name = names[0]
- var last_name = names.length >= 2? names[names.length - 1]: ""
+ var names = fullname.split(" ");
+ var first_name = names[0];
+ var last_name = names.length >= 2 ? names[names.length - 1] : "";
frappe.route_options = {
- "email_id": frm.doc.sender || "",
- "first_name": first_name,
- "last_name": last_name,
- "mobile_no": frm.doc.phone_no || ""
- }
- frappe.new_doc("Contact")
+ email_id: frm.doc.sender || "",
+ first_name: first_name,
+ last_name: last_name,
+ mobile_no: frm.doc.phone_no || "",
+ };
+ frappe.new_doc("Contact");
},
- mark_as_spam: function(frm) {
+ mark_as_spam: function (frm) {
frappe.call({
method: "frappe.email.inbox.mark_as_spam",
args: {
communication: frm.doc.name,
- sender: frm.doc.sender
+ sender: frm.doc.sender,
},
freeze: true,
- callback: function(r) {
- frappe.msgprint(__("Email has been marked as spam"))
- }
- })
+ callback: function (r) {
+ frappe.msgprint(__("Email has been marked as spam"));
+ },
+ });
},
- move_to_trash: function(frm) {
+ move_to_trash: function (frm) {
frappe.call({
method: "frappe.email.inbox.mark_as_trash",
args: {
- communication: frm.doc.name
+ communication: frm.doc.name,
},
freeze: true,
- callback: function(r) {
- frappe.msgprint(__("Email has been moved to trash"))
- }
- })
- }
+ callback: function (r) {
+ frappe.msgprint(__("Email has been moved to trash"));
+ },
+ });
+ },
});
diff --git a/frappe/core/doctype/communication/communication_list.js b/frappe/core/doctype/communication/communication_list.js
index 315b74a39c..4ef3a384ff 100644
--- a/frappe/core/doctype/communication/communication_list.js
+++ b/frappe/core/doctype/communication/communication_list.js
@@ -1,25 +1,32 @@
-frappe.listview_settings['Communication'] = {
+frappe.listview_settings["Communication"] = {
add_fields: [
- "sent_or_received","recipients", "subject",
- "communication_medium", "communication_type",
- "sender", "seen", "reference_doctype", "reference_name",
- "has_attachment", "communication_date"
+ "sent_or_received",
+ "recipients",
+ "subject",
+ "communication_medium",
+ "communication_type",
+ "sender",
+ "seen",
+ "reference_doctype",
+ "reference_name",
+ "has_attachment",
+ "communication_date",
],
filters: [["status", "=", "Open"]],
- onload: function(list_view) {
- let method = "frappe.email.inbox.create_email_flag_queue"
+ onload: function (list_view) {
+ let method = "frappe.email.inbox.create_email_flag_queue";
- list_view.page.add_menu_item(__("Mark as Read"), function() {
+ list_view.page.add_menu_item(__("Mark as Read"), function () {
list_view.call_for_selected_items(method, { action: "Read" });
});
- list_view.page.add_menu_item(__("Mark as Unread"), function() {
+ list_view.page.add_menu_item(__("Mark as Unread"), function () {
list_view.call_for_selected_items(method, { action: "Unread" });
});
},
- primary_action: function() {
+ primary_action: function () {
new frappe.views.CommunicationComposer();
- }
+ },
};
diff --git a/frappe/core/doctype/custom_docperm/custom_docperm.js b/frappe/core/doctype/custom_docperm/custom_docperm.js
index 1f04a638a1..0da50217d2 100644
--- a/frappe/core/doctype/custom_docperm/custom_docperm.js
+++ b/frappe/core/doctype/custom_docperm/custom_docperm.js
@@ -1,8 +1,6 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Custom DocPerm', {
- refresh: function(frm) {
-
- }
+frappe.ui.form.on("Custom DocPerm", {
+ refresh: function (frm) {},
});
diff --git a/frappe/core/doctype/custom_role/custom_role.js b/frappe/core/doctype/custom_role/custom_role.js
index 85302a48b7..16b86485ed 100644
--- a/frappe/core/doctype/custom_role/custom_role.js
+++ b/frappe/core/doctype/custom_role/custom_role.js
@@ -1,8 +1,6 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Custom Role', {
- refresh: function(frm) {
-
- }
+frappe.ui.form.on("Custom Role", {
+ refresh: function (frm) {},
});
diff --git a/frappe/core/doctype/data_export/data_export.js b/frappe/core/doctype/data_export/data_export.js
index f195e79119..8d65a209b5 100644
--- a/frappe/core/doctype/data_export/data_export.js
+++ b/frappe/core/doctype/data_export/data_export.js
@@ -1,64 +1,65 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Data Export', {
- refresh: frm => {
+frappe.ui.form.on("Data Export", {
+ refresh: (frm) => {
frm.disable_save();
- frm.page.set_primary_action('Export', () => {
+ frm.page.set_primary_action("Export", () => {
can_export(frm) ? export_data(frm) : null;
});
},
onload: (frm) => {
frm.set_query("reference_doctype", () => {
return {
- "filters": {
- "issingle": 0,
- "istable": 0,
- "name": ['in', frappe.boot.user.can_export]
- }
+ filters: {
+ issingle: 0,
+ istable: 0,
+ name: ["in", frappe.boot.user.can_export],
+ },
};
});
},
- reference_doctype: frm => {
+ reference_doctype: (frm) => {
const doctype = frm.doc.reference_doctype;
if (doctype) {
frappe.model.with_doctype(doctype, () => set_field_options(frm));
} else {
reset_filter_and_field(frm);
}
- }
+ },
});
-const can_export = frm => {
+const can_export = (frm) => {
const doctype = frm.doc.reference_doctype;
- const parent_multicheck_options = frm.fields_multicheck[doctype] ?
- frm.fields_multicheck[doctype].get_checked_options() : [];
+ const parent_multicheck_options = frm.fields_multicheck[doctype]
+ ? frm.fields_multicheck[doctype].get_checked_options()
+ : [];
let is_valid_form = false;
if (!doctype) {
- frappe.msgprint(__('Please select the Document Type.'));
+ frappe.msgprint(__("Please select the Document Type."));
} else if (!parent_multicheck_options.length) {
- frappe.msgprint(__('Atleast one field of Parent Document Type is mandatory'));
+ frappe.msgprint(__("Atleast one field of Parent Document Type is mandatory"));
} else {
is_valid_form = true;
}
return is_valid_form;
};
-const export_data = frm => {
- let get_template_url = '/api/method/frappe.core.doctype.data_export.exporter.export_data';
+const export_data = (frm) => {
+ let get_template_url = "/api/method/frappe.core.doctype.data_export.exporter.export_data";
var export_params = () => {
let columns = {};
- Object.keys(frm.fields_multicheck).forEach(dt => {
+ Object.keys(frm.fields_multicheck).forEach((dt) => {
const options = frm.fields_multicheck[dt].get_checked_options();
columns[dt] = options;
});
return {
doctype: frm.doc.reference_doctype,
select_columns: JSON.stringify(columns),
- filters: frm.filter_list.get_filters().map(filter => filter.slice(1, 4)),
+ filters: frm.filter_list.get_filters().map((filter) => filter.slice(1, 4)),
file_type: frm.doc.file_type,
template: true,
- with_data: 1
+ with_data: 1,
};
};
@@ -86,26 +87,24 @@ const set_field_options = (frm) => {
frm.filter_list = new frappe.ui.FilterGroup({
parent: filter_wrapper,
doctype: doctype,
- on_change: () => { },
+ on_change: () => {},
});
// Add 'Select All' and 'Unselect All' button
make_multiselect_buttons(parent_wrapper);
frm.fields_multicheck = {};
- related_doctypes.forEach(dt => {
+ related_doctypes.forEach((dt) => {
frm.fields_multicheck[dt] = add_doctype_field_multicheck_control(dt, parent_wrapper);
});
frm.refresh();
};
-const make_multiselect_buttons = parent_wrapper => {
- const button_container = $(parent_wrapper)
- .append('
')
- .find('.flex');
+const make_multiselect_buttons = (parent_wrapper) => {
+ const button_container = $(parent_wrapper).append('
').find(".flex");
- ["Select All", "Unselect All"].map(d => {
+ ["Select All", "Unselect All"].map((d) => {
frappe.ui.form.make_control({
parent: $(button_container),
df: {
@@ -113,59 +112,59 @@ const make_multiselect_buttons = parent_wrapper => {
fieldname: frappe.scrub(d),
fieldtype: "Button",
click: () => {
- checkbox_toggle(d !== 'Select All');
- }
+ checkbox_toggle(d !== "Select All");
+ },
},
- render_input: true
+ render_input: true,
});
});
- $(button_container).find('.frappe-control').map((index, button) => {
- $(button).css({"margin-right": "1em"});
- });
+ $(button_container)
+ .find(".frappe-control")
+ .map((index, button) => {
+ $(button).css({ "margin-right": "1em" });
+ });
function checkbox_toggle(checked) {
- $(parent_wrapper).find('[data-fieldtype="MultiCheck"]').map((index, element) => {
- $(element).find(`:checkbox`).prop("checked", checked).trigger('click');
- });
+ $(parent_wrapper)
+ .find('[data-fieldtype="MultiCheck"]')
+ .map((index, element) => {
+ $(element).find(`:checkbox`).prop("checked", checked).trigger("click");
+ });
}
-
};
-const get_doctypes = parentdt => {
- return [parentdt].concat(
- frappe.meta.get_table_fields(parentdt).map(df => df.options)
- );
+const get_doctypes = (parentdt) => {
+ return [parentdt].concat(frappe.meta.get_table_fields(parentdt).map((df) => df.options));
};
const add_doctype_field_multicheck_control = (doctype, parent_wrapper) => {
const fields = get_fields(doctype);
- const options = fields
- .map(df => {
- return {
- label: df.label,
- value: df.fieldname,
- danger: df.reqd,
- checked: 1
- };
- });
+ const options = fields.map((df) => {
+ return {
+ label: df.label,
+ value: df.fieldname,
+ danger: df.reqd,
+ checked: 1,
+ };
+ });
const multicheck_control = frappe.ui.form.make_control({
parent: parent_wrapper,
df: {
- "label": doctype,
- "fieldname": doctype + '_fields',
- "fieldtype": "MultiCheck",
- "options": options,
- "columns": 3,
+ label: doctype,
+ fieldname: doctype + "_fields",
+ fieldtype: "MultiCheck",
+ options: options,
+ columns: 3,
},
- render_input: true
+ render_input: true,
});
multicheck_control.refresh_input();
return multicheck_control;
};
-const filter_fields = df => frappe.model.is_value_type(df) && !df.hidden;
-const get_fields = dt => frappe.meta.get_docfields(dt).filter(filter_fields);
\ No newline at end of file
+const filter_fields = (df) => frappe.model.is_value_type(df) && !df.hidden;
+const get_fields = (dt) => frappe.meta.get_docfields(dt).filter(filter_fields);
diff --git a/frappe/core/doctype/data_import/data_import.js b/frappe/core/doctype/data_import/data_import.js
index dfc560a98a..7db3aa9629 100644
--- a/frappe/core/doctype/data_import/data_import.js
+++ b/frappe/core/doctype/data_import/data_import.js
@@ -1,17 +1,17 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Data Import', {
+frappe.ui.form.on("Data Import", {
setup(frm) {
- frappe.realtime.on('data_import_refresh', ({ data_import }) => {
+ frappe.realtime.on("data_import_refresh", ({ data_import }) => {
frm.import_in_progress = false;
if (data_import !== frm.doc.name) return;
- frappe.model.clear_doc('Data Import', frm.doc.name);
- frappe.model.with_doc('Data Import', frm.doc.name).then(() => {
+ frappe.model.clear_doc("Data Import", frm.doc.name);
+ frappe.model.with_doc("Data Import", frm.doc.name).then(() => {
frm.refresh();
});
});
- frappe.realtime.on('data_import_progress', data => {
+ frappe.realtime.on("data_import_progress", (data) => {
frm.import_in_progress = true;
if (data.data_import !== frm.doc.name) {
return;
@@ -31,20 +31,16 @@ frappe.ui.form.on('Data Import', {
if (data.success) {
let message_args = [data.current, data.total, eta_message];
message =
- frm.doc.import_type === 'Insert New Records'
- ? __('Importing {0} of {1}, {2}', message_args)
- : __('Updating {0} of {1}, {2}', message_args);
+ frm.doc.import_type === "Insert New Records"
+ ? __("Importing {0} of {1}, {2}", message_args)
+ : __("Updating {0} of {1}, {2}", message_args);
}
if (data.skipping) {
- message = __('Skipping {0} of {1}, {2}', [
- data.current,
- data.total,
- eta_message
- ]);
+ message = __("Skipping {0} of {1}, {2}", [data.current, data.total, eta_message]);
}
- frm.dashboard.show_progress(__('Import Progress'), percent, message);
- frm.page.set_indicator(__('In Progress'), 'orange');
- frm.trigger('update_primary_action');
+ frm.dashboard.show_progress(__("Import Progress"), percent, message);
+ frm.page.set_indicator(__("In Progress"), "orange");
+ frm.trigger("update_primary_action");
// hide progress when complete
if (data.current === data.total) {
@@ -55,18 +51,18 @@ frappe.ui.form.on('Data Import', {
}
});
- frm.set_query('reference_doctype', () => {
+ frm.set_query("reference_doctype", () => {
return {
filters: {
- name: ['in', frappe.boot.user.can_import]
- }
+ name: ["in", frappe.boot.user.can_import],
+ },
};
});
- frm.get_field('import_file').df.options = {
+ frm.get_field("import_file").df.options = {
restrictions: {
- allowed_file_types: ['.csv', '.xls', '.xlsx']
- }
+ allowed_file_types: [".csv", ".xls", ".xlsx"],
+ },
};
frm.has_import_file = () => {
@@ -76,33 +72,31 @@ frappe.ui.form.on('Data Import', {
refresh(frm) {
frm.page.hide_icon_group();
- frm.trigger('update_indicators');
- frm.trigger('import_file');
- frm.trigger('show_import_log');
- frm.trigger('show_import_warnings');
- frm.trigger('toggle_submit_after_import');
+ frm.trigger("update_indicators");
+ frm.trigger("import_file");
+ frm.trigger("show_import_log");
+ frm.trigger("show_import_warnings");
+ frm.trigger("toggle_submit_after_import");
- if (frm.doc.status != 'Pending')
- frm.trigger('show_import_status');
+ if (frm.doc.status != "Pending") frm.trigger("show_import_status");
- frm.trigger('show_report_error_button');
+ frm.trigger("show_report_error_button");
- if (frm.doc.status === 'Partial Success') {
- frm.add_custom_button(__('Export Errored Rows'), () =>
- frm.trigger('export_errored_rows')
+ if (frm.doc.status === "Partial Success") {
+ frm.add_custom_button(__("Export Errored Rows"), () =>
+ frm.trigger("export_errored_rows")
);
}
- if (frm.doc.status.includes('Success')) {
- frm.add_custom_button(
- __('Go to {0} List', [__(frm.doc.reference_doctype)]),
- () => frappe.set_route('List', frm.doc.reference_doctype)
+ if (frm.doc.status.includes("Success")) {
+ frm.add_custom_button(__("Go to {0} List", [__(frm.doc.reference_doctype)]), () =>
+ frappe.set_route("List", frm.doc.reference_doctype)
);
}
},
onload_post_render(frm) {
- frm.trigger('update_primary_action');
+ frm.trigger("update_primary_action");
},
update_primary_action(frm) {
@@ -111,13 +105,12 @@ frappe.ui.form.on('Data Import', {
return;
}
frm.disable_save();
- if (frm.doc.status !== 'Success') {
- if (!frm.is_new() && (frm.has_import_file())) {
- let label =
- frm.doc.status === 'Pending' ? __('Start Import') : __('Retry');
+ if (frm.doc.status !== "Success") {
+ if (!frm.is_new() && frm.has_import_file()) {
+ let label = frm.doc.status === "Pending" ? __("Start Import") : __("Retry");
frm.page.set_primary_action(label, () => frm.events.start_import(frm));
} else {
- frm.page.set_primary_action(__('Save'), () => frm.save());
+ frm.page.set_primary_action(__("Save"), () => frm.save());
}
}
},
@@ -133,11 +126,11 @@ frappe.ui.form.on('Data Import', {
show_import_status(frm) {
frappe.call({
- 'method': 'frappe.core.doctype.data_import.data_import.get_import_status',
- 'args': {
- 'data_import_name': frm.doc.name
+ method: "frappe.core.doctype.data_import.data_import.get_import_status",
+ args: {
+ data_import_name: frm.doc.name,
},
- 'callback': function(r) {
+ callback: function (r) {
let successful_records = cint(r.message.success);
let failed_records = cint(r.message.failed);
let total_records = cint(r.message.total_records);
@@ -147,52 +140,64 @@ frappe.ui.form.on('Data Import', {
let message;
if (failed_records === 0) {
let message_args = [successful_records];
- if (frm.doc.import_type === 'Insert New Records') {
+ if (frm.doc.import_type === "Insert New Records") {
message =
successful_records > 1
- ? __('Successfully imported {0} records.', message_args)
- : __('Successfully imported {0} record.', message_args);
+ ? __("Successfully imported {0} records.", message_args)
+ : __("Successfully imported {0} record.", message_args);
} else {
message =
successful_records > 1
- ? __('Successfully updated {0} records.', message_args)
- : __('Successfully updated {0} record.', message_args);
+ ? __("Successfully updated {0} records.", message_args)
+ : __("Successfully updated {0} record.", message_args);
}
} else {
let message_args = [successful_records, total_records];
- if (frm.doc.import_type === 'Insert New Records') {
+ if (frm.doc.import_type === "Insert New Records") {
message =
successful_records > 1
- ? __('Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.', message_args)
- : __('Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.', message_args);
+ ? __(
+ "Successfully imported {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
+ message_args
+ )
+ : __(
+ "Successfully imported {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
+ message_args
+ );
} else {
message =
successful_records > 1
- ? __('Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.', message_args)
- : __('Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.', message_args);
+ ? __(
+ "Successfully updated {0} records out of {1}. Click on Export Errored Rows, fix the errors and import again.",
+ message_args
+ )
+ : __(
+ "Successfully updated {0} record out of {1}. Click on Export Errored Rows, fix the errors and import again.",
+ message_args
+ );
}
}
frm.dashboard.set_headline(message);
- }
+ },
});
},
show_report_error_button(frm) {
- if (frm.doc.status === 'Error') {
+ if (frm.doc.status === "Error") {
frappe.db
- .get_list('Error Log', {
+ .get_list("Error Log", {
filters: { method: frm.doc.name },
- fields: ['method', 'error'],
- order_by: 'creation desc',
- limit: 1
+ fields: ["method", "error"],
+ order_by: "creation desc",
+ limit: 1,
})
- .then(result => {
+ .then((result) => {
if (result.length > 0) {
- frm.add_custom_button('Report Error', () => {
+ frm.add_custom_button("Report Error", () => {
let fake_xhr = {
responseText: JSON.stringify({
- exc: result[0].error
- })
+ exc: result[0].error,
+ }),
};
frappe.request.report_error(fake_xhr, {});
});
@@ -202,21 +207,19 @@ frappe.ui.form.on('Data Import', {
},
start_import(frm) {
- frm
- .call({
- method: 'form_start_import',
- args: { data_import: frm.doc.name },
- btn: frm.page.btn_primary
- })
- .then(r => {
- if (r.message === true) {
- frm.disable_save();
- }
- });
+ frm.call({
+ method: "form_start_import",
+ args: { data_import: frm.doc.name },
+ btn: frm.page.btn_primary,
+ }).then((r) => {
+ if (r.message === true) {
+ frm.disable_save();
+ }
+ });
},
download_template(frm) {
- frappe.require('data_import_tools.bundle.js', () => {
+ frappe.require("data_import_tools.bundle.js", () => {
frm.data_exporter = new frappe.data_import.DataExporter(
frm.doc.reference_doctype,
frm.doc.import_type
@@ -225,127 +228,123 @@ frappe.ui.form.on('Data Import', {
},
reference_doctype(frm) {
- frm.trigger('toggle_submit_after_import');
+ frm.trigger("toggle_submit_after_import");
},
toggle_submit_after_import(frm) {
- frm.toggle_display('submit_after_import', false);
+ frm.toggle_display("submit_after_import", false);
let doctype = frm.doc.reference_doctype;
if (doctype) {
frappe.model.with_doctype(doctype, () => {
let meta = frappe.get_meta(doctype);
- frm.toggle_display('submit_after_import', meta.is_submittable);
+ frm.toggle_display("submit_after_import", meta.is_submittable);
});
}
},
google_sheets_url(frm) {
if (!frm.is_dirty()) {
- frm.trigger('import_file');
+ frm.trigger("import_file");
} else {
- frm.trigger('update_primary_action');
+ frm.trigger("update_primary_action");
}
},
refresh_google_sheet(frm) {
- frm.trigger('import_file');
+ frm.trigger("import_file");
},
import_file(frm) {
- frm.toggle_display('section_import_preview', frm.has_import_file());
+ frm.toggle_display("section_import_preview", frm.has_import_file());
if (!frm.has_import_file()) {
- frm.get_field('import_preview').$wrapper.empty();
+ frm.get_field("import_preview").$wrapper.empty();
return;
} else {
- frm.trigger('update_primary_action');
+ frm.trigger("update_primary_action");
}
// load import preview
- frm.get_field('import_preview').$wrapper.empty();
+ frm.get_field("import_preview").$wrapper.empty();
$('')
- .html(__('Loading import file...'))
- .appendTo(frm.get_field('import_preview').$wrapper);
+ .html(__("Loading import file..."))
+ .appendTo(frm.get_field("import_preview").$wrapper);
- frm
- .call({
- method: 'get_preview_from_template',
- args: {
- data_import: frm.doc.name,
- import_file: frm.doc.import_file,
- google_sheets_url: frm.doc.google_sheets_url
+ frm.call({
+ method: "get_preview_from_template",
+ args: {
+ data_import: frm.doc.name,
+ import_file: frm.doc.import_file,
+ google_sheets_url: frm.doc.google_sheets_url,
+ },
+ error_handlers: {
+ TimestampMismatchError() {
+ // ignore this error
},
- error_handlers: {
- TimestampMismatchError() {
- // ignore this error
- }
- }
- })
- .then(r => {
- let preview_data = r.message;
- frm.events.show_import_preview(frm, preview_data);
- frm.events.show_import_warnings(frm, preview_data);
- });
+ },
+ }).then((r) => {
+ let preview_data = r.message;
+ frm.events.show_import_preview(frm, preview_data);
+ frm.events.show_import_warnings(frm, preview_data);
+ });
},
show_import_preview(frm, preview_data) {
let import_log = preview_data.import_log;
- if (
- frm.import_preview &&
- frm.import_preview.doctype === frm.doc.reference_doctype
- ) {
+ if (frm.import_preview && frm.import_preview.doctype === frm.doc.reference_doctype) {
frm.import_preview.preview_data = preview_data;
frm.import_preview.import_log = import_log;
frm.import_preview.refresh();
return;
}
- frappe.require('data_import_tools.bundle.js', () => {
+ frappe.require("data_import_tools.bundle.js", () => {
frm.import_preview = new frappe.data_import.ImportPreview({
- wrapper: frm.get_field('import_preview').$wrapper,
+ wrapper: frm.get_field("import_preview").$wrapper,
doctype: frm.doc.reference_doctype,
preview_data,
import_log,
frm,
events: {
remap_column(changed_map) {
- let template_options = JSON.parse(frm.doc.template_options || '{}');
- template_options.column_to_field_map = template_options.column_to_field_map || {};
+ let template_options = JSON.parse(frm.doc.template_options || "{}");
+ template_options.column_to_field_map =
+ template_options.column_to_field_map || {};
Object.assign(template_options.column_to_field_map, changed_map);
- frm.set_value('template_options', JSON.stringify(template_options));
- frm.save().then(() => frm.trigger('import_file'));
- }
- }
+ frm.set_value("template_options", JSON.stringify(template_options));
+ frm.save().then(() => frm.trigger("import_file"));
+ },
+ },
});
});
},
export_errored_rows(frm) {
open_url_post(
- '/api/method/frappe.core.doctype.data_import.data_import.download_errored_template',
+ "/api/method/frappe.core.doctype.data_import.data_import.download_errored_template",
{
- data_import_name: frm.doc.name
+ data_import_name: frm.doc.name,
}
);
},
export_import_log(frm) {
open_url_post(
- '/api/method/frappe.core.doctype.data_import.data_import.download_import_log',
+ "/api/method/frappe.core.doctype.data_import.data_import.download_import_log",
{
- data_import_name: frm.doc.name
+ data_import_name: frm.doc.name,
}
);
},
show_import_warnings(frm, preview_data) {
let columns = preview_data.columns;
- let warnings = JSON.parse(frm.doc.template_warnings || '[]');
+ let warnings = JSON.parse(frm.doc.template_warnings || "[]");
warnings = warnings.concat(preview_data.warnings || []);
- frm.toggle_display('import_warnings_section', warnings.length > 0);
+ frm.toggle_display("import_warnings_section", warnings.length > 0);
if (warnings.length === 0) {
- frm.get_field('import_warnings').$wrapper.html('');
+ frm.get_field("import_warnings").$wrapper.html("");
return;
}
@@ -361,36 +360,38 @@ frappe.ui.form.on('Data Import', {
}
}
- let html = '';
+ let html = "";
html += Object.keys(warnings_by_row)
- .map(row_number => {
+ .map((row_number) => {
let message = warnings_by_row[row_number]
- .map(w => {
+ .map((w) => {
if (w.field) {
let label =
w.field.label +
(w.field.parent !== frm.doc.reference_doctype
? ` (${w.field.parent})`
- : '');
+ : "");
return `${label}: ${w.message} `;
}
return `${w.message} `;
})
- .join('');
+ .join("");
return `
-
${__('Row {0}', [row_number])}
+
${__("Row {0}", [row_number])}
`;
})
- .join('');
+ .join("");
html += other_warnings
- .map(warning => {
- let header = '';
+ .map((warning) => {
+ let header = "";
if (warning.col) {
- let column_number = `${__('Column {0}', [warning.col])} `;
+ let column_number = `${__("Column {0}", [
+ warning.col,
+ ])} `;
let column_header = columns[warning.col].header_title;
header = `${column_number} (${column_header})`;
}
@@ -401,8 +402,8 @@ frappe.ui.form.on('Data Import', {
`;
})
- .join('');
- frm.get_field('import_warnings').$wrapper.html(`
+ .join("");
+ frm.get_field("import_warnings").$wrapper.html(`
@@ -410,62 +411,62 @@ frappe.ui.form.on('Data Import', {
},
show_failed_logs(frm) {
- frm.trigger('show_import_log');
+ frm.trigger("show_import_log");
},
render_import_log(frm) {
frappe.call({
- 'method': 'frappe.client.get_list',
- 'args': {
- 'doctype': 'Data Import Log',
- 'filters': {
- 'data_import': frm.doc.name
+ method: "frappe.client.get_list",
+ args: {
+ doctype: "Data Import Log",
+ filters: {
+ data_import: frm.doc.name,
},
- 'fields': ['success', 'docname', 'messages', 'exception', 'row_indexes'],
- 'limit_page_length': 5000,
- 'order_by': 'log_index'
+ fields: ["success", "docname", "messages", "exception", "row_indexes"],
+ limit_page_length: 5000,
+ order_by: "log_index",
},
- callback: function(r) {
+ callback: function (r) {
let logs = r.message;
if (logs.length === 0) return;
- frm.toggle_display('import_log_section', true);
+ frm.toggle_display("import_log_section", true);
let rows = logs
- .map(log => {
- let html = '';
+ .map((log) => {
+ let html = "";
if (log.success) {
- if (frm.doc.import_type === 'Insert New Records') {
- html = __('Successfully imported {0}', [
+ if (frm.doc.import_type === "Insert New Records") {
+ html = __("Successfully imported {0}", [
`${frappe.utils.get_form_link(
frm.doc.reference_doctype,
log.docname,
true
- )}`
+ )}`,
]);
} else {
- html = __('Successfully updated {0}', [
+ html = __("Successfully updated {0}", [
`${frappe.utils.get_form_link(
frm.doc.reference_doctype,
log.docname,
true
- )}`
+ )}`,
]);
}
} else {
- let messages = (JSON.parse(log.messages || '[]'))
+ let messages = JSON.parse(log.messages || "[]")
.map(JSON.parse)
- .map(m => {
- let title = m.title ? `${m.title} ` : '';
- let message = m.message ? `${m.message}
` : '';
+ .map((m) => {
+ let title = m.title ? `${m.title} ` : "";
+ let message = m.message ? `${m.message}
` : "";
return title + message;
})
- .join('');
+ .join("");
let id = frappe.dom.get_unique_id();
html = `${messages}
- ${__('Show Traceback')}
+ ${__("Show Traceback")}
@@ -473,15 +474,15 @@ frappe.ui.form.on('Data Import', {
`;
}
- let indicator_color = log.success ? 'green' : 'red';
- let title = log.success ? __('Success') : __('Failure');
+ let indicator_color = log.success ? "green" : "red";
+ let title = log.success ? __("Success") : __("Failure");
if (frm.doc.show_failed_logs && log.success) {
- return '';
+ return "";
}
return `
- ${JSON.parse(log.row_indexes).join(', ')}
+ ${JSON.parse(log.row_indexes).join(", ")}
${title}
@@ -490,54 +491,54 @@ frappe.ui.form.on('Data Import', {
`;
})
- .join('');
+ .join("");
if (!rows && frm.doc.show_failed_logs) {
rows = `
- ${__('No failed logs')}
+ ${__("No failed logs")}
`;
}
- frm.get_field('import_log_preview').$wrapper.html(`
+ frm.get_field("import_log_preview").$wrapper.html(`
- ${__('Row Number')}
- ${__('Status')}
- ${__('Message')}
+ ${__("Row Number")}
+ ${__("Status")}
+ ${__("Message")}
${rows}
`);
- }
+ },
});
},
show_import_log(frm) {
- frm.toggle_display('import_log_section', false);
+ frm.toggle_display("import_log_section", false);
if (frm.import_in_progress) {
return;
}
frappe.call({
- 'method': 'frappe.client.get_count',
- 'args': {
- 'doctype': 'Data Import Log',
- 'filters': {
- 'data_import': frm.doc.name
- }
+ method: "frappe.client.get_count",
+ args: {
+ doctype: "Data Import Log",
+ filters: {
+ data_import: frm.doc.name,
+ },
},
- 'callback': function(r) {
+ callback: function (r) {
let count = r.message;
if (count < 5000) {
- frm.trigger('render_import_log');
+ frm.trigger("render_import_log");
} else {
- frm.toggle_display('import_log_section', false);
- frm.add_custom_button(__('Export Import Log'), () =>
- frm.trigger('export_import_log')
+ frm.toggle_display("import_log_section", false);
+ frm.add_custom_button(__("Export Import Log"), () =>
+ frm.trigger("export_import_log")
);
}
- }
+ },
});
},
});
diff --git a/frappe/core/doctype/data_import/data_import_list.js b/frappe/core/doctype/data_import/data_import_list.js
index 6ab750ba25..c054655e62 100644
--- a/frappe/core/doctype/data_import/data_import_list.js
+++ b/frappe/core/doctype/data_import/data_import_list.js
@@ -1,46 +1,44 @@
let imports_in_progress = [];
-frappe.listview_settings['Data Import'] = {
+frappe.listview_settings["Data Import"] = {
onload(listview) {
- frappe.realtime.on('data_import_progress', data => {
+ frappe.realtime.on("data_import_progress", (data) => {
if (!imports_in_progress.includes(data.data_import)) {
imports_in_progress.push(data.data_import);
}
});
- frappe.realtime.on('data_import_refresh', data => {
- imports_in_progress = imports_in_progress.filter(
- d => d !== data.data_import
- );
+ frappe.realtime.on("data_import_refresh", (data) => {
+ imports_in_progress = imports_in_progress.filter((d) => d !== data.data_import);
listview.refresh();
});
},
- get_indicator: function(doc) {
+ get_indicator: function (doc) {
var colors = {
- 'Pending': 'orange',
- 'Not Started': 'orange',
- 'Partial Success': 'orange',
- 'Success': 'green',
- 'In Progress': 'orange',
- 'Error': 'red'
+ Pending: "orange",
+ "Not Started": "orange",
+ "Partial Success": "orange",
+ Success: "green",
+ "In Progress": "orange",
+ Error: "red",
};
let status = doc.status;
if (imports_in_progress.includes(doc.name)) {
- status = 'In Progress';
+ status = "In Progress";
}
- if (status == 'Pending') {
- status = 'Not Started';
+ if (status == "Pending") {
+ status = "Not Started";
}
- return [__(status), colors[status], 'status,=,' + doc.status];
+ return [__(status), colors[status], "status,=," + doc.status];
},
formatters: {
import_type(value) {
return {
- 'Insert New Records': __('Insert'),
- 'Update Existing Records': __('Update')
+ "Insert New Records": __("Insert"),
+ "Update Existing Records": __("Update"),
}[value];
- }
+ },
},
- hide_name_column: true
+ hide_name_column: true,
};
diff --git a/frappe/core/doctype/data_import_log/data_import_log.js b/frappe/core/doctype/data_import_log/data_import_log.js
index c376edeec9..19ba0eb727 100644
--- a/frappe/core/doctype/data_import_log/data_import_log.js
+++ b/frappe/core/doctype/data_import_log/data_import_log.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Data Import Log', {
+frappe.ui.form.on("Data Import Log", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/deleted_document/deleted_document.js b/frappe/core/doctype/deleted_document/deleted_document.js
index 3125cb2f1c..34e13c01ab 100644
--- a/frappe/core/doctype/deleted_document/deleted_document.js
+++ b/frappe/core/doctype/deleted_document/deleted_document.js
@@ -1,22 +1,22 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Deleted Document', {
- refresh: function(frm) {
- if(frm.doc.restored) {
- frm.add_custom_button(__('Open'), function() {
- frappe.set_route('Form', frm.doc.deleted_doctype, frm.doc.new_name);
+frappe.ui.form.on("Deleted Document", {
+ refresh: function (frm) {
+ if (frm.doc.restored) {
+ frm.add_custom_button(__("Open"), function () {
+ frappe.set_route("Form", frm.doc.deleted_doctype, frm.doc.new_name);
});
} else {
- frm.add_custom_button(__('Restore'), function() {
+ frm.add_custom_button(__("Restore"), function () {
frappe.call({
- method: 'frappe.core.doctype.deleted_document.deleted_document.restore',
- args: {name: frm.doc.name},
- callback: function(r) {
+ method: "frappe.core.doctype.deleted_document.deleted_document.restore",
+ args: { name: frm.doc.name },
+ callback: function (r) {
frm.reload_doc();
- }
+ },
});
});
}
- }
+ },
});
diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js
index 92413bfdf4..6a271f5ae9 100644
--- a/frappe/core/doctype/deleted_document/deleted_document_list.js
+++ b/frappe/core/doctype/deleted_document/deleted_document_list.js
@@ -3,27 +3,36 @@ frappe.listview_settings["Deleted Document"] = {
const action = () => {
const selected_docs = doclist.get_checked_items();
if (selected_docs.length > 0) {
- let docnames = selected_docs.map(doc => doc.name);
+ let docnames = selected_docs.map((doc) => doc.name);
frappe.call({
method: "frappe.core.doctype.deleted_document.deleted_document.bulk_restore",
args: { docnames },
callback: function (r) {
if (r.message) {
let body = (docnames) => {
- const html = docnames.map(docname => {
+ const html = docnames.map((docname) => {
return `${docname} `;
});
return "" + html.join("");
};
let message = (title, docnames) => {
- return (docnames.length > 0) ? title + body(docnames) + " ": "";
+ return docnames.length > 0 ? title + body(docnames) + "" : "";
};
const { restored, invalid, failed } = r.message;
- const restored_summary = message(__("Documents restored successfully"), restored);
- const invalid_summary = message(__("Documents that were already restored"), invalid);
- const failed_summary = message(__("Documents that failed to restore"), failed);
+ const restored_summary = message(
+ __("Documents restored successfully"),
+ restored
+ );
+ const invalid_summary = message(
+ __("Documents that were already restored"),
+ invalid
+ );
+ const failed_summary = message(
+ __("Documents that failed to restore"),
+ failed
+ );
const summary = restored_summary + invalid_summary + failed_summary;
frappe.msgprint(summary, __("Document Restoration Summary"), true);
diff --git a/frappe/core/doctype/docshare/docshare.js b/frappe/core/doctype/docshare/docshare.js
index 48db47a8cc..4d68c65cff 100644
--- a/frappe/core/doctype/docshare/docshare.js
+++ b/frappe/core/doctype/docshare/docshare.js
@@ -1,8 +1,6 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('DocShare', {
- refresh: function(frm) {
-
- }
+frappe.ui.form.on("DocShare", {
+ refresh: function (frm) {},
});
diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js
index 3a9b1f63dc..e91a05e17d 100644
--- a/frappe/core/doctype/doctype/doctype.js
+++ b/frappe/core/doctype/doctype/doctype.js
@@ -1,19 +1,19 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
-frappe.ui.form.on('DocType', {
- refresh: function(frm) {
- frm.set_query('role', 'permissions', function(doc) {
- if (doc.custom && frappe.session.user != 'Administrator') {
+frappe.ui.form.on("DocType", {
+ refresh: function (frm) {
+ frm.set_query("role", "permissions", function (doc) {
+ if (doc.custom && frappe.session.user != "Administrator") {
return {
query: "frappe.core.doctype.role.role.role_query",
- filters: [['Role', 'name', '!=', 'All']]
+ filters: [["Role", "name", "!=", "All"]],
};
}
});
- if(frappe.session.user !== "Administrator" || !frappe.boot.developer_mode) {
- if(frm.is_new()) {
+ if (frappe.session.user !== "Administrator" || !frappe.boot.developer_mode) {
+ if (frm.is_new()) {
frm.set_value("custom", 1);
}
frm.toggle_enable("custom", 0);
@@ -23,37 +23,46 @@ frappe.ui.form.on('DocType', {
if (!frm.is_new() && !frm.doc.istable) {
if (frm.doc.issingle) {
- frm.add_custom_button(__('Go to {0}', [__(frm.doc.name)]), () => {
+ frm.add_custom_button(__("Go to {0}", [__(frm.doc.name)]), () => {
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
});
} else {
- frm.add_custom_button(__('Go to {0} List', [__(frm.doc.name)]), () => {
+ frm.add_custom_button(__("Go to {0} List", [__(frm.doc.name)]), () => {
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
});
}
}
const customize_form_link = "Customize Form ";
- if(!frappe.boot.developer_mode && !frm.doc.custom) {
+ if (!frappe.boot.developer_mode && !frm.doc.custom) {
// make the document read-only
frm.set_read_only();
- frm.dashboard.add_comment(__("DocTypes can not be modified, please use {0} instead", [customize_form_link]), "blue", true);
+ frm.dashboard.add_comment(
+ __("DocTypes can not be modified, please use {0} instead", [customize_form_link]),
+ "blue",
+ true
+ );
} else if (frappe.boot.developer_mode) {
- let msg = __("This site is running in developer mode. Any change made here will be updated in code.");
+ let msg = __(
+ "This site is running in developer mode. Any change made here will be updated in code."
+ );
msg += " ";
- msg += __("If you just want to customize for your site, use {0} instead.", [customize_form_link]);
+ msg += __("If you just want to customize for your site, use {0} instead.", [
+ customize_form_link,
+ ]);
frm.dashboard.add_comment(msg, "yellow");
}
- if(frm.is_new()) {
+ if (frm.is_new()) {
frm.events.set_default_permission(frm);
} else {
frm.toggle_enable("engine", 0);
}
// set label for "In List View" for child tables
- frm.get_docfield('fields', 'in_list_view').label = frm.doc.istable ?
- __('In Grid View') : __('In List View');
+ frm.get_docfield("fields", "in_list_view").label = frm.doc.istable
+ ? __("In Grid View")
+ : __("In List View");
frm.cscript.autoname(frm);
frm.cscript.set_naming_rule_description(frm);
@@ -61,8 +70,8 @@ frappe.ui.form.on('DocType', {
istable: (frm) => {
if (frm.doc.istable && frm.is_new()) {
- frm.set_value('autoname', 'autoincrement');
- frm.set_value('allow_rename', 0);
+ frm.set_value("autoname", "autoincrement");
+ frm.set_value("allow_rename", 0);
} else if (!frm.doc.istable && !frm.is_new()) {
frm.events.set_default_permission(frm);
}
@@ -70,7 +79,7 @@ frappe.ui.form.on('DocType', {
set_default_permission: (frm) => {
if (!(frm.doc.permissions && frm.doc.permissions.length)) {
- frm.add_child('permissions', {role: 'System Manager'});
+ frm.add_child("permissions", { role: "System Manager" });
}
},
});
@@ -98,15 +107,15 @@ frappe.ui.form.on("DocField", {
}
let doctypes = frm.doc.fields
- .filter(df => df.fieldtype == "Link")
- .filter(df => df.options && df.fieldname != row.fieldname)
- .map(df => ({
+ .filter((df) => df.fieldtype == "Link")
+ .filter((df) => df.options && df.fieldname != row.fieldname)
+ .map((df) => ({
label: `${df.options} (${df.fieldname})`,
- value: df.fieldname
+ value: df.fieldname,
}));
$doctype_select.add_options([
{ label: __("Select DocType"), value: "", selected: true },
- ...doctypes
+ ...doctypes,
]);
$doctype_select.on("change", () => {
@@ -120,27 +129,25 @@ frappe.ui.form.on("DocField", {
let link_fieldname = $doctype_select.val();
if (!link_fieldname) return;
- let link_field = frm.doc.fields.find(
- df => df.fieldname === link_fieldname
- );
+ let link_field = frm.doc.fields.find((df) => df.fieldname === link_fieldname);
let link_doctype = link_field.options;
frappe.model.with_doctype(link_doctype, () => {
let fields = frappe.meta
.get_docfields(link_doctype, null, {
- fieldtype: ["not in", frappe.model.no_value_type]
+ fieldtype: ["not in", frappe.model.no_value_type],
})
- .map(df => ({
+ .map((df) => ({
label: `${df.label} (${df.fieldtype})`,
- value: df.fieldname
+ value: df.fieldname,
}));
$field_select.add_options([
{
label: __("Select Field"),
value: "",
selected: true,
- disabled: true
+ disabled: true,
},
- ...fields
+ ...fields,
]);
if (curr_value.fieldname) {
@@ -161,9 +168,9 @@ frappe.ui.form.on("DocField", {
}
},
- fieldtype: function(frm) {
+ fieldtype: function (frm) {
frm.trigger("max_attachments");
- }
+ },
});
-extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm }));
diff --git a/frappe/core/doctype/document_naming_rule/document_naming_rule.js b/frappe/core/doctype/document_naming_rule/document_naming_rule.js
index 097a4e9a6e..70d95673e6 100644
--- a/frappe/core/doctype/document_naming_rule/document_naming_rule.js
+++ b/frappe/core/doctype/document_naming_rule/document_naming_rule.js
@@ -1,64 +1,70 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Document Naming Rule', {
- refresh: function(frm) {
- frm.trigger('document_type');
+frappe.ui.form.on("Document Naming Rule", {
+ refresh: function (frm) {
+ frm.trigger("document_type");
if (!frm.doc.__islocal) frm.trigger("add_update_counter_button");
},
document_type: (frm) => {
// update the select field options with fieldnames
if (frm.doc.document_type) {
frappe.model.with_doctype(frm.doc.document_type, () => {
- let fieldnames = frappe.get_meta(frm.doc.document_type).fields
- .filter((d) => {
+ let fieldnames = frappe
+ .get_meta(frm.doc.document_type)
+ .fields.filter((d) => {
return frappe.model.no_value_type.indexOf(d.fieldtype) === -1;
- }).map((d) => {
- return {label: `${d.label} (${d.fieldname})`, value: d.fieldname};
+ })
+ .map((d) => {
+ return { label: `${d.label} (${d.fieldname})`, value: d.fieldname };
});
frm.fields_dict.conditions.grid.update_docfield_property(
- 'field', 'options', fieldnames
+ "field",
+ "options",
+ fieldnames
);
});
}
},
add_update_counter_button: (frm) => {
- frm.add_custom_button(__('Update Counter'), function() {
+ frm.add_custom_button(__("Update Counter"), function () {
+ const fields = [
+ {
+ fieldtype: "Data",
+ fieldname: "new_counter",
+ label: __("New Counter"),
+ default: frm.doc.counter,
+ reqd: 1,
+ description: __(
+ "Warning: Updating counter may lead to document name conflicts if not done properly"
+ ),
+ },
+ ];
- const fields = [{
- fieldtype: 'Data',
- fieldname: 'new_counter',
- label: __('New Counter'),
- default: frm.doc.counter,
- reqd: 1,
- description: __('Warning: Updating counter may lead to document name conflicts if not done properly')
- }];
-
- let primary_action_label = __('Save');
+ let primary_action_label = __("Save");
let primary_action = (fields) => {
frappe.call({
- method: 'frappe.core.doctype.document_naming_rule.document_naming_rule.update_current',
+ method: "frappe.core.doctype.document_naming_rule.document_naming_rule.update_current",
args: {
name: frm.doc.name,
- new_counter: fields.new_counter
+ new_counter: fields.new_counter,
},
- callback: function() {
+ callback: function () {
frm.set_value("counter", fields.new_counter);
dialog.hide();
- }
+ },
});
};
const dialog = new frappe.ui.Dialog({
- title: __('Update Counter Value for Prefix: {0}', [frm.doc.prefix]),
+ title: __("Update Counter Value for Prefix: {0}", [frm.doc.prefix]),
fields,
primary_action_label,
- primary_action
+ primary_action,
});
dialog.show();
-
});
- }
+ },
});
diff --git a/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.js b/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.js
index 8ef39c7b70..fdf46e82e0 100644
--- a/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.js
+++ b/frappe/core/doctype/document_naming_rule_condition/document_naming_rule_condition.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Document Naming Rule Condition', {
+frappe.ui.form.on("Document Naming Rule Condition", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/document_naming_settings/document_naming_settings.js b/frappe/core/doctype/document_naming_settings/document_naming_settings.js
index 2dc5fc4d58..2a9ec4aae5 100644
--- a/frappe/core/doctype/document_naming_settings/document_naming_settings.js
+++ b/frappe/core/doctype/document_naming_settings/document_naming_settings.js
@@ -2,28 +2,28 @@
// For license information, please see license.txt
frappe.ui.form.on("Document Naming Settings", {
- refresh: function(frm) {
+ refresh: function (frm) {
frm.trigger("setup_transaction_autocomplete");
frm.disable_save();
},
- setup_transaction_autocomplete: function(frm) {
+ setup_transaction_autocomplete: function (frm) {
frappe.call({
method: "get_transactions_and_prefixes",
doc: frm.doc,
- callback: function(r) {
+ callback: function (r) {
frm.fields_dict.transaction_type.set_data(r.message.transactions);
frm.fields_dict.prefix.set_data(r.message.prefixes);
},
});
},
- transaction_type: function(frm) {
+ transaction_type: function (frm) {
frm.set_value("user_must_always_select", 0);
frappe.call({
method: "get_options",
doc: frm.doc,
- callback: function(r) {
+ callback: function (r) {
frm.set_value("naming_series_options", r.message);
if (r.message && r.message.split("\n")[0] == "")
frm.set_value("user_must_always_select", 1);
@@ -31,23 +31,23 @@ frappe.ui.form.on("Document Naming Settings", {
});
},
- prefix: function(frm) {
+ prefix: function (frm) {
frappe.call({
method: "get_current",
doc: frm.doc,
- callback: function(r) {
+ callback: function (r) {
frm.refresh_field("current_value");
},
});
},
- update: function(frm) {
+ update: function (frm) {
frappe.call({
method: "update_series",
doc: frm.doc,
freeze: true,
freeze_msg: __("Updating naming series options"),
- callback: function(r) {
+ callback: function (r) {
frm.trigger("setup_transaction_autocomplete");
frm.trigger("transaction_type");
},
@@ -58,14 +58,11 @@ frappe.ui.form.on("Document Naming Settings", {
frappe.call({
method: "preview_series",
doc: frm.doc,
- callback: function(r) {
+ callback: function (r) {
if (!r.exc) {
frm.set_value("series_preview", r.message);
} else {
- frm.set_value(
- "series_preview",
- __("Failed to generate preview of series")
- );
+ frm.set_value("series_preview", __("Failed to generate preview of series"));
}
},
});
diff --git a/frappe/core/doctype/document_share_key/document_share_key.js b/frappe/core/doctype/document_share_key/document_share_key.js
index c51233e10f..7e1712beff 100644
--- a/frappe/core/doctype/document_share_key/document_share_key.js
+++ b/frappe/core/doctype/document_share_key/document_share_key.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Document Share Key', {
+frappe.ui.form.on("Document Share Key", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/domain/domain.js b/frappe/core/doctype/domain/domain.js
index 397ed4b19c..9b51c10d77 100644
--- a/frappe/core/doctype/domain/domain.js
+++ b/frappe/core/doctype/domain/domain.js
@@ -1,8 +1,6 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Domain', {
- refresh: function(frm) {
-
- }
+frappe.ui.form.on("Domain", {
+ refresh: function (frm) {},
});
diff --git a/frappe/core/doctype/domain_settings/domain_settings.js b/frappe/core/doctype/domain_settings/domain_settings.js
index 7178cb4cd6..87386ce9bd 100644
--- a/frappe/core/doctype/domain_settings/domain_settings.js
+++ b/frappe/core/doctype/domain_settings/domain_settings.js
@@ -1,66 +1,69 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Domain Settings', {
- before_load: function(frm) {
- if(!frm.domains_multicheck) {
+frappe.ui.form.on("Domain Settings", {
+ before_load: function (frm) {
+ if (!frm.domains_multicheck) {
frm.domains_multicheck = frappe.ui.form.make_control({
parent: frm.fields_dict.domains_html.$wrapper,
df: {
fieldname: "domains_multicheck",
fieldtype: "MultiCheck",
get_data: () => {
- let active_domains = (frm.doc.active_domains || []).map(row => row.domain);
- return frappe.boot.all_domains.map(domain => {
+ let active_domains = (frm.doc.active_domains || []).map(
+ (row) => row.domain
+ );
+ return frappe.boot.all_domains.map((domain) => {
return {
label: domain,
value: domain,
- checked: active_domains.includes(domain)
+ checked: active_domains.includes(domain),
};
});
},
on_change: () => {
frm.dirty();
- }
+ },
},
- render_input: true
+ render_input: true,
});
frm.domains_multicheck.refresh_input();
}
},
- validate: function(frm) {
- frm.trigger('set_options_in_table');
+ validate: function (frm) {
+ frm.trigger("set_options_in_table");
},
- set_options_in_table: function(frm) {
+ set_options_in_table: function (frm) {
let selected_options = frm.domains_multicheck.get_value();
let unselected_options = frm.domains_multicheck.options
- .map(option => option.value)
- .filter(value => {
+ .map((option) => option.value)
+ .filter((value) => {
return !selected_options.includes(value);
});
- let map = {}, list = [];
- (frm.doc.active_domains || []).map(row => {
+ let map = {},
+ list = [];
+ (frm.doc.active_domains || []).map((row) => {
map[row.domain] = row.name;
list.push(row.domain);
});
- unselected_options.map(option => {
- if(list.includes(option)) {
+ unselected_options.map((option) => {
+ if (list.includes(option)) {
frappe.model.clear_doc("Has Domain", map[option]);
}
});
- selected_options.map(option => {
- if(!list.includes(option)) {
+ selected_options.map((option) => {
+ if (!list.includes(option)) {
frappe.model.clear_doc("Has Domain", map[option]);
let row = frappe.model.add_child(frm.doc, "Has Domain", "active_domains");
row.domain = option;
}
});
- refresh_field('active_domains');
- }
+ refresh_field("active_domains");
+ },
});
diff --git a/frappe/core/doctype/error_log/error_log.js b/frappe/core/doctype/error_log/error_log.js
index 1262002b04..85b1c8b60a 100644
--- a/frappe/core/doctype/error_log/error_log.js
+++ b/frappe/core/doctype/error_log/error_log.js
@@ -2,11 +2,11 @@
// For license information, please see license.txt
frappe.ui.form.on("Error Log", {
- refresh: function(frm) {
+ refresh: function (frm) {
frm.disable_save();
if (frm.doc.reference_doctype && frm.doc.reference_name) {
- frm.add_custom_button(__("Show Related Errors"), function() {
+ frm.add_custom_button(__("Show Related Errors"), function () {
frappe.set_route("List", "Error Log", {
reference_doctype: frm.doc.reference_doctype,
reference_name: frm.doc.reference_name,
diff --git a/frappe/core/doctype/error_log/error_log_list.js b/frappe/core/doctype/error_log/error_log_list.js
index e92773a9de..dabe95d6d7 100644
--- a/frappe/core/doctype/error_log/error_log_list.js
+++ b/frappe/core/doctype/error_log/error_log_list.js
@@ -1,6 +1,6 @@
frappe.listview_settings["Error Log"] = {
add_fields: ["seen"],
- get_indicator: function(doc) {
+ get_indicator: function (doc) {
if (cint(doc.seen)) {
return [__("Seen"), "green", "seen,=,1"];
} else {
@@ -8,11 +8,11 @@ frappe.listview_settings["Error Log"] = {
}
},
order_by: "seen asc, modified desc",
- onload: function(listview) {
- listview.page.add_menu_item(__("Clear Error Logs"), function() {
+ onload: function (listview) {
+ listview.page.add_menu_item(__("Clear Error Logs"), function () {
frappe.call({
method: "frappe.core.doctype.error_log.error_log.clear_error_logs",
- callback: function() {
+ callback: function () {
listview.refresh();
},
});
@@ -20,6 +20,6 @@ frappe.listview_settings["Error Log"] = {
frappe.require("logtypes.bundle.js", () => {
frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
- })
+ });
},
};
diff --git a/frappe/core/doctype/error_snapshot/error_snapshot.js b/frappe/core/doctype/error_snapshot/error_snapshot.js
index c1b2d996a1..f8a7e3ded5 100644
--- a/frappe/core/doctype/error_snapshot/error_snapshot.js
+++ b/frappe/core/doctype/error_snapshot/error_snapshot.js
@@ -1,14 +1,18 @@
-frappe.ui.form.on("Error Snapshot", "load", function(frm){
+frappe.ui.form.on("Error Snapshot", "load", function (frm) {
frm.set_read_only(true);
});
-frappe.ui.form.on("Error Snapshot", "refresh", function(frm){
- frm.set_df_property("view", "options", frappe.render_template("error_snapshot", {"doc": frm.doc}));
+frappe.ui.form.on("Error Snapshot", "refresh", function (frm) {
+ frm.set_df_property(
+ "view",
+ "options",
+ frappe.render_template("error_snapshot", { doc: frm.doc })
+ );
if (frm.doc.relapses) {
- frm.add_custom_button(__('Show Relapses'), function() {
+ frm.add_custom_button(__("Show Relapses"), function () {
frappe.route_options = {
- parent_error_snapshot: frm.doc.name
+ parent_error_snapshot: frm.doc.name,
};
frappe.set_route("List", "Error Snapshot");
});
diff --git a/frappe/core/doctype/error_snapshot/error_snapshot_list.js b/frappe/core/doctype/error_snapshot/error_snapshot_list.js
index 553495beb1..b331788852 100644
--- a/frappe/core/doctype/error_snapshot/error_snapshot_list.js
+++ b/frappe/core/doctype/error_snapshot/error_snapshot_list.js
@@ -1,19 +1,19 @@
frappe.listview_settings["Error Snapshot"] = {
add_fields: ["parent_error_snapshot", "relapses", "seen"],
- filters:[
- ["parent_error_snapshot","=",null],
- ["seen", "=", false]
+ filters: [
+ ["parent_error_snapshot", "=", null],
+ ["seen", "=", false],
],
- get_indicator: function(doc){
- if (doc.parent_error_snapshot && doc.parent_error_snapshot.length){
+ get_indicator: function (doc) {
+ if (doc.parent_error_snapshot && doc.parent_error_snapshot.length) {
return [__("Relapsed"), !doc.seen ? "orange" : "blue", "parent_error_snapshot,!=,"];
} else {
return [__("First Level"), !doc.seen ? "red" : "green", "parent_error_snapshot,=,"];
}
},
- onload: function(listview) {
+ onload: function (listview) {
frappe.require("logtypes.bundle.js", () => {
frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
- })
+ });
},
-}
+};
diff --git a/frappe/core/doctype/file/file.js b/frappe/core/doctype/file/file.js
index ecad8d884a..f9051dc896 100644
--- a/frappe/core/doctype/file/file.js
+++ b/frappe/core/doctype/file/file.js
@@ -1,23 +1,27 @@
-frappe.ui.form.on("File", "refresh", function(frm) {
- if(!frm.doc.is_folder) {
- frm.add_custom_button(__('Download'), function() {
- var file_url = frm.doc.file_url;
- if (frm.doc.file_name) {
- file_url = file_url.replace(/#/g, '%23');
- }
- window.open(file_url);
- }, "fa fa-download");
+frappe.ui.form.on("File", "refresh", function (frm) {
+ if (!frm.doc.is_folder) {
+ frm.add_custom_button(
+ __("Download"),
+ function () {
+ var file_url = frm.doc.file_url;
+ if (frm.doc.file_name) {
+ file_url = file_url.replace(/#/g, "%23");
+ }
+ window.open(file_url);
+ },
+ "fa fa-download"
+ );
}
frm.get_field("preview_html").$wrapper.html(`
`);
- var is_raster_image = (/\.(gif|jpg|jpeg|tiff|png)$/i).test(frm.doc.file_url);
+ var is_raster_image = /\.(gif|jpg|jpeg|tiff|png)$/i.test(frm.doc.file_url);
var is_optimizable = !frm.doc.is_folder && is_raster_image && frm.doc.file_size > 0;
if (is_optimizable) {
- frm.add_custom_button(__("Optimize"), function() {
+ frm.add_custom_button(__("Optimize"), function () {
frappe.show_alert(__("Optimizing image..."));
frm.call("optimize_file").then(() => {
frappe.show_alert(__("Image optimized"));
@@ -25,16 +29,16 @@ frappe.ui.form.on("File", "refresh", function(frm) {
});
}
- if(frm.doc.file_name && frm.doc.file_name.split('.').splice(-1)[0]==='zip') {
- frm.add_custom_button(__('Unzip'), function() {
+ if (frm.doc.file_name && frm.doc.file_name.split(".").splice(-1)[0] === "zip") {
+ frm.add_custom_button(__("Unzip"), function () {
frappe.call({
method: "frappe.core.api.file.unzip_file",
args: {
name: frm.doc.name,
},
- callback: function() {
- frappe.set_route('List', 'File');
- }
+ callback: function () {
+ frappe.set_route("List", "File");
+ },
});
});
}
diff --git a/frappe/core/doctype/installed_applications/installed_applications.js b/frappe/core/doctype/installed_applications/installed_applications.js
index 9a1fd5ac18..223c028e7a 100644
--- a/frappe/core/doctype/installed_applications/installed_applications.js
+++ b/frappe/core/doctype/installed_applications/installed_applications.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Installed Applications', {
+frappe.ui.form.on("Installed Applications", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/language/language.js b/frappe/core/doctype/language/language.js
index e60282ebbf..9c7852f9e0 100644
--- a/frappe/core/doctype/language/language.js
+++ b/frappe/core/doctype/language/language.js
@@ -1,8 +1,6 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Language', {
- refresh: function(frm) {
-
- }
+frappe.ui.form.on("Language", {
+ refresh: function (frm) {},
});
diff --git a/frappe/core/doctype/log_setting_user/log_setting_user.js b/frappe/core/doctype/log_setting_user/log_setting_user.js
index a1eb824e22..61f3aa67c5 100644
--- a/frappe/core/doctype/log_setting_user/log_setting_user.js
+++ b/frappe/core/doctype/log_setting_user/log_setting_user.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Log Setting User', {
+frappe.ui.form.on("Log Setting User", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/log_settings/log_settings.js b/frappe/core/doctype/log_settings/log_settings.js
index dc7cc7eac2..b72cd5285a 100644
--- a/frappe/core/doctype/log_settings/log_settings.js
+++ b/frappe/core/doctype/log_settings/log_settings.js
@@ -7,9 +7,7 @@ frappe.ui.form.on("Log Settings", {
const added_doctypes = frm.doc.logs_to_clear.map((r) => r.ref_doctype);
return {
query: "frappe.core.doctype.log_settings.log_settings.get_log_doctypes",
- filters: [
- ["name", "not in", added_doctypes],
- ],
+ filters: [["name", "not in", added_doctypes]],
};
});
},
diff --git a/frappe/core/doctype/module_def/module_def.js b/frappe/core/doctype/module_def/module_def.js
index 73d2d6562c..8d542e620d 100644
--- a/frappe/core/doctype/module_def/module_def.js
+++ b/frappe/core/doctype/module_def/module_def.js
@@ -1,13 +1,13 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Module Def', {
- refresh: function(frm) {
- frappe.xcall('frappe.core.doctype.module_def.module_def.get_installed_apps').then(r => {
- frm.set_df_property('app_name', 'options', JSON.parse(r));
+frappe.ui.form.on("Module Def", {
+ refresh: function (frm) {
+ frappe.xcall("frappe.core.doctype.module_def.module_def.get_installed_apps").then((r) => {
+ frm.set_df_property("app_name", "options", JSON.parse(r));
if (!frm.doc.app_name) {
- frm.set_value('app_name', 'frappe');
+ frm.set_value("app_name", "frappe");
}
});
- }
+ },
});
diff --git a/frappe/core/doctype/module_profile/module_profile.js b/frappe/core/doctype/module_profile/module_profile.js
index 3714d31ade..7860577a6c 100644
--- a/frappe/core/doctype/module_profile/module_profile.js
+++ b/frappe/core/doctype/module_profile/module_profile.js
@@ -19,5 +19,5 @@ frappe.ui.form.on("Module Profile", {
if (frm.module_editor) {
frm.module_editor.set_modules_in_table();
}
- }
+ },
});
diff --git a/frappe/core/doctype/navbar_item/navbar_item.js b/frappe/core/doctype/navbar_item/navbar_item.js
index bd4274db49..b14d0a5670 100644
--- a/frappe/core/doctype/navbar_item/navbar_item.js
+++ b/frappe/core/doctype/navbar_item/navbar_item.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Navbar Item', {
+frappe.ui.form.on("Navbar Item", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.js b/frappe/core/doctype/navbar_settings/navbar_settings.js
index e2c157fe6a..c0e1113087 100644
--- a/frappe/core/doctype/navbar_settings/navbar_settings.js
+++ b/frappe/core/doctype/navbar_settings/navbar_settings.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Navbar Settings', {
+frappe.ui.form.on("Navbar Settings", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/package/package.js b/frappe/core/doctype/package/package.js
index 90e2eed1e3..97d9c32c85 100644
--- a/frappe/core/doctype/package/package.js
+++ b/frappe/core/doctype/package/package.js
@@ -1,17 +1,20 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Package', {
- validate: function(frm) {
+frappe.ui.form.on("Package", {
+ validate: function (frm) {
if (!frm.doc.package_name) {
- frm.set_value('package_name', frm.doc.name.toLowerCase().replace(' ', '-'));
+ frm.set_value("package_name", frm.doc.name.toLowerCase().replace(" ", "-"));
}
},
- license_type: function(frm) {
- frappe.call('frappe.core.doctype.package.package.get_license_text',
- {'license_type': frm.doc.license_type}).then(r => {
- frm.set_value('license', r.message);
- });
- }
+ license_type: function (frm) {
+ frappe
+ .call("frappe.core.doctype.package.package.get_license_text", {
+ license_type: frm.doc.license_type,
+ })
+ .then((r) => {
+ frm.set_value("license", r.message);
+ });
+ },
});
diff --git a/frappe/core/doctype/package_import/package_import.js b/frappe/core/doctype/package_import/package_import.js
index c01a6266cc..72f5bbf681 100644
--- a/frappe/core/doctype/package_import/package_import.js
+++ b/frappe/core/doctype/package_import/package_import.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Package Import', {
+frappe.ui.form.on("Package Import", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/package_release/package_release.js b/frappe/core/doctype/package_release/package_release.js
index 9eabe36839..af482fc4a0 100644
--- a/frappe/core/doctype/package_release/package_release.js
+++ b/frappe/core/doctype/package_release/package_release.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Package Release', {
+frappe.ui.form.on("Package Release", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/page/page.js b/frappe/core/doctype/page/page.js
index d1d9600e59..295dca1aae 100644
--- a/frappe/core/doctype/page/page.js
+++ b/frappe/core/doctype/page/page.js
@@ -1,16 +1,16 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Page', {
- refresh: function(frm) {
- if (!frappe.boot.developer_mode && frappe.session.user != 'Administrator') {
+frappe.ui.form.on("Page", {
+ refresh: function (frm) {
+ if (!frappe.boot.developer_mode && frappe.session.user != "Administrator") {
// make the document read-only
frm.set_read_only();
}
if (!frm.is_new() && !frm.doc.istable) {
- frm.add_custom_button(__('Go to {0} Page', [frm.doc.title || frm.doc.name]), () => {
+ frm.add_custom_button(__("Go to {0} Page", [frm.doc.title || frm.doc.name]), () => {
frappe.set_route(frm.doc.name);
});
}
- }
+ },
});
diff --git a/frappe/core/doctype/patch_log/patch_log.js b/frappe/core/doctype/patch_log/patch_log.js
index b52876ac97..171a1d3a0f 100644
--- a/frappe/core/doctype/patch_log/patch_log.js
+++ b/frappe/core/doctype/patch_log/patch_log.js
@@ -1,8 +1,8 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Patch Log', {
- refresh: function(frm) {
+frappe.ui.form.on("Patch Log", {
+ refresh: function (frm) {
frm.disable_save();
- }
+ },
});
diff --git a/frappe/core/doctype/prepared_report/prepared_report.js b/frappe/core/doctype/prepared_report/prepared_report.js
index 6a7cf2728c..58f4c1957f 100644
--- a/frappe/core/doctype/prepared_report/prepared_report.js
+++ b/frappe/core/doctype/prepared_report/prepared_report.js
@@ -1,15 +1,15 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Prepared Report', {
- onload: function(frm) {
+frappe.ui.form.on("Prepared Report", {
+ onload: function (frm) {
var wrapper = $(frm.fields_dict["filter_values"].wrapper).empty();
let filter_table = $(`
- ${ __("Filter") }
- ${ __("Value") }
+ ${__("Filter")}
+ ${__("Value")}
@@ -17,29 +17,29 @@ frappe.ui.form.on('Prepared Report', {
const filters = JSON.parse(frm.doc.filters);
- Object.keys(filters).forEach(key => {
+ Object.keys(filters).forEach((key) => {
const filter_row = $(`
${frappe.model.unscrub(key)}
${filters[key]}
`);
- filter_table.find('tbody').append(filter_row);
+ filter_table.find("tbody").append(filter_row);
});
wrapper.append(filter_table);
},
- refresh: function(frm) {
+ refresh: function (frm) {
frm.disable_save();
- if (frm.doc.status == 'Completed') {
+ if (frm.doc.status == "Completed") {
frm.page.set_primary_action(__("Show Report"), () => {
frappe.set_route(
"query-report",
frm.doc.report_name,
frappe.utils.make_query_string({
- prepared_report_name: frm.doc.name
+ prepared_report_name: frm.doc.name,
})
);
});
}
- }
+ },
});
diff --git a/frappe/core/doctype/prepared_report/prepared_report_list.js b/frappe/core/doctype/prepared_report/prepared_report_list.js
index 8acb3bc75a..1414dd7580 100644
--- a/frappe/core/doctype/prepared_report/prepared_report_list.js
+++ b/frappe/core/doctype/prepared_report/prepared_report_list.js
@@ -1,12 +1,12 @@
-frappe.listview_settings['Prepared Report'] = {
+frappe.listview_settings["Prepared Report"] = {
add_fields: ["status"],
- get_indicator: function(doc) {
- if(doc.status==="Completed"){
+ get_indicator: function (doc) {
+ if (doc.status === "Completed") {
return [__("Completed"), "green", "status,=,Completed"];
- } else if(doc.status ==="Error"){
+ } else if (doc.status === "Error") {
return [__("Error"), "red", "status,=,Error"];
- } else if(doc.status ==="Queued"){
+ } else if (doc.status === "Queued") {
return [__("Queued"), "orange", "status,=,Queued"];
}
- }
-};
\ No newline at end of file
+ },
+};
diff --git a/frappe/core/doctype/report/report.js b/frappe/core/doctype/report/report.js
index 71ed0dac64..c912e217a6 100644
--- a/frappe/core/doctype/report/report.js
+++ b/frappe/core/doctype/report/report.js
@@ -1,5 +1,5 @@
-frappe.ui.form.on('Report', {
- refresh: function(frm) {
+frappe.ui.form.on("Report", {
+ refresh: function (frm) {
if (frm.doc.is_standard === "Yes" && !frappe.boot.developer_mode) {
// make the document read-only
frm.disable_form();
@@ -8,43 +8,51 @@ frappe.ui.form.on('Report', {
}
let doc = frm.doc;
- frm.add_custom_button(__("Show Report"), function() {
- switch(doc.report_type) {
- case "Report Builder":
- frappe.set_route('List', doc.ref_doctype, 'Report', doc.name);
- break;
- case "Query Report":
- frappe.set_route("query-report", doc.name);
- break;
- case "Script Report":
- frappe.set_route("query-report", doc.name);
- break;
- case "Custom Report":
- frappe.set_route("query-report", doc.name);
- break;
- }
- }, "fa fa-table");
+ frm.add_custom_button(
+ __("Show Report"),
+ function () {
+ switch (doc.report_type) {
+ case "Report Builder":
+ frappe.set_route("List", doc.ref_doctype, "Report", doc.name);
+ break;
+ case "Query Report":
+ frappe.set_route("query-report", doc.name);
+ break;
+ case "Script Report":
+ frappe.set_route("query-report", doc.name);
+ break;
+ case "Custom Report":
+ frappe.set_route("query-report", doc.name);
+ break;
+ }
+ },
+ "fa fa-table"
+ );
if (doc.is_standard === "Yes" && frm.perm[0].write) {
- frm.add_custom_button(doc.disabled ? __("Enable Report") : __("Disable Report"), function() {
- frm.call('toggle_disable', {
- disable: doc.disabled ? 0 : 1
- }).then(() => {
- frm.reload_doc();
- });
- }, doc.disabled ? "fa fa-check" : "fa fa-off");
+ frm.add_custom_button(
+ doc.disabled ? __("Enable Report") : __("Disable Report"),
+ function () {
+ frm.call("toggle_disable", {
+ disable: doc.disabled ? 0 : 1,
+ }).then(() => {
+ frm.reload_doc();
+ });
+ },
+ doc.disabled ? "fa fa-check" : "fa fa-off"
+ );
}
},
- ref_doctype: function(frm) {
- if(frm.doc.ref_doctype) {
+ ref_doctype: function (frm) {
+ if (frm.doc.ref_doctype) {
frm.trigger("set_doctype_roles");
}
},
- set_doctype_roles: function(frm) {
- return frm.call('set_doctype_roles').then(() => {
- frm.refresh_field('roles');
+ set_doctype_roles: function (frm) {
+ return frm.call("set_doctype_roles").then(() => {
+ frm.refresh_field("roles");
});
- }
-})
+ },
+});
diff --git a/frappe/core/doctype/role/role.js b/frappe/core/doctype/role/role.js
index 595e857d02..c0a65bcf58 100644
--- a/frappe/core/doctype/role/role.js
+++ b/frappe/core/doctype/role/role.js
@@ -1,8 +1,8 @@
// Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See LICENSE
-frappe.ui.form.on('Role', {
- refresh: function(frm) {
+frappe.ui.form.on("Role", {
+ refresh: function (frm) {
if (frm.doc.name === "All") {
frm.dashboard.add_comment(
__("Role 'All' will be given to all System Users."),
@@ -10,15 +10,15 @@ frappe.ui.form.on('Role', {
);
}
- frm.set_df_property('is_custom', 'read_only', frappe.session.user !== 'Administrator');
+ frm.set_df_property("is_custom", "read_only", frappe.session.user !== "Administrator");
- frm.add_custom_button("Role Permissions Manager", function() {
- frappe.route_options = {"role": frm.doc.name};
+ frm.add_custom_button("Role Permissions Manager", function () {
+ frappe.route_options = { role: frm.doc.name };
frappe.set_route("permission-manager");
});
- frm.add_custom_button("Show Users", function() {
- frappe.route_options = {"role": frm.doc.name};
+ frm.add_custom_button("Show Users", function () {
+ frappe.route_options = { role: frm.doc.name };
frappe.set_route("List", "User", "Report");
});
- }
+ },
});
diff --git a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js
index 5048d24077..86d09bef27 100644
--- a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js
+++ b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js
@@ -1,22 +1,22 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Role Permission for Page and Report', {
- setup: function(frm) {
+frappe.ui.form.on("Role Permission for Page and Report", {
+ setup: function (frm) {
frm.trigger("set_queries");
},
- refresh: function(frm) {
+ refresh: function (frm) {
frm.disable_save();
frm.role_area.hide();
frm.events.setup_buttons(frm);
},
- setup_buttons: function(frm) {
+ setup_buttons: function (frm) {
frm.clear_custom_buttons();
frm.page.clear_actions();
if (frm.doc.set_role_for && frm.doc[frappe.model.scrub(frm.doc.set_role_for)]) {
- frm.add_custom_button(__("Reset to defaults"), function() {
+ frm.add_custom_button(__("Reset to defaults"), function () {
frm.trigger("reset_roles");
});
@@ -26,34 +26,34 @@ frappe.ui.form.on('Role Permission for Page and Report', {
}
},
- onload: function(frm) {
+ onload: function (frm) {
if (!frm.roles_editor) {
frm.role_area = $(frm.fields_dict.roles_html.wrapper);
frm.roles_editor = new frappe.RoleEditor(frm.role_area, frm);
}
},
- set_queries: function(frm) {
- frm.set_query("page", function() {
+ set_queries: function (frm) {
+ frm.set_query("page", function () {
return {
filters: {
- system_page: 0
- }
- }
+ system_page: 0,
+ },
+ };
});
},
- set_role_for: function(frm) {
- frm.trigger("clear_fields")
- frm.toggle_display('roles_html', false)
+ set_role_for: function (frm) {
+ frm.trigger("clear_fields");
+ frm.toggle_display("roles_html", false);
},
- clear_fields: function(frm) {
- var field = (frm.doc.set_role_for == 'Report') ? 'page' : 'report';
- frm.set_value(field, '');
+ clear_fields: function (frm) {
+ var field = frm.doc.set_role_for == "Report" ? "page" : "report";
+ frm.set_value(field, "");
},
- page: function(frm) {
+ page: function (frm) {
frm.events.setup_buttons(frm);
if (frm.doc.page) {
frm.trigger("set_report_page_data");
@@ -62,7 +62,7 @@ frappe.ui.form.on('Role Permission for Page and Report', {
}
},
- report: function(frm) {
+ report: function (frm) {
frm.events.setup_buttons(frm);
if (frm.doc.report) {
frm.trigger("set_report_page_data");
@@ -71,57 +71,57 @@ frappe.ui.form.on('Role Permission for Page and Report', {
}
},
- set_report_page_data: function(frm) {
- frm.toggle_display('roles_html', true)
+ set_report_page_data: function (frm) {
+ frm.toggle_display("roles_html", true);
frm.role_area.show();
return frm.call({
- method:"set_report_page_data",
+ method: "set_report_page_data",
doc: frm.doc,
- callback: function(r) {
- refresh_field('roles')
- frm.roles_editor.show()
- }
- })
+ callback: function (r) {
+ refresh_field("roles");
+ frm.roles_editor.show();
+ },
+ });
},
- update_report_page_data: function(frm) {
- frm.trigger("validate_mandatory_fields")
- if(frm.roles_editor) {
- frm.roles_editor.set_roles_in_table()
+ update_report_page_data: function (frm) {
+ frm.trigger("validate_mandatory_fields");
+ if (frm.roles_editor) {
+ frm.roles_editor.set_roles_in_table();
}
return frm.call({
- method:"update_report_page_data",
+ method: "update_report_page_data",
doc: frm.doc,
- callback: function(r) {
- refresh_field('roles')
- frm.roles_editor.show()
- frappe.msgprint(__("Successfully Updated"))
- }
- })
+ callback: function (r) {
+ refresh_field("roles");
+ frm.roles_editor.show();
+ frappe.msgprint(__("Successfully Updated"));
+ },
+ });
},
- reset_roles: function(frm) {
- frm.trigger("validate_mandatory_fields")
+ reset_roles: function (frm) {
+ frm.trigger("validate_mandatory_fields");
return frm.call({
- method:"reset_roles",
+ method: "reset_roles",
doc: frm.doc,
- callback: function(r) {
- refresh_field('roles')
- frm.roles_editor.show()
- frappe.msgprint(__("Successfully Updated"))
- }
- })
+ callback: function (r) {
+ refresh_field("roles");
+ frm.roles_editor.show();
+ frappe.msgprint(__("Successfully Updated"));
+ },
+ });
},
- validate_mandatory_fields: function(frm) {
- if(!frm.doc.set_role_for){
- frappe.throw(__("Mandatory field: set role for"))
+ validate_mandatory_fields: function (frm) {
+ if (!frm.doc.set_role_for) {
+ frappe.throw(__("Mandatory field: set role for"));
}
- if(frm.doc.set_role_for && !frm.doc[frm.doc.set_role_for.toLocaleLowerCase()]) {
- frappe.throw(__("Mandatory field: {0}", [frm.doc.set_role_for]))
+ if (frm.doc.set_role_for && !frm.doc[frm.doc.set_role_for.toLocaleLowerCase()]) {
+ frappe.throw(__("Mandatory field: {0}", [frm.doc.set_role_for]));
}
- }
+ },
});
diff --git a/frappe/core/doctype/role_profile/role_profile.js b/frappe/core/doctype/role_profile/role_profile.js
index e43980770a..1a5ed95287 100644
--- a/frappe/core/doctype/role_profile/role_profile.js
+++ b/frappe/core/doctype/role_profile/role_profile.js
@@ -1,21 +1,20 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Role Profile', {
- refresh: function(frm) {
+frappe.ui.form.on("Role Profile", {
+ refresh: function (frm) {
if (has_common(frappe.user_roles, ["Administrator", "System Manager"])) {
if (!frm.roles_editor) {
const role_area = $(frm.fields_dict.roles_html.wrapper);
frm.roles_editor = new frappe.RoleEditor(role_area, frm);
}
frm.roles_editor.show();
-
}
},
- validate: function(frm) {
+ validate: function (frm) {
if (frm.roles_editor) {
frm.roles_editor.set_roles_in_table();
}
- }
+ },
});
diff --git a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.js b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.js
index d43160c658..dd9691854d 100644
--- a/frappe/core/doctype/scheduled_job_log/scheduled_job_log.js
+++ b/frappe/core/doctype/scheduled_job_log/scheduled_job_log.js
@@ -1,8 +1,7 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Scheduled Job Log', {
+frappe.ui.form.on("Scheduled Job Log", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js b/frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js
index 5ddccb5d44..1edd718651 100644
--- a/frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js
+++ b/frappe/core/doctype/scheduled_job_log/scheduled_job_log_list.js
@@ -1,7 +1,7 @@
frappe.listview_settings["Scheduled Job Log"] = {
- onload: function(listview) {
+ onload: function (listview) {
frappe.require("logtypes.bundle.js", () => {
frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
- })
+ });
},
};
diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.js b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.js
index 55907b17fc..238754277b 100644
--- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.js
+++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.js
@@ -1,8 +1,7 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Scheduled Job Type', {
+frappe.ui.form.on("Scheduled Job Type", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/server_script/server_script.js b/frappe/core/doctype/server_script/server_script.js
index ca34af11ab..ca5b8d721b 100644
--- a/frappe/core/doctype/server_script/server_script.js
+++ b/frappe/core/doctype/server_script/server_script.js
@@ -1,31 +1,30 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Server Script', {
- setup: function(frm) {
- frm.trigger('setup_help');
+frappe.ui.form.on("Server Script", {
+ setup: function (frm) {
+ frm.trigger("setup_help");
},
- refresh: function(frm) {
- if (frm.doc.script_type != 'Scheduler Event') {
+ refresh: function (frm) {
+ if (frm.doc.script_type != "Scheduler Event") {
frm.dashboard.hide();
}
if (!frm.is_new()) {
- frm.add_custom_button(__('Compare Versions'), () => {
+ frm.add_custom_button(__("Compare Versions"), () => {
new frappe.ui.DiffView("Server Script", "script", frm.doc.name);
});
}
-
- frm.call('get_autocompletion_items')
- .then(r => r.message)
- .then(items => {
- frm.set_df_property('script', 'autocompletions', items);
+ frm.call("get_autocompletion_items")
+ .then((r) => r.message)
+ .then((items) => {
+ frm.set_df_property("script", "autocompletions", items);
});
},
setup_help(frm) {
- frm.get_field('help_html').html(`
+ frm.get_field("help_html").html(`
DocType Event
Add logic for standard doctype events like Before Insert, After Submit, etc.
@@ -77,6 +76,5 @@ where tenant_id = 2
order by creation desc
`);
- }
-
+ },
});
diff --git a/frappe/core/doctype/session_default_settings/session_default_settings.js b/frappe/core/doctype/session_default_settings/session_default_settings.js
index f7cce14809..af333e29a3 100644
--- a/frappe/core/doctype/session_default_settings/session_default_settings.js
+++ b/frappe/core/doctype/session_default_settings/session_default_settings.js
@@ -1,15 +1,15 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
-frappe.ui.form.on('Session Default Settings', {
- refresh: function(frm) {
- frm.set_query('ref_doctype', 'session_defaults', function() {
+frappe.ui.form.on("Session Default Settings", {
+ refresh: function (frm) {
+ frm.set_query("ref_doctype", "session_defaults", function () {
return {
filters: {
issingle: 0,
- istable: 0
- }
+ istable: 0,
+ },
};
});
- }
+ },
});
diff --git a/frappe/core/doctype/success_action/success_action.js b/frappe/core/doctype/success_action/success_action.js
index 50ddb3b66a..993f6eabf4 100644
--- a/frappe/core/doctype/success_action/success_action.js
+++ b/frappe/core/doctype/success_action/success_action.js
@@ -1,28 +1,28 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Success Action', {
+frappe.ui.form.on("Success Action", {
on_load: (frm) => {
if (!frm.action_multicheck) {
- frm.trigger('set_next_action_multicheck');
+ frm.trigger("set_next_action_multicheck");
}
},
refresh: (frm) => {
if (!frm.action_multicheck) {
- frm.trigger('set_next_action_multicheck');
+ frm.trigger("set_next_action_multicheck");
}
},
validate: (frm) => {
const checked_actions = frm.action_multicheck.get_checked_options();
if (checked_actions.length < 2) {
- frappe.msgprint(__('Select atleast 2 actions'));
+ frappe.msgprint(__("Select atleast 2 actions"));
} else {
return true;
}
},
before_save: (frm) => {
const checked_actions = frm.action_multicheck.get_checked_options();
- frm.doc.next_actions = checked_actions.join('\n');
+ frm.doc.next_actions = checked_actions.join("\n");
},
after_save: (frm) => {
frappe.boot.success_action.push(frm.doc);
@@ -30,31 +30,31 @@ frappe.ui.form.on('Success Action', {
},
set_next_action_multicheck: (frm) => {
const next_actions_wrapper = frm.fields_dict.next_actions_html.$wrapper;
- const checked_actions = frm.doc.next_actions ? frm.doc.next_actions.split('\n') : [];
- const action_multicheck_options = get_default_next_actions().map(action => {
+ const checked_actions = frm.doc.next_actions ? frm.doc.next_actions.split("\n") : [];
+ const action_multicheck_options = get_default_next_actions().map((action) => {
return {
label: action.label,
value: action.value,
- checked: checked_actions.length ? checked_actions.includes(action.value) : 1
+ checked: checked_actions.length ? checked_actions.includes(action.value) : 1,
};
});
frm.action_multicheck = frappe.ui.form.make_control({
parent: next_actions_wrapper,
df: {
- 'label': 'Next Actions',
- 'fieldname': 'next_actions_multicheck',
- 'fieldtype': 'MultiCheck',
- 'options': action_multicheck_options,
+ label: "Next Actions",
+ fieldname: "next_actions_multicheck",
+ fieldtype: "MultiCheck",
+ options: action_multicheck_options,
},
});
- }
+ },
});
const get_default_next_actions = () => {
return [
- { label: __('New'), value: 'new' },
- { label: __('Print'), value: 'print' },
- { label: __('Email'), value: 'email' },
- { label: __('View All'), value: 'list' }
+ { label: __("New"), value: "new" },
+ { label: __("Print"), value: "print" },
+ { label: __("Email"), value: "email" },
+ { label: __("View All"), value: "list" },
];
-};
\ No newline at end of file
+};
diff --git a/frappe/core/doctype/system_settings/system_settings.js b/frappe/core/doctype/system_settings/system_settings.js
index 5128ae24cb..f7c43045d2 100644
--- a/frappe/core/doctype/system_settings/system_settings.js
+++ b/frappe/core/doctype/system_settings/system_settings.js
@@ -1,12 +1,12 @@
frappe.ui.form.on("System Settings", {
- refresh: function(frm) {
+ refresh: function (frm) {
frappe.call({
method: "frappe.core.doctype.system_settings.system_settings.load",
- callback: function(data) {
+ callback: function (data) {
frappe.all_timezones = data.message.timezones;
frm.set_df_property("time_zone", "options", frappe.all_timezones);
- $.each(data.message.defaults, function(key, val) {
+ $.each(data.message.defaults, function (key, val) {
frm.set_value(key, val);
frappe.sys_defaults[key] = val;
});
@@ -14,30 +14,30 @@ frappe.ui.form.on("System Settings", {
frappe.app.setup_moment();
delete frm.re_setup_moment;
}
- }
+ },
});
},
- enable_password_policy: function(frm) {
+ enable_password_policy: function (frm) {
if (frm.doc.enable_password_policy == 0) {
frm.set_value("minimum_password_score", "");
} else {
frm.set_value("minimum_password_score", "2");
}
},
- enable_two_factor_auth: function(frm) {
+ enable_two_factor_auth: function (frm) {
if (frm.doc.enable_two_factor_auth == 0) {
frm.set_value("bypass_2fa_for_retricted_ip_users", 0);
frm.set_value("bypass_restrict_ip_check_if_2fa_enabled", 0);
}
},
- enable_prepared_report_auto_deletion: function(frm) {
+ enable_prepared_report_auto_deletion: function (frm) {
if (frm.doc.enable_prepared_report_auto_deletion) {
if (!frm.doc.prepared_report_expiry_period) {
- frm.set_value('prepared_report_expiry_period', 7);
+ frm.set_value("prepared_report_expiry_period", 7);
}
}
},
- on_update: function(frm) {
+ on_update: function (frm) {
if (frappe.boot.time_zone && frappe.boot.time_zone.system !== frm.doc.time_zone) {
// Clear cache after saving to refresh the values of boot.
frappe.ui.toolbar.clear_cache();
diff --git a/frappe/core/doctype/transaction_log/transaction_log.js b/frappe/core/doctype/transaction_log/transaction_log.js
index 569cd9bf61..8f22b859f7 100644
--- a/frappe/core/doctype/transaction_log/transaction_log.js
+++ b/frappe/core/doctype/transaction_log/transaction_log.js
@@ -1,6 +1,4 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Transaction Log', {
-
-});
+frappe.ui.form.on("Transaction Log", {});
diff --git a/frappe/core/doctype/translation/translation.js b/frappe/core/doctype/translation/translation.js
index 454f2564ba..a1b5b182b8 100644
--- a/frappe/core/doctype/translation/translation.js
+++ b/frappe/core/doctype/translation/translation.js
@@ -1,9 +1,8 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-
-frappe.ui.form.on('Translation', {
- refresh: function() {
+frappe.ui.form.on("Translation", {
+ refresh: function () {
//
- }
+ },
});
diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js
index 05a1102c03..24f9eb2cea 100644
--- a/frappe/core/doctype/user/user.js
+++ b/frappe/core/doctype/user/user.js
@@ -1,78 +1,85 @@
-frappe.ui.form.on('User', {
- before_load: function(frm) {
- var update_tz_select = function(user_language) {
+frappe.ui.form.on("User", {
+ before_load: function (frm) {
+ var update_tz_select = function (user_language) {
frm.set_df_property("time_zone", "options", [""].concat(frappe.all_timezones));
};
- if(!frappe.all_timezones) {
+ if (!frappe.all_timezones) {
frappe.call({
method: "frappe.core.doctype.user.user.get_timezones",
- callback: function(r) {
+ callback: function (r) {
frappe.all_timezones = r.message.timezones;
update_tz_select();
- }
+ },
});
} else {
update_tz_select();
}
-
},
- role_profile_name: function(frm) {
- if(frm.doc.role_profile_name) {
+ role_profile_name: function (frm) {
+ if (frm.doc.role_profile_name) {
frappe.call({
- "method": "frappe.core.doctype.user.user.get_role_profile",
+ method: "frappe.core.doctype.user.user.get_role_profile",
args: {
- role_profile: frm.doc.role_profile_name
+ role_profile: frm.doc.role_profile_name,
},
- callback: function(data) {
+ callback: function (data) {
frm.set_value("roles", []);
- $.each(data.message || [], function(i, v) {
+ $.each(data.message || [], function (i, v) {
var d = frm.add_child("roles");
d.role = v.role;
});
frm.roles_editor.show();
- }
+ },
});
}
},
- module_profile: function(frm) {
+ module_profile: function (frm) {
if (frm.doc.module_profile) {
frappe.call({
- "method": "frappe.core.doctype.user.user.get_module_profile",
+ method: "frappe.core.doctype.user.user.get_module_profile",
args: {
- module_profile: frm.doc.module_profile
+ module_profile: frm.doc.module_profile,
},
- callback: function(data) {
+ callback: function (data) {
frm.set_value("block_modules", []);
- $.each(data.message || [], function(i, v) {
+ $.each(data.message || [], function (i, v) {
let d = frm.add_child("block_modules");
d.module = v.module;
});
frm.module_editor && frm.module_editor.show();
- }
+ },
});
}
},
- onload: function(frm) {
+ onload: function (frm) {
frm.can_edit_roles = has_access_to_edit_user();
if (frm.is_new() && frm.roles_editor) {
frm.roles_editor.reset();
}
- if (frm.can_edit_roles && !frm.is_new() && in_list(['System User', 'Website User'], frm.doc.user_type)) {
+ if (
+ frm.can_edit_roles &&
+ !frm.is_new() &&
+ in_list(["System User", "Website User"], frm.doc.user_type)
+ ) {
if (!frm.roles_editor) {
- const role_area = $('')
- .appendTo(frm.fields_dict.roles_html.wrapper);
+ const role_area = $('
').appendTo(
+ frm.fields_dict.roles_html.wrapper
+ );
- frm.roles_editor = new frappe.RoleEditor(role_area, frm, frm.doc.role_profile_name ? 1 : 0);
+ frm.roles_editor = new frappe.RoleEditor(
+ role_area,
+ frm,
+ frm.doc.role_profile_name ? 1 : 0
+ );
- if (frm.doc.user_type == 'System User') {
- var module_area = $('
')
- .appendTo(frm.fields_dict.modules_html.wrapper);
+ if (frm.doc.user_type == "System User") {
+ var module_area = $("
").appendTo(frm.fields_dict.modules_html.wrapper);
frm.module_editor = new frappe.ModuleEditor(frm, module_area);
}
} else {
@@ -80,111 +87,140 @@ frappe.ui.form.on('User', {
}
}
},
- refresh: function(frm) {
+ refresh: function (frm) {
let doc = frm.doc;
if (frm.is_new()) {
frm.set_value("time_zone", frappe.sys_defaults.time_zone);
}
- if (in_list(['System User', 'Website User'], frm.doc.user_type)
- && !frm.is_new() && !frm.roles_editor && frm.can_edit_roles) {
+ if (
+ in_list(["System User", "Website User"], frm.doc.user_type) &&
+ !frm.is_new() &&
+ !frm.roles_editor &&
+ frm.can_edit_roles
+ ) {
frm.reload_doc();
return;
}
- if(doc.name===frappe.session.user && !doc.__unsaved
- && frappe.all_timezones
- && (doc.language || frappe.boot.user.language)
- && doc.language !== frappe.boot.user.language) {
+ if (
+ doc.name === frappe.session.user &&
+ !doc.__unsaved &&
+ frappe.all_timezones &&
+ (doc.language || frappe.boot.user.language) &&
+ doc.language !== frappe.boot.user.language
+ ) {
frappe.msgprint(__("Refreshing..."));
window.location.reload();
}
- frm.toggle_display(['sb1', 'sb3', 'modules_access'], false);
+ frm.toggle_display(["sb1", "sb3", "modules_access"], false);
- if(!frm.is_new()) {
- if(has_access_to_edit_user()) {
+ if (!frm.is_new()) {
+ if (has_access_to_edit_user()) {
+ frm.add_custom_button(
+ __("Set User Permissions"),
+ function () {
+ frappe.route_options = {
+ user: doc.name,
+ };
+ frappe.set_route("List", "User Permission");
+ },
+ __("Permissions")
+ );
- frm.add_custom_button(__("Set User Permissions"), function() {
- frappe.route_options = {
- "user": doc.name
- };
- frappe.set_route('List', 'User Permission');
- }, __("Permissions"));
+ frm.add_custom_button(
+ __("View Permitted Documents"),
+ () =>
+ frappe.set_route("query-report", "Permitted Documents For User", {
+ user: frm.doc.name,
+ }),
+ __("Permissions")
+ );
- frm.add_custom_button(__('View Permitted Documents'),
- () => frappe.set_route('query-report', 'Permitted Documents For User',
- {user: frm.doc.name}), __("Permissions"));
-
- frm.toggle_display(['sb1', 'sb3', 'modules_access'], true);
+ frm.toggle_display(["sb1", "sb3", "modules_access"], true);
}
- frm.add_custom_button(__("Reset Password"), function() {
- frappe.call({
- method: "frappe.core.doctype.user.user.reset_password",
- args: {
- "user": frm.doc.name
- }
- });
- }, __("Password"));
+ frm.add_custom_button(
+ __("Reset Password"),
+ function () {
+ frappe.call({
+ method: "frappe.core.doctype.user.user.reset_password",
+ args: {
+ user: frm.doc.name,
+ },
+ });
+ },
+ __("Password")
+ );
if (frappe.user.has_role("System Manager")) {
frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => {
if (value === 1 && frm.doc.name != "Administrator") {
- frm.add_custom_button(__("Reset LDAP Password"), function() {
- const d = new frappe.ui.Dialog({
- title: __("Reset LDAP Password"),
- fields: [
- {
- label: __("New Password"),
- fieldtype: "Password",
- fieldname: "new_password",
- reqd: 1
+ frm.add_custom_button(
+ __("Reset LDAP Password"),
+ function () {
+ const d = new frappe.ui.Dialog({
+ title: __("Reset LDAP Password"),
+ fields: [
+ {
+ label: __("New Password"),
+ fieldtype: "Password",
+ fieldname: "new_password",
+ reqd: 1,
+ },
+ {
+ label: __("Confirm New Password"),
+ fieldtype: "Password",
+ fieldname: "confirm_password",
+ reqd: 1,
+ },
+ {
+ label: __("Logout All Sessions"),
+ fieldtype: "Check",
+ fieldname: "logout_sessions",
+ },
+ ],
+ primary_action: (values) => {
+ d.hide();
+ if (values.new_password !== values.confirm_password) {
+ frappe.throw(__("Passwords do not match!"));
+ }
+ frappe.call(
+ "frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password",
+ {
+ user: frm.doc.email,
+ password: values.new_password,
+ logout: values.logout_sessions,
+ }
+ );
},
- {
- label: __("Confirm New Password"),
- fieldtype: "Password",
- fieldname: "confirm_password",
- reqd: 1
- },
- {
- label: __("Logout All Sessions"),
- fieldtype: "Check",
- fieldname: "logout_sessions"
- }
- ],
- primary_action: (values) => {
- d.hide();
- if (values.new_password !== values.confirm_password) {
- frappe.throw(__("Passwords do not match!"));
- }
- frappe.call(
- "frappe.integrations.doctype.ldap_settings.ldap_settings.reset_password", {
- user: frm.doc.email,
- password: values.new_password,
- logout: values.logout_sessions
- });
- }
- });
- d.show();
- }, __("Password"));
+ });
+ d.show();
+ },
+ __("Password")
+ );
}
});
}
if (frappe.session.user == doc.name || frappe.user.has_role("System Manager")) {
- frm.add_custom_button(__("Reset OTP Secret"), function() {
- frappe.call({
- method: "frappe.twofactor.reset_otp_secret",
- args: {
- "user": frm.doc.name
- }
- });
- }, __("Password"));
+ frm.add_custom_button(
+ __("Reset OTP Secret"),
+ function () {
+ frappe.call({
+ method: "frappe.twofactor.reset_otp_secret",
+ args: {
+ user: frm.doc.name,
+ },
+ });
+ },
+ __("Password")
+ );
}
- frm.trigger('enabled');
+ frm.trigger("enabled");
if (frm.roles_editor && frm.can_edit_roles) {
frm.roles_editor.disable = frm.doc.role_profile_name ? 1 : 0;
@@ -193,10 +229,12 @@ frappe.ui.form.on('User', {
frm.module_editor && frm.module_editor.show();
- if(frappe.session.user==doc.name) {
+ if (frappe.session.user == doc.name) {
// update display settings
- if(doc.user_image) {
- frappe.boot.user_info[frappe.session.user].image = frappe.utils.get_file_link(doc.user_image);
+ if (doc.user_image) {
+ frappe.boot.user_info[frappe.session.user].image = frappe.utils.get_file_link(
+ doc.user_image
+ );
}
}
}
@@ -208,51 +246,50 @@ frappe.ui.form.on('User', {
}
}
if (!found) {
- frm.add_custom_button(__("Create User Email"), function() {
+ frm.add_custom_button(__("Create User Email"), function () {
frm.events.create_user_email(frm);
});
}
}
- if (frappe.route_flags.unsaved===1){
+ if (frappe.route_flags.unsaved === 1) {
delete frappe.route_flags.unsaved;
- for ( var i=0;i
{
- child_row.used_oauth = value.auth_method === "OAuth";
- frm.refresh_field("user_emails", cdn, "used_oauth");
- });
- }
+ frappe.model.get_value(
+ "Email Account",
+ child_row.email_account,
+ "auth_method",
+ (value) => {
+ child_row.used_oauth = value.auth_method === "OAuth";
+ frm.refresh_field("user_emails", cdn, "used_oauth");
+ }
+ );
+ },
});
-
function has_access_to_edit_user() {
return has_common(frappe.user_roles, get_roles_for_editing_user());
}
function get_roles_for_editing_user() {
- return frappe.get_meta('User').permissions
- .filter(perm => perm.permlevel >= 1 && perm.write)
- .map(perm => perm.role) || ['System Manager'];
+ return (
+ frappe
+ .get_meta("User")
+ .permissions.filter((perm) => perm.permlevel >= 1 && perm.write)
+ .map((perm) => perm.role) || ["System Manager"]
+ );
}
diff --git a/frappe/core/doctype/user/user_list.js b/frappe/core/doctype/user/user_list.js
index 5632edf0cc..334ed0b370 100644
--- a/frappe/core/doctype/user/user_list.js
+++ b/frappe/core/doctype/user/user_list.js
@@ -1,19 +1,19 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
-frappe.listview_settings['User'] = {
+frappe.listview_settings["User"] = {
add_fields: ["enabled", "user_type", "user_image"],
- filters: [["enabled","=",1]],
- prepare_data: function(data) {
+ filters: [["enabled", "=", 1]],
+ prepare_data: function (data) {
data["user_for_avatar"] = data["name"];
},
- get_indicator: function(doc) {
- if(doc.enabled) {
+ get_indicator: function (doc) {
+ if (doc.enabled) {
return [__("Active"), "green", "enabled,=,1"];
} else {
return [__("Disabled"), "grey", "enabled,=,0"];
}
- }
+ },
};
frappe.help.youtube_id["User"] = "8Slw1hsTmUI";
diff --git a/frappe/core/doctype/user_group/user_group.js b/frappe/core/doctype/user_group/user_group.js
index 2aa9b68658..cab1f5dff1 100644
--- a/frappe/core/doctype/user_group/user_group.js
+++ b/frappe/core/doctype/user_group/user_group.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('User Group', {
+frappe.ui.form.on("User Group", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/user_group_member/user_group_member.js b/frappe/core/doctype/user_group_member/user_group_member.js
index 0b2dbe0d46..4c4011c8b4 100644
--- a/frappe/core/doctype/user_group_member/user_group_member.js
+++ b/frappe/core/doctype/user_group_member/user_group_member.js
@@ -1,8 +1,7 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('User Group Member', {
+frappe.ui.form.on("User Group Member", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/core/doctype/user_permission/user_permission.js b/frappe/core/doctype/user_permission/user_permission.js
index f6989db5d8..39ee4348b9 100644
--- a/frappe/core/doctype/user_permission/user_permission.js
+++ b/frappe/core/doctype/user_permission/user_permission.js
@@ -1,59 +1,58 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('User Permission', {
- setup: frm => {
+frappe.ui.form.on("User Permission", {
+ setup: (frm) => {
frm.set_query("allow", () => {
return {
- "filters": {
+ filters: {
issingle: 0,
- istable: 0
- }
+ istable: 0,
+ },
};
});
- frm.set_query('applicable_for', () => {
+ frm.set_query("applicable_for", () => {
return {
- 'query': 'frappe.core.doctype.user_permission.user_permission.get_applicable_for_doctype_list',
- 'doctype': frm.doc.allow
+ query: "frappe.core.doctype.user_permission.user_permission.get_applicable_for_doctype_list",
+ doctype: frm.doc.allow,
};
});
-
},
- refresh: frm => {
- frm.add_custom_button(__('View Permitted Documents'),
- () => frappe.set_route('query-report', 'Permitted Documents For User',
- { user: frm.doc.user }));
- frm.trigger('set_applicable_for_constraint');
- frm.trigger('toggle_hide_descendants');
+ refresh: (frm) => {
+ frm.add_custom_button(__("View Permitted Documents"), () =>
+ frappe.set_route("query-report", "Permitted Documents For User", {
+ user: frm.doc.user,
+ })
+ );
+ frm.trigger("set_applicable_for_constraint");
+ frm.trigger("toggle_hide_descendants");
},
- allow: frm => {
+ allow: (frm) => {
if (frm.doc.allow) {
if (frm.doc.for_value) {
- frm.set_value('for_value', null);
+ frm.set_value("for_value", null);
}
- frm.trigger('toggle_hide_descendants');
+ frm.trigger("toggle_hide_descendants");
}
},
- apply_to_all_doctypes: frm => {
- frm.trigger('set_applicable_for_constraint');
+ apply_to_all_doctypes: (frm) => {
+ frm.trigger("set_applicable_for_constraint");
},
- set_applicable_for_constraint: frm => {
- frm.toggle_reqd('applicable_for', !frm.doc.apply_to_all_doctypes);
+ set_applicable_for_constraint: (frm) => {
+ frm.toggle_reqd("applicable_for", !frm.doc.apply_to_all_doctypes);
if (frm.doc.apply_to_all_doctypes && frm.doc.applicable_for) {
- frm.set_value('applicable_for', null, null, true);
+ frm.set_value("applicable_for", null, null, true);
}
},
- toggle_hide_descendants: frm => {
+ toggle_hide_descendants: (frm) => {
let show = frappe.boot.nested_set_doctypes.includes(frm.doc.allow);
- frm.toggle_display('hide_descendants', show);
- }
-
-
+ frm.toggle_display("hide_descendants", show);
+ },
});
diff --git a/frappe/core/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js
index 0ce66fa8e3..ce5e624403 100644
--- a/frappe/core/doctype/user_permission/user_permission_list.js
+++ b/frappe/core/doctype/user_permission/user_permission_list.js
@@ -1,18 +1,17 @@
-frappe.listview_settings['User Permission'] = {
-
- onload: function(list_view) {
+frappe.listview_settings["User Permission"] = {
+ onload: function (list_view) {
var me = this;
- list_view.page.add_inner_button( __("Add / Update"), function() {
- let dialog =new frappe.ui.Dialog({
- title : __('Add User Permissions'),
+ list_view.page.add_inner_button(__("Add / Update"), function () {
+ let dialog = new frappe.ui.Dialog({
+ title: __("Add User Permissions"),
fields: [
{
- fieldname: 'user',
- label: __('For User'),
- fieldtype: 'Link',
- options: 'User',
+ fieldname: "user",
+ label: __("For User"),
+ fieldtype: "Link",
+ options: "User",
reqd: 1,
- onchange: function() {
+ onchange: function () {
dialog.fields_dict.doctype.set_input(undefined);
dialog.fields_dict.docname.set_input(undefined);
dialog.set_df_property("docname", "hidden", 1);
@@ -20,77 +19,87 @@ frappe.listview_settings['User Permission'] = {
dialog.set_df_property("apply_to_all_doctypes", "hidden", 1);
dialog.set_df_property("applicable_doctypes", "hidden", 1);
dialog.set_df_property("hide_descendants", "hidden", 1);
- }
+ },
},
{
- fieldname: 'doctype',
- label: __('Document Type'),
- fieldtype: 'Link',
- options: 'DocType',
+ fieldname: "doctype",
+ label: __("Document Type"),
+ fieldtype: "Link",
+ options: "DocType",
reqd: 1,
- onchange: function() {
+ onchange: function () {
me.on_doctype_change(dialog);
- }
+ },
},
{
- fieldname: 'docname',
- label: __('Document Name'),
- fieldtype: 'Dynamic Link',
- options: 'doctype',
+ fieldname: "docname",
+ label: __("Document Name"),
+ fieldtype: "Dynamic Link",
+ options: "doctype",
hidden: 1,
- onchange: function() {
+ onchange: function () {
let field = dialog.fields_dict["docname"];
- if(field.value != field.last_value) {
- if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){
- me.get_applicable_doctype(dialog).then(applicable => {
- me.get_multi_select_options(dialog, applicable).then(options => {
- me.applicable_options = options;
- me.on_docname_change(dialog, options, applicable);
- if(options.length > 5){
- dialog.fields_dict.applicable_doctypes.setup_select_all();
+ if (field.value != field.last_value) {
+ if (
+ dialog.fields_dict.doctype.value &&
+ dialog.fields_dict.docname.value &&
+ dialog.fields_dict.user.value
+ ) {
+ me.get_applicable_doctype(dialog).then((applicable) => {
+ me.get_multi_select_options(dialog, applicable).then(
+ (options) => {
+ me.applicable_options = options;
+ me.on_docname_change(dialog, options, applicable);
+ if (options.length > 5) {
+ dialog.fields_dict.applicable_doctypes.setup_select_all();
+ }
}
- });
+ );
});
}
}
- }
+ },
},
{
fieldtype: "Section Break",
- hide_border: 1
+ hide_border: 1,
},
{
- fieldname: 'is_default',
- label: __('Is Default'),
- fieldtype: 'Check',
- hidden: 1
- },
- {
- fieldname: 'apply_to_all_doctypes',
- label: __('Apply to all Documents Types'),
- fieldtype: 'Check',
+ fieldname: "is_default",
+ label: __("Is Default"),
+ fieldtype: "Check",
hidden: 1,
- onchange: function() {
- if(dialog.fields_dict.doctype.value && dialog.fields_dict.docname.value && dialog.fields_dict.user.value){
+ },
+ {
+ fieldname: "apply_to_all_doctypes",
+ label: __("Apply to all Documents Types"),
+ fieldtype: "Check",
+ hidden: 1,
+ onchange: function () {
+ if (
+ dialog.fields_dict.doctype.value &&
+ dialog.fields_dict.docname.value &&
+ dialog.fields_dict.user.value
+ ) {
me.on_apply_to_all_doctypes_change(dialog, me.applicable_options);
- if(me.applicable_options.length > 5){
+ if (me.applicable_options.length > 5) {
dialog.fields_dict.applicable_doctypes.setup_select_all();
}
}
- }
+ },
},
{
- fieldtype: "Column Break"
+ fieldtype: "Column Break",
},
{
- fieldname: 'hide_descendants',
- label: __('Hide Descendants'),
- fieldtype: 'Check',
- hidden: 1
+ fieldname: "hide_descendants",
+ label: __("Hide Descendants"),
+ fieldtype: "Check",
+ hidden: 1,
},
{
fieldtype: "Section Break",
- hide_border: 1
+ hide_border: 1,
},
{
label: __("Applicable Document Types"),
@@ -98,7 +107,7 @@ frappe.listview_settings['User Permission'] = {
fieldtype: "MultiCheck",
options: [],
columns: 2,
- hidden: 1
+ hidden: 1,
},
],
primary_action: (data) => {
@@ -107,126 +116,137 @@ frappe.listview_settings['User Permission'] = {
async: false,
method: "frappe.core.doctype.user_permission.user_permission.add_user_permissions",
args: {
- data : data
+ data: data,
},
- callback: function(r) {
- if(r.message === 1) {
- frappe.show_alert({message:__("User Permissions created sucessfully"), indicator:'blue'});
+ callback: function (r) {
+ if (r.message === 1) {
+ frappe.show_alert({
+ message: __("User Permissions created sucessfully"),
+ indicator: "blue",
+ });
} else {
- frappe.show_alert({message:__("Nothing to update"), indicator:'red'});
-
+ frappe.show_alert({
+ message: __("Nothing to update"),
+ indicator: "red",
+ });
}
- }
+ },
});
dialog.hide();
list_view.refresh();
},
- primary_action_label: __('Submit')
+ primary_action_label: __("Submit"),
});
dialog.show();
});
- list_view.page.add_inner_button( __("Bulk Delete"), function() {
+ list_view.page.add_inner_button(__("Bulk Delete"), function () {
const dialog = new frappe.ui.Dialog({
- title: __('Clear User Permissions'),
+ title: __("Clear User Permissions"),
fields: [
{
- fieldname: 'user',
- label: __('For User'),
- fieldtype: 'Link',
- options: 'User',
- reqd: 1
+ fieldname: "user",
+ label: __("For User"),
+ fieldtype: "Link",
+ options: "User",
+ reqd: 1,
},
{
- fieldname: 'for_doctype',
- label: __('For Document Type'),
- fieldtype: 'Link',
- options: 'DocType',
- reqd: 1
+ fieldname: "for_doctype",
+ label: __("For Document Type"),
+ fieldtype: "Link",
+ options: "DocType",
+ reqd: 1,
},
],
primary_action: (data) => {
// mandatory not filled
if (!data) return;
- frappe.confirm(__('Are you sure?'), () => {
+ frappe.confirm(__("Are you sure?"), () => {
frappe
- .xcall('frappe.core.doctype.user_permission.user_permission.clear_user_permissions', data)
- .then(data => {
+ .xcall(
+ "frappe.core.doctype.user_permission.user_permission.clear_user_permissions",
+ data
+ )
+ .then((data) => {
dialog.hide();
- let message = '';
+ let message = "";
if (data === 0) {
- message = __('No records deleted');
- } else if(data === 1) {
- message = __('{0} record deleted', [data]);
+ message = __("No records deleted");
+ } else if (data === 1) {
+ message = __("{0} record deleted", [data]);
} else {
- message = __('{0} records deleted', [data]);
+ message = __("{0} records deleted", [data]);
}
frappe.show_alert({
message,
- indicator: 'info'
+ indicator: "info",
});
list_view.refresh();
});
});
-
},
- primary_action_label: __('Delete')
+ primary_action_label: __("Delete"),
});
dialog.show();
});
},
- validate: function(dialog, data) {
- if(dialog.fields_dict.applicable_doctypes.get_unchecked_options().length == 0) {
+ validate: function (dialog, data) {
+ if (dialog.fields_dict.applicable_doctypes.get_unchecked_options().length == 0) {
data.apply_to_all_doctypes = 1;
data.applicable_doctypes = [];
return data;
}
- if(data.apply_to_all_doctypes == 0 && !("applicable_doctypes" in data)) {
+ if (data.apply_to_all_doctypes == 0 && !("applicable_doctypes" in data)) {
frappe.throw(__("Please select applicable Doctypes"));
}
return data;
},
- get_applicable_doctype: function(dialog) {
- return new Promise(resolve => {
- frappe.call({
- method: 'frappe.core.doctype.user_permission.user_permission.check_applicable_doc_perm',
- async: false,
- args:{
- user: dialog.fields_dict.user.value,
- doctype: dialog.fields_dict.doctype.value,
- docname: dialog.fields_dict.docname.value
- }
- }).then(r => {
- resolve(r.message);
- });
+ get_applicable_doctype: function (dialog) {
+ return new Promise((resolve) => {
+ frappe
+ .call({
+ method: "frappe.core.doctype.user_permission.user_permission.check_applicable_doc_perm",
+ async: false,
+ args: {
+ user: dialog.fields_dict.user.value,
+ doctype: dialog.fields_dict.doctype.value,
+ docname: dialog.fields_dict.docname.value,
+ },
+ })
+ .then((r) => {
+ resolve(r.message);
+ });
});
},
- get_multi_select_options: function(dialog, applicable){
- return new Promise(resolve => {
- frappe.call({
- method: 'frappe.desk.form.linked_with.get_linked_doctypes',
- async: false,
- args:{
- user: dialog.fields_dict.user.value,
- doctype: dialog.fields_dict.doctype.value,
- docname: dialog.fields_dict.docname.value
- }
- }).then(r => {
- var options = [];
- for(var d in r.message){
- var checked = ($.inArray(d, applicable) != -1) ? 1 : 0;
- options.push({ "label":d, "value": d , "checked": checked});
- }
- resolve(options);
- });
+ get_multi_select_options: function (dialog, applicable) {
+ return new Promise((resolve) => {
+ frappe
+ .call({
+ method: "frappe.desk.form.linked_with.get_linked_doctypes",
+ async: false,
+ args: {
+ user: dialog.fields_dict.user.value,
+ doctype: dialog.fields_dict.doctype.value,
+ docname: dialog.fields_dict.docname.value,
+ },
+ })
+ .then((r) => {
+ var options = [];
+ for (var d in r.message) {
+ var checked = $.inArray(d, applicable) != -1 ? 1 : 0;
+ options.push({ label: d, value: d, checked: checked });
+ }
+ resolve(options);
+ });
});
},
- on_doctype_change: function(dialog) {
+ on_doctype_change: function (dialog) {
dialog.set_df_property("docname", "hidden", 0);
dialog.set_df_property("docname", "reqd", 1);
dialog.set_df_property("is_default", "hidden", 0);
@@ -237,12 +257,15 @@ frappe.listview_settings['User Permission'] = {
dialog.refresh();
},
- on_docname_change: function(dialog, options, applicable) {
- if(applicable.length != 0 ) {
+ on_docname_change: function (dialog, options, applicable) {
+ if (applicable.length != 0) {
dialog.set_primary_action("Update");
dialog.set_title("Update User Permissions");
dialog.set_df_property("applicable_doctypes", "options", options);
- if(dialog.fields_dict.applicable_doctypes.get_checked_options().length == options.length) {
+ if (
+ dialog.fields_dict.applicable_doctypes.get_checked_options().length ==
+ options.length
+ ) {
dialog.set_df_property("applicable_doctypes", "hidden", 1);
} else {
dialog.set_df_property("applicable_doctypes", "hidden", 0);
@@ -257,8 +280,8 @@ frappe.listview_settings['User Permission'] = {
dialog.refresh();
},
- on_apply_to_all_doctypes_change: function(dialog, options) {
- if(dialog.fields_dict.apply_to_all_doctypes.get_value() == 0) {
+ on_apply_to_all_doctypes_change: function (dialog, options) {
+ if (dialog.fields_dict.apply_to_all_doctypes.get_value() == 0) {
dialog.set_df_property("applicable_doctypes", "hidden", 0);
dialog.set_df_property("applicable_doctypes", "options", options);
} else {
@@ -266,5 +289,5 @@ frappe.listview_settings['User Permission'] = {
dialog.set_df_property("applicable_doctypes", "hidden", 1);
}
dialog.refresh_sections();
- }
+ },
};
diff --git a/frappe/core/doctype/user_type/user_type.js b/frappe/core/doctype/user_type/user_type.js
index 6b53248fd4..5cf0dbb25f 100644
--- a/frappe/core/doctype/user_type/user_type.js
+++ b/frappe/core/doctype/user_type/user_type.js
@@ -1,72 +1,71 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('User Type', {
- refresh: function(frm) {
- if (frm.is_new() && !frappe.boot.developer_mode)
- frm.set_value('is_standard', 1);
+frappe.ui.form.on("User Type", {
+ refresh: function (frm) {
+ if (frm.is_new() && !frappe.boot.developer_mode) frm.set_value("is_standard", 1);
- frm.set_query('document_type', 'user_doctypes', function() {
+ frm.set_query("document_type", "user_doctypes", function () {
return {
filters: {
- istable: 0
- }
+ istable: 0,
+ },
};
});
- frm.set_query('document_type', 'select_doctypes', function() {
+ frm.set_query("document_type", "select_doctypes", function () {
return {
filters: {
- istable: 0
- }
+ istable: 0,
+ },
};
});
- frm.set_query('document_type', 'custom_select_doctypes', function() {
+ frm.set_query("document_type", "custom_select_doctypes", function () {
return {
filters: {
- istable: 0
- }
+ istable: 0,
+ },
};
});
- frm.set_query('role', function() {
+ frm.set_query("role", function () {
return {
filters: {
is_custom: 1,
disabled: 0,
- desk_access: 1
- }
+ desk_access: 1,
+ },
};
});
- frm.set_query('apply_user_permission_on', function() {
+ frm.set_query("apply_user_permission_on", function () {
return {
- query: "frappe.core.doctype.user_type.user_type.get_user_linked_doctypes"
+ query: "frappe.core.doctype.user_type.user_type.get_user_linked_doctypes",
};
});
},
- onload: function(frm) {
- frm.trigger('get_user_id_fields');
+ onload: function (frm) {
+ frm.trigger("get_user_id_fields");
},
- apply_user_permission_on: function(frm) {
- frm.set_value('user_id_field', '');
- frm.trigger('get_user_id_fields');
+ apply_user_permission_on: function (frm) {
+ frm.set_value("user_id_field", "");
+ frm.trigger("get_user_id_fields");
},
- get_user_id_fields: function(frm) {
+ get_user_id_fields: function (frm) {
if (frm.doc.apply_user_permission_on) {
frappe.call({
- method: 'frappe.core.doctype.user_type.user_type.get_user_id',
+ method: "frappe.core.doctype.user_type.user_type.get_user_id",
args: {
- parent: frm.doc.apply_user_permission_on
+ parent: frm.doc.apply_user_permission_on,
+ },
+ callback: function (r) {
+ set_field_options("user_id_field", [""].concat(r.message));
},
- callback: function(r) {
- set_field_options('user_id_field', [""].concat(r.message));
- }
});
}
- }
+ },
});
diff --git a/frappe/core/doctype/user_type/user_type_list.js b/frappe/core/doctype/user_type/user_type_list.js
index 9a9ef417ac..856fe8985e 100644
--- a/frappe/core/doctype/user_type/user_type_list.js
+++ b/frappe/core/doctype/user_type/user_type_list.js
@@ -1,4 +1,4 @@
-frappe.listview_settings['User Type'] = {
+frappe.listview_settings["User Type"] = {
add_fields: ["is_standard"],
get_indicator: function (doc) {
if (doc.is_standard) {
@@ -6,5 +6,5 @@ frappe.listview_settings['User Type'] = {
} else {
return [__("Custom"), "blue", "is_standard,=,0"];
}
- }
+ },
};
diff --git a/frappe/core/doctype/version/version.js b/frappe/core/doctype/version/version.js
index d39d2eac03..1e26e5f748 100644
--- a/frappe/core/doctype/version/version.js
+++ b/frappe/core/doctype/version/version.js
@@ -1,9 +1,12 @@
-frappe.ui.form.on("Version", "refresh", function(frm) {
- $(frappe.render_template('version_view', {doc:frm.doc, data:JSON.parse(frm.doc.data)}))
- .appendTo(frm.fields_dict.table_html.$wrapper.empty());
+frappe.ui.form.on("Version", "refresh", function (frm) {
+ $(
+ frappe.render_template("version_view", { doc: frm.doc, data: JSON.parse(frm.doc.data) })
+ ).appendTo(frm.fields_dict.table_html.$wrapper.empty());
- frm.add_custom_button(__('Show all Versions'), function() {
- frappe.set_route('List', 'Version',
- {ref_doctype: frm.doc.ref_doctype, docname: frm.doc.docname});
+ frm.add_custom_button(__("Show all Versions"), function () {
+ frappe.set_route("List", "Version", {
+ ref_doctype: frm.doc.ref_doctype,
+ docname: frm.doc.docname,
+ });
});
});
diff --git a/frappe/core/doctype/view_log/view_log.js b/frappe/core/doctype/view_log/view_log.js
index a8c95b01e8..06d23802be 100644
--- a/frappe/core/doctype/view_log/view_log.js
+++ b/frappe/core/doctype/view_log/view_log.js
@@ -1,8 +1,6 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('View Log', {
- refresh: function(frm) {
-
- }
+frappe.ui.form.on("View Log", {
+ refresh: function (frm) {},
});
diff --git a/frappe/core/page/background_jobs/background_jobs.js b/frappe/core/page/background_jobs/background_jobs.js
index 7334bfd5dd..94c7bbf3bc 100644
--- a/frappe/core/page/background_jobs/background_jobs.js
+++ b/frappe/core/page/background_jobs/background_jobs.js
@@ -1,4 +1,4 @@
-frappe.pages["background_jobs"].on_page_load = wrapper => {
+frappe.pages["background_jobs"].on_page_load = (wrapper) => {
const background_job = new BackgroundJobs(wrapper);
$(wrapper).bind("show", () => {
@@ -13,20 +13,15 @@ class BackgroundJobs {
this.page = frappe.ui.make_app_page({
parent: wrapper,
title: __("Background Jobs"),
- single_column: true
+ single_column: true,
});
this.page.add_inner_button(__("Remove Failed Jobs"), () => {
- frappe.confirm(
- __("Are you sure you want to remove all failed jobs?"),
- () => {
- frappe
- .call(
- "frappe.core.page.background_jobs.background_jobs.remove_failed_jobs"
- )
- .then(() => this.refresh_jobs());
- }
- );
+ frappe.confirm(__("Are you sure you want to remove all failed jobs?"), () => {
+ frappe
+ .call("frappe.core.page.background_jobs.background_jobs.remove_failed_jobs")
+ .then(() => this.refresh_jobs());
+ });
});
this.page.main.addClass("frappe-card");
@@ -34,10 +29,7 @@ class BackgroundJobs {
this.$content = $(this.page.body).find(".table-area");
this.make_filters();
- this.refresh_jobs = frappe.utils.throttle(
- this.refresh_jobs.bind(this),
- 1000
- );
+ this.refresh_jobs = frappe.utils.throttle(this.refresh_jobs.bind(this), 1000);
}
make_filters() {
@@ -50,7 +42,7 @@ class BackgroundJobs {
change: () => {
this.queue_timeout.toggle(this.view.get_value() === "Jobs");
this.job_status.toggle(this.view.get_value() === "Jobs");
- }
+ },
});
this.queue_timeout = this.page.add_field({
label: __("Queue"),
@@ -60,9 +52,9 @@ class BackgroundJobs {
{ label: "All Queues", value: "all" },
{ label: "Default", value: "default" },
{ label: "Short", value: "short" },
- { label: "Long", value: "long" }
+ { label: "Long", value: "long" },
],
- default: "all"
+ default: "all",
});
this.job_status = this.page.add_field({
label: __("Job Status"),
@@ -74,9 +66,9 @@ class BackgroundJobs {
{ label: "Deferred", value: "deferred" },
{ label: "Started", value: "started" },
{ label: "Finished", value: "finished" },
- { label: "Failed", value: "failed" }
+ { label: "Failed", value: "failed" },
],
- default: "all"
+ default: "all",
});
this.auto_refresh = this.page.add_field({
label: __("Auto Refresh"),
@@ -87,7 +79,7 @@ class BackgroundJobs {
if (this.auto_refresh.get_value()) {
this.refresh_jobs();
}
- }
+ },
});
}
@@ -98,16 +90,15 @@ class BackgroundJobs {
update_scheduler_status() {
frappe.call({
- method:
- "frappe.core.page.background_jobs.background_jobs.get_scheduler_status",
- callback: r => {
+ method: "frappe.core.page.background_jobs.background_jobs.get_scheduler_status",
+ callback: (r) => {
let { status } = r.message;
if (status === "active") {
this.page.set_indicator(__("Scheduler: Active"), "green");
} else {
this.page.set_indicator(__("Scheduler: Inactive"), "red");
}
- }
+ },
});
}
@@ -125,25 +116,21 @@ class BackgroundJobs {
frappe.call({
method: "frappe.core.page.background_jobs.background_jobs.get_info",
args,
- callback: res => {
+ callback: (res) => {
this.page.add_inner_message("");
- let template =
- view === "Jobs" ? "background_jobs" : "background_workers";
+ let template = view === "Jobs" ? "background_jobs" : "background_workers";
this.$content.html(
frappe.render_template(template, {
- jobs: res.message || []
+ jobs: res.message || [],
})
);
let auto_refresh = this.auto_refresh.get_value();
- if (
- frappe.get_route()[0] === "background_jobs" &&
- auto_refresh
- ) {
+ if (frappe.get_route()[0] === "background_jobs" && auto_refresh) {
setTimeout(() => this.refresh_jobs(), 2000);
}
- }
+ },
});
}
}
diff --git a/frappe/core/page/dashboard_view/dashboard_view.js b/frappe/core/page/dashboard_view/dashboard_view.js
index bf9fb2a286..8f2c56910c 100644
--- a/frappe/core/page/dashboard_view/dashboard_view.js
+++ b/frappe/core/page/dashboard_view/dashboard_view.js
@@ -1,19 +1,18 @@
// Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
-frappe.provide('frappe.dashboards');
-frappe.provide('frappe.dashboards.chart_sources');
+frappe.provide("frappe.dashboards");
+frappe.provide("frappe.dashboards.chart_sources");
-
-frappe.pages['dashboard-view'].on_page_load = function(wrapper) {
+frappe.pages["dashboard-view"].on_page_load = function (wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __("Dashboard"),
- single_column: true
+ single_column: true,
});
frappe.dashboard = new Dashboard(wrapper);
- $(wrapper).bind('show', function() {
+ $(wrapper).bind("show", function () {
frappe.dashboard.show();
});
};
@@ -37,20 +36,20 @@ class Dashboard {
} else {
// last opened
if (frappe.last_dashboard) {
- frappe.set_re_route('dashboard-view', frappe.last_dashboard);
+ frappe.set_re_route("dashboard-view", frappe.last_dashboard);
} else {
// default dashboard
- frappe.db.get_list('Dashboard', {filters: {is_default: 1}}).then(data => {
+ frappe.db.get_list("Dashboard", { filters: { is_default: 1 } }).then((data) => {
if (data && data.length) {
- frappe.set_re_route('dashboard-view', data[0].name);
+ frappe.set_re_route("dashboard-view", data[0].name);
} else {
// no default, get the latest one
- frappe.db.get_list('Dashboard', {limit: 1}).then(data => {
+ frappe.db.get_list("Dashboard", { limit: 1 }).then((data) => {
if (data && data.length) {
- frappe.set_re_route('dashboard-view', data[0].name);
+ frappe.set_re_route("dashboard-view", data[0].name);
} else {
// create a new dashboard!
- frappe.new_doc('Dashboard');
+ frappe.new_doc("Dashboard");
}
});
}
@@ -63,9 +62,9 @@ class Dashboard {
if (this.dashboard_name !== current_dashboard_name) {
this.dashboard_name = current_dashboard_name;
let title = this.dashboard_name;
- if (!this.dashboard_name.toLowerCase().includes(__('dashboard'))) {
+ if (!this.dashboard_name.toLowerCase().includes(__("dashboard"))) {
// ensure dashboard title has "dashboard"
- title = __('{0} Dashboard', [title]);
+ title = __("{0} Dashboard", [title]);
}
this.page.set_title(title);
this.set_dropdown();
@@ -81,31 +80,30 @@ class Dashboard {
}
refresh() {
- frappe.run_serially([
- () => this.render_cards(),
- () => this.render_charts()
- ]);
+ frappe.run_serially([() => this.render_cards(), () => this.render_charts()]);
}
render_charts() {
return this.get_permitted_items(
- 'frappe.desk.doctype.dashboard.dashboard.get_permitted_charts'
- ).then(charts => {
+ "frappe.desk.doctype.dashboard.dashboard.get_permitted_charts"
+ ).then((charts) => {
if (!charts.length) {
- frappe.msgprint(__('No Permitted Charts on this Dashboard'), __('No Permitted Charts'))
+ frappe.msgprint(
+ __("No Permitted Charts on this Dashboard"),
+ __("No Permitted Charts")
+ );
}
frappe.dashboard_utils.get_dashboard_settings().then((settings) => {
- let chart_config = settings.chart_config? JSON.parse(settings.chart_config): {};
- this.charts =
- charts.map(chart => {
- return {
- chart_name: chart.chart,
- label: chart.chart,
- chart_settings: chart_config[chart.chart] || {},
- ...chart
- }
- });
+ let chart_config = settings.chart_config ? JSON.parse(settings.chart_config) : {};
+ this.charts = charts.map((chart) => {
+ return {
+ chart_name: chart.chart,
+ label: chart.chart,
+ chart_settings: chart_config[chart.chart] || {},
+ ...chart,
+ };
+ });
this.chart_group = new frappe.widget.WidgetGroup({
title: null,
@@ -121,24 +119,23 @@ class Dashboard {
},
widgets: this.charts,
});
- })
+ });
});
}
render_cards() {
return this.get_permitted_items(
- 'frappe.desk.doctype.dashboard.dashboard.get_permitted_cards'
- ).then(cards => {
+ "frappe.desk.doctype.dashboard.dashboard.get_permitted_cards"
+ ).then((cards) => {
if (!cards.length) {
return;
}
- this.number_cards =
- cards.map(card => {
- return {
- name: card.card,
- };
- });
+ this.number_cards = cards.map((card) => {
+ return {
+ name: card.card,
+ };
+ });
this.number_card_group = new frappe.widget.WidgetGroup({
container: this.container,
@@ -157,41 +154,43 @@ class Dashboard {
}
get_permitted_items(method) {
- return frappe.xcall(
- method,
- {
- dashboard_name: this.dashboard_name
- }
- ).then(items => {
- return items;
- });
+ return frappe
+ .xcall(method, {
+ dashboard_name: this.dashboard_name,
+ })
+ .then((items) => {
+ return items;
+ });
}
set_dropdown() {
this.page.clear_menu();
- this.page.add_menu_item(__('Edit'), () => {
- frappe.set_route('Form', 'Dashboard', frappe.dashboard.dashboard_name);
+ this.page.add_menu_item(__("Edit"), () => {
+ frappe.set_route("Form", "Dashboard", frappe.dashboard.dashboard_name);
});
- this.page.add_menu_item(__('New'), () => {
- frappe.new_doc('Dashboard');
+ this.page.add_menu_item(__("New"), () => {
+ frappe.new_doc("Dashboard");
});
- this.page.add_menu_item(__('Refresh All'), () => {
- this.chart_group &&
- this.chart_group.widgets_list.forEach(chart => chart.refresh());
+ this.page.add_menu_item(__("Refresh All"), () => {
+ this.chart_group && this.chart_group.widgets_list.forEach((chart) => chart.refresh());
this.number_card_group &&
- this.number_card_group.widgets_list.forEach(card => card.render_card());
+ this.number_card_group.widgets_list.forEach((card) => card.render_card());
});
- frappe.db.get_list('Dashboard').then(dashboards => {
- dashboards.map(dashboard => {
+ frappe.db.get_list("Dashboard").then((dashboards) => {
+ dashboards.map((dashboard) => {
let name = dashboard.name;
if (name != this.dashboard_name) {
- this.page.add_menu_item(name, () => frappe.set_route("dashboard-view", name), 1);
+ this.page.add_menu_item(
+ name,
+ () => frappe.set_route("dashboard-view", name),
+ 1
+ );
}
});
});
}
-}
\ No newline at end of file
+}
diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js
index 8a06a9aac5..f29df0d3e5 100644
--- a/frappe/core/page/permission_manager/permission_manager.js
+++ b/frappe/core/page/permission_manager/permission_manager.js
@@ -1,20 +1,21 @@
-frappe.pages['permission-manager'].on_page_load = (wrapper) => {
+frappe.pages["permission-manager"].on_page_load = (wrapper) => {
let page = frappe.ui.make_app_page({
parent: wrapper,
- title: __('Role Permissions Manager'),
+ title: __("Role Permissions Manager"),
card_layout: true,
- single_column: true
+ single_column: true,
});
frappe.breadcrumbs.add("Setup");
- $("
").appendTo(page.main);
+ $("
").appendTo(
+ page.main
+ );
$(frappe.render_template("permission_manager_help", {})).appendTo(page.main);
wrapper.permission_engine = new frappe.PermissionEngine(wrapper);
-
};
-frappe.pages['permission-manager'].refresh = function (wrapper) {
+frappe.pages["permission-manager"].refresh = function (wrapper) {
wrapper.permission_engine.set_from_route();
};
@@ -30,33 +31,38 @@ frappe.PermissionEngine = class PermissionEngine {
make() {
this.make_reset_button();
- frappe.call({
- module: "frappe.core",
- page: "permission_manager",
- method: "get_roles_and_doctypes"
- }).then((res) => {
- this.options = res.message;
- this.setup_page();
- });
+ frappe
+ .call({
+ module: "frappe.core",
+ page: "permission_manager",
+ method: "get_roles_and_doctypes",
+ })
+ .then((res) => {
+ this.options = res.message;
+ this.setup_page();
+ });
}
setup_page() {
- this.doctype_select
- = this.wrapper.page.add_select(__("Document Type"),
- [{ value: "", label: __("Select Document Type") + "..." }].concat(this.options.doctypes))
- .change(function () {
- frappe.set_route("permission-manager", $(this).val());
- });
+ this.doctype_select = this.wrapper.page
+ .add_select(
+ __("Document Type"),
+ [{ value: "", label: __("Select Document Type") + "..." }].concat(
+ this.options.doctypes
+ )
+ )
+ .change(function () {
+ frappe.set_route("permission-manager", $(this).val());
+ });
- this.role_select
- = this.wrapper.page.add_select(__("Roles"),
- [__("Select Role") + "..."].concat(this.options.roles))
- .change(() => {
- this.refresh();
- });
+ this.role_select = this.wrapper.page
+ .add_select(__("Roles"), [__("Select Role") + "..."].concat(this.options.roles))
+ .change(() => {
+ this.refresh();
+ });
- this.page.add_inner_button(__('Set User Permissions'), () => {
- return frappe.set_route('List', 'User Permission');
+ this.page.add_inner_button(__("Set User Permissions"), () => {
+ return frappe.set_route("List", "User Permission");
});
this.set_from_route();
}
@@ -91,7 +97,7 @@ frappe.PermissionEngine = class PermissionEngine {
page: "permission_manager",
method: "get_standard_permissions",
args: { doctype: doctype },
- callback: callback
+ callback: callback,
});
}
return false;
@@ -100,18 +106,22 @@ frappe.PermissionEngine = class PermissionEngine {
reset_std_permissions(data) {
let doctype = this.get_doctype();
let d = frappe.confirm(__("Reset Permissions for {0}?", [doctype]), () => {
- return frappe.call({
- module: "frappe.core",
- page: "permission_manager",
- method: "reset",
- args: { doctype }
- }).then(() => {
- this.refresh();
- });
+ return frappe
+ .call({
+ module: "frappe.core",
+ page: "permission_manager",
+ method: "reset",
+ args: { doctype },
+ })
+ .then(() => {
+ this.refresh();
+ });
});
// show standard permissions
- let $d = $(d.wrapper).find(".frappe-confirm-message").append("Standard Permissions: ");
+ let $d = $(d.wrapper)
+ .find(".frappe-confirm-message")
+ .append("Standard Permissions: ");
let $wrapper = $("
").appendTo($d);
data.message.forEach((d) => {
let rights = this.rights
@@ -164,14 +174,16 @@ frappe.PermissionEngine = class PermissionEngine {
}
// get permissions
- frappe.call({
- module: "frappe.core",
- page: "permission_manager",
- method: "get_permissions",
- args: { doctype, role }
- }).then((r) => {
- this.render(r.message);
- });
+ frappe
+ .call({
+ module: "frappe.core",
+ page: "permission_manager",
+ method: "get_permissions",
+ args: { doctype, role },
+ })
+ .then((r) => {
+ this.render(r.message);
+ });
}
render(perm_list) {
@@ -187,19 +199,21 @@ frappe.PermissionEngine = class PermissionEngine {
}
show_permission_table(perm_list) {
- this.table = $("\
+ this.table = $(
+ "
").appendTo(this.body);
+
"
+ ).appendTo(this.body);
const table_columns = [
[__("Document Type"), 150],
[__("Role"), 170],
[__("Level"), 40],
[__("Permissions"), 350],
- ["", 40]
+ ["", 40],
];
table_columns.forEach((col) => {
@@ -236,9 +250,9 @@ frappe.PermissionEngine = class PermissionEngine {
let perm_cell = this.add_cell(row, d, "permissions");
let perm_container = $("
").appendTo(perm_cell);
- this.rights.forEach(r => {
- if (!d.is_submittable && ['submit', 'cancel', 'amend'].includes(r)) return;
- if (d.in_create && ['create', 'write', 'delete'].includes(r)) return;
+ this.rights.forEach((r) => {
+ if (!d.is_submittable && ["submit", "cancel", "amend"].includes(r)) return;
+ if (d.in_create && ["create", "write", "delete"].includes(r)) return;
this.add_check(perm_container, d, r);
});
@@ -248,7 +262,8 @@ frappe.PermissionEngine = class PermissionEngine {
}
add_cell(row, d, fieldname) {
- return $("").appendTo(row)
+ return $(" ")
+ .appendTo(row)
.attr("data-fieldname", fieldname)
.addClass("pt-4")
.html(__(d[fieldname]));
@@ -266,19 +281,20 @@ frappe.PermissionEngine = class PermissionEngine {
${__(label)}
${__(description)}
- `)
+ `
+ )
.appendTo(cell)
.attr("data-fieldname", fieldname);
- checkbox.find("input")
+ checkbox
+ .find("input")
.prop("checked", d[fieldname] ? true : false)
.attr("data-ptype", fieldname)
.attr("data-role", d.role)
.attr("data-permlevel", d.permlevel)
.attr("data-doctype", d.parent);
- checkbox.find("label")
- .css("text-transform", "capitalize");
+ checkbox.find("label").css("text-transform", "capitalize");
return checkbox;
}
@@ -290,8 +306,23 @@ frappe.PermissionEngine = class PermissionEngine {
}
get rights() {
- return ["select", "read", "write", "create", "delete", "submit", "cancel", "amend",
- "print", "email", "report", "import", "export", "set_user_permissions", "share"];
+ return [
+ "select",
+ "read",
+ "write",
+ "create",
+ "delete",
+ "submit",
+ "cancel",
+ "amend",
+ "print",
+ "email",
+ "report",
+ "import",
+ "export",
+ "set_user_permissions",
+ "share",
+ ];
}
set_show_users(cell, role) {
@@ -305,22 +336,29 @@ frappe.PermissionEngine = class PermissionEngine {
page: "permission_manager",
method: "get_users_with_role",
args: {
- role: role
+ role: role,
},
callback: function (r) {
r.message = $.map(r.message, function (p) {
return $.format('{1} ', [p, p]);
});
- frappe.msgprint(__("Users with role {0}:", [__(role)])
- + " " + r.message.join(" "));
- }
+ frappe.msgprint(
+ __("Users with role {0}:", [__(role)]) +
+ " " +
+ r.message.join(" ")
+ );
+ },
});
return false;
});
}
add_delete_button(row, d) {
- $(`${frappe.utils.icon('delete')} `)
+ $(
+ `${frappe.utils.icon(
+ "delete"
+ )} `
+ )
.appendTo($(` `).appendTo(row))
.attr("data-doctype", d.parent)
.attr("data-role", d.role)
@@ -333,7 +371,7 @@ frappe.PermissionEngine = class PermissionEngine {
args: {
doctype: d.parent,
role: d.role,
- permlevel: d.permlevel
+ permlevel: d.permlevel,
},
callback: (r) => {
if (r.exc) {
@@ -341,7 +379,7 @@ frappe.PermissionEngine = class PermissionEngine {
} else {
this.refresh();
}
- }
+ },
});
});
}
@@ -350,7 +388,7 @@ frappe.PermissionEngine = class PermissionEngine {
let me = this;
this.body.on("click", ".show-user-permissions", () => {
frappe.route_options = { allow: this.get_doctype() || "" };
- frappe.set_route('List', 'User Permission');
+ frappe.set_route("List", "User Permission");
});
this.body.on("click", "input[type='checkbox']", function () {
@@ -361,7 +399,7 @@ frappe.PermissionEngine = class PermissionEngine {
permlevel: chk.attr("data-permlevel"),
doctype: chk.attr("data-doctype"),
ptype: chk.attr("data-ptype"),
- value: chk.prop("checked") ? 1 : 0
+ value: chk.prop("checked") ? 1 : 0,
};
return frappe.call({
module: "frappe.core",
@@ -376,7 +414,7 @@ frappe.PermissionEngine = class PermissionEngine {
} else {
me.get_perm(args.role)[args.ptype] = args.value;
}
- }
+ },
});
});
}
@@ -389,19 +427,30 @@ frappe.PermissionEngine = class PermissionEngine {
title: __("Add New Permission Rule"),
fields: [
{
- fieldtype: "Select", label: __("Document Type"),
- options: this.options.doctypes, reqd: 1, fieldname: "parent"
+ fieldtype: "Select",
+ label: __("Document Type"),
+ options: this.options.doctypes,
+ reqd: 1,
+ fieldname: "parent",
},
{
- fieldtype: "Select", label: __("Role"),
- options: this.options.roles, reqd: 1, fieldname: "role"
+ fieldtype: "Select",
+ label: __("Role"),
+ options: this.options.roles,
+ reqd: 1,
+ fieldname: "role",
},
{
- fieldtype: "Select", label: __("Permission Level"),
- options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], reqd: 1, fieldname: "permlevel",
- description: __("Level 0 is for document level permissions, higher levels for field level permissions.")
- }
- ]
+ fieldtype: "Select",
+ label: __("Permission Level"),
+ options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9],
+ reqd: 1,
+ fieldname: "permlevel",
+ description: __(
+ "Level 0 is for document level permissions, higher levels for field level permissions."
+ ),
+ },
+ ],
});
if (this.get_doctype()) {
d.set_value("parent", this.get_doctype());
@@ -412,7 +461,7 @@ frappe.PermissionEngine = class PermissionEngine {
d.get_input("role").prop("disabled", true);
}
d.set_value("permlevel", "0");
- d.set_primary_action(__('Add'), () => {
+ d.set_primary_action(__("Add"), () => {
let args = d.get_values();
if (!args) {
return;
@@ -428,7 +477,7 @@ frappe.PermissionEngine = class PermissionEngine {
} else {
this.refresh();
}
- }
+ },
});
d.hide();
});
@@ -439,13 +488,11 @@ frappe.PermissionEngine = class PermissionEngine {
}
make_reset_button() {
- this.page.set_secondary_action(
- __("Restore Original Permissions"),
- () => {
- this.get_standard_permissions((data) => {
- this.reset_std_permissions(data);
- });
+ this.page.set_secondary_action(__("Restore Original Permissions"), () => {
+ this.get_standard_permissions((data) => {
+ this.reset_std_permissions(data);
});
+ });
}
get_perm(role) {
@@ -455,7 +502,9 @@ frappe.PermissionEngine = class PermissionEngine {
}
get_link_fields(doctype) {
- return frappe.get_children("DocType", doctype, "fields",
- { fieldtype: "Link", options: ["not in", ["User", '[Select]']] });
+ return frappe.get_children("DocType", doctype, "fields", {
+ fieldtype: "Link",
+ options: ["not in", ["User", "[Select]"]],
+ });
}
};
diff --git a/frappe/core/page/recorder/recorder.js b/frappe/core/page/recorder/recorder.js
index f1f74daf71..83b8d1a636 100644
--- a/frappe/core/page/recorder/recorder.js
+++ b/frappe/core/page/recorder/recorder.js
@@ -1,28 +1,28 @@
-frappe.pages['recorder'].on_page_load = function(wrapper) {
+frappe.pages["recorder"].on_page_load = function (wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
- title: __('Recorder'),
+ title: __("Recorder"),
single_column: true,
- card_layout: true
+ card_layout: true,
});
frappe.recorder = new Recorder(wrapper);
- $(wrapper).bind('show', function() {
+ $(wrapper).bind("show", function () {
frappe.recorder.show();
});
- frappe.require('recorder.bundle.js');
+ frappe.require("recorder.bundle.js");
};
class Recorder {
constructor(wrapper) {
this.wrapper = $(wrapper);
- this.container = this.wrapper.find('.layout-main-section');
+ this.container = this.wrapper.find(".layout-main-section");
this.container.append($('
'));
}
show() {
if (!this.view || this.view.$route.name == "recorder-detail") return;
- this.view.$router.replace({name: "recorder-detail"});
+ this.view.$router.replace({ name: "recorder-detail" });
}
}
diff --git a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js
index 195f25f533..f840a49c92 100644
--- a/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js
+++ b/frappe/core/report/permitted_documents_for_user/permitted_documents_for_user.js
@@ -2,33 +2,33 @@
// MIT License. See license.txt
frappe.query_reports["Permitted Documents For User"] = {
- "filters": [
+ filters: [
{
- "fieldname": "user",
- "label": __("User"),
- "fieldtype": "Link",
- "options": "User",
- "reqd": 1
+ fieldname: "user",
+ label: __("User"),
+ fieldtype: "Link",
+ options: "User",
+ reqd: 1,
},
{
- "fieldname": "doctype",
- "label": __("DocType"),
- "fieldtype": "Link",
- "options": "DocType",
- "reqd": 1,
- "get_query": function () {
+ fieldname: "doctype",
+ label: __("DocType"),
+ fieldtype: "Link",
+ options: "DocType",
+ reqd: 1,
+ get_query: function () {
return {
- "query": "frappe.core.report.permitted_documents_for_user.permitted_documents_for_user.query_doctypes",
- "filters": {
- "user": frappe.query_report.get_filter_value('user')
- }
- }
- }
+ query: "frappe.core.report.permitted_documents_for_user.permitted_documents_for_user.query_doctypes",
+ filters: {
+ user: frappe.query_report.get_filter_value("user"),
+ },
+ };
+ },
},
{
- "fieldname": "show_permissions",
- "label": __("Show Permissions"),
- "fieldtype": "Check"
- }
- ]
-}
+ fieldname: "show_permissions",
+ label: __("Show Permissions"),
+ fieldtype: "Check",
+ },
+ ],
+};
diff --git a/frappe/core/report/transaction_log_report/transaction_log_report.js b/frappe/core/report/transaction_log_report/transaction_log_report.js
index 54ecf3fcf1..3c7261306d 100644
--- a/frappe/core/report/transaction_log_report/transaction_log_report.js
+++ b/frappe/core/report/transaction_log_report/transaction_log_report.js
@@ -3,9 +3,9 @@
/* eslint-disable */
frappe.query_reports["Transaction Log Report"] = {
- onload: function(query_report) {
- query_report.add_make_chart_button = function() {
+ onload: function (query_report) {
+ query_report.add_make_chart_button = function () {
//
};
- }
-}
+ },
+};
diff --git a/frappe/core/web_form/edit_profile/edit_profile.js b/frappe/core/web_form/edit_profile/edit_profile.js
index 699703c579..8f56ebb353 100644
--- a/frappe/core/web_form/edit_profile/edit_profile.js
+++ b/frappe/core/web_form/edit_profile/edit_profile.js
@@ -1,3 +1,3 @@
-frappe.ready(function() {
+frappe.ready(function () {
// bind events here
-})
\ No newline at end of file
+});
diff --git a/frappe/custom/doctype/client_script/client_script.js b/frappe/custom/doctype/client_script/client_script.js
index 18786c62cf..67bb0083c8 100644
--- a/frappe/custom/doctype/client_script/client_script.js
+++ b/frappe/custom/doctype/client_script/client_script.js
@@ -1,46 +1,49 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Client Script', {
+frappe.ui.form.on("Client Script", {
setup(frm) {
frm.get_field("sample").html(SAMPLE_HTML);
},
refresh(frm) {
if (frm.doc.dt && frm.doc.script) {
- frm.add_custom_button(__('Go to {0}', [frm.doc.dt]),
- () => frappe.set_route('List', frm.doc.dt, 'List'));
+ frm.add_custom_button(__("Go to {0}", [frm.doc.dt]), () =>
+ frappe.set_route("List", frm.doc.dt, "List")
+ );
}
- if (frm.doc.view == 'Form') {
- frm.add_custom_button(__('Add script for Child Table'), () => {
+ if (frm.doc.view == "Form") {
+ frm.add_custom_button(__("Add script for Child Table"), () => {
frappe.model.with_doctype(frm.doc.dt, () => {
- const child_tables = frappe.meta.get_docfields(frm.doc.dt, null, {
- fieldtype: 'Table'
- }).map(df => df.options);
+ const child_tables = frappe.meta
+ .get_docfields(frm.doc.dt, null, {
+ fieldtype: "Table",
+ })
+ .map((df) => df.options);
const d = new frappe.ui.Dialog({
- title: __('Select Child Table'),
+ title: __("Select Child Table"),
fields: [
{
- label: __('Select Child Table'),
- fieldtype: 'Link',
- fieldname: 'cdt',
- options: 'DocType',
+ label: __("Select Child Table"),
+ fieldtype: "Link",
+ fieldname: "cdt",
+ options: "DocType",
get_query: () => {
return {
filters: {
istable: 1,
- name: ['in', child_tables]
- }
+ name: ["in", child_tables],
+ },
};
- }
- }
+ },
+ },
],
primary_action: ({ cdt }) => {
- cdt = d.get_field('cdt').value;
+ cdt = d.get_field("cdt").value;
frm.events.add_script_for_doctype(frm, cdt);
d.hide();
- }
+ },
});
d.show();
@@ -48,39 +51,39 @@ frappe.ui.form.on('Client Script', {
});
if (!frm.is_new()) {
- frm.add_custom_button(__('Compare Versions'), () => {
+ frm.add_custom_button(__("Compare Versions"), () => {
new frappe.ui.DiffView("Client Script", "script", frm.doc.name);
});
}
}
- frm.set_query('dt', {
+ frm.set_query("dt", {
filters: {
- istable: 0
- }
+ istable: 0,
+ },
});
},
dt(frm) {
- frm.toggle_display('view', !frappe.boot.single_types.includes(frm.doc.dt));
+ frm.toggle_display("view", !frappe.boot.single_types.includes(frm.doc.dt));
if (!frm.doc.script) {
frm.events.add_script_for_doctype(frm, frm.doc.dt);
}
if (frm.doc.script && !frm.doc.script.includes(frm.doc.dt)) {
- frm.doc.script = '';
+ frm.doc.script = "";
frm.events.add_script_for_doctype(frm, frm.doc.dt);
}
},
view(frm) {
- let has_form_boilerplate = frm.doc.script.includes('frappe.ui.form.on')
- if (frm.doc.view === 'List' && has_form_boilerplate) {
- frm.set_value('script', '');
+ let has_form_boilerplate = frm.doc.script.includes("frappe.ui.form.on");
+ if (frm.doc.view === "List" && has_form_boilerplate) {
+ frm.set_value("script", "");
}
- if (frm.doc.view === 'Form' && !has_form_boilerplate) {
- frm.trigger('dt');
+ if (frm.doc.view === "Form" && !has_form_boilerplate) {
+ frm.trigger("dt");
}
},
@@ -93,12 +96,12 @@ frappe.ui.form.on('${doctype}', {
}
})
`.trim();
- let script = (frm.doc.script || '');
+ let script = frm.doc.script || "";
if (script) {
- script += '\n\n';
+ script += "\n\n";
}
- frm.set_value('script', script + boilerplate);
- }
+ frm.set_value("script", script + boilerplate);
+ },
});
const SAMPLE_HTML = `Client Script Help
diff --git a/frappe/custom/doctype/client_script/ui_test_client_script.js b/frappe/custom/doctype/client_script/ui_test_client_script.js
index 022f677151..0d202d697c 100644
--- a/frappe/custom/doctype/client_script/ui_test_client_script.js
+++ b/frappe/custom/doctype/client_script/ui_test_client_script.js
@@ -12,14 +12,14 @@ context("Client Script", () => {
dt: "ToDo",
view: "Form",
enabled: 1,
- script: `console.log('todo form script')`
+ script: `console.log('todo form script')`,
},
true
);
cy.visit("/app/todo/new", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
- }
+ },
});
cy.get("@consoleLog").should("be.calledWith", "todo form script");
});
@@ -32,14 +32,14 @@ context("Client Script", () => {
dt: "ToDo",
view: "List",
enabled: 1,
- script: `console.log('todo list script')`
+ script: `console.log('todo list script')`,
},
true
);
cy.visit("/app/todo", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
- }
+ },
});
cy.get("@consoleLog").should("be.calledWith", "todo list script");
});
@@ -52,19 +52,16 @@ context("Client Script", () => {
dt: "ToDo",
view: "List",
enabled: 0,
- script: `console.log('todo disabled script')`
+ script: `console.log('todo disabled script')`,
},
true
);
cy.visit("/app/todo", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
- }
+ },
});
- cy.get("@consoleLog").should(
- "not.be.calledWith",
- "todo disabled script"
- );
+ cy.get("@consoleLog").should("not.be.calledWith", "todo disabled script");
});
it("should run multiple scripts", () => {
@@ -75,7 +72,7 @@ context("Client Script", () => {
dt: "ToDo",
view: "Form",
enabled: 1,
- script: `console.log('todo form script 1')`
+ script: `console.log('todo form script 1')`,
},
true
);
@@ -86,14 +83,14 @@ context("Client Script", () => {
dt: "ToDo",
view: "Form",
enabled: 1,
- script: `console.log('todo form script 2')`
+ script: `console.log('todo form script 2')`,
},
true
);
cy.visit("/app/todo/new", {
onBeforeLoad(win) {
cy.spy(win.console, "log").as("consoleLog");
- }
+ },
});
cy.get("@consoleLog").should("be.calledWith", "todo form script 1");
cy.get("@consoleLog").should("be.calledWith", "todo form script 2");
diff --git a/frappe/custom/doctype/custom_field/custom_field.js b/frappe/custom/doctype/custom_field/custom_field.js
index c59fabeaa6..fba19ca45e 100644
--- a/frappe/custom/doctype/custom_field/custom_field.js
+++ b/frappe/custom/doctype/custom_field/custom_field.js
@@ -4,88 +4,99 @@
// Refresh
// --------
-frappe.ui.form.on('Custom Field', {
- setup: function(frm) {
- frm.set_query('dt', function(doc) {
+frappe.ui.form.on("Custom Field", {
+ setup: function (frm) {
+ frm.set_query("dt", function (doc) {
var filters = [
- ['DocType', 'issingle', '=', 0],
- ['DocType', 'custom', '=', 0],
- ['DocType', 'name', 'not in', frappe.model.core_doctypes_list],
- ['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains]
+ ["DocType", "issingle", "=", 0],
+ ["DocType", "custom", "=", 0],
+ ["DocType", "name", "not in", frappe.model.core_doctypes_list],
+ ["DocType", "restrict_to_domain", "in", frappe.boot.active_domains],
];
- if(frappe.session.user!=="Administrator") {
- filters.push(['DocType', 'module', 'not in', ['Core', 'Custom']])
+ if (frappe.session.user !== "Administrator") {
+ filters.push(["DocType", "module", "not in", ["Core", "Custom"]]);
}
return {
- "filters": filters
- }
+ filters: filters,
+ };
});
},
- refresh: function(frm) {
- frm.toggle_enable('dt', frm.doc.__islocal);
- frm.trigger('dt');
- frm.toggle_reqd('label', !frm.doc.fieldname);
+ refresh: function (frm) {
+ frm.toggle_enable("dt", frm.doc.__islocal);
+ frm.trigger("dt");
+ frm.toggle_reqd("label", !frm.doc.fieldname);
},
- dt: function(frm) {
- if(!frm.doc.dt) {
- set_field_options('insert_after', '');
+ dt: function (frm) {
+ if (!frm.doc.dt) {
+ set_field_options("insert_after", "");
return;
}
var insert_after = frm.doc.insert_after || null;
return frappe.call({
- method: 'frappe.custom.doctype.custom_field.custom_field.get_fields_label',
+ method: "frappe.custom.doctype.custom_field.custom_field.get_fields_label",
args: { doctype: frm.doc.dt, fieldname: frm.doc.fieldname },
- callback: function(r) {
- if(r) {
- if(r._server_messages && r._server_messages.length) {
+ callback: function (r) {
+ if (r) {
+ if (r._server_messages && r._server_messages.length) {
frm.set_value("dt", "");
} else {
- set_field_options('insert_after', r.message);
- var fieldnames = $.map(r.message, function(v) { return v.value; });
+ set_field_options("insert_after", r.message);
+ var fieldnames = $.map(r.message, function (v) {
+ return v.value;
+ });
- if(insert_after==null || !in_list(fieldnames, insert_after)) {
+ if (insert_after == null || !in_list(fieldnames, insert_after)) {
insert_after = fieldnames[-1];
}
- frm.set_value('insert_after', insert_after);
+ frm.set_value("insert_after", insert_after);
}
}
- }
+ },
});
-
},
- label: function(frm) {
- if(frm.doc.label && frappe.utils.has_special_chars(frm.doc.label)) {
- frm.fields_dict['label_help'].disp_area.innerHTML =
- ''+__('Special Characters are not allowed')+' ';
- frm.set_value('label', '');
+ label: function (frm) {
+ if (frm.doc.label && frappe.utils.has_special_chars(frm.doc.label)) {
+ frm.fields_dict["label_help"].disp_area.innerHTML =
+ '' + __("Special Characters are not allowed") + " ";
+ frm.set_value("label", "");
} else {
- frm.fields_dict['label_help'].disp_area.innerHTML = '';
+ frm.fields_dict["label_help"].disp_area.innerHTML = "";
}
},
- fieldtype: function(frm) {
- if(frm.doc.fieldtype == 'Link') {
- frm.fields_dict['options_help'].disp_area.innerHTML =
- __('Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer');
- } else if(frm.doc.fieldtype == 'Select') {
- frm.fields_dict['options_help'].disp_area.innerHTML =
- __('Options for select. Each option on a new line.')+' '+__('e.g.:')+' '+__('Option 1')+' '+__('Option 2')+' '+__('Option 3')+' ';
- } else if(frm.doc.fieldtype == 'Dynamic Link') {
- frm.fields_dict['options_help'].disp_area.innerHTML =
- __('Fieldname which will be the DocType for this link field.');
+ fieldtype: function (frm) {
+ if (frm.doc.fieldtype == "Link") {
+ frm.fields_dict["options_help"].disp_area.innerHTML = __(
+ "Name of the Document Type (DocType) you want this field to be linked to. e.g. Customer"
+ );
+ } else if (frm.doc.fieldtype == "Select") {
+ frm.fields_dict["options_help"].disp_area.innerHTML =
+ __("Options for select. Each option on a new line.") +
+ " " +
+ __("e.g.:") +
+ " " +
+ __("Option 1") +
+ " " +
+ __("Option 2") +
+ " " +
+ __("Option 3") +
+ " ";
+ } else if (frm.doc.fieldtype == "Dynamic Link") {
+ frm.fields_dict["options_help"].disp_area.innerHTML = __(
+ "Fieldname which will be the DocType for this link field."
+ );
} else {
- frm.fields_dict['options_help'].disp_area.innerHTML = '';
+ frm.fields_dict["options_help"].disp_area.innerHTML = "";
}
- }
+ },
});
-
-frappe.utils.has_special_chars = function(t) {
- var iChars = "!@#$%^&*()+=-[]\\\';,./{}|\":<>?";
+frappe.utils.has_special_chars = function (t) {
+ var iChars = "!@#$%^&*()+=-[]\\';,./{}|\":<>?";
for (var i = 0; i < t.length; i++) {
if (iChars.indexOf(t.charAt(i)) != -1) {
return true;
}
}
return false;
-}
+};
diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js
index 3ec6795f0e..a35db2ca18 100644
--- a/frappe/custom/doctype/customize_form/customize_form.js
+++ b/frappe/custom/doctype/customize_form/customize_form.js
@@ -4,7 +4,7 @@
frappe.provide("frappe.customize_form");
frappe.ui.form.on("Customize Form", {
- setup: function(frm) {
+ setup: function (frm) {
// save the last setting if refreshing
window.addEventListener("beforeunload", () => {
if (frm.doc.doc_type && frm.doc.doc_type != "undefined") {
@@ -13,67 +13,59 @@ frappe.ui.form.on("Customize Form", {
});
},
- onload: function(frm) {
- frm.set_query("doc_type", function() {
+ onload: function (frm) {
+ frm.set_query("doc_type", function () {
return {
filters: [
["DocType", "issingle", "=", 0],
["DocType", "custom", "=", 0],
- [
- "DocType",
- "name",
- "not in",
- frappe.model.core_doctypes_list
- ],
- [
- "DocType",
- "restrict_to_domain",
- "in",
- frappe.boot.active_domains
- ]
- ]
+ ["DocType", "name", "not in", frappe.model.core_doctypes_list],
+ ["DocType", "restrict_to_domain", "in", frappe.boot.active_domains],
+ ],
};
});
- frm.set_query("default_print_format", function() {
+ frm.set_query("default_print_format", function () {
return {
filters: {
print_format_type: ["!=", "JS"],
- doc_type: ["=", frm.doc.doc_type]
- }
+ doc_type: ["=", frm.doc.doc_type],
+ },
};
});
- $(frm.wrapper).on("grid-row-render", function(e, grid_row) {
+ $(frm.wrapper).on("grid-row-render", function (e, grid_row) {
if (grid_row.doc && grid_row.doc.fieldtype == "Section Break") {
$(grid_row.row).css({ "font-weight": "bold" });
}
grid_row.row.removeClass("highlight");
- if (grid_row.doc.is_custom_field &&
- !grid_row.row.hasClass('highlight') &&
- !grid_row.doc.is_system_generated) {
+ if (
+ grid_row.doc.is_custom_field &&
+ !grid_row.row.hasClass("highlight") &&
+ !grid_row.doc.is_system_generated
+ ) {
grid_row.row.addClass("highlight");
}
});
- $(frm.wrapper).on("grid-make-sortable", function(e, frm) {
+ $(frm.wrapper).on("grid-make-sortable", function (e, frm) {
frm.trigger("setup_sortable");
});
- $(frm.wrapper).on("grid-move-row", function(e, frm) {
+ $(frm.wrapper).on("grid-move-row", function (e, frm) {
frm.trigger("setup_sortable");
});
},
- doc_type: function(frm) {
+ doc_type: function (frm) {
if (frm.doc.doc_type) {
return frm.call({
method: "fetch_to_customize",
doc: frm.doc,
freeze: true,
- callback: function(r) {
+ callback: function (r) {
if (r) {
if (r._server_messages && r._server_messages.length) {
frm.set_value("doc_type", "");
@@ -83,15 +75,15 @@ frappe.ui.form.on("Customize Form", {
}
}
localStorage["customize_doctype"] = frm.doc.doc_type;
- }
+ },
});
} else {
frm.refresh();
}
},
- setup_sortable: function(frm) {
- frm.doc.fields.forEach(function(f, i) {
+ setup_sortable: function (frm) {
+ frm.doc.fields.forEach(function (f, i) {
if (!f.is_custom_field) {
f._sortable = false;
}
@@ -99,7 +91,7 @@ frappe.ui.form.on("Customize Form", {
if (f.fieldtype == "Table") {
frm.add_custom_button(
f.options,
- function() {
+ function () {
frm.set_value("doc_type", f.options);
},
__("Customize Child Table")
@@ -109,17 +101,17 @@ frappe.ui.form.on("Customize Form", {
frm.fields_dict.fields.grid.refresh();
},
- refresh: function(frm) {
+ refresh: function (frm) {
frm.disable_save(true);
frm.page.clear_icons();
if (frm.doc.doc_type) {
- frm.page.set_title(__('Customize Form - {0}', [frm.doc.doc_type]));
+ frm.page.set_title(__("Customize Form - {0}", [frm.doc.doc_type]));
frappe.customize_form.set_primary_action(frm);
frm.add_custom_button(
__("Go to {0} List", [__(frm.doc.doc_type)]),
- function() {
+ function () {
frappe.set_route("List", frm.doc.doc_type);
},
__("Actions")
@@ -127,7 +119,7 @@ frappe.ui.form.on("Customize Form", {
frm.add_custom_button(
__("Reload"),
- function() {
+ function () {
frm.script_manager.trigger("doc_type");
},
__("Actions")
@@ -135,24 +127,21 @@ frappe.ui.form.on("Customize Form", {
frm.add_custom_button(
__("Reset to defaults"),
- function() {
- frappe.customize_form.confirm(
- __("Remove all customizations?"),
- frm
- );
+ function () {
+ frappe.customize_form.confirm(__("Remove all customizations?"), frm);
},
__("Actions")
);
frm.add_custom_button(
__("Set Permissions"),
- function() {
+ function () {
frappe.set_route("permission-manager", frm.doc.doc_type);
},
__("Actions")
);
- const is_autoname_autoincrement = frm.doc.autoname === 'autoincrement';
+ const is_autoname_autoincrement = frm.doc.autoname === "autoincrement";
frm.set_df_property("naming_rule", "hidden", is_autoname_autoincrement);
frm.set_df_property("autoname", "read_only", is_autoname_autoincrement);
}
@@ -181,37 +170,37 @@ frappe.ui.form.on("Customize Form", {
if (frappe.boot.developer_mode) {
frm.add_custom_button(
__("Export Customizations"),
- function() {
+ function () {
frappe.prompt(
[
{
fieldtype: "Link",
fieldname: "module",
options: "Module Def",
- label: __("Module to Export")
+ label: __("Module to Export"),
},
{
fieldtype: "Check",
fieldname: "sync_on_migrate",
label: __("Sync on Migrate"),
- default: 1
+ default: 1,
},
{
fieldtype: "Check",
fieldname: "with_permissions",
label: __("Export Custom Permissions"),
- default: 1
- }
+ default: 1,
+ },
],
- function(data) {
+ function (data) {
frappe.call({
method: "frappe.modules.utils.export_customizations",
args: {
doctype: frm.doc.doc_type,
module: data.module,
sync_on_migrate: data.sync_on_migrate,
- with_permissions: data.with_permissions
- }
+ with_permissions: data.with_permissions,
+ },
});
},
__("Select Module")
@@ -225,127 +214,131 @@ frappe.ui.form.on("Customize Form", {
setup_sort_order(frm) {
// sort order select
if (frm.doc.doc_type) {
- var fields = $.map(frm.doc.fields, function(df) {
- return frappe.model.is_value_type(df.fieldtype)
- ? df.fieldname
- : null;
+ var fields = $.map(frm.doc.fields, function (df) {
+ return frappe.model.is_value_type(df.fieldtype) ? df.fieldname : null;
});
fields = ["", "name", "modified"].concat(fields);
frm.set_df_property("sort_field", "options", fields);
}
- }
+ },
});
// can't delete standard fields
frappe.ui.form.on("Customize Form Field", {
- before_fields_remove: function(frm, doctype, name) {
+ before_fields_remove: function (frm, doctype, name) {
var row = frappe.get_doc(doctype, name);
if (!(row.is_custom_field || row.__islocal)) {
frappe.msgprint(__("Cannot delete standard field. You can hide it if you want"));
throw "cannot delete standard field";
}
},
- fields_add: function(frm, cdt, cdn) {
+ fields_add: function (frm, cdt, cdn) {
var f = frappe.model.get_doc(cdt, cdn);
f.is_system_generated = false;
f.is_custom_field = true;
- }
+ },
});
// can't delete standard links
frappe.ui.form.on("DocType Link", {
- before_links_remove: function(frm, doctype, name) {
+ before_links_remove: function (frm, doctype, name) {
let row = frappe.get_doc(doctype, name);
if (!(row.custom || row.__islocal)) {
frappe.msgprint(__("Cannot delete standard link. You can hide it if you want"));
throw "cannot delete standard link";
}
},
- links_add: function(frm, cdt, cdn) {
+ links_add: function (frm, cdt, cdn) {
let f = frappe.model.get_doc(cdt, cdn);
f.custom = 1;
- }
+ },
});
// can't delete standard actions
frappe.ui.form.on("DocType Action", {
- before_actions_remove: function(frm, doctype, name) {
+ before_actions_remove: function (frm, doctype, name) {
let row = frappe.get_doc(doctype, name);
if (!(row.custom || row.__islocal)) {
frappe.msgprint(__("Cannot delete standard action. You can hide it if you want"));
throw "cannot delete standard action";
}
},
- actions_add: function(frm, cdt, cdn) {
+ actions_add: function (frm, cdt, cdn) {
let f = frappe.model.get_doc(cdt, cdn);
f.custom = 1;
- }
+ },
});
// can't delete standard states
frappe.ui.form.on("DocType State", {
- before_states_remove: function(frm, doctype, name) {
+ before_states_remove: function (frm, doctype, name) {
let row = frappe.get_doc(doctype, name);
if (!(row.custom || row.__islocal)) {
frappe.msgprint(__("Cannot delete standard document state."));
throw "cannot delete standard document state";
}
},
- states_add: function(frm, cdt, cdn) {
+ states_add: function (frm, cdt, cdn) {
let f = frappe.model.get_doc(cdt, cdn);
f.custom = 1;
- }
+ },
});
-frappe.customize_form.set_primary_action = function(frm) {
- frm.page.set_primary_action(__("Update"), function() {
+frappe.customize_form.set_primary_action = function (frm) {
+ frm.page.set_primary_action(__("Update"), function () {
if (frm.doc.doc_type) {
return frm.call({
doc: frm.doc,
freeze: true,
btn: frm.page.btn_primary,
method: "save_customization",
- callback: function(r) {
+ callback: function (r) {
if (!r.exc) {
frappe.customize_form.clear_locals_and_refresh(frm);
frm.script_manager.trigger("doc_type");
}
- }
+ },
});
}
});
};
-frappe.customize_form.confirm = function(msg, frm) {
+frappe.customize_form.confirm = function (msg, frm) {
if (!frm.doc.doc_type) return;
var d = new frappe.ui.Dialog({
- title: 'Reset To Defaults',
+ title: "Reset To Defaults",
fields: [
- {fieldtype:"HTML", options:__("All customizations will be removed. Please confirm.")},
+ {
+ fieldtype: "HTML",
+ options: __("All customizations will be removed. Please confirm."),
+ },
],
- primary_action: function() {
+ primary_action: function () {
return frm.call({
doc: frm.doc,
method: "reset_to_defaults",
- callback: function(r) {
+ callback: function (r) {
if (r.exc) {
frappe.msgprint(r.exc);
} else {
d.hide();
- frappe.show_alert({message:__('Customizations Reset'), indicator:'green'});
+ frappe.show_alert({
+ message: __("Customizations Reset"),
+ indicator: "green",
+ });
frappe.customize_form.clear_locals_and_refresh(frm);
}
- }
+ },
});
- }
+ },
});
frappe.customize_form.confirm.dialog = d;
d.show();
-}
+};
-frappe.customize_form.clear_locals_and_refresh = function(frm) {
+frappe.customize_form.clear_locals_and_refresh = function (frm) {
delete frm.doc.__unsaved;
// clear doctype from locals
frappe.model.clear_doc("DocType", frm.doc.doc_type);
@@ -353,4 +346,4 @@ frappe.customize_form.clear_locals_and_refresh = function(frm) {
frm.refresh();
};
-extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({frm: cur_frm}));
+extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm }));
diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.js b/frappe/custom/doctype/doctype_layout/doctype_layout.js
index 533efea9b8..f91f04f762 100644
--- a/frappe/custom/doctype/doctype_layout/doctype_layout.js
+++ b/frappe/custom/doctype/doctype_layout/doctype_layout.js
@@ -1,30 +1,32 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('DocType Layout', {
- refresh: function(frm) {
- frm.trigger('document_type');
+frappe.ui.form.on("DocType Layout", {
+ refresh: function (frm) {
+ frm.trigger("document_type");
frm.events.set_button(frm);
},
document_type(frm) {
- frm.set_fields_as_options('fields', frm.doc.document_type, null, [], 'fieldname').then(() => {
- // child table empty? then show all fields as default
- if (frm.doc.document_type) {
- if (!(frm.doc.fields || []).length) {
- for (let f of frappe.get_doc('DocType', frm.doc.document_type).fields) {
- frm.add_child('fields', { fieldname: f.fieldname, label: f.label });
+ frm.set_fields_as_options("fields", frm.doc.document_type, null, [], "fieldname").then(
+ () => {
+ // child table empty? then show all fields as default
+ if (frm.doc.document_type) {
+ if (!(frm.doc.fields || []).length) {
+ for (let f of frappe.get_doc("DocType", frm.doc.document_type).fields) {
+ frm.add_child("fields", { fieldname: f.fieldname, label: f.label });
+ }
}
}
}
- });
+ );
},
set_button(frm) {
if (!frm.is_new()) {
- frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
+ frm.add_custom_button(__("Go to {0} List", [frm.doc.name]), () => {
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
});
}
- }
+ },
});
diff --git a/frappe/custom/doctype/property_setter/property_setter.js b/frappe/custom/doctype/property_setter/property_setter.js
index bff5ad0e63..955e01c33e 100644
--- a/frappe/custom/doctype/property_setter/property_setter.js
+++ b/frappe/custom/doctype/property_setter/property_setter.js
@@ -1,10 +1,10 @@
// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors
// MIT License. See license.txt
-frappe.ui.form.on('Property Setter', {
- validate: function(frm) {
- if(frm.doc.property_type=='Check' && !in_list(['0','1'], frm.doc.value)) {
- frappe.throw(__('Value for a check field can be either 0 or 1'));
+frappe.ui.form.on("Property Setter", {
+ validate: function (frm) {
+ if (frm.doc.property_type == "Check" && !in_list(["0", "1"], frm.doc.value)) {
+ frappe.throw(__("Value for a check field can be either 0 or 1"));
}
- }
+ },
});
diff --git a/frappe/desk/doctype/bulk_update/bulk_update.js b/frappe/desk/doctype/bulk_update/bulk_update.js
index bb9cf2af51..017eee1480 100644
--- a/frappe/desk/doctype/bulk_update/bulk_update.js
+++ b/frappe/desk/doctype/bulk_update/bulk_update.js
@@ -1,65 +1,69 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Bulk Update', {
- refresh: function(frm) {
- frm.set_query("document_type", function() {
+frappe.ui.form.on("Bulk Update", {
+ refresh: function (frm) {
+ frm.set_query("document_type", function () {
return {
filters: [
- ['DocType', 'issingle', '=', 0],
- ['DocType', 'name', 'not in', frappe.model.core_doctypes_list]
- ]
+ ["DocType", "issingle", "=", 0],
+ ["DocType", "name", "not in", frappe.model.core_doctypes_list],
+ ],
};
});
- frm.page.set_primary_action(__('Update'), function() {
+ frm.page.set_primary_action(__("Update"), function () {
if (!frm.doc.update_value) {
frappe.throw(__('Field "value" is mandatory. Please specify value to be updated'));
} else {
- frappe.call({
- method: 'frappe.desk.doctype.bulk_update.bulk_update.update',
- args: {
- doctype: frm.doc.document_type,
- field: frm.doc.field,
- value: frm.doc.update_value,
- condition: frm.doc.condition,
- limit: frm.doc.limit
- },
- }).then(r => {
- let failed = r.message;
- if (!failed) failed = [];
+ frappe
+ .call({
+ method: "frappe.desk.doctype.bulk_update.bulk_update.update",
+ args: {
+ doctype: frm.doc.document_type,
+ field: frm.doc.field,
+ value: frm.doc.update_value,
+ condition: frm.doc.condition,
+ limit: frm.doc.limit,
+ },
+ })
+ .then((r) => {
+ let failed = r.message;
+ if (!failed) failed = [];
- if (failed.length && !r._server_messages) {
- frappe.throw(__('Cannot update {0}', [failed.map(f => f.bold ? f.bold(): f).join(', ')]));
- } else {
- frappe.msgprint({
- title: __('Success'),
- message: __('Updated Successfully'),
- indicator: 'green'
- });
- }
+ if (failed.length && !r._server_messages) {
+ frappe.throw(
+ __("Cannot update {0}", [
+ failed.map((f) => (f.bold ? f.bold() : f)).join(", "),
+ ])
+ );
+ } else {
+ frappe.msgprint({
+ title: __("Success"),
+ message: __("Updated Successfully"),
+ indicator: "green",
+ });
+ }
- frappe.hide_progress();
- frm.save();
- });
+ frappe.hide_progress();
+ frm.save();
+ });
}
});
},
- document_type: function(frm) {
+ document_type: function (frm) {
// set field options
- if(!frm.doc.document_type) return;
+ if (!frm.doc.document_type) return;
- frappe.model.with_doctype(frm.doc.document_type, function() {
- var options = $.map(frappe.get_meta(frm.doc.document_type).fields,
- function(d) {
- if(d.fieldname && frappe.model.no_value_type.indexOf(d.fieldtype)===-1) {
- return d.fieldname;
- }
- return null;
+ frappe.model.with_doctype(frm.doc.document_type, function () {
+ var options = $.map(frappe.get_meta(frm.doc.document_type).fields, function (d) {
+ if (d.fieldname && frappe.model.no_value_type.indexOf(d.fieldtype) === -1) {
+ return d.fieldname;
}
- );
- frm.set_df_property('field', 'options', options);
+ return null;
+ });
+ frm.set_df_property("field", "options", options);
});
- }
+ },
});
diff --git a/frappe/desk/doctype/calendar_view/calendar_view.js b/frappe/desk/doctype/calendar_view/calendar_view.js
index a58a9555db..c302c1a4d8 100644
--- a/frappe/desk/doctype/calendar_view/calendar_view.js
+++ b/frappe/desk/doctype/calendar_view/calendar_view.js
@@ -1,35 +1,36 @@
// Copyright (c) 2017, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Calendar View', {
- onload: function(frm) {
- frm.trigger('reference_doctype');
+frappe.ui.form.on("Calendar View", {
+ onload: function (frm) {
+ frm.trigger("reference_doctype");
},
- refresh: function(frm) {
+ refresh: function (frm) {
if (!frm.is_new()) {
- frm.add_custom_button(__('Show Calendar'),
- () => frappe.set_route('List', frm.doc.reference_doctype, 'Calendar', frm.doc.name));
+ frm.add_custom_button(__("Show Calendar"), () =>
+ frappe.set_route("List", frm.doc.reference_doctype, "Calendar", frm.doc.name)
+ );
}
},
- reference_doctype: function(frm) {
+ reference_doctype: function (frm) {
const { reference_doctype } = frm.doc;
if (!reference_doctype) return;
frappe.model.with_doctype(reference_doctype, () => {
const meta = frappe.get_meta(reference_doctype);
- const subject_options = meta.fields.filter(
- df => !frappe.model.no_value_type.includes(df.fieldtype)
- ).map(df => df.fieldname);
+ const subject_options = meta.fields
+ .filter((df) => !frappe.model.no_value_type.includes(df.fieldtype))
+ .map((df) => df.fieldname);
- const date_options = meta.fields.filter(
- df => ['Date', 'Datetime'].includes(df.fieldtype)
- ).map(df => df.fieldname);
+ const date_options = meta.fields
+ .filter((df) => ["Date", "Datetime"].includes(df.fieldtype))
+ .map((df) => df.fieldname);
- frm.set_df_property('subject_field', 'options', subject_options);
- frm.set_df_property('start_date_field', 'options', date_options);
- frm.set_df_property('end_date_field', 'options', date_options);
+ frm.set_df_property("subject_field", "options", subject_options);
+ frm.set_df_property("start_date_field", "options", date_options);
+ frm.set_df_property("end_date_field", "options", date_options);
frm.refresh();
});
- }
+ },
});
diff --git a/frappe/desk/doctype/console_log/console_log.js b/frappe/desk/doctype/console_log/console_log.js
index 1ef4fdce59..9a980667ac 100644
--- a/frappe/desk/doctype/console_log/console_log.js
+++ b/frappe/desk/doctype/console_log/console_log.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Console Log', {
+frappe.ui.form.on("Console Log", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/desk/doctype/dashboard/dashboard.js b/frappe/desk/doctype/dashboard/dashboard.js
index c640259cf2..9f584ca552 100644
--- a/frappe/desk/doctype/dashboard/dashboard.js
+++ b/frappe/desk/doctype/dashboard/dashboard.js
@@ -1,30 +1,30 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Dashboard', {
- refresh: function(frm) {
- frm.add_custom_button(__("Show Dashboard"),
- () => frappe.set_route('dashboard-view', frm.doc.name)
+frappe.ui.form.on("Dashboard", {
+ refresh: function (frm) {
+ frm.add_custom_button(__("Show Dashboard"), () =>
+ frappe.set_route("dashboard-view", frm.doc.name)
);
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
frm.disable_form();
}
- frm.set_query("chart", "charts", function() {
+ frm.set_query("chart", "charts", function () {
return {
filters: {
is_public: 1,
- }
+ },
};
});
- frm.set_query("card", "cards", function() {
+ frm.set_query("card", "cards", function () {
return {
filters: {
is_public: 1,
- }
+ },
};
});
- }
+ },
});
diff --git a/frappe/desk/doctype/dashboard/dashboard_list.js b/frappe/desk/doctype/dashboard/dashboard_list.js
index d60a324048..80ebbd4355 100644
--- a/frappe/desk/doctype/dashboard/dashboard_list.js
+++ b/frappe/desk/doctype/dashboard/dashboard_list.js
@@ -1,4 +1,4 @@
-frappe.listview_settings['Dashboard'] = {
+frappe.listview_settings["Dashboard"] = {
button: {
show(doc) {
return doc.name;
@@ -7,10 +7,10 @@ frappe.listview_settings['Dashboard'] = {
return frappe.utils.icon("dashboard-list", "sm");
},
get_description(doc) {
- return __('View {0}', [`${doc.name}`]);
+ return __("View {0}", [`${doc.name}`]);
},
action(doc) {
- frappe.set_route('dashboard-view', doc.name);
- }
+ frappe.set_route("dashboard-view", doc.name);
+ },
},
-};
\ No newline at end of file
+};
diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js
index 0b93786e8e..b1c23eba28 100644
--- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.js
+++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.js
@@ -1,42 +1,44 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.provide('frappe.dashboards.chart_sources');
+frappe.provide("frappe.dashboards.chart_sources");
-frappe.ui.form.on('Dashboard Chart', {
- setup: function(frm) {
+frappe.ui.form.on("Dashboard Chart", {
+ setup: function (frm) {
// fetch timeseries from source
- frm.add_fetch('source', 'timeseries', 'timeseries');
+ frm.add_fetch("source", "timeseries", "timeseries");
},
- before_save: function(frm) {
- let dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || 'null');
- let static_filters = JSON.parse(frm.doc.filters_json || 'null');
- static_filters =
- frappe.dashboard_utils.remove_common_static_filter_values(static_filters, dynamic_filters);
+ before_save: function (frm) {
+ let dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || "null");
+ let static_filters = JSON.parse(frm.doc.filters_json || "null");
+ static_filters = frappe.dashboard_utils.remove_common_static_filter_values(
+ static_filters,
+ dynamic_filters
+ );
- frm.set_value('filters_json', JSON.stringify(static_filters));
- frm.trigger('show_filters');
+ frm.set_value("filters_json", JSON.stringify(static_filters));
+ frm.trigger("show_filters");
},
- refresh: function(frm) {
+ refresh: function (frm) {
frm.chart_filters = null;
frm.is_disabled = !frappe.boot.developer_mode && frm.doc.is_standard;
if (frm.is_disabled) {
- !frm.doc.custom_options && frm.set_df_property('chart_options_section', 'hidden', 1);
+ !frm.doc.custom_options && frm.set_df_property("chart_options_section", "hidden", 1);
frm.disable_form();
}
- frm.add_custom_button('Add Chart to Dashboard', () => {
+ frm.add_custom_button("Add Chart to Dashboard", () => {
const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog(
frm.doc.name,
- 'Dashboard Chart',
- 'frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard'
+ "Dashboard Chart",
+ "frappe.desk.doctype.dashboard_chart.dashboard_chart.add_chart_to_dashboard"
);
if (!frm.doc.chart_name) {
- frappe.msgprint(__('Please create chart first'));
+ frappe.msgprint(__("Please create chart first"));
} else {
dialog.show();
}
@@ -45,199 +47,227 @@ frappe.ui.form.on('Dashboard Chart', {
frm.set_df_property("filters_section", "hidden", 1);
frm.set_df_property("dynamic_filters_section", "hidden", 1);
- frm.trigger('set_parent_document_type');
- frm.trigger('set_time_series');
- frm.set_query('document_type', function() {
+ frm.trigger("set_parent_document_type");
+ frm.trigger("set_time_series");
+ frm.set_query("document_type", function () {
return {
filters: {
- 'issingle': false
- }
- }
+ issingle: false,
+ },
+ };
});
- frm.trigger('update_options');
- frm.trigger('set_heatmap_year_options');
+ frm.trigger("update_options");
+ frm.trigger("set_heatmap_year_options");
if (frm.doc.report_name) {
- frm.trigger('set_chart_report_filters');
+ frm.trigger("set_chart_report_filters");
}
},
- is_standard: function(frm) {
+ is_standard: function (frm) {
if (frappe.boot.developer_mode && frm.doc.is_standard) {
- frm.trigger('render_dynamic_filters_table');
+ frm.trigger("render_dynamic_filters_table");
} else {
frm.set_df_property("dynamic_filters_section", "hidden", 1);
}
},
- source: function(frm) {
+ source: function (frm) {
frm.trigger("show_filters");
},
- set_heatmap_year_options: function(frm) {
- if (frm.doc.type == 'Heatmap') {
- frappe.db.get_doc('System Settings').then(doc => {
+ set_heatmap_year_options: function (frm) {
+ if (frm.doc.type == "Heatmap") {
+ frappe.db.get_doc("System Settings").then((doc) => {
const creation_date = doc.creation;
- frm.set_df_property('heatmap_year', 'options', frappe.dashboard_utils.get_years_since_creation(creation_date));
+ frm.set_df_property(
+ "heatmap_year",
+ "options",
+ frappe.dashboard_utils.get_years_since_creation(creation_date)
+ );
});
}
},
- chart_type: function(frm) {
- frm.trigger('set_time_series');
- if (frm.doc.chart_type == 'Report') {
- frm.set_query('report_name', () => {
+ chart_type: function (frm) {
+ frm.trigger("set_time_series");
+ if (frm.doc.chart_type == "Report") {
+ frm.set_query("report_name", () => {
return {
filters: {
- 'report_type': ['!=', 'Report Builder']
- }
- }
+ report_type: ["!=", "Report Builder"],
+ },
+ };
});
} else {
- frm.set_value('document_type', '');
+ frm.set_value("document_type", "");
}
},
- set_time_series: function(frm) {
+ set_time_series: function (frm) {
// set timeseries based on chart type
- if (['Count', 'Average', 'Sum'].includes(frm.doc.chart_type)) {
- frm.set_value('timeseries', 1);
+ if (["Count", "Average", "Sum"].includes(frm.doc.chart_type)) {
+ frm.set_value("timeseries", 1);
} else {
- frm.set_value('timeseries', 0);
+ frm.set_value("timeseries", 0);
}
},
- document_type: function(frm) {
+ document_type: function (frm) {
// update `based_on` options based on date / datetime fields
- frm.set_value('source', '');
- frm.set_value('based_on', '');
- frm.set_value('value_based_on', '');
- frm.set_value('parent_document_type', '');
- frm.set_value('filters_json', '[]');
- frm.set_value('dynamic_filters_json', '[]');
- frm.trigger('update_options');
- frm.trigger('set_parent_document_type');
+ frm.set_value("source", "");
+ frm.set_value("based_on", "");
+ frm.set_value("value_based_on", "");
+ frm.set_value("parent_document_type", "");
+ frm.set_value("filters_json", "[]");
+ frm.set_value("dynamic_filters_json", "[]");
+ frm.trigger("update_options");
+ frm.trigger("set_parent_document_type");
},
- report_name: function(frm) {
- frm.set_value('x_field', '');
- frm.set_value('y_axis', []);
- frm.set_df_property('x_field', 'options', []);
- frm.set_value('filters_json', '{}');
- frm.set_value('dynamic_filters_json', '{}');
- frm.set_value('use_report_chart', 0);
- frm.trigger('set_chart_report_filters');
+ report_name: function (frm) {
+ frm.set_value("x_field", "");
+ frm.set_value("y_axis", []);
+ frm.set_df_property("x_field", "options", []);
+ frm.set_value("filters_json", "{}");
+ frm.set_value("dynamic_filters_json", "{}");
+ frm.set_value("use_report_chart", 0);
+ frm.trigger("set_chart_report_filters");
},
- set_chart_report_filters: function(frm) {
+ set_chart_report_filters: function (frm) {
let report_name = frm.doc.report_name;
if (report_name) {
if (frm.doc.filters_json.length > 2) {
- frm.trigger('show_filters');
- frm.trigger('set_chart_field_options');
+ frm.trigger("show_filters");
+ frm.trigger("set_chart_field_options");
} else {
- frappe.report_utils.get_report_filters(report_name).then(filters => {
+ frappe.report_utils.get_report_filters(report_name).then((filters) => {
if (filters) {
frm.chart_filters = filters;
let filter_values = frappe.report_utils.get_filter_values(filters);
- frm.set_value('filters_json', JSON.stringify(filter_values));
+ frm.set_value("filters_json", JSON.stringify(filter_values));
}
- frm.trigger('show_filters');
- frm.trigger('set_chart_field_options');
+ frm.trigger("show_filters");
+ frm.trigger("set_chart_field_options");
});
}
-
}
},
- use_report_chart: function(frm) {
- !frm.doc.use_report_chart && frm.trigger('set_chart_field_options');
+ use_report_chart: function (frm) {
+ !frm.doc.use_report_chart && frm.trigger("set_chart_field_options");
},
- set_chart_field_options: function(frm) {
+ set_chart_field_options: function (frm) {
let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null;
if (frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2) {
filters = frappe.dashboard_utils.get_all_filters(frm.doc);
}
- frappe.xcall(
- 'frappe.desk.query_report.run',
- {
+ frappe
+ .xcall("frappe.desk.query_report.run", {
report_name: frm.doc.report_name,
filters: filters,
- ignore_prepared_report: 1
- }
- ).then(data => {
- frm.report_data = data;
- let report_has_chart = Boolean(data.chart);
+ ignore_prepared_report: 1,
+ })
+ .then((data) => {
+ frm.report_data = data;
+ let report_has_chart = Boolean(data.chart);
- frm.set_df_property('use_report_chart', 'hidden', !report_has_chart);
+ frm.set_df_property("use_report_chart", "hidden", !report_has_chart);
- if (!frm.doc.use_report_chart) {
- if (data.result.length) {
- frm.field_options = frappe.report_utils.get_field_options_from_report(data.columns, data);
- frm.set_df_property('x_field', 'options', frm.field_options.non_numeric_fields);
- if (!frm.field_options.numeric_fields.length) {
- frappe.msgprint(__("Report has no numeric fields, please change the Report Name"));
+ if (!frm.doc.use_report_chart) {
+ if (data.result.length) {
+ frm.field_options = frappe.report_utils.get_field_options_from_report(
+ data.columns,
+ data
+ );
+ frm.set_df_property(
+ "x_field",
+ "options",
+ frm.field_options.non_numeric_fields
+ );
+ if (!frm.field_options.numeric_fields.length) {
+ frappe.msgprint(
+ __("Report has no numeric fields, please change the Report Name")
+ );
+ } else {
+ let y_field_df = frappe.meta.get_docfield(
+ "Dashboard Chart Field",
+ "y_field",
+ frm.doc.name
+ );
+ y_field_df.options = frm.field_options.numeric_fields;
+ }
} else {
- let y_field_df = frappe.meta.get_docfield('Dashboard Chart Field', 'y_field', frm.doc.name);
- y_field_df.options = frm.field_options.numeric_fields;
+ frappe.msgprint(
+ __(
+ "Report has no data, please modify the filters or change the Report Name"
+ )
+ );
}
} else {
- frappe.msgprint(__('Report has no data, please modify the filters or change the Report Name'));
+ frm.set_value("use_report_chart", 1);
+ frm.set_df_property("use_report_chart", "hidden", false);
}
- } else {
- frm.set_value('use_report_chart', 1);
- frm.set_df_property('use_report_chart', 'hidden', false);
- }
- });
+ });
},
- timespan: function(frm) {
+ timespan: function (frm) {
const time_interval_options = {
"Select Date Range": ["Quarterly", "Monthly", "Weekly", "Daily"],
"All Time": ["Yearly", "Monthly"],
"Last Year": ["Quarterly", "Monthly", "Weekly", "Daily"],
"Last Quarter": ["Monthly", "Weekly", "Daily"],
"Last Month": ["Weekly", "Daily"],
- "Last Week": ["Daily"]
+ "Last Week": ["Daily"],
};
if (frm.doc.timespan) {
- frm.set_df_property('time_interval', 'options', time_interval_options[frm.doc.timespan]);
+ frm.set_df_property(
+ "time_interval",
+ "options",
+ time_interval_options[frm.doc.timespan]
+ );
}
},
- update_options: function(frm) {
+ update_options: function (frm) {
let doctype = frm.doc.document_type;
let date_fields = [
- {label: __('Created On'), value: 'creation'},
- {label: __('Last Modified On'), value: 'modified'}
+ { label: __("Created On"), value: "creation" },
+ { label: __("Last Modified On"), value: "modified" },
];
let value_fields = [];
- let group_by_fields = [{label: 'Created By', value: 'owner'}];
+ let group_by_fields = [{ label: "Created By", value: "owner" }];
let aggregate_function_fields = [];
- let update_form = function() {
+ let update_form = function () {
// update select options
- frm.set_df_property('based_on', 'options', date_fields);
- frm.set_df_property('value_based_on', 'options', value_fields);
- frm.set_df_property('group_by_based_on', 'options', group_by_fields);
- frm.set_df_property('aggregate_function_based_on', 'options', aggregate_function_fields);
+ frm.set_df_property("based_on", "options", date_fields);
+ frm.set_df_property("value_based_on", "options", value_fields);
+ frm.set_df_property("group_by_based_on", "options", group_by_fields);
+ frm.set_df_property(
+ "aggregate_function_based_on",
+ "options",
+ aggregate_function_fields
+ );
frm.trigger("show_filters");
- }
-
+ };
if (doctype) {
frappe.model.with_doctype(doctype, () => {
// get all date and datetime fields
- frappe.get_meta(doctype).fields.map(df => {
- if (['Date', 'Datetime'].includes(df.fieldtype)) {
- date_fields.push({label: df.label, value: df.fieldname});
+ frappe.get_meta(doctype).fields.map((df) => {
+ if (["Date", "Datetime"].includes(df.fieldtype)) {
+ date_fields.push({ label: df.label, value: df.fieldname });
}
- if (['Int', 'Float', 'Currency', 'Percent', 'Duration'].includes(df.fieldtype)) {
- value_fields.push({label: df.label, value: df.fieldname});
- aggregate_function_fields.push({label: df.label, value: df.fieldname});
+ if (
+ ["Int", "Float", "Currency", "Percent", "Duration"].includes(df.fieldtype)
+ ) {
+ value_fields.push({ label: df.label, value: df.fieldname });
+ aggregate_function_fields.push({ label: df.label, value: df.fieldname });
}
- if (['Link', 'Select'].includes(df.fieldtype)) {
- group_by_fields.push({label: df.label, value: df.fieldname});
+ if (["Link", "Select"].includes(df.fieldtype)) {
+ group_by_fields.push({ label: df.label, value: df.fieldname });
}
});
update_form();
@@ -246,92 +276,89 @@ frappe.ui.form.on('Dashboard Chart', {
// update select options
update_form();
}
-
},
- show_filters: function(frm) {
+ show_filters: function (frm) {
frm.chart_filters = [];
- frappe.dashboard_utils.get_filters_for_chart_type(frm.doc).then(filters => {
+ frappe.dashboard_utils.get_filters_for_chart_type(frm.doc).then((filters) => {
if (filters) {
frm.chart_filters = filters;
}
- frm.trigger('render_filters_table');
+ frm.trigger("render_filters_table");
if (frappe.boot.developer_mode && frm.doc.is_standard) {
- frm.trigger('render_dynamic_filters_table');
+ frm.trigger("render_dynamic_filters_table");
}
});
},
- render_filters_table: function(frm) {
+ render_filters_table: function (frm) {
frm.set_df_property("filters_section", "hidden", 0);
- let is_document_type = frm.doc.chart_type!== 'Report' && frm.doc.chart_type!=='Custom';
- let is_dynamic_filter = f => ['Date', 'DateRange'].includes(f.fieldtype) && f.default;
+ let is_document_type = frm.doc.chart_type !== "Report" && frm.doc.chart_type !== "Custom";
+ let is_dynamic_filter = (f) => ["Date", "DateRange"].includes(f.fieldtype) && f.default;
- let wrapper = $(frm.get_field('filters_json').wrapper).empty();
+ let wrapper = $(frm.get_field("filters_json").wrapper).empty();
let table = $(`
- ${__('Filter')}
- ${__('Condition')}
- ${__('Value')}
+ ${__("Filter")}
+ ${__("Condition")}
+ ${__("Value")}
`).appendTo(wrapper);
$(`${__("Click table to edit")}
`).appendTo(wrapper);
- let filters = JSON.parse(frm.doc.filters_json || '[]');
+ let filters = JSON.parse(frm.doc.filters_json || "[]");
var filters_set = false;
// Set dynamic filters for reports
- if (frm.doc.chart_type == 'Report') {
+ if (frm.doc.chart_type == "Report") {
let set_filters = false;
- frm.chart_filters.forEach(f => {
+ frm.chart_filters.forEach((f) => {
if (is_dynamic_filter(f)) {
filters[f.fieldname] = f.default;
set_filters = true;
}
});
- set_filters && frm.set_value('filters_json', JSON.stringify(filters));
+ set_filters && frm.set_value("filters_json", JSON.stringify(filters));
}
let fields = [];
if (is_document_type) {
fields = [
{
- fieldtype: 'HTML',
- fieldname: 'filter_area',
- }
+ fieldtype: "HTML",
+ fieldname: "filter_area",
+ },
];
if (filters.length > 0) {
- filters.forEach( filter => {
- const filter_row =
- $(`
+ filters.forEach((filter) => {
+ const filter_row = $(`
${filter[1]}
${filter[2] || ""}
${filter[3]}
`);
- table.find('tbody').append(filter_row);
+ table.find("tbody").append(filter_row);
filters_set = true;
});
}
} else if (frm.chart_filters.length) {
- fields = frm.chart_filters.filter(f => f.fieldname);
+ fields = frm.chart_filters.filter((f) => f.fieldname);
- fields.map(f => {
+ fields.map((f) => {
if (filters[f.fieldname]) {
- let condition = '=';
- const filter_row =
- $(`
+ let condition = "=";
+ const filter_row = $(`
${f.label}
${condition}
${filters[f.fieldname] || ""}
`);
- table.find('tbody').append(filter_row);
+ table.find("tbody").append(filter_row);
filters_set = true;
}
});
@@ -340,39 +367,39 @@ frappe.ui.form.on('Dashboard Chart', {
if (!filters_set) {
const filter_row = $(`
${__("Click to Set Filters")} `);
- table.find('tbody').append(filter_row);
+ table.find("tbody").append(filter_row);
}
- table.on('click', () => {
- frm.is_disabled && frappe.throw(__('Cannot edit filters for standard charts'));
+ table.on("click", () => {
+ frm.is_disabled && frappe.throw(__("Cannot edit filters for standard charts"));
let dialog = new frappe.ui.Dialog({
- title: __('Set Filters'),
- fields: fields.filter(f => !is_dynamic_filter(f)),
- primary_action: function() {
+ title: __("Set Filters"),
+ fields: fields.filter((f) => !is_dynamic_filter(f)),
+ primary_action: function () {
let values = this.get_values();
if (values) {
this.hide();
if (is_document_type) {
let filters = frm.filter_group.get_filters();
- frm.set_value('filters_json', JSON.stringify(filters));
+ frm.set_value("filters_json", JSON.stringify(filters));
} else {
- frm.set_value('filters_json', JSON.stringify(values));
+ frm.set_value("filters_json", JSON.stringify(values));
}
- frm.trigger('show_filters');
- if (frm.doc.chart_type == 'Report') {
- frm.trigger('set_chart_report_filters');
+ frm.trigger("show_filters");
+ if (frm.doc.chart_type == "Report") {
+ frm.trigger("set_chart_report_filters");
}
}
},
- primary_action_label: "Set"
+ primary_action_label: "Set",
});
frappe.dashboards.filters_dialog = dialog;
if (is_document_type) {
frm.filter_group = new frappe.ui.FilterGroup({
- parent: dialog.get_field('filter_area').$wrapper,
+ parent: dialog.get_field("filter_area").$wrapper,
doctype: frm.doc.document_type,
parent_doctype: frm.doc.parent_document_type,
on_change: () => {},
@@ -383,12 +410,14 @@ frappe.ui.form.on('Dashboard Chart', {
dialog.show();
- if (frm.doc.chart_type == 'Report') {
+ if (frm.doc.chart_type == "Report") {
//Set query report object so that it can be used while fetching filter values in the report
- frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
- frappe.query_reports[frm.doc.report_name]
- && frappe.query_reports[frm.doc.report_name].onload
- && frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
+ frappe.query_report = new frappe.views.QueryReport({
+ filters: dialog.fields_list,
+ });
+ frappe.query_reports[frm.doc.report_name] &&
+ frappe.query_reports[frm.doc.report_name].onload &&
+ frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
}
dialog.set_values(filters);
@@ -398,37 +427,40 @@ frappe.ui.form.on('Dashboard Chart', {
render_dynamic_filters_table(frm) {
frm.set_df_property("dynamic_filters_section", "hidden", 0);
- let is_document_type = frm.doc.chart_type !== 'Report'
- && frm.doc.chart_type !== 'Custom';
+ let is_document_type = frm.doc.chart_type !== "Report" && frm.doc.chart_type !== "Custom";
- let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty();
+ let wrapper = $(frm.get_field("dynamic_filters_json").wrapper).empty();
- frm.dynamic_filter_table = $(`
+ frm.dynamic_filter_table =
+ $(`
- ${__('Filter')}
- ${__('Condition')}
- ${__('Value')}
+ ${__("Filter")}
+ ${__("Condition")}
+ ${__("Value")}
`).appendTo(wrapper);
- frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
- ? JSON.parse(frm.doc.dynamic_filters_json)
- : null;
+ frm.dynamic_filters =
+ frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
+ ? JSON.parse(frm.doc.dynamic_filters_json)
+ : null;
- frm.trigger('set_dynamic_filters_in_table');
+ frm.trigger("set_dynamic_filters_in_table");
- let filters = JSON.parse(frm.doc.filters_json || '[]');
+ let filters = JSON.parse(frm.doc.filters_json || "[]");
let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog(
- is_document_type, filters, frm.dynamic_filters
+ is_document_type,
+ filters,
+ frm.dynamic_filters
);
- frm.dynamic_filter_table.on('click', () => {
+ frm.dynamic_filter_table.on("click", () => {
let dialog = new frappe.ui.Dialog({
- title: __('Set Dynamic Filters'),
+ title: __("Set Dynamic Filters"),
fields: fields,
primary_action: () => {
let values = dialog.get_values();
@@ -436,19 +468,19 @@ frappe.ui.form.on('Dashboard Chart', {
let dynamic_filters = [];
for (let key of Object.keys(values)) {
if (is_document_type) {
- let [doctype, fieldname] = key.split(':');
- dynamic_filters.push([doctype, fieldname, '=', values[key]]);
+ let [doctype, fieldname] = key.split(":");
+ dynamic_filters.push([doctype, fieldname, "=", values[key]]);
}
}
if (is_document_type) {
- frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters));
+ frm.set_value("dynamic_filters_json", JSON.stringify(dynamic_filters));
} else {
- frm.set_value('dynamic_filters_json', JSON.stringify(values));
+ frm.set_value("dynamic_filters_json", JSON.stringify(values));
}
- frm.trigger('set_dynamic_filters_in_table');
+ frm.trigger("set_dynamic_filters_in_table");
},
- primary_action_label: "Set"
+ primary_action_label: "Set",
});
dialog.show();
@@ -456,71 +488,70 @@ frappe.ui.form.on('Dashboard Chart', {
});
},
- set_dynamic_filters_in_table: function(frm) {
- frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
- ? JSON.parse(frm.doc.dynamic_filters_json)
- : null;
+ set_dynamic_filters_in_table: function (frm) {
+ frm.dynamic_filters =
+ frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
+ ? JSON.parse(frm.doc.dynamic_filters_json)
+ : null;
if (!frm.dynamic_filters) {
const filter_row = $(`
${__("Click to Set Dynamic Filters")} `);
- frm.dynamic_filter_table.find('tbody').html(filter_row);
+ frm.dynamic_filter_table.find("tbody").html(filter_row);
} else {
- let filter_rows = '';
+ let filter_rows = "";
if ($.isArray(frm.dynamic_filters)) {
- frm.dynamic_filters.forEach(filter => {
- filter_rows +=
- `
+ frm.dynamic_filters.forEach((filter) => {
+ filter_rows += `
${filter[1]}
${filter[2] || ""}
${filter[3]}
`;
});
} else {
- let condition = '=';
+ let condition = "=";
for (let [key, val] of Object.entries(frm.dynamic_filters)) {
- filter_rows +=
- `
+ filter_rows += `
${key}
${condition}
${val || ""}
- `
- ;
+ `;
}
}
- frm.dynamic_filter_table.find('tbody').html(filter_rows);
+ frm.dynamic_filter_table.find("tbody").html(filter_rows);
}
},
- set_parent_document_type: async function(frm) {
+ set_parent_document_type: async function (frm) {
let document_type = frm.doc.document_type;
- let doc_is_table = document_type &&
- (await frappe.db.get_value('DocType', document_type, 'istable')).message.istable;
+ let doc_is_table =
+ document_type &&
+ (await frappe.db.get_value("DocType", document_type, "istable")).message.istable;
- frm.set_df_property('parent_document_type', 'hidden', !doc_is_table);
+ frm.set_df_property("parent_document_type", "hidden", !doc_is_table);
if (document_type && doc_is_table) {
- let parent = await frappe.db.get_list('DocField', {
+ let parent = await frappe.db.get_list("DocField", {
filters: {
- 'fieldtype': 'Table',
- 'options': document_type
+ fieldtype: "Table",
+ options: document_type,
},
- fields: ['parent']
+ fields: ["parent"],
});
- parent && frm.set_query('parent_document_type', function() {
- return {
- filters: {
- "name": ['in', parent.map(({ parent }) => parent)]
- }
- };
- });
+ parent &&
+ frm.set_query("parent_document_type", function () {
+ return {
+ filters: {
+ name: ["in", parent.map(({ parent }) => parent)],
+ },
+ };
+ });
if (parent.length === 1) {
- frm.set_value('parent_document_type', parent[0].parent);
+ frm.set_value("parent_document_type", parent[0].parent);
}
}
- }
-
+ },
});
diff --git a/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.js b/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.js
index 96dd40d3c1..6f1fa36ffd 100644
--- a/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.js
+++ b/frappe/desk/doctype/dashboard_chart_source/dashboard_chart_source.js
@@ -1,5 +1,4 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Dashboard Chart Source', {
-});
+frappe.ui.form.on("Dashboard Chart Source", {});
diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.js b/frappe/desk/doctype/dashboard_settings/dashboard_settings.js
index 8e7966366d..aa5be2b1a5 100644
--- a/frappe/desk/doctype/dashboard_settings/dashboard_settings.js
+++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Dashboard Settings', {
+frappe.ui.form.on("Dashboard Settings", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/desk/doctype/desktop_icon/desktop_icon.js b/frappe/desk/doctype/desktop_icon/desktop_icon.js
index 58ea09e732..72ef1f7a12 100644
--- a/frappe/desk/doctype/desktop_icon/desktop_icon.js
+++ b/frappe/desk/doctype/desktop_icon/desktop_icon.js
@@ -1,8 +1,6 @@
// Copyright (c) 2016, Frappe Technologies Pvt. Ltd. and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Desktop Icon', {
- refresh: function(frm) {
-
- }
+frappe.ui.form.on("Desktop Icon", {
+ refresh: function (frm) {},
});
diff --git a/frappe/desk/doctype/event/event.js b/frappe/desk/doctype/event/event.js
index 87d78bae94..61bf66f5e5 100644
--- a/frappe/desk/doctype/event/event.js
+++ b/frappe/desk/doctype/event/event.js
@@ -3,70 +3,79 @@
frappe.provide("frappe.desk");
frappe.ui.form.on("Event", {
- onload: function(frm) {
- frm.set_query('reference_doctype', "event_participants", function() {
- return {
- "filters": {
- "issingle": 0,
- }
- };
- });
- frm.set_query('google_calendar', function() {
+ onload: function (frm) {
+ frm.set_query("reference_doctype", "event_participants", function () {
return {
filters: {
- "owner": frappe.session.user
- }
+ issingle: 0,
+ },
+ };
+ });
+ frm.set_query("google_calendar", function () {
+ return {
+ filters: {
+ owner: frappe.session.user,
+ },
};
});
},
- refresh: function(frm) {
- if(frm.doc.event_participants) {
- frm.doc.event_participants.forEach(value => {
- frm.add_custom_button(__(value.reference_docname), function() {
- frappe.set_route("Form", value.reference_doctype, value.reference_docname);
- }, __("Participants"));
- })
+ refresh: function (frm) {
+ if (frm.doc.event_participants) {
+ frm.doc.event_participants.forEach((value) => {
+ frm.add_custom_button(
+ __(value.reference_docname),
+ function () {
+ frappe.set_route("Form", value.reference_doctype, value.reference_docname);
+ },
+ __("Participants")
+ );
+ });
}
frm.page.set_inner_btn_group_as_primary(__("Add Participants"));
- frm.add_custom_button(__('Add Contacts'), function() {
- new frappe.desk.eventParticipants(frm, "Contact");
- }, __("Add Participants"));
+ frm.add_custom_button(
+ __("Add Contacts"),
+ function () {
+ new frappe.desk.eventParticipants(frm, "Contact");
+ },
+ __("Add Participants")
+ );
},
- repeat_on: function(frm) {
- if(frm.doc.repeat_on==="Every Day") {
- ["monday", "tuesday", "wednesday", "thursday",
- "friday", "saturday", "sunday"].map(function(v) {
+ repeat_on: function (frm) {
+ if (frm.doc.repeat_on === "Every Day") {
+ ["monday", "tuesday", "wednesday", "thursday", "friday", "saturday", "sunday"].map(
+ function (v) {
frm.set_value(v, 1);
- });
+ }
+ );
}
- }
+ },
});
frappe.ui.form.on("Event Participants", {
- event_participants_remove: function(frm, cdt, cdn) {
- if (cdt&&!cdn.includes("New Event Participants")){
+ event_participants_remove: function (frm, cdt, cdn) {
+ if (cdt && !cdn.includes("New Event Participants")) {
frappe.call({
type: "POST",
method: "frappe.desk.doctype.event.event.delete_communication",
args: {
- "event": frm.doc,
- "reference_doctype": cdt,
- "reference_docname": cdn
+ event: frm.doc,
+ reference_doctype: cdt,
+ reference_docname: cdn,
},
freeze: true,
- callback: function(r) {
- if(r.exc) {
+ callback: function (r) {
+ if (r.exc) {
frappe.show_alert({
message: __("{0}", [r.exc]),
- indicator: 'orange'
+ indicator: "orange",
});
}
- }
+ },
});
}
- }
+ },
});
frappe.desk.eventParticipants = class eventParticipants {
@@ -86,7 +95,7 @@ frappe.desk.eventParticipants = class eventParticipants {
dynamic_link_reference: me.doctype,
fieldname: "reference_docname",
target: table,
- txt: ""
+ txt: "",
});
}
};
diff --git a/frappe/desk/doctype/event/event_calendar.js b/frappe/desk/doctype/event/event_calendar.js
index df474a0258..bfdd09b864 100644
--- a/frappe/desk/doctype/event/event_calendar.js
+++ b/frappe/desk/doctype/event/event_calendar.js
@@ -1,16 +1,16 @@
frappe.views.calendar["Event"] = {
field_map: {
- "start": "starts_on",
- "end": "ends_on",
- "id": "name",
- "allDay": "all_day",
- "title": "subject",
- "status": "event_type",
- "color": "color"
+ start: "starts_on",
+ end: "ends_on",
+ id: "name",
+ allDay: "all_day",
+ title: "subject",
+ status: "event_type",
+ color: "color",
},
style_map: {
- "Public": "success",
- "Private": "info"
+ Public: "success",
+ Private: "info",
},
- get_events_method: "frappe.desk.doctype.event.event.get_events"
-}
\ No newline at end of file
+ get_events_method: "frappe.desk.doctype.event.event.get_events",
+};
diff --git a/frappe/desk/doctype/event/event_list.js b/frappe/desk/doctype/event/event_list.js
index 5d73d9dd1a..f6460288d8 100644
--- a/frappe/desk/doctype/event/event_list.js
+++ b/frappe/desk/doctype/event/event_list.js
@@ -1,8 +1,8 @@
-frappe.listview_settings['Event'] = {
+frappe.listview_settings["Event"] = {
add_fields: ["starts_on", "ends_on"],
- onload: function() {
+ onload: function () {
frappe.route_options = {
- "status": "Open"
+ status: "Open",
};
- }
-}
\ No newline at end of file
+ },
+};
diff --git a/frappe/desk/doctype/form_tour/form_tour.js b/frappe/desk/doctype/form_tour/form_tour.js
index 3f3fc0ff8a..1e67e10779 100644
--- a/frappe/desk/doctype/form_tour/form_tour.js
+++ b/frappe/desk/doctype/form_tour/form_tour.js
@@ -1,10 +1,10 @@
// Copyright (c) 2021, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Form Tour', {
- setup: function(frm) {
+frappe.ui.form.on("Form Tour", {
+ setup: function (frm) {
if (!frm.doc.is_standard || frappe.boot.developer_mode) {
- frm.trigger('setup_queries');
+ frm.trigger("setup_queries");
}
},
@@ -13,28 +13,26 @@ frappe.ui.form.on('Form Tour', {
frm.trigger("disable_form");
}
- frm.add_custom_button(__('Show Tour'), async () => {
+ frm.add_custom_button(__("Show Tour"), async () => {
const issingle = await check_if_single(frm.doc.reference_doctype);
let route_changed = null;
if (issingle) {
- route_changed = frappe.set_route('Form', frm.doc.reference_doctype);
+ route_changed = frappe.set_route("Form", frm.doc.reference_doctype);
} else if (frm.doc.first_document) {
const name = await get_first_document(frm.doc.reference_doctype);
- route_changed = frappe.set_route('Form', frm.doc.reference_doctype, name);
+ route_changed = frappe.set_route("Form", frm.doc.reference_doctype, name);
} else {
- route_changed = frappe.set_route('Form', frm.doc.reference_doctype, 'new');
+ route_changed = frappe.set_route("Form", frm.doc.reference_doctype, "new");
}
route_changed.then(() => {
const tour_name = frm.doc.name;
- cur_frm.tour
- .init({ tour_name })
- .then(() => cur_frm.tour.start());
+ cur_frm.tour.init({ tour_name }).then(() => cur_frm.tour.start());
});
});
},
- disable_form: function(frm) {
+ disable_form: function (frm) {
frm.set_read_only();
frm.fields
.filter((field) => field.has_input)
@@ -45,51 +43,48 @@ frappe.ui.form.on('Form Tour', {
},
setup_queries(frm) {
- frm.set_query("reference_doctype", function() {
+ frm.set_query("reference_doctype", function () {
return {
filters: {
- istable: 0
- }
+ istable: 0,
+ },
};
});
- frm.trigger('reference_doctype');
+ frm.trigger("reference_doctype");
},
reference_doctype(frm) {
if (!frm.doc.reference_doctype) return;
- frm.set_fields_as_options(
- "fieldname",
- frm.doc.reference_doctype,
- df => !df.hidden
- ).then(options => {
- frm.fields_dict.steps.grid.update_docfield_property(
- "fieldname",
- "options",
- [""].concat(options)
- );
- });
+ frm.set_fields_as_options("fieldname", frm.doc.reference_doctype, (df) => !df.hidden).then(
+ (options) => {
+ frm.fields_dict.steps.grid.update_docfield_property(
+ "fieldname",
+ "options",
+ [""].concat(options)
+ );
+ }
+ );
frm.set_fields_as_options(
- 'parent_fieldname',
+ "parent_fieldname",
frm.doc.reference_doctype,
- (df) => df.fieldtype == "Table" && !df.hidden,
- ).then(options => {
+ (df) => df.fieldtype == "Table" && !df.hidden
+ ).then((options) => {
frm.fields_dict.steps.grid.update_docfield_property(
"parent_fieldname",
"options",
[""].concat(options)
);
});
-
- }
+ },
});
-frappe.ui.form.on('Form Tour Step', {
+frappe.ui.form.on("Form Tour Step", {
form_render(frm, cdt, cdn) {
if (locals[cdt][cdn].is_table_field) {
- frm.trigger('parent_fieldname', cdt, cdn);
+ frm.trigger("parent_fieldname", cdt, cdn);
}
},
parent_fieldname(frm, cdt, cdn) {
@@ -97,37 +92,36 @@ frappe.ui.form.on('Form Tour Step', {
const parent_fieldname_df = frappe
.get_meta(frm.doc.reference_doctype)
- .fields.find(df => df.fieldname == child_row.parent_fieldname);
+ .fields.find((df) => df.fieldname == child_row.parent_fieldname);
frm.set_fields_as_options(
- 'fieldname',
+ "fieldname",
parent_fieldname_df.options,
- (df) => !df.hidden,
- ).then(options => {
+ (df) => !df.hidden
+ ).then((options) => {
frm.fields_dict.steps.grid.update_docfield_property(
"fieldname",
"options",
[""].concat(options)
);
if (child_row.fieldname) {
- frappe.model.set_value(cdt, cdn, 'fieldname', child_row.fieldname);
+ frappe.model.set_value(cdt, cdn, "fieldname", child_row.fieldname);
}
});
- }
+ },
});
async function check_if_single(doctype) {
- const { message } = await frappe.db.get_value('DocType', doctype, 'issingle');
+ const { message } = await frappe.db.get_value("DocType", doctype, "issingle");
return message.issingle || 0;
}
async function get_first_document(doctype) {
let docname;
- await frappe.db.get_list(doctype, { order_by: "creation" }).then(res => {
- if (Array.isArray(res) && res.length)
- docname = res[0].name;
+ await frappe.db.get_list(doctype, { order_by: "creation" }).then((res) => {
+ if (Array.isArray(res) && res.length) docname = res[0].name;
});
- return docname || 'new';
+ return docname || "new";
}
diff --git a/frappe/desk/doctype/global_search_settings/global_search_settings.js b/frappe/desk/doctype/global_search_settings/global_search_settings.js
index c333f83585..147a72eef1 100644
--- a/frappe/desk/doctype/global_search_settings/global_search_settings.js
+++ b/frappe/desk/doctype/global_search_settings/global_search_settings.js
@@ -1,14 +1,17 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Global Search Settings', {
- refresh: function(frm) {
-
- frappe.realtime.on('global_search_settings', (data) => {
+frappe.ui.form.on("Global Search Settings", {
+ refresh: function (frm) {
+ frappe.realtime.on("global_search_settings", (data) => {
if (data.progress) {
- frm.dashboard.show_progress('Setting up Global Search', data.progress / data.total * 100, data.msg);
+ frm.dashboard.show_progress(
+ "Setting up Global Search",
+ (data.progress / data.total) * 100,
+ data.msg
+ );
if (data.progress === data.total) {
- frm.dashboard.hide_progress('Setting up Global Search');
+ frm.dashboard.hide_progress("Setting up Global Search");
}
}
});
@@ -16,14 +19,14 @@ frappe.ui.form.on('Global Search Settings', {
frm.add_custom_button(__("Reset"), function () {
frappe.call({
method: "frappe.desk.doctype.global_search_settings.global_search_settings.reset_global_search_settings_doctypes",
- callback: function() {
+ callback: function () {
frappe.show_alert({
message: __("Global Search Document Types Reset."),
- indicator: "green"
+ indicator: "green",
});
frm.refresh();
- }
+ },
});
});
- }
+ },
});
diff --git a/frappe/desk/doctype/kanban_board/kanban_board.js b/frappe/desk/doctype/kanban_board/kanban_board.js
index ff80a58fa0..3b815fef0e 100644
--- a/frappe/desk/doctype/kanban_board/kanban_board.js
+++ b/frappe/desk/doctype/kanban_board/kanban_board.js
@@ -1,43 +1,45 @@
// Copyright (c) 2016, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Kanban Board', {
- onload: function(frm) {
- frm.trigger('reference_doctype');
+frappe.ui.form.on("Kanban Board", {
+ onload: function (frm) {
+ frm.trigger("reference_doctype");
},
- refresh: function(frm) {
- if(frm.is_new()) return;
- frm.add_custom_button("Show Board", function() {
+ refresh: function (frm) {
+ if (frm.is_new()) return;
+ frm.add_custom_button("Show Board", function () {
frappe.set_route("List", frm.doc.reference_doctype, "Kanban", frm.doc.name);
});
},
- reference_doctype: function(frm) {
-
+ reference_doctype: function (frm) {
// set field options
- if(!frm.doc.reference_doctype) return;
+ if (!frm.doc.reference_doctype) return;
- frappe.model.with_doctype(frm.doc.reference_doctype, function() {
- var options = $.map(frappe.get_meta(frm.doc.reference_doctype).fields,
- function(d) {
- if(d.fieldname && d.fieldtype === 'Select' &&
- frappe.model.no_value_type.indexOf(d.fieldtype)===-1) {
- return d.fieldname;
- }
- return null;
- });
- frm.set_df_property('field_name', 'options', options);
- frm.get_field('field_name').refresh();
+ frappe.model.with_doctype(frm.doc.reference_doctype, function () {
+ var options = $.map(frappe.get_meta(frm.doc.reference_doctype).fields, function (d) {
+ if (
+ d.fieldname &&
+ d.fieldtype === "Select" &&
+ frappe.model.no_value_type.indexOf(d.fieldtype) === -1
+ ) {
+ return d.fieldname;
+ }
+ return null;
+ });
+ frm.set_df_property("field_name", "options", options);
+ frm.get_field("field_name").refresh();
});
},
- field_name: function(frm) {
+ field_name: function (frm) {
var field = frappe.meta.get_field(frm.doc.reference_doctype, frm.doc.field_name);
frm.doc.columns = [];
- field.options && field.options.split('\n').forEach(function(o) {
- o = o.trim();
- if(!o) return;
- var d = frm.add_child('columns');
- d.column_name = o;
- });
+ field.options &&
+ field.options.split("\n").forEach(function (o) {
+ o = o.trim();
+ if (!o) return;
+ var d = frm.add_child("columns");
+ d.column_name = o;
+ });
frm.refresh();
- }
+ },
});
diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js
index db33f71675..007a242dd3 100644
--- a/frappe/desk/doctype/list_view_settings/list_view_settings.js
+++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('List View Settings', {
+frappe.ui.form.on("List View Settings", {
// refresh: function(frm) {
-
// }
-});
\ No newline at end of file
+});
diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.js b/frappe/desk/doctype/module_onboarding/module_onboarding.js
index d95920e2ca..0e312025bf 100644
--- a/frappe/desk/doctype/module_onboarding/module_onboarding.js
+++ b/frappe/desk/doctype/module_onboarding/module_onboarding.js
@@ -2,7 +2,7 @@
// For license information, please see license.txt
frappe.ui.form.on("Module Onboarding", {
- refresh: function(frm) {
+ refresh: function (frm) {
frappe.boot.developer_mode &&
frm.set_intro(
__(
@@ -15,7 +15,7 @@ frappe.ui.form.on("Module Onboarding", {
}
},
- disable_form: function(frm) {
+ disable_form: function (frm) {
frm.set_read_only();
frm.fields
.filter((field) => field.has_input)
diff --git a/frappe/desk/doctype/note/note.js b/frappe/desk/doctype/note/note.js
index 5718180b70..dd556a3969 100644
--- a/frappe/desk/doctype/note/note.js
+++ b/frappe/desk/doctype/note/note.js
@@ -1,5 +1,5 @@
frappe.ui.form.on("Note", {
- refresh: function(frm) {
+ refresh: function (frm) {
if (frm.doc.__islocal) {
frm.events.set_editable(frm, true);
} else {
@@ -8,13 +8,13 @@ frappe.ui.form.on("Note", {
}
// toggle edit
- frm.add_custom_button("Edit", function() {
+ frm.add_custom_button("Edit", function () {
frm.events.set_editable(frm, !frm.is_note_editable);
});
frm.events.set_editable(frm, false);
}
},
- set_editable: function(frm, editable) {
+ set_editable: function (frm, editable) {
// hide all fields other than content
// no permission
@@ -24,7 +24,7 @@ frappe.ui.form.on("Note", {
frm.set_df_property("content", "read_only", editable ? 0 : 1);
// hide all other fields
- $.each(frm.fields_dict, function(fieldname) {
+ $.each(frm.fields_dict, function (fieldname) {
if (fieldname !== "content") {
frm.set_df_property(fieldname, "hidden", editable ? 0 : 1);
}
@@ -36,10 +36,10 @@ frappe.ui.form.on("Note", {
// set flag for toggle
frm.is_note_editable = editable;
- }
+ },
});
-frappe.tour['Note'] = [
+frappe.tour["Note"] = [
{
fieldname: "title",
title: "Title of the Note",
@@ -48,6 +48,7 @@ frappe.tour['Note'] = [
{
fieldname: "public",
title: "Sets the Note to Public",
- description: "You can change the visibility of the note with this, setting it to public will allow other users to view it.",
+ description:
+ "You can change the visibility of the note with this, setting it to public will allow other users to view it.",
},
-];
\ No newline at end of file
+];
diff --git a/frappe/desk/doctype/note/note_list.js b/frappe/desk/doctype/note/note_list.js
index f7f8d37dcf..1e0ed40880 100644
--- a/frappe/desk/doctype/note/note_list.js
+++ b/frappe/desk/doctype/note/note_list.js
@@ -1,13 +1,13 @@
-frappe.listview_settings['Note'] = {
- onload: function(me) {
+frappe.listview_settings["Note"] = {
+ onload: function (me) {
me.page.set_title(__("Notes"));
},
add_fields: ["title", "public"],
- get_indicator: function(doc) {
- if(doc.public) {
+ get_indicator: function (doc) {
+ if (doc.public) {
return [__("Public"), "green", "public,=,Yes"];
} else {
return [__("Private"), "gray", "public,=,No"];
}
- }
-}
+ },
+};
diff --git a/frappe/desk/doctype/notification_log/notification_log.js b/frappe/desk/doctype/notification_log/notification_log.js
index 1f381d115b..ea5fdc6400 100644
--- a/frappe/desk/doctype/notification_log/notification_log.js
+++ b/frappe/desk/doctype/notification_log/notification_log.js
@@ -1,25 +1,25 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Notification Log', {
- refresh: function(frm) {
+frappe.ui.form.on("Notification Log", {
+ refresh: function (frm) {
if (frm.doc.attached_file) {
- frm.trigger('set_attachment');
+ frm.trigger("set_attachment");
} else {
- frm.get_field('attachment_link').$wrapper.empty();
+ frm.get_field("attachment_link").$wrapper.empty();
}
},
- open_reference_document: function(frm) {
+ open_reference_document: function (frm) {
const dt = frm.doc.document_type;
const dn = frm.doc.document_name;
- frappe.set_route('Form', dt, dn);
+ frappe.set_route("Form", dt, dn);
},
- set_attachment: function(frm) {
+ set_attachment: function (frm) {
const attachment = JSON.parse(frm.doc.attached_file);
- const $wrapper = frm.get_field('attachment_link').$wrapper;
+ const $wrapper = frm.get_field("attachment_link").$wrapper;
$wrapper.html(`
@@ -41,5 +41,5 @@ frappe.ui.form.on('Notification Log', {
frappe.msgprint(__("Please enable pop-ups"));
}
});
- }
+ },
});
diff --git a/frappe/desk/doctype/notification_settings/notification_settings.js b/frappe/desk/doctype/notification_settings/notification_settings.js
index cc2fd95204..ba72369273 100644
--- a/frappe/desk/doctype/notification_settings/notification_settings.js
+++ b/frappe/desk/doctype/notification_settings/notification_settings.js
@@ -1,28 +1,27 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Notification Settings', {
+frappe.ui.form.on("Notification Settings", {
onload: (frm) => {
frappe.breadcrumbs.add({
- label: __('Settings'),
- route: '#modules/Settings',
- type: 'Custom'
+ label: __("Settings"),
+ route: "#modules/Settings",
+ type: "Custom",
});
- frm.set_query('subscribed_documents', () => {
+ frm.set_query("subscribed_documents", () => {
return {
filters: {
- istable: 0
- }
+ istable: 0,
+ },
};
});
},
refresh: (frm) => {
- if (frappe.user.has_role('System Manager')) {
- frm.add_custom_button(__('Go to Notification Settings List'), () => {
- frappe.set_route('List', 'Notification Settings');
+ if (frappe.user.has_role("System Manager")) {
+ frm.add_custom_button(__("Go to Notification Settings List"), () => {
+ frappe.set_route("List", "Notification Settings");
});
}
- }
-
+ },
});
diff --git a/frappe/desk/doctype/number_card/number_card.js b/frappe/desk/doctype/number_card/number_card.js
index 79ddb71187..77ab2b4ef8 100644
--- a/frappe/desk/doctype/number_card/number_card.js
+++ b/frappe/desk/doctype/number_card/number_card.js
@@ -1,73 +1,75 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Number Card', {
- refresh: function(frm) {
+frappe.ui.form.on("Number Card", {
+ refresh: function (frm) {
if (!frappe.boot.developer_mode && frm.doc.is_standard) {
frm.disable_form();
}
frm.set_df_property("filters_section", "hidden", 1);
frm.set_df_property("dynamic_filters_section", "hidden", 1);
- frm.trigger('set_options');
+ frm.trigger("set_options");
if (!frm.doc.type) {
- frm.set_value('type', 'Document Type');
+ frm.set_value("type", "Document Type");
}
- if (frm.doc.type == 'Report' && frm.doc.report_name) {
- frm.trigger('set_report_filters');
+ if (frm.doc.type == "Report" && frm.doc.report_name) {
+ frm.trigger("set_report_filters");
}
- if (frm.doc.type == 'Custom') {
+ if (frm.doc.type == "Custom") {
if (!frappe.boot.developer_mode) {
frm.disable_form();
}
frm.filters = eval(frm.doc.filters_config);
- frm.trigger('set_filters_description');
- frm.trigger('set_method_description');
- frm.trigger('render_filters_table');
+ frm.trigger("set_filters_description");
+ frm.trigger("set_method_description");
+ frm.trigger("render_filters_table");
}
- frm.trigger('set_parent_document_type');
+ frm.trigger("set_parent_document_type");
if (!frm.is_new()) {
- frm.trigger('create_add_to_dashboard_button');
+ frm.trigger("create_add_to_dashboard_button");
}
},
- create_add_to_dashboard_button: function(frm) {
- frm.add_custom_button('Add Card to Dashboard', () => {
+ create_add_to_dashboard_button: function (frm) {
+ frm.add_custom_button("Add Card to Dashboard", () => {
const dialog = frappe.dashboard_utils.get_add_to_dashboard_dialog(
frm.doc.name,
- 'Number Card',
- 'frappe.desk.doctype.number_card.number_card.add_card_to_dashboard'
+ "Number Card",
+ "frappe.desk.doctype.number_card.number_card.add_card_to_dashboard"
);
if (!frm.doc.name) {
- frappe.msgprint(__('Please create Card first'));
+ frappe.msgprint(__("Please create Card first"));
} else {
dialog.show();
}
});
},
- before_save: function(frm) {
- let dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || 'null');
- let static_filters = JSON.parse(frm.doc.filters_json || 'null');
- static_filters =
- frappe.dashboard_utils.remove_common_static_filter_values(static_filters, dynamic_filters);
+ before_save: function (frm) {
+ let dynamic_filters = JSON.parse(frm.doc.dynamic_filters_json || "null");
+ let static_filters = JSON.parse(frm.doc.filters_json || "null");
+ static_filters = frappe.dashboard_utils.remove_common_static_filter_values(
+ static_filters,
+ dynamic_filters
+ );
- frm.set_value('filters_json', JSON.stringify(static_filters));
- frm.trigger('render_filters_table');
- frm.trigger('render_dynamic_filters_table');
+ frm.set_value("filters_json", JSON.stringify(static_filters));
+ frm.trigger("render_filters_table");
+ frm.trigger("render_dynamic_filters_table");
},
- is_standard: function(frm) {
- frm.trigger('render_dynamic_filters_table');
+ is_standard: function (frm) {
+ frm.trigger("render_dynamic_filters_table");
frm.set_df_property("dynamic_filters_section", "hidden", 1);
},
- set_filters_description: function(frm) {
- if (frm.doc.type == 'Custom') {
+ set_filters_description: function (frm) {
+ if (frm.doc.type == "Custom") {
frm.fields_dict.filters_config.set_description(`
Set the filters here. For example:
@@ -91,8 +93,8 @@ frappe.ui.form.on('Number Card', {
}
},
- set_method_description: function(frm) {
- if (frm.doc.type == 'Custom') {
+ set_method_description: function (frm) {
+ if (frm.doc.type == "Custom") {
frm.fields_dict.method.set_description(`
Set the path to a whitelisted function that will return the number on the card in the format:
@@ -105,53 +107,52 @@ frappe.ui.form.on('Number Card', {
}
},
- type: function(frm) {
- frm.trigger('set_filters_description');
- if (frm.doc.type == 'Report') {
- frm.set_query('report_name', () => {
+ type: function (frm) {
+ frm.trigger("set_filters_description");
+ if (frm.doc.type == "Report") {
+ frm.set_query("report_name", () => {
return {
filters: {
- 'report_type': ['!=', 'Report Builder']
- }
+ report_type: ["!=", "Report Builder"],
+ },
};
});
}
-
},
- report_name: function(frm) {
+ report_name: function (frm) {
frm.filters = [];
- frm.set_value('filters_json', '{}');
- frm.set_value('dynamic_filters_json', '{}');
- frm.set_df_property('report_field', 'options', []);
- frm.trigger('set_report_filters');
+ frm.set_value("filters_json", "{}");
+ frm.set_value("dynamic_filters_json", "{}");
+ frm.set_df_property("report_field", "options", []);
+ frm.trigger("set_report_filters");
},
- filters_config: function(frm) {
+ filters_config: function (frm) {
frm.filters = eval(frm.doc.filters_config);
const filter_values = frappe.report_utils.get_filter_values(frm.filters);
- frm.set_value('filters_json', JSON.stringify(filter_values));
- frm.trigger('render_filters_table');
+ frm.set_value("filters_json", JSON.stringify(filter_values));
+ frm.trigger("render_filters_table");
},
- document_type: function(frm) {
- frm.set_query('document_type', function() {
+ document_type: function (frm) {
+ frm.set_query("document_type", function () {
return {
filters: {
- 'issingle': false
- }
+ issingle: false,
+ },
};
});
- frm.set_value('filters_json', '[]');
- frm.set_value('dynamic_filters_json', '[]');
- frm.set_value('aggregate_function_based_on', '');
- frm.set_value('parent_document_type', '');
- frm.trigger('set_options');
- frm.trigger('set_parent_document_type');
+ frm.set_value("filters_json", "[]");
+ frm.set_value("dynamic_filters_json", "[]");
+ frm.set_value("aggregate_function_based_on", "");
+ frm.set_value("parent_document_type", "");
+ frm.trigger("set_options");
+ frm.trigger("set_parent_document_type");
},
- set_options: function(frm) {
- if (frm.doc.type !== 'Document Type') {
+ set_options: function (frm) {
+ if (frm.doc.type !== "Document Type") {
return;
}
@@ -160,134 +161,148 @@ frappe.ui.form.on('Number Card', {
if (doctype) {
frappe.model.with_doctype(doctype, () => {
- frappe.get_meta(doctype).fields.map(df => {
+ frappe.get_meta(doctype).fields.map((df) => {
if (frappe.model.numeric_fieldtypes.includes(df.fieldtype)) {
- if (df.fieldtype == 'Currency') {
- if (!df.options || df.options !== 'Company:company:default_currency') {
+ if (df.fieldtype == "Currency") {
+ if (!df.options || df.options !== "Company:company:default_currency") {
return;
}
}
- aggregate_based_on_fields.push({label: df.label, value: df.fieldname});
+ aggregate_based_on_fields.push({ label: df.label, value: df.fieldname });
}
});
- frm.set_df_property('aggregate_function_based_on', 'options', aggregate_based_on_fields);
+ frm.set_df_property(
+ "aggregate_function_based_on",
+ "options",
+ aggregate_based_on_fields
+ );
});
- frm.trigger('render_filters_table');
- frm.trigger('render_dynamic_filters_table');
+ frm.trigger("render_filters_table");
+ frm.trigger("render_dynamic_filters_table");
}
},
- set_report_filters: function(frm) {
+ set_report_filters: function (frm) {
const report_name = frm.doc.report_name;
if (report_name) {
- frappe.report_utils.get_report_filters(report_name).then(filters => {
+ frappe.report_utils.get_report_filters(report_name).then((filters) => {
if (filters) {
frm.filters = filters;
const filter_values = frappe.report_utils.get_filter_values(filters);
if (frm.doc.filters_json.length <= 2) {
- frm.set_value('filters_json', JSON.stringify(filter_values));
+ frm.set_value("filters_json", JSON.stringify(filter_values));
}
}
- frm.trigger('render_filters_table');
- frm.trigger('set_report_field_options');
- frm.trigger('render_dynamic_filters_table');
+ frm.trigger("render_filters_table");
+ frm.trigger("set_report_field_options");
+ frm.trigger("render_dynamic_filters_table");
});
}
},
- set_report_field_options: function(frm) {
+ set_report_field_options: function (frm) {
let filters = frm.doc.filters_json.length > 2 ? JSON.parse(frm.doc.filters_json) : null;
if (frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2) {
filters = frappe.dashboard_utils.get_all_filters(frm.doc);
}
- frappe.xcall(
- 'frappe.desk.query_report.run',
- {
+ frappe
+ .xcall("frappe.desk.query_report.run", {
report_name: frm.doc.report_name,
filters: filters,
- ignore_prepared_report: 1
- }
- ).then(data => {
- if (data.result.length) {
- frm.field_options = frappe.report_utils.get_field_options_from_report(data.columns, data);
- frm.set_df_property('report_field', 'options', frm.field_options.numeric_fields);
- if (!frm.field_options.numeric_fields.length) {
- frappe.msgprint(__("Report has no numeric fields, please change the Report Name"));
+ ignore_prepared_report: 1,
+ })
+ .then((data) => {
+ if (data.result.length) {
+ frm.field_options = frappe.report_utils.get_field_options_from_report(
+ data.columns,
+ data
+ );
+ frm.set_df_property(
+ "report_field",
+ "options",
+ frm.field_options.numeric_fields
+ );
+ if (!frm.field_options.numeric_fields.length) {
+ frappe.msgprint(
+ __("Report has no numeric fields, please change the Report Name")
+ );
+ }
+ } else {
+ frappe.msgprint(
+ __(
+ "Report has no data, please modify the filters or change the Report Name"
+ )
+ );
}
- } else {
- frappe.msgprint(__('Report has no data, please modify the filters or change the Report Name'));
- }
- });
+ });
},
- render_filters_table: function(frm) {
+ render_filters_table: function (frm) {
frm.set_df_property("filters_section", "hidden", 0);
- let is_document_type = frm.doc.type == 'Document Type';
- let is_dynamic_filter = f => ['Date', 'DateRange'].includes(f.fieldtype) && f.default;
+ let is_document_type = frm.doc.type == "Document Type";
+ let is_dynamic_filter = (f) => ["Date", "DateRange"].includes(f.fieldtype) && f.default;
- let wrapper = $(frm.get_field('filters_json').wrapper).empty();
+ let wrapper = $(frm.get_field("filters_json").wrapper).empty();
let table = $(`
- ${__('Filter')}
- ${__('Condition')}
- ${__('Value')}
+ ${__("Filter")}
+ ${__("Condition")}
+ ${__("Value")}
`).appendTo(wrapper);
$(`
${__("Click table to edit")}
`).appendTo(wrapper);
- let filters = JSON.parse(frm.doc.filters_json || '[]');
+ let filters = JSON.parse(frm.doc.filters_json || "[]");
let filters_set = false;
// Set dynamic filters for reports
- if (frm.doc.type == 'Report') {
+ if (frm.doc.type == "Report") {
let set_filters = false;
- frm.filters.forEach(f => {
+ frm.filters.forEach((f) => {
if (is_dynamic_filter(f)) {
filters[f.fieldname] = f.default;
set_filters = true;
}
});
- set_filters && frm.set_value('filters_json', JSON.stringify(filters));
+ set_filters && frm.set_value("filters_json", JSON.stringify(filters));
}
let fields = [];
if (is_document_type) {
fields = [
{
- fieldtype: 'HTML',
- fieldname: 'filter_area',
- }
+ fieldtype: "HTML",
+ fieldname: "filter_area",
+ },
];
if (filters.length) {
- filters.forEach(filter => {
- const filter_row =
- $(`
+ filters.forEach((filter) => {
+ const filter_row = $(`
${filter[1]}
${filter[2] || ""}
${filter[3]}
`);
- table.find('tbody').append(filter_row);
+ table.find("tbody").append(filter_row);
});
filters_set = true;
}
} else if (frm.filters.length) {
- fields = frm.filters.filter(f => f.fieldname);
- fields.map(f => {
+ fields = frm.filters.filter((f) => f.fieldname);
+ fields.map((f) => {
if (filters[f.fieldname]) {
- let condition = '=';
- const filter_row =
- $(`
+ let condition = "=";
+ const filter_row = $(`
${f.label}
${condition}
${filters[f.fieldname] || ""}
`);
- table.find('tbody').append(filter_row);
+ table.find("tbody").append(filter_row);
if (!filters_set) filters_set = true;
}
});
@@ -296,32 +311,32 @@ frappe.ui.form.on('Number Card', {
if (!filters_set) {
const filter_row = $(`
${__("Click to Set Filters")} `);
- table.find('tbody').append(filter_row);
+ table.find("tbody").append(filter_row);
}
- table.on('click', () => {
+ table.on("click", () => {
let dialog = new frappe.ui.Dialog({
- title: __('Set Filters'),
- fields: fields.filter(f => !is_dynamic_filter(f)),
- primary_action: function() {
+ title: __("Set Filters"),
+ fields: fields.filter((f) => !is_dynamic_filter(f)),
+ primary_action: function () {
let values = this.get_values();
if (values) {
this.hide();
if (is_document_type) {
let filters = frm.filter_group.get_filters();
- frm.set_value('filters_json', JSON.stringify(filters));
+ frm.set_value("filters_json", JSON.stringify(filters));
} else {
- frm.set_value('filters_json', JSON.stringify(values));
+ frm.set_value("filters_json", JSON.stringify(values));
}
- frm.trigger('render_filters_table');
+ frm.trigger("render_filters_table");
}
},
- primary_action_label: "Set"
+ primary_action_label: "Set",
});
if (is_document_type) {
frm.filter_group = new frappe.ui.FilterGroup({
- parent: dialog.get_field('filter_area').$wrapper,
+ parent: dialog.get_field("filter_area").$wrapper,
doctype: frm.doc.document_type,
parent_doctype: frm.doc.parent_document_type,
on_change: () => {},
@@ -331,56 +346,61 @@ frappe.ui.form.on('Number Card', {
dialog.show();
- if (frm.doc.type == 'Report') {
+ if (frm.doc.type == "Report") {
//Set query report object so that it can be used while fetching filter values in the report
- frappe.query_report = new frappe.views.QueryReport({'filters': dialog.fields_list});
- frappe.query_reports[frm.doc.report_name]
- && frappe.query_reports[frm.doc.report_name].onload
- && frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
+ frappe.query_report = new frappe.views.QueryReport({
+ filters: dialog.fields_list,
+ });
+ frappe.query_reports[frm.doc.report_name] &&
+ frappe.query_reports[frm.doc.report_name].onload &&
+ frappe.query_reports[frm.doc.report_name].onload(frappe.query_report);
}
dialog.set_values(filters);
});
-
},
render_dynamic_filters_table(frm) {
- if (!frappe.boot.developer_mode || !frm.doc.is_standard || frm.doc.type == 'Custom') {
+ if (!frappe.boot.developer_mode || !frm.doc.is_standard || frm.doc.type == "Custom") {
return;
}
frm.set_df_property("dynamic_filters_section", "hidden", 0);
- let is_document_type = frm.doc.type == 'Document Type';
+ let is_document_type = frm.doc.type == "Document Type";
- let wrapper = $(frm.get_field('dynamic_filters_json').wrapper).empty();
+ let wrapper = $(frm.get_field("dynamic_filters_json").wrapper).empty();
- frm.dynamic_filter_table = $(`
+ frm.dynamic_filter_table =
+ $(`
- ${__('Filter')}
- ${__('Condition')}
- ${__('Value')}
+ ${__("Filter")}
+ ${__("Condition")}
+ ${__("Value")}
`).appendTo(wrapper);
- frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
- ? JSON.parse(frm.doc.dynamic_filters_json)
- : null;
+ frm.dynamic_filters =
+ frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
+ ? JSON.parse(frm.doc.dynamic_filters_json)
+ : null;
- frm.trigger('set_dynamic_filters_in_table');
+ frm.trigger("set_dynamic_filters_in_table");
- let filters = JSON.parse(frm.doc.filters_json || '[]');
+ let filters = JSON.parse(frm.doc.filters_json || "[]");
let fields = frappe.dashboard_utils.get_fields_for_dynamic_filter_dialog(
- is_document_type, filters, frm.dynamic_filters
+ is_document_type,
+ filters,
+ frm.dynamic_filters
);
- frm.dynamic_filter_table.on('click', () => {
+ frm.dynamic_filter_table.on("click", () => {
let dialog = new frappe.ui.Dialog({
- title: __('Set Dynamic Filters'),
+ title: __("Set Dynamic Filters"),
fields: fields,
primary_action: () => {
let values = dialog.get_values();
@@ -388,19 +408,19 @@ frappe.ui.form.on('Number Card', {
let dynamic_filters = [];
for (let key of Object.keys(values)) {
if (is_document_type) {
- let [doctype, fieldname] = key.split(':');
- dynamic_filters.push([doctype, fieldname, '=', values[key]]);
+ let [doctype, fieldname] = key.split(":");
+ dynamic_filters.push([doctype, fieldname, "=", values[key]]);
}
}
if (is_document_type) {
- frm.set_value('dynamic_filters_json', JSON.stringify(dynamic_filters));
+ frm.set_value("dynamic_filters_json", JSON.stringify(dynamic_filters));
} else {
- frm.set_value('dynamic_filters_json', JSON.stringify(values));
+ frm.set_value("dynamic_filters_json", JSON.stringify(values));
}
- frm.trigger('set_dynamic_filters_in_table');
+ frm.trigger("set_dynamic_filters_in_table");
},
- primary_action_label: "Set"
+ primary_action_label: "Set",
});
dialog.show();
@@ -408,71 +428,70 @@ frappe.ui.form.on('Number Card', {
});
},
- set_dynamic_filters_in_table: function(frm) {
- frm.dynamic_filters = frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
- ? JSON.parse(frm.doc.dynamic_filters_json)
- : null;
+ set_dynamic_filters_in_table: function (frm) {
+ frm.dynamic_filters =
+ frm.doc.dynamic_filters_json && frm.doc.dynamic_filters_json.length > 2
+ ? JSON.parse(frm.doc.dynamic_filters_json)
+ : null;
if (!frm.dynamic_filters) {
const filter_row = $(`
${__("Click to Set Dynamic Filters")} `);
- frm.dynamic_filter_table.find('tbody').html(filter_row);
+ frm.dynamic_filter_table.find("tbody").html(filter_row);
} else {
- let filter_rows = '';
+ let filter_rows = "";
if ($.isArray(frm.dynamic_filters)) {
- frm.dynamic_filters.forEach(filter => {
- filter_rows +=
- `
+ frm.dynamic_filters.forEach((filter) => {
+ filter_rows += `
${filter[1]}
${filter[2] || ""}
${filter[3]}
`;
});
} else {
- let condition = '=';
+ let condition = "=";
for (let [key, val] of Object.entries(frm.dynamic_filters)) {
- filter_rows +=
- `
+ filter_rows += `
${key}
${condition}
${val || ""}
- `
- ;
+ `;
}
}
- frm.dynamic_filter_table.find('tbody').html(filter_rows);
+ frm.dynamic_filter_table.find("tbody").html(filter_rows);
}
},
- set_parent_document_type: async function(frm) {
+ set_parent_document_type: async function (frm) {
let document_type = frm.doc.document_type;
- let doc_is_table = document_type &&
- (await frappe.db.get_value('DocType', document_type, 'istable')).message.istable;
+ let doc_is_table =
+ document_type &&
+ (await frappe.db.get_value("DocType", document_type, "istable")).message.istable;
- frm.set_df_property('parent_document_type', 'hidden', !doc_is_table);
+ frm.set_df_property("parent_document_type", "hidden", !doc_is_table);
if (document_type && doc_is_table) {
- let parent = await frappe.db.get_list('DocField', {
+ let parent = await frappe.db.get_list("DocField", {
filters: {
- 'fieldtype': 'Table',
- 'options': document_type
+ fieldtype: "Table",
+ options: document_type,
},
- fields: ['parent']
+ fields: ["parent"],
});
- parent && frm.set_query('parent_document_type', function() {
- return {
- filters: {
- "name": ['in', parent.map(({ parent }) => parent)]
- }
- };
- });
+ parent &&
+ frm.set_query("parent_document_type", function () {
+ return {
+ filters: {
+ name: ["in", parent.map(({ parent }) => parent)],
+ },
+ };
+ });
if (parent.length === 1) {
- frm.set_value('parent_document_type', parent[0].parent);
+ frm.set_value("parent_document_type", parent[0].parent);
}
}
- }
-
+ },
});
diff --git a/frappe/desk/doctype/onboarding_permission/onboarding_permission.js b/frappe/desk/doctype/onboarding_permission/onboarding_permission.js
index 752b8a02cc..ec2c8a03b0 100644
--- a/frappe/desk/doctype/onboarding_permission/onboarding_permission.js
+++ b/frappe/desk/doctype/onboarding_permission/onboarding_permission.js
@@ -1,8 +1,7 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Onboarding Permission', {
+frappe.ui.form.on("Onboarding Permission", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.js b/frappe/desk/doctype/onboarding_step/onboarding_step.js
index 3c9bbab9ac..67b2ed0501 100644
--- a/frappe/desk/doctype/onboarding_step/onboarding_step.js
+++ b/frappe/desk/doctype/onboarding_step/onboarding_step.js
@@ -2,18 +2,17 @@
// For license information, please see license.txt
frappe.ui.form.on("Onboarding Step", {
-
- setup: function(frm) {
- frm.set_query("form_tour", function() {
+ setup: function (frm) {
+ frm.set_query("form_tour", function () {
return {
filters: {
- reference_doctype: frm.doc.reference_document
- }
+ reference_doctype: frm.doc.reference_document,
+ },
};
});
},
- refresh: function(frm) {
+ refresh: function (frm) {
frappe.boot.developer_mode &&
frm.set_intro(
__(
@@ -30,15 +29,16 @@ frappe.ui.form.on("Onboarding Step", {
}
},
- reference_document: function(frm) {
+ reference_document: function (frm) {
if (frm.doc.reference_document && frm.doc.action == "Update Settings") {
setup_fields(frm);
}
},
- action: function(frm) {
+ action: function (frm) {
if (frm.doc.action == "Show Form Tour") {
- frm.fields_dict.reference_document.set_description(`You need to add the steps in the contoller JS file. For example: note.js
+ frm.fields_dict.reference_document
+ .set_description(`You need to add the steps in the contoller JS file. For example: note.js
frappe.tour['Note'] = [
{
@@ -54,7 +54,7 @@ frappe.tour['Note'] = [
}
},
- disable_form: function(frm) {
+ disable_form: function (frm) {
frm.set_read_only();
frm.fields
.filter((field) => field.has_input)
@@ -71,9 +71,7 @@ function setup_fields(frm) {
let fields = frappe
.get_meta(frm.doc.reference_document)
.fields.filter((df) => {
- return ["Data", "Check", "Int", "Link", "Select"].includes(
- df.fieldtype
- );
+ return ["Data", "Check", "Int", "Link", "Select"].includes(df.fieldtype);
})
.map((df) => {
return {
diff --git a/frappe/desk/doctype/route_history/route_history.js b/frappe/desk/doctype/route_history/route_history.js
index 19689e406b..c68d4e2b54 100644
--- a/frappe/desk/doctype/route_history/route_history.js
+++ b/frappe/desk/doctype/route_history/route_history.js
@@ -1,8 +1,6 @@
// Copyright (c) 2018, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Route History', {
- refresh: function() {
-
- }
+frappe.ui.form.on("Route History", {
+ refresh: function () {},
});
diff --git a/frappe/desk/doctype/route_history/route_history_list.js b/frappe/desk/doctype/route_history/route_history_list.js
index 84a441852c..03bf86b9fd 100644
--- a/frappe/desk/doctype/route_history/route_history_list.js
+++ b/frappe/desk/doctype/route_history/route_history_list.js
@@ -1,7 +1,7 @@
frappe.listview_settings["Route History"] = {
- onload: function(listview) {
+ onload: function (listview) {
frappe.require("logtypes.bundle.js", () => {
frappe.utils.logtypes.show_log_retention_message(cur_list.doctype);
- })
+ });
},
};
diff --git a/frappe/desk/doctype/system_console/system_console.js b/frappe/desk/doctype/system_console/system_console.js
index 7751ffe860..dc73f33b67 100644
--- a/frappe/desk/doctype/system_console/system_console.js
+++ b/frappe/desk/doctype/system_console/system_console.js
@@ -1,21 +1,21 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('System Console', {
- onload: function(frm) {
+frappe.ui.form.on("System Console", {
+ onload: function (frm) {
frappe.ui.keys.add_shortcut({
- shortcut: 'shift+enter',
- action: () => frm.page.btn_primary.trigger('click'),
+ shortcut: "shift+enter",
+ action: () => frm.page.btn_primary.trigger("click"),
page: frm.page,
- description: __('Execute Console script'),
+ description: __("Execute Console script"),
ignore_inputs: true,
});
frm.set_value("type", "Python");
},
- refresh: function(frm) {
+ refresh: function (frm) {
frm.disable_save();
- frm.page.set_primary_action(__("Execute"), $btn => {
+ frm.page.set_primary_action(__("Execute"), ($btn) => {
$btn.text(__("Executing..."));
return frm
.execute_action("Execute")
@@ -24,7 +24,7 @@ frappe.ui.form.on('System Console', {
});
},
- type: function(frm) {
+ type: function (frm) {
if (frm.doc.type == "Python") {
frm.set_value("output", "");
if (frm.sql_output) {
@@ -34,7 +34,7 @@ frappe.ui.form.on('System Console', {
}
},
- render_sql_output: function(frm) {
+ render_sql_output: function (frm) {
if (frm.doc.type !== "SQL") return;
if (frm.sql_output) {
frm.sql_output.destroy();
@@ -46,50 +46,51 @@ frappe.ui.form.on('System Console', {
}
let result = JSON.parse(frm.doc.output);
- frm.set_value("output", `${result.length} ${result.length == 1 ? 'row' : 'rows'}`);
+ frm.set_value("output", `${result.length} ${result.length == 1 ? "row" : "rows"}`);
if (result.length) {
let columns = Object.keys(result[0]);
- frm.sql_output = new DataTable(
- frm.get_field("sql_output").$wrapper.get(0),
- {
- columns,
- data: result
- }
- );
+ frm.sql_output = new DataTable(frm.get_field("sql_output").$wrapper.get(0), {
+ columns,
+ data: result,
+ });
}
},
- show_processlist: function(frm) {
+ show_processlist: function (frm) {
if (frm.doc.show_processlist) {
// keep refreshing every 5 seconds
frm.events.refresh_processlist(frm);
- frm.processlist_interval = setInterval(() => frm.events.refresh_processlist(frm), 5000);
+ frm.processlist_interval = setInterval(
+ () => frm.events.refresh_processlist(frm),
+ 5000
+ );
} else {
if (frm.processlist_interval) {
-
// end it
clearInterval(frm.processlist_interval);
- frm.get_field("processlist").html('');
+ frm.get_field("processlist").html("");
}
}
},
- refresh_processlist: function(frm) {
+ refresh_processlist: function (frm) {
let timestamp = new Date();
- frappe.call('frappe.desk.doctype.system_console.system_console.show_processlist').then(r => {
- let rows = '';
- for (let row of r.message) {
- rows += `
+ frappe
+ .call("frappe.desk.doctype.system_console.system_console.show_processlist")
+ .then((r) => {
+ let rows = "";
+ for (let row of r.message) {
+ rows += `
${row.Id}
${row.Time}
${row.State}
${row.Info}
${row.Progress}
- `
- }
+ `;
+ }
- frm.get_field('processlist').html(`
+ frm.get_field("processlist").html(`
Requested on: ${timestamp}
@@ -100,6 +101,6 @@ frappe.ui.form.on('System Console', {
Progress / Wait Event
${rows}`);
- });
+ });
},
});
diff --git a/frappe/desk/doctype/tag/tag.js b/frappe/desk/doctype/tag/tag.js
index f55f98c3d0..1c60f417e0 100644
--- a/frappe/desk/doctype/tag/tag.js
+++ b/frappe/desk/doctype/tag/tag.js
@@ -1,8 +1,7 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Tag', {
+frappe.ui.form.on("Tag", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/desk/doctype/tag_link/tag_link.js b/frappe/desk/doctype/tag_link/tag_link.js
index d85655bb90..e2cb4fcd7f 100644
--- a/frappe/desk/doctype/tag_link/tag_link.js
+++ b/frappe/desk/doctype/tag_link/tag_link.js
@@ -1,8 +1,7 @@
// Copyright (c) 2019, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Tag Link', {
+frappe.ui.form.on("Tag Link", {
// refresh: function(frm) {
-
// }
});
diff --git a/frappe/desk/doctype/todo/todo.js b/frappe/desk/doctype/todo/todo.js
index 0317281371..6c1af67d2b 100644
--- a/frappe/desk/doctype/todo/todo.js
+++ b/frappe/desk/doctype/todo/todo.js
@@ -1,40 +1,55 @@
// bind events
frappe.ui.form.on("ToDo", {
- onload: function(frm) {
- frm.set_query("reference_type", function(txt) {
+ onload: function (frm) {
+ frm.set_query("reference_type", function (txt) {
return {
- "filters": {
- "issingle": 0,
- }
+ filters: {
+ issingle: 0,
+ },
};
});
},
- refresh: function(frm) {
- if(frm.doc.reference_type && frm.doc.reference_name) {
- frm.add_custom_button(__(frm.doc.reference_name), function() {
+ refresh: function (frm) {
+ if (frm.doc.reference_type && frm.doc.reference_name) {
+ frm.add_custom_button(__(frm.doc.reference_name), function () {
frappe.set_route("Form", frm.doc.reference_type, frm.doc.reference_name);
});
}
if (!frm.doc.__islocal) {
- if(frm.doc.status!=="Closed") {
- frm.add_custom_button(__("Close"), function() {
- frm.set_value("status", "Closed");
- frm.save(null, function() {
- // back to list
- frappe.set_route("List", "ToDo");
- });
- }, "fa fa-check", "btn-success");
+ if (frm.doc.status !== "Closed") {
+ frm.add_custom_button(
+ __("Close"),
+ function () {
+ frm.set_value("status", "Closed");
+ frm.save(null, function () {
+ // back to list
+ frappe.set_route("List", "ToDo");
+ });
+ },
+ "fa fa-check",
+ "btn-success"
+ );
} else {
- frm.add_custom_button(__("Reopen"), function() {
- frm.set_value("status", "Open");
- frm.save();
- }, null, "btn-default");
+ frm.add_custom_button(
+ __("Reopen"),
+ function () {
+ frm.set_value("status", "Open");
+ frm.save();
+ },
+ null,
+ "btn-default"
+ );
}
- frm.add_custom_button(__("New"), function() {
- frappe.new_doc("ToDo")
- }, null, "btn-default");
+ frm.add_custom_button(
+ __("New"),
+ function () {
+ frappe.new_doc("ToDo");
+ },
+ null,
+ "btn-default"
+ );
}
- }
+ },
});
diff --git a/frappe/desk/doctype/todo/todo_calendar.js b/frappe/desk/doctype/todo/todo_calendar.js
index 8ba020fac1..f79243a86e 100644
--- a/frappe/desk/doctype/todo/todo_calendar.js
+++ b/frappe/desk/doctype/todo/todo_calendar.js
@@ -3,29 +3,27 @@
frappe.views.calendar["ToDo"] = {
field_map: {
- "start": "date",
- "end": "date",
- "id": "name",
- "title": "description",
- "allDay": "allDay",
- "progress": "progress"
+ start: "date",
+ end: "date",
+ id: "name",
+ title: "description",
+ allDay: "allDay",
+ progress: "progress",
},
gantt: true,
filters: [
{
- "fieldtype": "Link",
- "fieldname": "reference_type",
- "options": "Task",
- "label": __("Task")
+ fieldtype: "Link",
+ fieldname: "reference_type",
+ options: "Task",
+ label: __("Task"),
},
{
- "fieldtype": "Dynamic Link",
- "fieldname": "reference_name",
- "options": "reference_type",
- "label": __("Task")
- }
-
+ fieldtype: "Dynamic Link",
+ fieldname: "reference_name",
+ options: "reference_type",
+ label: __("Task"),
+ },
],
- get_events_method: "frappe.desk.calendar.get_events"
+ get_events_method: "frappe.desk.calendar.get_events",
};
-
diff --git a/frappe/desk/doctype/todo/todo_list.js b/frappe/desk/doctype/todo/todo_list.js
index 53564cc017..2e4534e05c 100644
--- a/frappe/desk/doctype/todo/todo_list.js
+++ b/frappe/desk/doctype/todo/todo_list.js
@@ -1,40 +1,44 @@
-frappe.listview_settings['ToDo'] = {
+frappe.listview_settings["ToDo"] = {
hide_name_column: true,
add_fields: ["reference_type", "reference_name"],
- onload: function(me) {
+ onload: function (me) {
if (!frappe.route_options) {
frappe.route_options = {
- "owner": frappe.session.user,
- "status": "Open"
+ owner: frappe.session.user,
+ status: "Open",
};
}
me.page.set_title(__("To Do"));
},
button: {
- show: function(doc) {
+ show: function (doc) {
return doc.reference_name;
},
- get_label: function() {
- return __('Open');
+ get_label: function () {
+ return __("Open");
},
- get_description: function(doc) {
- return __('Open {0}', [`${doc.reference_type} ${doc.reference_name}`])
+ get_description: function (doc) {
+ return __("Open {0}", [`${doc.reference_type} ${doc.reference_name}`]);
+ },
+ action: function (doc) {
+ frappe.set_route("Form", doc.reference_type, doc.reference_name);
},
- action: function(doc) {
- frappe.set_route('Form', doc.reference_type, doc.reference_name);
- }
},
- refresh: function(me) {
+ refresh: function (me) {
if (me.todo_sidebar_setup) return;
// add assigned by me
- me.page.add_sidebar_item(__("Assigned By Me"), function() {
- me.filter_area.add([[me.doctype, "assigned_by", '=', frappe.session.user]]);
- }, ('.list-link[data-view="Kanban"]'));
+ me.page.add_sidebar_item(
+ __("Assigned By Me"),
+ function () {
+ me.filter_area.add([[me.doctype, "assigned_by", "=", frappe.session.user]]);
+ },
+ '.list-link[data-view="Kanban"]'
+ );
me.todo_sidebar_setup = true;
},
-}
\ No newline at end of file
+};
diff --git a/frappe/desk/doctype/workspace/workspace.js b/frappe/desk/doctype/workspace/workspace.js
index 3f912127fc..25721f9ae2 100644
--- a/frappe/desk/doctype/workspace/workspace.js
+++ b/frappe/desk/doctype/workspace/workspace.js
@@ -1,26 +1,30 @@
// Copyright (c) 2020, Frappe Technologies and contributors
// For license information, please see license.txt
-frappe.ui.form.on('Workspace', {
- setup: function() {
- frappe.meta.get_field('Workspace Link', 'only_for').no_default = true;
+frappe.ui.form.on("Workspace", {
+ setup: function () {
+ frappe.meta.get_field("Workspace Link", "only_for").no_default = true;
},
- refresh: function(frm) {
+ refresh: function (frm) {
frm.enable_save();
- if (frm.doc.for_user || (frm.doc.public && !frm.has_perm('write') &&
- !frappe.user.has_role('Workspace Manager'))) {
- frm.trigger('disable_form');
+ if (
+ frm.doc.for_user ||
+ (frm.doc.public &&
+ !frm.has_perm("write") &&
+ !frappe.user.has_role("Workspace Manager"))
+ ) {
+ frm.trigger("disable_form");
}
},
- disable_form: function(frm) {
+ disable_form: function (frm) {
frm.fields
- .filter(field => field.has_input)
- .forEach(field => {
+ .filter((field) => field.has_input)
+ .forEach((field) => {
frm.set_df_property(field.df.fieldname, "read_only", "1");
});
frm.disable_save();
- }
-});
\ No newline at end of file
+ },
+});
diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js
index 7b4e8ddc1a..0291edb225 100644
--- a/frappe/desk/page/activity/activity.js
+++ b/frappe/desk/page/activity/activity.js
@@ -3,12 +3,12 @@
frappe.provide("frappe.activity");
-frappe.pages['activity'].on_page_load = function (wrapper) {
+frappe.pages["activity"].on_page_load = function (wrapper) {
var me = this;
frappe.ui.make_app_page({
parent: wrapper,
- single_column: true
+ single_column: true,
});
me.page = wrapper.page;
@@ -16,8 +16,8 @@ frappe.pages['activity'].on_page_load = function (wrapper) {
frappe.model.with_doctype("Communication", function () {
me.page.list = new frappe.views.Activity({
- doctype: 'Communication',
- parent: wrapper
+ doctype: "Communication",
+ parent: wrapper,
});
});
@@ -29,17 +29,21 @@ frappe.pages['activity'].on_page_load = function (wrapper) {
doctype = $(this).attr("data-doctype"),
docname = $(this).attr("data-docname");
- [link_doctype, link_name, doctype, docname] =
- [link_doctype, link_name, doctype, docname].map(decodeURIComponent);
+ [link_doctype, link_name, doctype, docname] = [
+ link_doctype,
+ link_name,
+ doctype,
+ docname,
+ ].map(decodeURIComponent);
- link_doctype = link_doctype && link_doctype !== 'null' ? link_doctype : null;
- link_name = link_name && link_name !== 'null' ? link_name : null;
+ link_doctype = link_doctype && link_doctype !== "null" ? link_doctype : null;
+ link_name = link_name && link_name !== "null" ? link_name : null;
if (doctype && docname) {
if (link_doctype && link_name) {
frappe.route_options = {
- scroll_to: { "doctype": doctype, "name": docname }
- }
+ scroll_to: { doctype: doctype, name: docname },
+ };
}
frappe.set_route(["Form", link_doctype || doctype, link_name || docname]);
@@ -48,37 +52,46 @@ frappe.pages['activity'].on_page_load = function (wrapper) {
// Build Report Button
if (frappe.boot.user.can_get_report.indexOf("Feed") != -1) {
- this.page.add_menu_item(__('Build Report'), function () {
- frappe.set_route("List", "Feed", "Report");
- }, 'fa fa-th')
+ this.page.add_menu_item(
+ __("Build Report"),
+ function () {
+ frappe.set_route("List", "Feed", "Report");
+ },
+ "fa fa-th"
+ );
}
- this.page.add_menu_item(__('Activity Log'), function () {
- frappe.route_options = {
- "user": frappe.session.user
- }
+ this.page.add_menu_item(
+ __("Activity Log"),
+ function () {
+ frappe.route_options = {
+ user: frappe.session.user,
+ };
- frappe.set_route("List", "Activity Log", "Report");
- }, 'fa fa-th');
+ frappe.set_route("List", "Activity Log", "Report");
+ },
+ "fa fa-th"
+ );
};
-frappe.pages['activity'].on_page_show = function () {
+frappe.pages["activity"].on_page_show = function () {
frappe.breadcrumbs.add("Desk");
-}
+};
frappe.activity.last_feed_date = false;
frappe.activity.Feed = class Feed {
constructor(row, data) {
this.scrub_data(data);
this.add_date_separator(row, data);
- if (!data.add_class)
- data.add_class = "label-default";
+ if (!data.add_class) data.add_class = "label-default";
data.link = "";
if (data.link_doctype && data.link_name) {
- data.link = frappe.format(data.link_name, { fieldtype: "Link", options: data.link_doctype },
- { label: __(data.link_doctype) + " " + __(data.link_name) });
-
+ data.link = frappe.format(
+ data.link_name,
+ { fieldtype: "Link", options: data.link_doctype },
+ { label: __(data.link_doctype) + " " + __(data.link_name) }
+ );
} else if (data.feed_type === "Comment" && data.comment_type === "Comment") {
// hack for backward compatiblity
data.link_doctype = data.reference_doctype;
@@ -86,17 +99,20 @@ frappe.activity.Feed = class Feed {
data.reference_doctype = "Communication";
data.reference_name = data.name;
- data.link = frappe.format(data.link_name, { fieldtype: "Link", options: data.link_doctype },
- { label: __(data.link_doctype) + " " + __(data.link_name) });
-
+ data.link = frappe.format(
+ data.link_name,
+ { fieldtype: "Link", options: data.link_doctype },
+ { label: __(data.link_doctype) + " " + __(data.link_name) }
+ );
} else if (data.reference_doctype && data.reference_name) {
- data.link = frappe.format(data.reference_name, { fieldtype: "Link", options: data.reference_doctype },
- { label: __(data.reference_doctype) + " " + __(data.reference_name) });
+ data.link = frappe.format(
+ data.reference_name,
+ { fieldtype: "Link", options: data.reference_doctype },
+ { label: __(data.reference_doctype) + " " + __(data.reference_name) }
+ );
}
- $(row)
- .append(frappe.render_template("activity_row", data))
- .find("a").addClass("grey");
+ $(row).append(frappe.render_template("activity_row", data)).find("a").addClass("grey");
}
scrub_data(data) {
@@ -106,11 +122,12 @@ frappe.activity.Feed = class Feed {
data.icon = "fa fa-flag";
// color for comment
- data.add_class = {
- "Comment": "label-danger",
- "Assignment": "label-warning",
- "Login": "label-default"
- }[data.comment_type || data.communication_medium] || "label-info"
+ data.add_class =
+ {
+ Comment: "label-danger",
+ Assignment: "label-warning",
+ Login: "label-default",
+ }[data.comment_type || data.communication_medium] || "label-info";
data.when = comment_when(data.creation);
data.feed_type = data.comment_type || data.communication_medium;
@@ -120,18 +137,24 @@ frappe.activity.Feed = class Feed {
var date = frappe.datetime.str_to_obj(data.creation);
var last = frappe.activity.last_feed_date;
- if ((last && frappe.datetime.obj_to_str(last) != frappe.datetime.obj_to_str(date)) || (!last)) {
- var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date));
+ if (
+ (last && frappe.datetime.obj_to_str(last) != frappe.datetime.obj_to_str(date)) ||
+ !last
+ ) {
+ var diff = frappe.datetime.get_day_diff(
+ frappe.datetime.get_today(),
+ frappe.datetime.obj_to_str(date)
+ );
var pdate;
if (diff < 1) {
- pdate = 'Today';
+ pdate = "Today";
} else if (diff < 2) {
- pdate = 'Yesterday';
+ pdate = "Yesterday";
} else {
pdate = frappe.datetime.global_date_format(date);
}
data.date_sep = pdate;
- data.date_class = pdate == 'Today' ? "date-indicator blue" : "date-indicator";
+ data.date_class = pdate == "Today" ? "date-indicator blue" : "date-indicator";
} else {
data.date_sep = null;
data.date_class = "";
@@ -141,26 +164,28 @@ frappe.activity.Feed = class Feed {
};
frappe.activity.render_heatmap = function (page) {
- $('\
+ $(
+ '
\
-
').prependTo(page.main);
+
'
+ ).prependTo(page.main);
frappe.call({
method: "frappe.desk.page.activity.activity.get_heatmap_data",
callback: function (r) {
if (r.message) {
new frappe.Chart(".heatmap", {
- type: 'heatmap',
- start: new Date(moment().subtract(1, 'year').toDate()),
+ type: "heatmap",
+ start: new Date(moment().subtract(1, "year").toDate()),
countLabel: "actions",
discreteDomains: 1,
radius: 3, // default 0
data: {
- 'dataPoints': r.message
- }
+ dataPoints: r.message,
+ },
});
}
- }
+ },
});
};
@@ -173,10 +198,9 @@ frappe.views.Activity = class Activity extends frappe.views.BaseList {
setup_defaults() {
super.setup_defaults();
- this.page_title = __('Activity');
- this.doctype = 'Communication';
- this.method = 'frappe.desk.page.activity.activity.get_feed';
-
+ this.page_title = __("Activity");
+ this.doctype = "Communication";
+ this.method = "frappe.desk.page.activity.activity.get_feed";
}
setup_filter_area() {
@@ -187,18 +211,14 @@ frappe.views.Activity = class Activity extends frappe.views.BaseList {
//
}
- setup_sort_selector() {
+ setup_sort_selector() {}
- }
-
- setup_side_bar() {
-
- }
+ setup_side_bar() {}
get_args() {
return {
start: this.start,
- page_length: this.page_length
+ page_length: this.page_length,
};
}
@@ -213,8 +233,11 @@ frappe.views.Activity = class Activity extends frappe.views.BaseList {
}
render() {
- this.data.map(value => {
- const row = $('
').data("data", value).appendTo(this.$result).get(0);
+ this.data.map((value) => {
+ const row = $('
')
+ .data("data", value)
+ .appendTo(this.$result)
+ .get(0);
new frappe.activity.Feed(row, value);
});
}
diff --git a/frappe/desk/page/backups/backups.js b/frappe/desk/page/backups/backups.js
index d6cab750f0..08289cab2d 100644
--- a/frappe/desk/page/backups/backups.js
+++ b/frappe/desk/page/backups/backups.js
@@ -1,18 +1,18 @@
-frappe.pages['backups'].on_page_load = function (wrapper) {
+frappe.pages["backups"].on_page_load = function (wrapper) {
var page = frappe.ui.make_app_page({
parent: wrapper,
- title: __('Download Backups'),
- single_column: true
+ title: __("Download Backups"),
+ single_column: true,
});
page.add_inner_button(__("Set Number of Backups"), function () {
- frappe.set_route('Form', 'System Settings');
+ frappe.set_route("Form", "System Settings");
});
page.add_inner_button(__("Download Files Backup"), function () {
frappe.call({
method: "frappe.desk.page.backups.backups.schedule_files_backup",
- args: { "user_email": frappe.session.user_email }
+ args: { user_email: frappe.session.user_email },
});
});
@@ -23,18 +23,18 @@ frappe.pages['backups'].on_page_load = function (wrapper) {
method: "frappe.utils.backups.get_backup_encryption_key",
callback: function (r) {
frappe.msgprint({
- title: __('Backup Encryption Key'),
+ title: __("Backup Encryption Key"),
message: __(r.message),
- indicator: 'blue'
+ indicator: "blue",
});
- }
+ },
});
});
} else {
frappe.msgprint({
- title: __('Error'),
- message: __('System Manager privileges required.'),
- indicator: 'red'
+ title: __("Error"),
+ message: __("System Manager privileges required."),
+ indicator: "red",
});
}
});
diff --git a/frappe/desk/page/leaderboard/leaderboard.js b/frappe/desk/page/leaderboard/leaderboard.js
index aa1678af37..845dac0d63 100644
--- a/frappe/desk/page/leaderboard/leaderboard.js
+++ b/frappe/desk/page/leaderboard/leaderboard.js
@@ -1,7 +1,7 @@
frappe.pages["leaderboard"].on_page_load = (wrapper) => {
frappe.leaderboard = new Leaderboard(wrapper);
- $(wrapper).bind('show', ()=> {
+ $(wrapper).bind("show", () => {
// Get which leaderboard to show
let doctype = frappe.get_route()[1];
frappe.leaderboard.show_leaderboard(doctype);
@@ -9,7 +9,6 @@ frappe.pages["leaderboard"].on_page_load = (wrapper) => {
};
class Leaderboard {
-
constructor(parent) {
frappe.ui.make_app_page({
parent: parent,
@@ -20,11 +19,12 @@ class Leaderboard {
this.parent = parent;
this.page = this.parent.page;
- this.page.sidebar.html(`
`);
- this.$sidebar_list = this.page.sidebar.find('ul');
+ this.page.sidebar.html(
+ `
`
+ );
+ this.$sidebar_list = this.page.sidebar.find("ul");
this.get_leaderboard_config();
-
}
get_leaderboard_config() {
@@ -32,47 +32,57 @@ class Leaderboard {
this.filters = {};
this.leaderboard_limit = 20;
- frappe.xcall("frappe.desk.page.leaderboard.leaderboard.get_leaderboard_config").then(config => {
- this.leaderboard_config = config;
- for (let doctype in this.leaderboard_config) {
- this.doctypes.push(doctype);
- this.filters[doctype] = this.leaderboard_config[doctype].fields.map(field => {
- if (typeof field ==='object') {
- return field.label || field.fieldname;
- }
- return field;
- });
- }
+ frappe
+ .xcall("frappe.desk.page.leaderboard.leaderboard.get_leaderboard_config")
+ .then((config) => {
+ this.leaderboard_config = config;
+ for (let doctype in this.leaderboard_config) {
+ this.doctypes.push(doctype);
+ this.filters[doctype] = this.leaderboard_config[doctype].fields.map(
+ (field) => {
+ if (typeof field === "object") {
+ return field.label || field.fieldname;
+ }
+ return field;
+ }
+ );
+ }
- // For translation. Do not remove this
- // __("This Week"), __("This Month"), __("This Quarter"), __("This Year"),
- // __("Last Week"), __("Last Month"), __("Last Quarter"), __("Last Year"),
- // __("All Time"), __("Select From Date")
- this.timespans = [
- "This Week", "This Month", "This Quarter", "This Year",
- "Last Week", "Last Month", "Last Quarter", "Last Year",
- "All Time", "Select Date Range"
- ];
+ // For translation. Do not remove this
+ // __("This Week"), __("This Month"), __("This Quarter"), __("This Year"),
+ // __("Last Week"), __("Last Month"), __("Last Quarter"), __("Last Year"),
+ // __("All Time"), __("Select From Date")
+ this.timespans = [
+ "This Week",
+ "This Month",
+ "This Quarter",
+ "This Year",
+ "Last Week",
+ "Last Month",
+ "Last Quarter",
+ "Last Year",
+ "All Time",
+ "Select Date Range",
+ ];
- // for saving current selected filters
- const _initial_doctype = frappe.get_route()[1] || this.doctypes[0];
- const _initial_timespan = this.timespans[0];
- const _initial_filter = this.filters[_initial_doctype];
+ // for saving current selected filters
+ const _initial_doctype = frappe.get_route()[1] || this.doctypes[0];
+ const _initial_timespan = this.timespans[0];
+ const _initial_filter = this.filters[_initial_doctype];
- this.options = {
- selected_doctype: _initial_doctype,
- selected_filter: _initial_filter,
- selected_filter_item: _initial_filter[0],
- selected_timespan: _initial_timespan,
- };
+ this.options = {
+ selected_doctype: _initial_doctype,
+ selected_filter: _initial_filter,
+ selected_filter_item: _initial_filter[0],
+ selected_timespan: _initial_timespan,
+ };
- this.message = null;
- this.make();
- });
+ this.message = null;
+ this.make();
+ });
}
make() {
-
this.$container = $(`
@@ -80,7 +90,7 @@ class Leaderboard {
this.$graph_area = this.$container.find(".leaderboard-graph");
- this.doctypes.map(doctype => {
+ this.doctypes.map((doctype) => {
const icon = this.leaderboard_config[doctype].icon;
this.get_sidebar_item(doctype, icon).appendTo(this.$sidebar_list);
});
@@ -94,7 +104,6 @@ class Leaderboard {
// Get which leaderboard to show
let doctype = frappe.get_route()[1];
this.show_leaderboard(doctype);
-
}
setup_leaderboard_fields() {
@@ -108,25 +117,27 @@ class Leaderboard {
change: (e) => {
this.options.selected_company = e.currentTarget.value;
this.make_request();
- }
+ },
});
- this.timespan_select = this.page.add_select(__("Timespan"),
- this.timespans.map(d => {
- return {"label": __(d), value: d };
+ this.timespan_select = this.page.add_select(
+ __("Timespan"),
+ this.timespans.map((d) => {
+ return { label: __(d), value: d };
})
);
this.create_date_range_field();
- this.type_select = this.page.add_select(__("Field"),
- this.options.selected_filter.map(d => {
- return {"label": __(frappe.model.unscrub(d)), value: d };
+ this.type_select = this.page.add_select(
+ __("Field"),
+ this.options.selected_filter.map((d) => {
+ return { label: __(frappe.model.unscrub(d)), value: d };
})
);
this.timespan_select.on("change", (e) => {
this.options.selected_timespan = e.currentTarget.value;
- if (this.options.selected_timespan === 'Select Date Range') {
+ if (this.options.selected_timespan === "Select Date Range") {
this.date_range_field.show();
} else {
this.date_range_field.hide();
@@ -141,30 +152,33 @@ class Leaderboard {
}
create_date_range_field() {
- let timespan_field = $(this.parent).find(`.frappe-control[data-original-title="${__('Timespan')}"]`);
- this.date_range_field = $(`
`).insertAfter(timespan_field).hide();
+ let timespan_field = $(this.parent).find(
+ `.frappe-control[data-original-title="${__("Timespan")}"]`
+ );
+ this.date_range_field = $(`
`)
+ .insertAfter(timespan_field)
+ .hide();
let date_field = frappe.ui.form.make_control({
df: {
- fieldtype: 'DateRange',
- fieldname: 'selected_date_range',
+ fieldtype: "DateRange",
+ fieldname: "selected_date_range",
placeholder: __("Date Range"),
default: [frappe.datetime.month_start(), frappe.datetime.now_date()],
- input_class: 'input-xs',
+ input_class: "input-xs",
reqd: 1,
change: () => {
this.selected_date_range = date_field.get_value();
if (this.selected_date_range) this.make_request();
- }
+ },
},
- parent: $(this.parent).find('.from-date-field'),
- render_input: 1
+ parent: $(this.parent).find(".from-date-field"),
+ render_input: 1,
});
}
render_selected_doctype() {
-
- this.$sidebar_list.on("click", "li", (e)=> {
+ this.$sidebar_list.on("click", "li", (e) => {
let $li = $(e.currentTarget);
let doctype = $li.find(".doctype-text").attr("doctype-value");
@@ -174,8 +188,8 @@ class Leaderboard {
this.options.selected_filter_item = this.filters[doctype][0];
this.type_select.empty().add_options(
- this.options.selected_filter.map(d => {
- return {"label": __(frappe.model.unscrub(d)), value: d };
+ this.options.selected_filter.map((d) => {
+ return { label: __(frappe.model.unscrub(d)), value: d };
})
);
if (this.leaderboard_config[this.options.selected_doctype].company_disabled) {
@@ -193,10 +207,10 @@ class Leaderboard {
}
render_search_box() {
-
- this.$search_box =
- $(`
-
+ this.$search_box = $(`
+
`);
$(this.parent).find(".page-form").append(this.$search_box);
@@ -206,7 +220,9 @@ class Leaderboard {
if (this.doctypes.length) {
if (this.doctypes.includes(doctype)) {
this.options.selected_doctype = doctype;
- this.$sidebar_list.find(`[doctype-value = "${this.options.selected_doctype}"]`).trigger("click");
+ this.$sidebar_list
+ .find(`[doctype-value = "${this.options.selected_doctype}"]`)
+ .trigger("click");
}
this.$search_box.find(".leaderboard-search-input").val("");
@@ -215,8 +231,7 @@ class Leaderboard {
}
make_request() {
-
- frappe.model.with_doctype(this.options.selected_doctype, ()=> {
+ frappe.model.with_doctype(this.options.selected_doctype, () => {
this.get_leaderboard(this.get_leaderboard_data);
});
}
@@ -225,33 +240,32 @@ class Leaderboard {
if (!this.options.selected_company) {
frappe.throw(__("Please select Company"));
}
- frappe.call(
- this.leaderboard_config[this.options.selected_doctype].method,
- {
- 'date_range': this.get_date_range(),
- 'company': this.options.selected_company,
- 'field': this.options.selected_filter_item,
- 'limit': this.leaderboard_limit,
- }
- ).then(r => {
- let results = r.message || [];
+ frappe
+ .call(this.leaderboard_config[this.options.selected_doctype].method, {
+ date_range: this.get_date_range(),
+ company: this.options.selected_company,
+ field: this.options.selected_filter_item,
+ limit: this.leaderboard_limit,
+ })
+ .then((r) => {
+ let results = r.message || [];
- let graph_items = results.slice(0, 10);
+ let graph_items = results.slice(0, 10);
- this.$graph_area.show().empty();
+ this.$graph_area.show().empty();
- const custom_options = {
- data: {
- datasets: [{ values: graph_items.map(d => d.value) }],
- labels: graph_items.map(d => d.name)
- },
- format_tooltip_x: d => d[this.options.selected_filter_item],
- height: 140
- };
- frappe.utils.make_chart('.leaderboard-graph', custom_options);
+ const custom_options = {
+ data: {
+ datasets: [{ values: graph_items.map((d) => d.value) }],
+ labels: graph_items.map((d) => d.name),
+ },
+ format_tooltip_x: (d) => d[this.options.selected_filter_item],
+ height: 140,
+ };
+ frappe.utils.make_chart(".leaderboard-graph", custom_options);
- notify(this, r);
- });
+ notify(this, r);
+ });
}
get_leaderboard_data(me, res) {
@@ -267,9 +281,7 @@ class Leaderboard {
}
render_list_view(items = []) {
-
- var html =
- `${this.render_message()}
+ var html = `${this.render_message()}
${this.render_result(items)}
`;
@@ -278,47 +290,43 @@ class Leaderboard {
}
render_result(items) {
-
- var html =
- `${this.render_list_header()}
+ var html = `${this.render_list_header()}
${this.render_list_result(items)}`;
return html;
}
render_list_header() {
- const _selected_filter = this.options.selected_filter
- .map(i => frappe.model.unscrub(i));
+ const _selected_filter = this.options.selected_filter.map((i) => frappe.model.unscrub(i));
const fields = ["rank", "name", this.options.selected_filter_item];
- const filters = fields.map(filter => {
- const col = __(frappe.model.unscrub(filter));
- return (
- `
+ ${col && _selected_filter.indexOf(col) !== -1 ? "text-right" : ""}">
${col}
-
`
- );
- }).join("");
+
`;
+ })
+ .join("");
- const html =
- `