From 788887367203e5074af3a41798dec4ee7e7aee58 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 15 Oct 2019 14:54:01 +0530 Subject: [PATCH 001/408] feat: allow email append to for other doctypes --- .../doctype/customize_form/customize_form.json | 9 ++++++++- .../custom/doctype/customize_form/customize_form.py | 3 ++- frappe/email/doctype/email_account/email_account.py | 12 +++++++++++- 3 files changed, 21 insertions(+), 3 deletions(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 0b1df62f9d..4778bf524f 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -20,6 +20,7 @@ "allow_auto_repeat", "allow_import", "image_view", + "allow_in_email_append_to", "column_break_5", "title_field", "image_field", @@ -174,13 +175,19 @@ "fieldname": "allow_import", "fieldtype": "Check", "label": "Allow Import (via Data Import Tool)" + }, + { + "default": "0", + "fieldname": "allow_in_email_append_to", + "fieldtype": "Check", + "label": "Allow in Email Append To" } ], "hide_toolbar": 1, "icon": "fa fa-glass", "idx": 1, "issingle": 1, - "modified": "2019-10-08 11:16:36.698006", + "modified": "2019-10-15 14:20:51.272870", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index b851d40b83..190d4cb567 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -31,7 +31,8 @@ doctype_properties = { 'track_changes': 'Check', 'track_views': 'Check', 'allow_auto_repeat': 'Check', - 'allow_import': 'Check' + 'allow_import': 'Check', + 'allow_in_email_append_to': 'Check' } docfield_properties = { diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index f10f08664c..73e85659b3 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -652,7 +652,17 @@ class EmailAccount(Document): @frappe.whitelist() def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None): if not txt: txt = "" - return [[d] for d in frappe.get_hooks("email_append_to") if txt in d] + + email_append_to_list = frappe.get_hooks("email_append_to") + custom_email_append_to_list = frappe.get_list("Property Setter", filters={ + "property": "allow_in_email_append_to", + "value": 1 + }, fields=["doc_type"]) + + for doctype in custom_email_append_to_list: + email_append_to_list.append(doctype.doc_type) + + return [[d] for d in set(email_append_to_list) if txt in d] def test_internet(host="8.8.8.8", port=53, timeout=3): """Returns True if internet is connected From f207d98d784ac7ab2f2b2882fbb5640e273c4120 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 15 Oct 2019 15:21:42 +0530 Subject: [PATCH 002/408] fix: check if subject and status exists --- frappe/custom/doctype/customize_form/customize_form.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 190d4cb567..57d0251f72 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -165,6 +165,7 @@ class CustomizeForm(Document): self.flags.update_db = False self.flags.rebuild_doctype_for_global_search = False + self.check_email_append_to() self.set_property_setters() self.update_custom_fields() self.set_name_translation() @@ -249,6 +250,13 @@ class CustomizeForm(Document): self.make_property_setter(property=property, value=df.get(property), property_type=docfield_properties[property], fieldname=df.fieldname) + def check_email_append_to(self): + meta = frappe.get_meta(self.doc_type) + + if self.allow_in_email_append_to and not (meta.has_field("subject") and meta.has_field("status")): + frappe.throw(_("Add custom fields for Subject and Status in Document Type \ + {0} to enable Email Append To").format(self.doc_type)) + def update_custom_fields(self): for i, df in enumerate(self.get("fields")): if df.get("is_custom_field"): From 93e64e4ce659c5c0919694b8ff7927bd6486da5c Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 26 Oct 2019 18:14:47 +0530 Subject: [PATCH 003/408] fix: check if meta has subject and status --- .../doctype/email_account/email_account.py | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 73e85659b3..703835e87b 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -653,16 +653,20 @@ class EmailAccount(Document): def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None): if not txt: txt = "" + if frappe.cache().hget("email_append_to", "email_append_to_dts"): + return frappe.cache().hget("email_append_to", "email_append_to_dts") + email_append_to_list = frappe.get_hooks("email_append_to") - custom_email_append_to_list = frappe.get_list("Property Setter", filters={ - "property": "allow_in_email_append_to", - "value": 1 - }, fields=["doc_type"]) - for doctype in custom_email_append_to_list: - email_append_to_list.append(doctype.doc_type) + for dt in frappe.get_list("DocType", filters={"istable": 0, "issingle": 0}): + meta = frappe.get_meta(dt.name) + if meta.allow_in_email_append_to and meta.has_field("subject") and meta.has_field("status"): + email_append_to_list.append(dt.name) - return [[d] for d in set(email_append_to_list) if txt in d] + email_append = [[d] for d in set(email_append_to_list) if txt in d] + frappe.cache().hset("email_append_to", "email_append_to_dts", email_append) + + return email_append def test_internet(host="8.8.8.8", port=53, timeout=3): """Returns True if internet is connected From 7e7c14e2e72652a2d6641596543848ecf59bc7ce Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 21 Nov 2019 12:21:30 +0530 Subject: [PATCH 004/408] fix: statushsould have open and closed in options --- .../doctype/customize_form/customize_form.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 57d0251f72..7f853324b5 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -253,9 +253,17 @@ class CustomizeForm(Document): def check_email_append_to(self): meta = frappe.get_meta(self.doc_type) - if self.allow_in_email_append_to and not (meta.has_field("subject") and meta.has_field("status")): - frappe.throw(_("Add custom fields for Subject and Status in Document Type \ - {0} to enable Email Append To").format(self.doc_type)) + if self.allow_in_email_append_to: + if not meta.has_field("subject"): + frappe.throw(_("Add custom fields for Subject in Document Type {0} to enable Email Append To").format(self.doc_type)) + + if not meta.has_field("status"): + frappe.throw(_("Add custom fields for Status in Document Type {0} to enable Email Append To").format(self.doc_type)) + else: + status = meta.get_field("status") + for option in ["Open", "Closed"]: + if not option in status.options: + frappe.throw(_("Status field should have status {0} in options").format(option)) def update_custom_fields(self): for i, df in enumerate(self.get("fields")): From 12c1d6cc57533107d27df3937b230f349c9f8aa3 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 21 Nov 2019 13:55:11 +0530 Subject: [PATCH 005/408] fix: check if property exists --- frappe/email/doctype/email_account/email_account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 703835e87b..e1f5a2adeb 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -660,7 +660,7 @@ def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len for dt in frappe.get_list("DocType", filters={"istable": 0, "issingle": 0}): meta = frappe.get_meta(dt.name) - if meta.allow_in_email_append_to and meta.has_field("subject") and meta.has_field("status"): + if meta.get("allow_in_email_append_to") and meta.allow_in_email_append_to: email_append_to_list.append(dt.name) email_append = [[d] for d in set(email_append_to_list) if txt in d] From 87eb7243fd61175b9f63e320ab31130a6612b0c0 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 21 Nov 2019 15:31:30 +0530 Subject: [PATCH 006/408] fix: allow custom doctypes --- frappe/email/doctype/email_account/email_account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index e1f5a2adeb..c45dab110d 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -660,7 +660,7 @@ def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len for dt in frappe.get_list("DocType", filters={"istable": 0, "issingle": 0}): meta = frappe.get_meta(dt.name) - if meta.get("allow_in_email_append_to") and meta.allow_in_email_append_to: + if meta.custom or (meta.get("allow_in_email_append_to") and meta.allow_in_email_append_to): email_append_to_list.append(dt.name) email_append = [[d] for d in set(email_append_to_list) if txt in d] From 8c9918bc96ef630a9d75589474c6e8f8e66d5814 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 12 Dec 2019 12:46:21 +0530 Subject: [PATCH 007/408] feat: allow document creation via email --- frappe/core/doctype/doctype/doctype.json | 28 ++++++++++- frappe/core/doctype/doctype/doctype.py | 46 +++++++++++++++++++ .../customize_form/customize_form.json | 28 +++++++++-- .../doctype/customize_form/customize_form.py | 24 +++------- frappe/database/mariadb/framework_mariadb.sql | 4 ++ .../database/postgres/framework_postgres.sql | 4 ++ .../doctype/email_account/email_account.py | 26 +++-------- 7 files changed, 118 insertions(+), 42 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 4e3f2fd84a..0fa90ac5df 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -35,6 +35,9 @@ "timeline_field", "nsm_parent_field", "max_attachments", + "subject_field", + "sender_field", + "status_field", "column_break_23", "hide_toolbar", "allow_copy", @@ -42,6 +45,7 @@ "allow_import", "allow_events_in_timeline", "allow_auto_repeat", + "email_append_to", "view_settings", "title_field", "search_fields", @@ -488,11 +492,33 @@ "fieldtype": "Table", "label": "Links", "options": "DocType Link" + }, + { + "fieldname": "subject_field", + "fieldtype": "Data", + "label": "Subject Field" + }, + { + "fieldname": "sender_field", + "fieldtype": "Data", + "label": "Sender Field" + }, + { + "default": "0", + "fieldname": "email_append_to", + "fieldtype": "Check", + "label": "Allow document creation via Email" + }, + { + "fieldname": "status_field", + "fieldtype": "Data", + "label": "Status Field" } ], "icon": "fa fa-bolt", "idx": 6, - "modified": "2019-11-25 17:24:03.690192", + "links": [], + "modified": "2019-12-12 12:05:39.317311", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 1223d50878..35cbdfa992 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -93,6 +93,8 @@ class DocType(Document): if not self.is_new(): self.setup_fields_to_fetch() + check_email_append_to(self) + if self.default_print_format and not self.custom: frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) @@ -1144,3 +1146,47 @@ def check_if_fieldname_conflicts_with_methods(doctype, fieldname): def clear_linked_doctype_cache(): frappe.cache().delete_value('linked_doctypes_without_ignore_user_permissions_enabled') + +def check_email_append_to(doc): + if not doc.email_append_to: + return + + # Subject Field + doc.subject_field = doc.subject_field.strip() if doc.subject_field else None + subject_field = get_field(doc, doc.subject_field) + + if not (doc.subject_field or subject_field): + frappe.throw(_("Add Subject Field for creating documents from Email")) + + if subject_field.fieldtype not in ["Data", "Text", "Long Text", "Small Text", "Text Editor"]: + frappe.throw(_("Subject Field type should be Data, Text, Long Text, Small Text, Text Editor")) + + # Sender Field + doc.sender_field = doc.sender_field.strip() if doc.sender_field else None + sender_field = get_field(doc, doc.sender_field) + + if not (doc.sender_field or sender_field): + frappe.throw(_("Add Sender Field for creating documents from Email")) + + # Status Field + doc.status_field = doc.status_field.strip() if doc.status_field else None + status_field = get_field(doc, doc.status_field) + + if not (doc.status_field or status_field): + frappe.throw(_("Add Status Field for creating documents from Email")) + + if status_field.fieldtype != "Select": + frappe.throw(_("Status Field type should be Select")) + + for option in ["Open", "Closed"]: + if not option in status_field.options: + frappe.throw(_("Status field should have status Open and Closed in options")) + + +def get_field(doc, fieldname): + if not (doc or fieldname): + return + + for field in doc.fields: + if field.fieldname == fieldname: + return field diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 4778bf524f..3e547fb22f 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "DL.####", "creation": "2013-01-29 17:55:08", "doctype": "DocType", @@ -20,11 +21,14 @@ "allow_auto_repeat", "allow_import", "image_view", - "allow_in_email_append_to", + "email_append_to", "column_break_5", "title_field", "image_field", "search_fields", + "subject_field", + "sender_field", + "status_field", "section_break_8", "sort_field", "column_break_10", @@ -176,18 +180,34 @@ "fieldtype": "Check", "label": "Allow Import (via Data Import Tool)" }, + { + "fieldname": "subject_field", + "fieldtype": "Data", + "label": "Subject Field" + }, + { + "fieldname": "sender_field", + "fieldtype": "Data", + "label": "Sender Field" + }, { "default": "0", - "fieldname": "allow_in_email_append_to", + "fieldname": "email_append_to", "fieldtype": "Check", - "label": "Allow in Email Append To" + "label": "Allow document creation via Email" + }, + { + "fieldname": "status_field", + "fieldtype": "Data", + "label": "Status Field" } ], "hide_toolbar": 1, "icon": "fa fa-glass", "idx": 1, "issingle": 1, - "modified": "2019-10-15 14:20:51.272870", + "links": [], + "modified": "2019-12-12 12:44:02.674456", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 7f853324b5..5e5a9f0c9f 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -12,7 +12,7 @@ from frappe import _ from frappe.utils import cint from frappe.model.document import Document from frappe.model import no_value_fields, core_doctypes_list -from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype +from frappe.core.doctype.doctype.doctype import validate_fields_for_doctype, check_email_append_to from frappe.custom.doctype.custom_field.custom_field import create_custom_field from frappe.model.docfield import supports_translation @@ -32,7 +32,10 @@ doctype_properties = { 'track_views': 'Check', 'allow_auto_repeat': 'Check', 'allow_import': 'Check', - 'allow_in_email_append_to': 'Check' + 'email_append_to': 'Check', + 'subject_field': 'Data', + 'sender_field': 'Data', + 'status_field': 'Data' } docfield_properties = { @@ -165,7 +168,7 @@ class CustomizeForm(Document): self.flags.update_db = False self.flags.rebuild_doctype_for_global_search = False - self.check_email_append_to() + check_email_append_to(self) self.set_property_setters() self.update_custom_fields() self.set_name_translation() @@ -250,21 +253,6 @@ class CustomizeForm(Document): self.make_property_setter(property=property, value=df.get(property), property_type=docfield_properties[property], fieldname=df.fieldname) - def check_email_append_to(self): - meta = frappe.get_meta(self.doc_type) - - if self.allow_in_email_append_to: - if not meta.has_field("subject"): - frappe.throw(_("Add custom fields for Subject in Document Type {0} to enable Email Append To").format(self.doc_type)) - - if not meta.has_field("status"): - frappe.throw(_("Add custom fields for Status in Document Type {0} to enable Email Append To").format(self.doc_type)) - else: - status = meta.get_field("status") - for option in ["Open", "Closed"]: - if not option in status.options: - frappe.throw(_("Status field should have status {0} in options").format(option)) - def update_custom_fields(self): for i, df in enumerate(self.get("fields")): if df.get("is_custom_field"): diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index b1a769b189..10e2ef7e7d 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -215,6 +215,10 @@ CREATE TABLE `tabDocType` ( `allow_guest_to_view` int(1) NOT NULL DEFAULT 0, `route` varchar(255) DEFAULT NULL, `is_published_field` varchar(255) DEFAULT NULL, + `email_append_to` int(1) NOT NULL DEFAULT 0, + `subject_field` varchar(255) DEFAULT NULL, + `sender_field` varchar(255) DEFAULT NULL, + `status_field` varchar(255) DEFAULT NULL, PRIMARY KEY (`name`), KEY `parent` (`parent`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index cd2f02d8e4..c89c9a8b16 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -220,6 +220,10 @@ CREATE TABLE "tabDocType" ( "allow_guest_to_view" smallint NOT NULL DEFAULT 0, "route" varchar(255) DEFAULT NULL, "is_published_field" varchar(255) DEFAULT NULL, + `email_append_to` smallint NOT NULL DEFAULT 0, + `subject_field` varchar(255) DEFAULT NULL, + `sender_field` varchar(255) DEFAULT NULL, + `status_field` varchar(255) DEFAULT NULL, PRIMARY KEY ("name") ) ; diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 4dde4826bf..b520c92399 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -419,42 +419,30 @@ class EmailAccount(Document): If no thread id is found and `append_to` is set for the email account, it will create a new parent transaction (e.g. Issue)""" - parent = None - - parent = self.find_parent_from_in_reply_to(communication, email) + parent = self.find_parent_from_in_reply_to(communication, email) or None if not parent and self.append_to: self.set_sender_field_and_subject_field() - - if not parent and self.append_to: parent = self.find_parent_based_on_subject_and_sender(communication, email) - if not parent and self.append_to and self.append_to!="Communication": - parent = self.create_new_parent(communication, email) + if self.append_to!="Communication": + parent = self.create_new_parent(communication, email) if parent: communication.reference_doctype = parent.doctype communication.reference_name = parent.name # check if message is notification and disable notifications for this message - isnotification = email.mail.get("isnotification") - if isnotification: - if "notification" in isnotification: - communication.unread_notification_sent = 1 + if email.mail.get("isnotification") and "notification" in email.mail.get("isnotification"): + communication.unread_notification_sent = 1 def set_sender_field_and_subject_field(self): '''Identify the sender and subject fields from the `append_to` DocType''' # set subject_field and sender_field - meta_module = frappe.get_meta_module(self.append_to) meta = frappe.get_meta(self.append_to) - self.subject_field = getattr(meta_module, "subject_field", "subject") - if not meta.get_field(self.subject_field): - self.subject_field = None - - self.sender_field = getattr(meta_module, "sender_field", "sender") - if not meta.get_field(self.sender_field): - self.sender_field = None + self.subject_field = meta.subject_field if meta.subject_field else None + self.sender_field = meta.sender_field if meta.sender_field else None def find_parent_based_on_subject_and_sender(self, communication, email): '''Find parent document based on subject and sender match''' From ec89d275a2bad2de14c7d4249cd17dc7b9a69dd4 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 12 Dec 2019 15:04:56 +0530 Subject: [PATCH 008/408] feat: remove hooks for email_append_to --- .../doctype/communication/communication.json | 8 ++++++- frappe/desk/doctype/event/event.json | 15 +++++++++++- frappe/desk/doctype/todo/todo.json | 10 ++++++-- frappe/desk/doctype/todo/todo.py | 2 -- .../doctype/email_account/email_account.py | 23 +++++++++++-------- 5 files changed, 42 insertions(+), 16 deletions(-) diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index 5e34804b93..3418fc073b 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -1,9 +1,11 @@ { + "actions": [], "allow_import": 1, "creation": "2013-01-29 10:47:14", "description": "Keeps track of all communications", "doctype": "DocType", "document_type": "Setup", + "email_append_to": 1, "engine": "InnoDB", "field_order": [ "subject", @@ -383,7 +385,8 @@ ], "icon": "fa fa-comment", "idx": 1, - "modified": "2019-10-09 14:22:27.664645", + "links": [], + "modified": "2019-12-12 13:01:23.716052", "modified_by": "Administrator", "module": "Core", "name": "Communication", @@ -430,8 +433,11 @@ } ], "search_fields": "subject", + "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", + "status_field": "status", + "subject_field": "subject", "title_field": "subject", "track_changes": 1, "track_seen": 1 diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 032030ddef..8a0372b13f 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -1,9 +1,11 @@ { + "actions": [], "allow_import": 1, "autoname": "EV.#####", "creation": "2013-06-10 13:17:47", "doctype": "DocType", "document_type": "Document", + "email_append_to": 1, "engine": "InnoDB", "field_order": [ "details", @@ -17,6 +19,7 @@ "starts_on", "ends_on", "status", + "sender", "all_day", "sync_with_google_calendar", "sb_00", @@ -262,11 +265,18 @@ "fieldtype": "Check", "label": "Pulled from Google Calendar", "read_only": 1 + }, + { + "fieldname": "sender", + "fieldtype": "Data", + "label": "Sender", + "read_only": 1 } ], "icon": "fa fa-calendar", "idx": 1, - "modified": "2019-08-08 16:01:19.489396", + "links": [], + "modified": "2019-12-12 12:58:36.621861", "modified_by": "Administrator", "module": "Desk", "name": "Event", @@ -297,8 +307,11 @@ } ], "read_only": 1, + "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", + "status_field": "status", + "subject_field": "subject", "title_field": "subject", "track_changes": 1, "track_seen": 1, diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index 508720a488..f80a785ccd 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -1,8 +1,10 @@ { + "actions": [], "autoname": "hash", "creation": "2012-07-03 13:30:35", "doctype": "DocType", "document_type": "Setup", + "email_append_to": 1, "engine": "InnoDB", "field_order": [ "description_and_status", @@ -154,7 +156,8 @@ ], "icon": "fa fa-check", "idx": 2, - "modified": "2019-09-10 14:34:59.161750", + "links": [], + "modified": "2019-12-12 12:53:17.565139", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", @@ -185,9 +188,12 @@ ], "quick_entry": 1, "search_fields": "description, reference_type, reference_name", + "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", + "status_field": "status", + "subject_field": "description", "title_field": "description", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py index 5d04f412c0..034afd3184 100644 --- a/frappe/desk/doctype/todo/todo.py +++ b/frappe/desk/doctype/todo/todo.py @@ -8,8 +8,6 @@ import json from frappe.model.document import Document from frappe.utils import get_fullname -subject_field = "description" -sender_field = "sender" exclude_from_linked_with = True class ToDo(Document): diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index b520c92399..626ecbf378 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -637,21 +637,24 @@ class EmailAccount(Document): frappe.throw(_("Automatic Linking can be activated only for one Email Account.")) @frappe.whitelist() -def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None): - if not txt: txt = "" +def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None, reset_cache=False): - if frappe.cache().hget("email_append_to", "email_append_to_dts"): - return frappe.cache().hget("email_append_to", "email_append_to_dts") + if frappe.cache().hget("email_append_to", "email_append_to_doctypes") and not reset_cache: + return frappe.cache().hget("email_append_to", "email_append_to_doctypes") - email_append_to_list = frappe.get_hooks("email_append_to") + txt = txt if txt else "" + email_append_to_list = [] - for dt in frappe.get_list("DocType", filters={"istable": 0, "issingle": 0}): - meta = frappe.get_meta(dt.name) - if meta.custom or (meta.get("allow_in_email_append_to") and meta.allow_in_email_append_to): + for dt in frappe.get_all("DocType", filters={"istable": 0, "issingle": 0}, fields=["name", "email_append_to"]): + if dt.get("email_append_to") and dt.email_append_to: email_append_to_list.append(dt.name) + else: + meta = frappe.get_meta(dt.name) + if meta.get("email_append_to") and meta.email_append_to: + email_append_to_list.append(dt.name) - email_append = [[d] for d in set(email_append_to_list) if txt in d] - frappe.cache().hset("email_append_to", "email_append_to_dts", email_append) + email_append_to = [[d] for d in set(email_append_to_list) if txt in d] + frappe.cache().hset("email_append_to", "email_append_to_doctypes", email_append_to) return email_append From b98ea6415016d5f4f08c6d15ec57f5b65506eab0 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 12 Dec 2019 19:23:22 +0530 Subject: [PATCH 009/408] fix: remove status field --- frappe/core/doctype/doctype/doctype.json | 8 +------- frappe/core/doctype/doctype/doctype.py | 14 -------------- .../doctype/customize_form/customize_form.json | 8 +------- .../doctype/customize_form/customize_form.py | 3 +-- 4 files changed, 3 insertions(+), 30 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 0fa90ac5df..9a8177d3a3 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -37,7 +37,6 @@ "max_attachments", "subject_field", "sender_field", - "status_field", "column_break_23", "hide_toolbar", "allow_copy", @@ -508,17 +507,12 @@ "fieldname": "email_append_to", "fieldtype": "Check", "label": "Allow document creation via Email" - }, - { - "fieldname": "status_field", - "fieldtype": "Data", - "label": "Status Field" } ], "icon": "fa fa-bolt", "idx": 6, "links": [], - "modified": "2019-12-12 12:05:39.317311", + "modified": "2019-12-12 19:16:50.130503", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 35cbdfa992..5610fa4f89 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -1168,20 +1168,6 @@ def check_email_append_to(doc): if not (doc.sender_field or sender_field): frappe.throw(_("Add Sender Field for creating documents from Email")) - # Status Field - doc.status_field = doc.status_field.strip() if doc.status_field else None - status_field = get_field(doc, doc.status_field) - - if not (doc.status_field or status_field): - frappe.throw(_("Add Status Field for creating documents from Email")) - - if status_field.fieldtype != "Select": - frappe.throw(_("Status Field type should be Select")) - - for option in ["Open", "Closed"]: - if not option in status_field.options: - frappe.throw(_("Status field should have status Open and Closed in options")) - def get_field(doc, fieldname): if not (doc or fieldname): diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 3e547fb22f..49e8dee366 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -28,7 +28,6 @@ "search_fields", "subject_field", "sender_field", - "status_field", "section_break_8", "sort_field", "column_break_10", @@ -195,11 +194,6 @@ "fieldname": "email_append_to", "fieldtype": "Check", "label": "Allow document creation via Email" - }, - { - "fieldname": "status_field", - "fieldtype": "Data", - "label": "Status Field" } ], "hide_toolbar": 1, @@ -207,7 +201,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2019-12-12 12:44:02.674456", + "modified": "2019-12-12 19:17:08.426469", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index 5e5a9f0c9f..4c5fe76bed 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -34,8 +34,7 @@ doctype_properties = { 'allow_import': 'Check', 'email_append_to': 'Check', 'subject_field': 'Data', - 'sender_field': 'Data', - 'status_field': 'Data' + 'sender_field': 'Data' } docfield_properties = { From 78f506226a12aa4cf32d769293cdb41d1ec994b4 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 12 Dec 2019 21:14:26 +0530 Subject: [PATCH 010/408] fix: return correct variable --- frappe/email/doctype/email_account/email_account.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 626ecbf378..7d9c2bb8fb 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -656,7 +656,7 @@ def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len email_append_to = [[d] for d in set(email_append_to_list) if txt in d] frappe.cache().hset("email_append_to", "email_append_to_doctypes", email_append_to) - return email_append + return email_append_to def test_internet(host="8.8.8.8", port=53, timeout=3): """Returns True if internet is connected From 2fe3430e743a2d4311018b02bc5d8737fc2b9ecc Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 27 Dec 2019 14:30:24 +0530 Subject: [PATCH 011/408] fix: create a new section for email append to --- frappe/core/doctype/doctype/doctype.js | 12 +++++++++++- frappe/core/doctype/doctype/doctype.json | 17 +++++++++++++---- .../doctype/customize_form/customize_form.js | 9 +++++++++ .../doctype/customize_form/customize_form.json | 17 ++++++++++++++--- 4 files changed, 47 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 9a19185cfc..379f7cdd9e 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -53,7 +53,17 @@ frappe.ui.form.on('DocType', { frm.events.autoname(frm); }, - autoname(frm) { + email_append_to: function (frm) { + frm.set_df_property("subject_field", "reqd", 0); + frm.set_df_property("sender_field", "reqd", 0); + + if (frm.doc.email_append_to) { + frm.set_df_property("subject_field", "reqd", 1); + frm.set_df_property("sender_field", "reqd", 1); + } + }, + + autoname: function(frm) { frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt'); } }) diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 9a8177d3a3..59fd55c402 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -35,8 +35,6 @@ "timeline_field", "nsm_parent_field", "max_attachments", - "subject_field", - "sender_field", "column_break_23", "hide_toolbar", "allow_copy", @@ -44,7 +42,6 @@ "allow_import", "allow_events_in_timeline", "allow_auto_repeat", - "email_append_to", "view_settings", "title_field", "search_fields", @@ -57,6 +54,10 @@ "color", "show_preview_popup", "show_name_in_global_search", + "email_settings_sb", + "email_append_to", + "subject_field", + "sender_field", "sb2", "permissions", "restrict_to_domain", @@ -493,11 +494,13 @@ "options": "DocType Link" }, { + "depends_on": "email_append_to", "fieldname": "subject_field", "fieldtype": "Data", "label": "Subject Field" }, { + "depends_on": "email_append_to", "fieldname": "sender_field", "fieldtype": "Data", "label": "Sender Field" @@ -507,12 +510,18 @@ "fieldname": "email_append_to", "fieldtype": "Check", "label": "Allow document creation via Email" + }, + { + "collapsible": 1, + "fieldname": "email_settings_sb", + "fieldtype": "Section Break", + "label": "Email Settings" } ], "icon": "fa fa-bolt", "idx": 6, "links": [], - "modified": "2019-12-12 19:16:50.130503", + "modified": "2019-12-27 13:28:06.201814", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index f1eadaaf2e..4ade51ae5b 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -132,6 +132,15 @@ frappe.ui.form.on("Customize Form", { }, + email_append_to: function(frm) { + frm.set_df_property("subject_field", "reqd", 0); + frm.set_df_property("sender_field", "reqd", 0); + + if (frm.doc.email_append_to) { + frm.set_df_property("subject_field", "reqd", 1); + frm.set_df_property("sender_field", "reqd", 1); + } + } }); frappe.ui.form.on("Customize Form Field", { diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 49e8dee366..f719add96c 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -26,12 +26,14 @@ "title_field", "image_field", "search_fields", - "subject_field", - "sender_field", "section_break_8", "sort_field", "column_break_10", "sort_order", + "section_break_23", + "subject_field", + "cb_01", + "sender_field", "fields_section_break", "fields" ], @@ -194,6 +196,15 @@ "fieldname": "email_append_to", "fieldtype": "Check", "label": "Allow document creation via Email" + }, + { + "depends_on": "doc_type", + "fieldname": "section_break_23", + "fieldtype": "Section Break" + }, + { + "fieldname": "cb_01", + "fieldtype": "Column Break" } ], "hide_toolbar": 1, @@ -201,7 +212,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2019-12-12 19:17:08.426469", + "modified": "2019-12-27 14:29:49.097980", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", From 9543b7d4afca27c4e7f618d3f65b3e4fe6541c52 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 27 Dec 2019 14:48:46 +0530 Subject: [PATCH 012/408] fix: remove status field --- .../doctype/communication/communication.json | 3 +-- frappe/database/mariadb/framework_mariadb.sql | 1 - frappe/database/postgres/framework_postgres.sql | 1 - frappe/desk/doctype/event/event.json | 3 +-- frappe/desk/doctype/todo/todo.json | 3 +-- .../email/doctype/email_account/email_account.py | 16 ++++++---------- 6 files changed, 9 insertions(+), 18 deletions(-) diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index 3418fc073b..ae6fb164ec 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -386,7 +386,7 @@ "icon": "fa fa-comment", "idx": 1, "links": [], - "modified": "2019-12-12 13:01:23.716052", + "modified": "2019-12-27 14:44:04.880373", "modified_by": "Administrator", "module": "Core", "name": "Communication", @@ -436,7 +436,6 @@ "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", - "status_field": "status", "subject_field": "subject", "title_field": "subject", "track_changes": 1, diff --git a/frappe/database/mariadb/framework_mariadb.sql b/frappe/database/mariadb/framework_mariadb.sql index 10e2ef7e7d..bf9e0e33c8 100644 --- a/frappe/database/mariadb/framework_mariadb.sql +++ b/frappe/database/mariadb/framework_mariadb.sql @@ -218,7 +218,6 @@ CREATE TABLE `tabDocType` ( `email_append_to` int(1) NOT NULL DEFAULT 0, `subject_field` varchar(255) DEFAULT NULL, `sender_field` varchar(255) DEFAULT NULL, - `status_field` varchar(255) DEFAULT NULL, PRIMARY KEY (`name`), KEY `parent` (`parent`) ) ENGINE=InnoDB ROW_FORMAT=COMPRESSED CHARACTER SET=utf8mb4 COLLATE=utf8mb4_unicode_ci; diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index c89c9a8b16..6bd20d241b 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -223,7 +223,6 @@ CREATE TABLE "tabDocType" ( `email_append_to` smallint NOT NULL DEFAULT 0, `subject_field` varchar(255) DEFAULT NULL, `sender_field` varchar(255) DEFAULT NULL, - `status_field` varchar(255) DEFAULT NULL, PRIMARY KEY ("name") ) ; diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 8a0372b13f..59ba7689db 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -276,7 +276,7 @@ "icon": "fa fa-calendar", "idx": 1, "links": [], - "modified": "2019-12-12 12:58:36.621861", + "modified": "2019-12-27 14:45:51.332025", "modified_by": "Administrator", "module": "Desk", "name": "Event", @@ -310,7 +310,6 @@ "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", - "status_field": "status", "subject_field": "subject", "title_field": "subject", "track_changes": 1, diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index f80a785ccd..cb6dc2fc03 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -157,7 +157,7 @@ "icon": "fa fa-check", "idx": 2, "links": [], - "modified": "2019-12-12 12:53:17.565139", + "modified": "2019-12-27 14:45:25.161055", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", @@ -191,7 +191,6 @@ "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", - "status_field": "status", "subject_field": "description", "title_field": "description", "track_changes": 1, diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index 0bd4beed0b..74285e2182 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -637,24 +637,20 @@ class EmailAccount(Document): frappe.throw(_("Automatic Linking can be activated only for one Email Account.")) @frappe.whitelist() -def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None, reset_cache=False): - - if frappe.cache().hget("email_append_to", "email_append_to_doctypes") and not reset_cache: - return frappe.cache().hget("email_append_to", "email_append_to_doctypes") - +def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len=None, filters=None): txt = txt if txt else "" email_append_to_list = [] + # Set Email Append To DocTypes via DocType for dt in frappe.get_all("DocType", filters={"istable": 0, "issingle": 0}, fields=["name", "email_append_to"]): if dt.get("email_append_to") and dt.email_append_to: email_append_to_list.append(dt.name) - else: - meta = frappe.get_meta(dt.name) - if meta.get("email_append_to") and meta.email_append_to: - email_append_to_list.append(dt.name) + + # Set Email Append To DocTypes set via Customize Form + for dt in frappe.get_list("Property Setter", filters={"property": "email_append_to", "value": 1}, fields=["doc_type"]): + email_append_to_list.append(dt.doc_type) email_append_to = [[d] for d in set(email_append_to_list) if txt in d] - frappe.cache().hset("email_append_to", "email_append_to_doctypes", email_append_to) return email_append_to From 3b517023dfd767f7ac96c7a89bcd59bf7e8f8bfc Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 13 Jan 2020 17:10:45 +0530 Subject: [PATCH 013/408] feat: make sender_field mandatory --- frappe/core/doctype/doctype/doctype.js | 2 -- frappe/core/doctype/doctype/doctype.py | 15 +++++++++------ 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 379f7cdd9e..844d49ed21 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -54,11 +54,9 @@ frappe.ui.form.on('DocType', { }, email_append_to: function (frm) { - frm.set_df_property("subject_field", "reqd", 0); frm.set_df_property("sender_field", "reqd", 0); if (frm.doc.email_append_to) { - frm.set_df_property("subject_field", "reqd", 1); frm.set_df_property("sender_field", "reqd", 1); } }, diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index b8e0bbbe7b..9a4b09b24b 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -1155,18 +1155,21 @@ def check_email_append_to(doc): doc.subject_field = doc.subject_field.strip() if doc.subject_field else None subject_field = get_field(doc, doc.subject_field) - if not (doc.subject_field or subject_field): - frappe.throw(_("Add Subject Field for creating documents from Email")) + if doc.subject_field and not subject_field: + frappe.throw(_("Select a valid Subject field for creating documents from Email")) - if subject_field.fieldtype not in ["Data", "Text", "Long Text", "Small Text", "Text Editor"]: + if subject_field and subject_field.fieldtype not in ["Data", "Text", "Long Text", "Small Text", "Text Editor"]: frappe.throw(_("Subject Field type should be Data, Text, Long Text, Small Text, Text Editor")) - # Sender Field + # Sender Field is mandatory doc.sender_field = doc.sender_field.strip() if doc.sender_field else None sender_field = get_field(doc, doc.sender_field) - if not (doc.sender_field or sender_field): - frappe.throw(_("Add Sender Field for creating documents from Email")) + if doc.sender_field and not sender_field: + frappe.throw(_("Select a valid Sender Field for creating documents from Email")) + + if not sender_field.options == "Email": + frappe.throw(_("Sender Field should have Email in options")) def get_field(doc, fieldname): From f62f80f33c0ceced29e9c245ba37ad74245b4c6e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 13 Jan 2020 23:54:47 +0530 Subject: [PATCH 014/408] fix: check if meta hasattr for subject and email --- frappe/email/doctype/email_account/email_account.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index ef8fb0ab28..ad11a50b29 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -432,7 +432,7 @@ class EmailAccount(Document): self.set_sender_field_and_subject_field() parent = self.find_parent_based_on_subject_and_sender(communication, email) - if self.append_to!="Communication": + if not self.append_to == "Communication": parent = self.create_new_parent(communication, email) if parent: @@ -447,9 +447,14 @@ class EmailAccount(Document): '''Identify the sender and subject fields from the `append_to` DocType''' # set subject_field and sender_field meta = frappe.get_meta(self.append_to) + self.subject_field = None + self.sender_field = None - self.subject_field = meta.subject_field if meta.subject_field else None - self.sender_field = meta.sender_field if meta.sender_field else None + if hasattr(meta, "subject_field"): + self.subject_field = meta.subject_field + + if hasattr(meta, "sender_field"): + self.sender_field = meta.sender_field def find_parent_based_on_subject_and_sender(self, communication, email): '''Find parent document based on subject and sender match''' From 84d740af100723bdff63099c509b09d6759b0705 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 14 Jan 2020 17:18:29 +0530 Subject: [PATCH 015/408] fix: add Email in options --- frappe/custom/doctype/customize_form/test_customize_form.py | 2 +- frappe/desk/doctype/todo/todo.json | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 1cd71ea05d..cace25a03d 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -46,7 +46,7 @@ class TestCustomizeForm(unittest.TestCase): d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") - self.assertEquals(len(d.get("fields")), 35) + self.assertEquals(len(d.get("fields")), 36) d = self.get_customize_form("Event") self.assertEquals(d.doc_type, "Event") diff --git a/frappe/desk/doctype/todo/todo.json b/frappe/desk/doctype/todo/todo.json index cb6dc2fc03..15e0e4abe1 100644 --- a/frappe/desk/doctype/todo/todo.json +++ b/frappe/desk/doctype/todo/todo.json @@ -144,7 +144,8 @@ "fieldname": "sender", "fieldtype": "Data", "hidden": 1, - "label": "Sender" + "label": "Sender", + "options": "Email" }, { "fieldname": "assignment_rule", @@ -157,7 +158,7 @@ "icon": "fa fa-check", "idx": 2, "links": [], - "modified": "2019-12-27 14:45:25.161055", + "modified": "2020-01-14 17:04:36.971002", "modified_by": "Administrator", "module": "Desk", "name": "ToDo", From b2d70447fd8aa49c35924d7db28fe4a7bbba0fc6 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 14 Jan 2020 22:20:05 +0530 Subject: [PATCH 016/408] fix: update event json and check attr --- frappe/core/doctype/doctype/doctype.py | 2 +- frappe/desk/doctype/event/event.json | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 9a4b09b24b..a60acaaeb8 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -1148,7 +1148,7 @@ def clear_linked_doctype_cache(): frappe.cache().delete_value('linked_doctypes_without_ignore_user_permissions_enabled') def check_email_append_to(doc): - if not doc.email_append_to: + if not hasattr(doc, "email_append_to") or not doc.email_append_to: return # Subject Field diff --git a/frappe/desk/doctype/event/event.json b/frappe/desk/doctype/event/event.json index 59ba7689db..5768f00f32 100644 --- a/frappe/desk/doctype/event/event.json +++ b/frappe/desk/doctype/event/event.json @@ -270,13 +270,14 @@ "fieldname": "sender", "fieldtype": "Data", "label": "Sender", + "options": "Email", "read_only": 1 } ], "icon": "fa fa-calendar", "idx": 1, "links": [], - "modified": "2019-12-27 14:45:51.332025", + "modified": "2020-01-14 21:47:15.825287", "modified_by": "Administrator", "module": "Desk", "name": "Event", From 858784d558fab178ff82db1e3d3385c8e836e950 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 19 Jan 2020 12:07:21 +0530 Subject: [PATCH 017/408] chore: revert code changes --- .../email/doctype/email_account/email_account.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index ad11a50b29..e4a3b41a56 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -426,22 +426,28 @@ class EmailAccount(Document): If no thread id is found and `append_to` is set for the email account, it will create a new parent transaction (e.g. Issue)""" - parent = self.find_parent_from_in_reply_to(communication, email) or None + parent = None + + parent = self.find_parent_from_in_reply_to(communication, email) if not parent and self.append_to: self.set_sender_field_and_subject_field() + + if not parent and self.append_to: parent = self.find_parent_based_on_subject_and_sender(communication, email) - if not self.append_to == "Communication": - parent = self.create_new_parent(communication, email) + if not parent and self.append_to and self.append_to!="Communication": + parent = self.create_new_parent(communication, email) if parent: communication.reference_doctype = parent.doctype communication.reference_name = parent.name # check if message is notification and disable notifications for this message - if email.mail.get("isnotification") and "notification" in email.mail.get("isnotification"): - communication.unread_notification_sent = 1 + isnotification = email.mail.get("isnotification") + if isnotification: + if "notification" in isnotification: + communication.unread_notification_sent = 1 def set_sender_field_and_subject_field(self): '''Identify the sender and subject fields from the `append_to` DocType''' From 230073ebf2131bfb6f176352416659703146f0d5 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sun, 19 Jan 2020 14:54:18 +0530 Subject: [PATCH 018/408] fix: change to quotes in postgres.sql file --- frappe/database/postgres/framework_postgres.sql | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/database/postgres/framework_postgres.sql b/frappe/database/postgres/framework_postgres.sql index 16114f8cf8..26760dbcc9 100644 --- a/frappe/database/postgres/framework_postgres.sql +++ b/frappe/database/postgres/framework_postgres.sql @@ -222,9 +222,9 @@ CREATE TABLE "tabDocType" ( "allow_guest_to_view" smallint NOT NULL DEFAULT 0, "route" varchar(255) DEFAULT NULL, "is_published_field" varchar(255) DEFAULT NULL, - `email_append_to` smallint NOT NULL DEFAULT 0, - `subject_field` varchar(255) DEFAULT NULL, - `sender_field` varchar(255) DEFAULT NULL, + "email_append_to" smallint NOT NULL DEFAULT 0, + "subject_field" varchar(255) DEFAULT NULL, + "sender_field" varchar(255) DEFAULT NULL, PRIMARY KEY ("name") ) ; From 0f5326a4cda82fcf2ff3fcc26b0fb45919d6a98f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 29 Jan 2020 18:21:34 +0530 Subject: [PATCH 019/408] feat: api indexing --- .../doctype/google_indexing/__init__.py | 0 .../google_indexing/google_indexing.js | 31 +++++ .../google_indexing/google_indexing.json | 59 +++++++++ .../google_indexing/google_indexing.py | 120 ++++++++++++++++++ .../google_indexing/test_google_indexing.py | 10 ++ 5 files changed, 220 insertions(+) create mode 100644 frappe/integrations/doctype/google_indexing/__init__.py create mode 100644 frappe/integrations/doctype/google_indexing/google_indexing.js create mode 100644 frappe/integrations/doctype/google_indexing/google_indexing.json create mode 100644 frappe/integrations/doctype/google_indexing/google_indexing.py create mode 100644 frappe/integrations/doctype/google_indexing/test_google_indexing.py diff --git a/frappe/integrations/doctype/google_indexing/__init__.py b/frappe/integrations/doctype/google_indexing/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.js b/frappe/integrations/doctype/google_indexing/google_indexing.js new file mode 100644 index 0000000000..d742effd05 --- /dev/null +++ b/frappe/integrations/doctype/google_indexing/google_indexing.js @@ -0,0 +1,31 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Google Indexing', { + refresh: function(frm) { + if (!frm.doc.enable) { + frm.dashboard.set_headline(__("To use Google API Indexing, enable {0}.", [`${__('Google Settings')}`])); + } + }, + + authorize_api_indexing_access: function(frm) { + let reauthorize = 0; + if(frm.doc.authorization_code) { + reauthorize = 1; + } + + frappe.call({ + method: "frappe.integrations.doctype.google_indexing.google_indexing.authorize_access", + args: { + "g_contact": frm.doc.name, + "reauthorize": reauthorize + }, + callback: function(r) { + if(!r.exc) { + frm.save(); + window.open(r.message.url); + } + } + }); + } +}); \ No newline at end of file diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.json b/frappe/integrations/doctype/google_indexing/google_indexing.json new file mode 100644 index 0000000000..669c46b5f1 --- /dev/null +++ b/frappe/integrations/doctype/google_indexing/google_indexing.json @@ -0,0 +1,59 @@ +{ + "actions": [], + "creation": "2020-02-03 09:24:27.503449", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enable_indexing", + "authorize_api_indexing_access", + "refresh_token", + "access_token" + ], + "fields": [ + { + "default": "0", + "fieldname": "enable_indexing", + "fieldtype": "Check", + "label": "Enable Indexing" + }, + { + "fieldname": "authorize_api_indexing_access", + "fieldtype": "Button", + "label": "Authorize API Indexing Access" + }, + { + "fieldname": "refresh_token", + "fieldtype": "Password", + "label": "Refresh Token" + }, + { + "fieldname": "access_token", + "fieldtype": "Password", + "label": "Access Token" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-02-03 09:24:51.894310", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Google Indexing", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.py b/frappe/integrations/doctype/google_indexing/google_indexing.py new file mode 100644 index 0000000000..f9b62ce5c8 --- /dev/null +++ b/frappe/integrations/doctype/google_indexing/google_indexing.py @@ -0,0 +1,120 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import requests +import googleapiclient.discovery +import google.oauth2.credentials +import os + +from frappe import _ +from googleapiclient.errors import HttpError +from frappe.model.document import Document +from frappe.integrations.doctype.google_settings.google_settings import get_auth_url + +SCOPES = "https://www.googleapis.com/auth/indexing" + +class GoogleIndexing(Document): + def validate(self): + if not frappe.db.get_single_value("Google Settings", "enable"): + frappe.throw(_("Enable Google API in Google Settings.")) + + def get_access_token(self): + google_settings = frappe.get_doc("Google Settings") + + if not google_settings.enable: + frappe.throw(_("Google Integration is disabled.")) + + if not self.refresh_token: + button_label = frappe.bold(_("Allow API Indexing Access")) + raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) + + data = { + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "refresh_token": self.get_password(fieldname="refresh_token", raise_exception=False), + "grant_type": "refresh_token", + "scope": SCOPES + } + + try: + r = requests.post(get_auth_url(), data=data).json() + except requests.exceptions.HTTPError: + button_label = frappe.bold(_("Allow Google Drive Access")) + frappe.throw(_("Something went wrong during the token generation. Click on {0} to generate a new one.").format(button_label)) + + return r.get("access_token") + +@frappe.whitelist() +def authorize_access(reauthorize=None): + """ + If no Authorization code get it from Google and then request for Refresh Token. + Google Contact Name is set to flags to set_value after Authorization Code is obtained. + """ + + google_settings = frappe.get_doc("Google Settings") + google_indexing = frappe.get_doc("Google Indexing") + + redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.google_indexing.google_indexing.google_callback" + + if not google_drive.authorization_code or reauthorize: + return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) + else: + try: + data = { + "code": google_indexing.authorization_code, + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "redirect_uri": redirect_uri, + "grant_type": "authorization_code" + } + r = requests.post(get_auth_url(), data=data).json() + + if "refresh_token" in r: + frappe.db.set_value("Google Indexing", google_indexing.name, "refresh_token", r.get("refresh_token")) + frappe.db.commit() + + frappe.local.response["type"] = "redirect" + frappe.local.response["location"] = "/desk#Form/{0}".format(quote("Google Indexing")) + + frappe.msgprint(_("Google Indexing has been configured.")) + except Exception as e: + frappe.throw(e) + +def get_authentication_url(client_id, redirect_uri): + return { + "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) + } + +@frappe.whitelist() +def google_callback(code=None): + """ + Authorization code is sent to callback as per the API configuration + """ + frappe.db.set_value("Google Indexing", None, "authorization_code", code) + frappe.db.commit() + + authorize_access() + +def get_google_indexing_object(): + """ + Returns an object of Google Indexing Service + """ + google_settings = frappe.get_doc("Google Settings") + account = frappe.get_doc("Google Indexing") + + credentials_dict = { + "token": account.get_access_token(), + "refresh_token": account.get_password(fieldname="refresh_token", raise_exception=False), + "token_uri": get_auth_url(), + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "scopes": "https://www.googleapis.com/auth/indexing" + } + + credentials = google.oauth2.credentials.Credentials(**credentials_dict) + google_indexing = googleapiclient.discovery.build("indexing", "v3", credentials=credentials) + + return google_indexing, account \ No newline at end of file diff --git a/frappe/integrations/doctype/google_indexing/test_google_indexing.py b/frappe/integrations/doctype/google_indexing/test_google_indexing.py new file mode 100644 index 0000000000..6bf7dc9d3e --- /dev/null +++ b/frappe/integrations/doctype/google_indexing/test_google_indexing.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestGoogleIndexing(unittest.TestCase): + pass From dcbbe68bec6cecbaf76b616596724af8d4d290e6 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 30 Jan 2020 12:29:50 +0530 Subject: [PATCH 020/408] feat: add methods to publish url --- .../google_indexing/google_indexing.json | 11 +++++++---- .../doctype/google_indexing/google_indexing.py | 17 +++++++++++++++-- 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.json b/frappe/integrations/doctype/google_indexing/google_indexing.json index 669c46b5f1..00b544449d 100644 --- a/frappe/integrations/doctype/google_indexing/google_indexing.json +++ b/frappe/integrations/doctype/google_indexing/google_indexing.json @@ -8,7 +8,7 @@ "enable_indexing", "authorize_api_indexing_access", "refresh_token", - "access_token" + "authorization_code" ], "fields": [ { @@ -18,6 +18,7 @@ "label": "Enable Indexing" }, { + "depends_on": "eval:!doc.__islocal && doc.enable_indexing", "fieldname": "authorize_api_indexing_access", "fieldtype": "Button", "label": "Authorize API Indexing Access" @@ -25,17 +26,19 @@ { "fieldname": "refresh_token", "fieldtype": "Password", + "hidden": 1, "label": "Refresh Token" }, { - "fieldname": "access_token", + "fieldname": "authorization_code", "fieldtype": "Password", - "label": "Access Token" + "hidden": 1, + "label": "Authorization Code" } ], "issingle": 1, "links": [], - "modified": "2020-02-03 09:24:51.894310", + "modified": "2020-02-03 17:11:52.094597", "modified_by": "Administrator", "module": "Integrations", "name": "Google Indexing", diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.py b/frappe/integrations/doctype/google_indexing/google_indexing.py index f9b62ce5c8..2ca09b60f1 100644 --- a/frappe/integrations/doctype/google_indexing/google_indexing.py +++ b/frappe/integrations/doctype/google_indexing/google_indexing.py @@ -11,7 +11,9 @@ import os from frappe import _ from googleapiclient.errors import HttpError +from frappe.utils import get_request_site_address from frappe.model.document import Document +from six.moves.urllib.parse import quote from frappe.integrations.doctype.google_settings.google_settings import get_auth_url SCOPES = "https://www.googleapis.com/auth/indexing" @@ -59,7 +61,7 @@ def authorize_access(reauthorize=None): redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.google_indexing.google_indexing.google_callback" - if not google_drive.authorization_code or reauthorize: + if not google_indexing.authorization_code or reauthorize: return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) else: try: @@ -117,4 +119,15 @@ def get_google_indexing_object(): credentials = google.oauth2.credentials.Credentials(**credentials_dict) google_indexing = googleapiclient.discovery.build("indexing", "v3", credentials=credentials) - return google_indexing, account \ No newline at end of file + return google_indexing + +def publish_site(url, operation_type="URL_UPDATED"): + google_indexing = get_google_indexing_object() + body = { + "url": url, + "type": operation_type + } + try: + google_indexing.urlNotifications().publish(body=body, x__xgafv='2').execute() + except HttpError as e: + frappe.log_error(message=e, title='API Indexing Issue') \ No newline at end of file From d8de63b341b1ee4f70816e4b4f63518a94507a17 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 31 Jan 2020 17:31:05 +0530 Subject: [PATCH 021/408] fix: enqueue publish on update and trash --- frappe/website/website_generator.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 654eed838b..36946964db 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -81,8 +81,18 @@ class WebsiteGenerator(Document): '''Return breadcrumbs''' pass + def on_update(self): + if frappe.db.get_single_value('Google Indexing', 'enable_indexing'): + url = frappe.utils.get_url(self.route) + frappe.enqueue('frappe.integrations.doctype.google_indexing.google_indexing.publish_site', \ + url=url) + def on_trash(self): self.clear_cache() + if frappe.db.get_single_value('Google Indexing', 'enable_indexing'): + url = frappe.utils.get_url(self.route) + frappe.enqueue('frappe.integrations.doctype.google_indexing.google_indexing.publish_site', \ + url=url, operation_type='URL_DELETED') def is_website_published(self): """Return true if published in website""" From 1ff6333666b369702ea9fbe25ba41995cf402811 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 3 Feb 2020 18:35:00 +0530 Subject: [PATCH 022/408] fix: update blog post and web page on update and on trash --- frappe/website/doctype/blog_post/blog_post.py | 4 ++++ frappe/website/doctype/web_page/web_page.py | 6 ++++++ 2 files changed, 10 insertions(+) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 8dbc176f89..148ba15be7 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -44,8 +44,12 @@ class BlogPost(WebsiteGenerator): WHERE `name`=%s""", (self.blogger,)) def on_update(self): + super(BlogPost, self).on_update() clear_cache("writers") + def on_trash(self): + super(BlogPost, self).on_trash() + def get_context(self, context): # this is for double precaution. usually it wont reach this code if not published if not cint(self.published): diff --git a/frappe/website/doctype/web_page/web_page.py b/frappe/website/doctype/web_page/web_page.py index 9509e57798..d6cb7cccb7 100644 --- a/frappe/website/doctype/web_page/web_page.py +++ b/frappe/website/doctype/web_page/web_page.py @@ -28,6 +28,12 @@ class WebPage(WebsiteGenerator): def get_feed(self): return self.title + def on_update(self): + super(BlogPost, self).on_update() + + def on_trash(self): + super(BlogPost, self).on_trash() + def get_context(self, context): context.main_section = get_html_content_based_on_type(self, 'main_section', self.content_type) self.render_dynamic(context) From 6684c0cd3ceec5761ccda709e484bece741a8956 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Wed, 5 Feb 2020 17:47:13 +0530 Subject: [PATCH 023/408] fix: add indexing configuration to website settings --- .../doctype/google_indexing/__init__.py | 0 .../google_indexing/google_indexing.js | 31 --------- .../google_indexing/google_indexing.json | 62 ------------------ .../website_settings}/google_indexing.py | 64 ++++++------------- .../test_website_settings.py} | 2 +- .../website_settings/website_settings.js | 21 ++++++ .../website_settings/website_settings.json | 34 +++++++++- .../website_settings/website_settings.py | 36 +++++++++++ frappe/website/website_generator.py | 20 +++--- 9 files changed, 121 insertions(+), 149 deletions(-) delete mode 100644 frappe/integrations/doctype/google_indexing/__init__.py delete mode 100644 frappe/integrations/doctype/google_indexing/google_indexing.js delete mode 100644 frappe/integrations/doctype/google_indexing/google_indexing.json rename frappe/{integrations/doctype/google_indexing => website/doctype/website_settings}/google_indexing.py (58%) rename frappe/{integrations/doctype/google_indexing/test_google_indexing.py => website/doctype/website_settings/test_website_settings.py} (79%) diff --git a/frappe/integrations/doctype/google_indexing/__init__.py b/frappe/integrations/doctype/google_indexing/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.js b/frappe/integrations/doctype/google_indexing/google_indexing.js deleted file mode 100644 index d742effd05..0000000000 --- a/frappe/integrations/doctype/google_indexing/google_indexing.js +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Google Indexing', { - refresh: function(frm) { - if (!frm.doc.enable) { - frm.dashboard.set_headline(__("To use Google API Indexing, enable {0}.", [`${__('Google Settings')}`])); - } - }, - - authorize_api_indexing_access: function(frm) { - let reauthorize = 0; - if(frm.doc.authorization_code) { - reauthorize = 1; - } - - frappe.call({ - method: "frappe.integrations.doctype.google_indexing.google_indexing.authorize_access", - args: { - "g_contact": frm.doc.name, - "reauthorize": reauthorize - }, - callback: function(r) { - if(!r.exc) { - frm.save(); - window.open(r.message.url); - } - } - }); - } -}); \ No newline at end of file diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.json b/frappe/integrations/doctype/google_indexing/google_indexing.json deleted file mode 100644 index 00b544449d..0000000000 --- a/frappe/integrations/doctype/google_indexing/google_indexing.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "actions": [], - "creation": "2020-02-03 09:24:27.503449", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "enable_indexing", - "authorize_api_indexing_access", - "refresh_token", - "authorization_code" - ], - "fields": [ - { - "default": "0", - "fieldname": "enable_indexing", - "fieldtype": "Check", - "label": "Enable Indexing" - }, - { - "depends_on": "eval:!doc.__islocal && doc.enable_indexing", - "fieldname": "authorize_api_indexing_access", - "fieldtype": "Button", - "label": "Authorize API Indexing Access" - }, - { - "fieldname": "refresh_token", - "fieldtype": "Password", - "hidden": 1, - "label": "Refresh Token" - }, - { - "fieldname": "authorization_code", - "fieldtype": "Password", - "hidden": 1, - "label": "Authorization Code" - } - ], - "issingle": 1, - "links": [], - "modified": "2020-02-03 17:11:52.094597", - "modified_by": "Administrator", - "module": "Integrations", - "name": "Google Indexing", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_indexing/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py similarity index 58% rename from frappe/integrations/doctype/google_indexing/google_indexing.py rename to frappe/website/doctype/website_settings/google_indexing.py index 2ca09b60f1..c674e66f6a 100644 --- a/frappe/integrations/doctype/google_indexing/google_indexing.py +++ b/frappe/website/doctype/website_settings/google_indexing.py @@ -7,7 +7,6 @@ import frappe import requests import googleapiclient.discovery import google.oauth2.credentials -import os from frappe import _ from googleapiclient.errors import HttpError @@ -18,94 +17,66 @@ from frappe.integrations.doctype.google_settings.google_settings import get_auth SCOPES = "https://www.googleapis.com/auth/indexing" -class GoogleIndexing(Document): - def validate(self): - if not frappe.db.get_single_value("Google Settings", "enable"): - frappe.throw(_("Enable Google API in Google Settings.")) - - def get_access_token(self): - google_settings = frappe.get_doc("Google Settings") - - if not google_settings.enable: - frappe.throw(_("Google Integration is disabled.")) - - if not self.refresh_token: - button_label = frappe.bold(_("Allow API Indexing Access")) - raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) - - data = { - "client_id": google_settings.client_id, - "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), - "refresh_token": self.get_password(fieldname="refresh_token", raise_exception=False), - "grant_type": "refresh_token", - "scope": SCOPES - } - - try: - r = requests.post(get_auth_url(), data=data).json() - except requests.exceptions.HTTPError: - button_label = frappe.bold(_("Allow Google Drive Access")) - frappe.throw(_("Something went wrong during the token generation. Click on {0} to generate a new one.").format(button_label)) - - return r.get("access_token") @frappe.whitelist() def authorize_access(reauthorize=None): """ If no Authorization code get it from Google and then request for Refresh Token. - Google Contact Name is set to flags to set_value after Authorization Code is obtained. """ google_settings = frappe.get_doc("Google Settings") - google_indexing = frappe.get_doc("Google Indexing") + website_settings = frappe.get_doc("Website Settings") - redirect_uri = get_request_site_address(True) + "?cmd=frappe.integrations.doctype.google_indexing.google_indexing.google_callback" + redirect_uri = get_request_site_address(True) + "?cmd=frappe.website.doctype.website_settings.google_indexing.google_callback" - if not google_indexing.authorization_code or reauthorize: + if not website_settings.authorization_code or reauthorize: return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) else: try: data = { - "code": google_indexing.authorization_code, + "code": website_settings.authorization_code, "client_id": google_settings.client_id, "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), "redirect_uri": redirect_uri, "grant_type": "authorization_code" } - r = requests.post(get_auth_url(), data=data).json() + res = requests.post(get_auth_url(), data=data).json() - if "refresh_token" in r: - frappe.db.set_value("Google Indexing", google_indexing.name, "refresh_token", r.get("refresh_token")) + if "refresh_token" in res: + frappe.db.set_value("Website Settings", website_settings.name, "refresh_token", res.get("refresh_token")) frappe.db.commit() frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/desk#Form/{0}".format(quote("Google Indexing")) + frappe.local.response["location"] = "/desk#Form/{0}".format(quote("Website Settings")) frappe.msgprint(_("Google Indexing has been configured.")) except Exception as e: frappe.throw(e) def get_authentication_url(client_id, redirect_uri): + ''' Return authentication url with the client id and redirect uri ''' return { "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) } + @frappe.whitelist() def google_callback(code=None): """ Authorization code is sent to callback as per the API configuration """ - frappe.db.set_value("Google Indexing", None, "authorization_code", code) + frappe.db.set_value("Website Settings", None, "authorization_code", code) frappe.db.commit() authorize_access() + def get_google_indexing_object(): """ - Returns an object of Google Indexing Service + Returns an object of Website Settings Service """ google_settings = frappe.get_doc("Google Settings") - account = frappe.get_doc("Google Indexing") + account = frappe.get_doc("Website Settings") credentials_dict = { "token": account.get_access_token(), @@ -121,13 +92,16 @@ def get_google_indexing_object(): return google_indexing + def publish_site(url, operation_type="URL_UPDATED"): + ''' Send an update/remove url request ''' + google_indexing = get_google_indexing_object() - body = { + body = { "url": url, "type": operation_type } try: google_indexing.urlNotifications().publish(body=body, x__xgafv='2').execute() except HttpError as e: - frappe.log_error(message=e, title='API Indexing Issue') \ No newline at end of file + frappe.log_error(message=e, title='API Indexing Issue') diff --git a/frappe/integrations/doctype/google_indexing/test_google_indexing.py b/frappe/website/doctype/website_settings/test_website_settings.py similarity index 79% rename from frappe/integrations/doctype/google_indexing/test_google_indexing.py rename to frappe/website/doctype/website_settings/test_website_settings.py index 6bf7dc9d3e..9eca957713 100644 --- a/frappe/integrations/doctype/google_indexing/test_google_indexing.py +++ b/frappe/website/doctype/website_settings/test_website_settings.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestGoogleIndexing(unittest.TestCase): +class TestWebsiteSettings(unittest.TestCase): pass diff --git a/frappe/website/doctype/website_settings/website_settings.js b/frappe/website/doctype/website_settings/website_settings.js index fd2d490c54..6afffb3bce 100644 --- a/frappe/website/doctype/website_settings/website_settings.js +++ b/frappe/website/doctype/website_settings/website_settings.js @@ -35,6 +35,27 @@ frappe.ui.form.on('Website Settings', { } }, + authorize_api_indexing_access: function(frm) { + let reauthorize = 0; + if(frm.doc.authorization_code) { + reauthorize = 1; + } + + frappe.call({ + method: "frappe.website.doctype.website_settings.google_indexing.authorize_access", + args: { + "g_indexing": frm.doc.name, + "reauthorize": reauthorize + }, + callback: function(r) { + if(!r.exc) { + frm.save(); + window.open(r.message.url); + } + } + }); + }, + set_parent_options: function(frm, doctype, name) { var item = frappe.get_doc(doctype, name); if(item.parentfield === "top_bar_items") { diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 025f7df750..2d0ac1f4e8 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2013-04-30 12:58:46", "doctype": "DocType", "document_type": "Other", @@ -28,8 +29,12 @@ "footer_items", "hide_footer_signup", "integrations", - "google_analytics_id", + "enable_google_indexing", + "authorize_api_indexing_access", + "indexing_refresh_token", + "indexing_authorization_code", "column_break_17", + "google_analytics_id", "misc_section", "subdomain", "disable_signup", @@ -287,13 +292,38 @@ "fieldtype": "Table", "label": "Route Redirects", "options": "Website Route Redirect" + }, + { + "default": "0", + "fieldname": "enable_google_indexing", + "fieldtype": "Check", + "label": "Enable Google Indexing" + }, + { + "fieldname": "indexing_refresh_token", + "fieldtype": "Password", + "hidden": 1, + "label": "Indexing Refresh Token" + }, + { + "fieldname": "indexing_authorization_code", + "fieldtype": "Password", + "label": "Indexing Authorization Code", + "read_only": 1 + }, + { + "depends_on": "eval: doc.enable_google_indexing", + "fieldname": "authorize_api_indexing_access", + "fieldtype": "Button", + "label": "Authorize API Indexing Access" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, + "links": [], "max_attachments": 10, - "modified": "2019-06-12 17:16:37.452872", + "modified": "2020-02-11 00:44:26.731674", "modified_by": "Administrator", "module": "Website", "name": "Website Settings", diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index 64558e751a..b4cf3c37a3 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -2,6 +2,7 @@ # MIT License. See license.txt from __future__ import unicode_literals +import requests import frappe from frappe import _ from frappe.utils import get_request_site_address, encode @@ -9,12 +10,16 @@ from frappe.model.document import Document from six.moves.urllib.parse import quote from frappe.website.router import resolve_route from frappe.website.doctype.website_theme.website_theme import add_website_theme +from frappe.integrations.doctype.google_settings.google_settings import get_auth_url + +INDEXING_SCOPES = "https://www.googleapis.com/auth/indexing" class WebsiteSettings(Document): def validate(self): self.validate_top_bar_items() self.validate_footer_items() self.validate_home_page() + self.validate_google_settings() def validate_home_page(self): if frappe.flags.in_install: @@ -53,6 +58,10 @@ class WebsiteSettings(Document): frappe.throw(_("{0} in row {1} cannot have both URL and child items").format(footer_item.parent_label, footer_item.idx)) + def validate_google_settings(self): + if not frappe.db.get_single_value("Google Settings", "enable"): + frappe.throw(_("Enable Google API in Google Settings.")) + def on_update(self): self.clear_cache() @@ -67,6 +76,33 @@ class WebsiteSettings(Document): # clears role based home pages frappe.clear_cache() + def get_access_token(self): + google_settings = frappe.get_doc("Google Settings") + + if not google_settings.enable: + frappe.throw(_("Google Integration is disabled.")) + + if not self.refresh_token: + button_label = frappe.bold(_("Allow API Indexing Access")) + raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) + + data = { + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "refresh_token": self.get_password(fieldname="indexing_refresh_token", raise_exception=False), + "grant_type": "refresh_token", + "scope": INDEXING_SCOPES + } + + try: + res = requests.post(get_auth_url(), data=data).json() + except requests.exceptions.HTTPError: + button_label = frappe.bold(_("Allow Google Indexing Access")) + frappe.throw(_("Something went wrong during the token generation. Click on {0} to generate a new one.").format(button_label)) + + return res.get("access_token") + + def get_website_settings(): hooks = frappe.get_hooks() context = frappe._dict({ diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 36946964db..017af714fe 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -82,17 +82,11 @@ class WebsiteGenerator(Document): pass def on_update(self): - if frappe.db.get_single_value('Google Indexing', 'enable_indexing'): - url = frappe.utils.get_url(self.route) - frappe.enqueue('frappe.integrations.doctype.google_indexing.google_indexing.publish_site', \ - url=url) + self.send_indexing_request() def on_trash(self): self.clear_cache() - if frappe.db.get_single_value('Google Indexing', 'enable_indexing'): - url = frappe.utils.get_url(self.route) - frappe.enqueue('frappe.integrations.doctype.google_indexing.google_indexing.publish_site', \ - url=url, operation_type='URL_DELETED') + self.send_indexing_request('URL_DELETED') def is_website_published(self): """Return true if published in website""" @@ -126,3 +120,13 @@ class WebsiteGenerator(Document): route.page_title = self.get(self.get_title_field()) return route + + def send_indexing_request(self, operation_type='URL_UPDATED'): + ''' Send indexing request on update/trash operation ''' + + if frappe.db.get_single_value('Google Indexing', 'enable_indexing')\ + and self.meta.is_published_field and self.meta.allow_guest_to_view: + + url = frappe.utils.get_url(self.route) + frappe.enqueue('frappe.integrations.doctype.google_indexing.google_indexing.publish_site', \ + url=url, operation_type=operation_type) \ No newline at end of file From 3633b452adf8387438c2b1b46a5e8ca738b346b7 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 10 Feb 2020 23:52:52 +0530 Subject: [PATCH 024/408] fix: make field names more descriptive --- .../doctype/website_settings/google_indexing.py | 10 +++++----- .../doctype/website_settings/website_settings.py | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/website/doctype/website_settings/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py index c674e66f6a..b6ddc043f9 100644 --- a/frappe/website/doctype/website_settings/google_indexing.py +++ b/frappe/website/doctype/website_settings/google_indexing.py @@ -29,12 +29,12 @@ def authorize_access(reauthorize=None): redirect_uri = get_request_site_address(True) + "?cmd=frappe.website.doctype.website_settings.google_indexing.google_callback" - if not website_settings.authorization_code or reauthorize: + if not website_settings.indexing_authorization_code or reauthorize: return get_authentication_url(client_id=google_settings.client_id, redirect_uri=redirect_uri) else: try: data = { - "code": website_settings.authorization_code, + "code": website_settings.indexing_authorization_code, "client_id": google_settings.client_id, "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), "redirect_uri": redirect_uri, @@ -43,7 +43,7 @@ def authorize_access(reauthorize=None): res = requests.post(get_auth_url(), data=data).json() if "refresh_token" in res: - frappe.db.set_value("Website Settings", website_settings.name, "refresh_token", res.get("refresh_token")) + frappe.db.set_value("Website Settings", website_settings.name, "indexing_refresh_token", res.get("refresh_token")) frappe.db.commit() frappe.local.response["type"] = "redirect" @@ -65,7 +65,7 @@ def google_callback(code=None): """ Authorization code is sent to callback as per the API configuration """ - frappe.db.set_value("Website Settings", None, "authorization_code", code) + frappe.db.set_value("Website Settings", None, "indexing_authorization_code", code) frappe.db.commit() authorize_access() @@ -80,7 +80,7 @@ def get_google_indexing_object(): credentials_dict = { "token": account.get_access_token(), - "refresh_token": account.get_password(fieldname="refresh_token", raise_exception=False), + "refresh_token": account.get_password(fieldname="indexing_refresh_token", raise_exception=False), "token_uri": get_auth_url(), "client_id": google_settings.client_id, "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index b4cf3c37a3..20e0fa67a2 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -82,7 +82,7 @@ class WebsiteSettings(Document): if not google_settings.enable: frappe.throw(_("Google Integration is disabled.")) - if not self.refresh_token: + if not self.indexing_refresh_token: button_label = frappe.bold(_("Allow API Indexing Access")) raise frappe.ValidationError(_("Click on {0} to generate Refresh Token.").format(button_label)) From 6dff5d383192f2480c8e6834c34869be168dec21 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 11 Feb 2020 09:58:43 +0530 Subject: [PATCH 025/408] fix: validate google settings --- .../website/doctype/website_settings/website_settings.json | 6 ++++-- frappe/website/doctype/website_settings/website_settings.py | 2 +- frappe/website/website_generator.py | 4 ++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index 2d0ac1f4e8..b14e8c246d 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -303,11 +303,13 @@ "fieldname": "indexing_refresh_token", "fieldtype": "Password", "hidden": 1, - "label": "Indexing Refresh Token" + "label": "Indexing Refresh Token", + "read_only": 1 }, { "fieldname": "indexing_authorization_code", "fieldtype": "Password", + "hidden": 1, "label": "Indexing Authorization Code", "read_only": 1 }, @@ -323,7 +325,7 @@ "issingle": 1, "links": [], "max_attachments": 10, - "modified": "2020-02-11 00:44:26.731674", + "modified": "2020-02-11 09:53:08.872362", "modified_by": "Administrator", "module": "Website", "name": "Website Settings", diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index 20e0fa67a2..4356b1aa82 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -59,7 +59,7 @@ class WebsiteSettings(Document): footer_item.idx)) def validate_google_settings(self): - if not frappe.db.get_single_value("Google Settings", "enable"): + if self.enable_google_indexing and not frappe.db.get_single_value("Google Settings", "enable"): frappe.throw(_("Enable Google API in Google Settings.")) def on_update(self): diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 017af714fe..3bec631131 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -124,9 +124,9 @@ class WebsiteGenerator(Document): def send_indexing_request(self, operation_type='URL_UPDATED'): ''' Send indexing request on update/trash operation ''' - if frappe.db.get_single_value('Google Indexing', 'enable_indexing')\ + if frappe.db.get_single_value('Website Settings', 'enable_google_indexing')\ and self.meta.is_published_field and self.meta.allow_guest_to_view: url = frappe.utils.get_url(self.route) - frappe.enqueue('frappe.integrations.doctype.google_indexing.google_indexing.publish_site', \ + frappe.enqueue('frappe.website.doctype.website_settings.google_indexing.publish_site', \ url=url, operation_type=operation_type) \ No newline at end of file From 12621d4a23ecf2ef15ec573f8bac75e08034802f Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 11 Feb 2020 12:22:30 +0530 Subject: [PATCH 026/408] style: formatting fixes --- frappe/website/doctype/web_page/web_page.py | 4 ++-- frappe/website/doctype/website_settings/website_settings.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/web_page/web_page.py b/frappe/website/doctype/web_page/web_page.py index d6cb7cccb7..ee0c4de074 100644 --- a/frappe/website/doctype/web_page/web_page.py +++ b/frappe/website/doctype/web_page/web_page.py @@ -29,10 +29,10 @@ class WebPage(WebsiteGenerator): return self.title def on_update(self): - super(BlogPost, self).on_update() + super(WebPage, self).on_update() def on_trash(self): - super(BlogPost, self).on_trash() + super(WebPage, self).on_trash() def get_context(self, context): context.main_section = get_html_content_based_on_type(self, 'main_section', self.content_type) diff --git a/frappe/website/doctype/website_settings/website_settings.js b/frappe/website/doctype/website_settings/website_settings.js index 6afffb3bce..38e1ff993a 100644 --- a/frappe/website/doctype/website_settings/website_settings.js +++ b/frappe/website/doctype/website_settings/website_settings.js @@ -37,7 +37,7 @@ frappe.ui.form.on('Website Settings', { authorize_api_indexing_access: function(frm) { let reauthorize = 0; - if(frm.doc.authorization_code) { + if (frm.doc.authorization_code) { reauthorize = 1; } @@ -48,7 +48,7 @@ frappe.ui.form.on('Website Settings', { "reauthorize": reauthorize }, callback: function(r) { - if(!r.exc) { + if (!r.exc) { frm.save(); window.open(r.message.url); } From c1c5a4e363134b5a1ab600b546da1c1cfe1b50c2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 18 Feb 2020 22:26:06 +0530 Subject: [PATCH 027/408] fix: minor changes --- frappe/website/doctype/website_settings/google_indexing.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/website/doctype/website_settings/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py index b6ddc043f9..dded822c9d 100644 --- a/frappe/website/doctype/website_settings/google_indexing.py +++ b/frappe/website/doctype/website_settings/google_indexing.py @@ -11,7 +11,6 @@ import google.oauth2.credentials from frappe import _ from googleapiclient.errors import HttpError from frappe.utils import get_request_site_address -from frappe.model.document import Document from six.moves.urllib.parse import quote from frappe.integrations.doctype.google_settings.google_settings import get_auth_url @@ -73,7 +72,7 @@ def google_callback(code=None): def get_google_indexing_object(): """ - Returns an object of Website Settings Service + Returns an object of Google Indexing object """ google_settings = frappe.get_doc("Google Settings") account = frappe.get_doc("Website Settings") From 6157719a7d24f5ac7befdd8c4d279bb59873e697 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 18 Feb 2020 23:37:32 +0530 Subject: [PATCH 028/408] feat(newsletter): add an option for hourly schedule send --- .../email/doctype/newsletter/newsletter.json | 20 ++++++++++++++++--- frappe/email/doctype/newsletter/newsletter.py | 10 +++++++++- frappe/hooks.py | 3 ++- 3 files changed, 28 insertions(+), 5 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index 1ef51027ed..fcd2d5bfe2 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_rename": 1, "creation": "2013-01-10 16:34:31", "description": "Create and Send Newsletters", @@ -6,9 +7,11 @@ "document_type": "Other", "engine": "InnoDB", "field_order": [ + "send_from", + "column_break_2", + "schedule_send", "recipients", "email_group", - "send_from", "email_sent", "newsletter_content", "subject", @@ -41,7 +44,7 @@ "default": "0", "fieldname": "email_sent", "fieldtype": "Check", - "label": "Email Sent?", + "label": "Email Sent", "no_copy": 1, "read_only": 1 }, @@ -115,14 +118,25 @@ "fieldname": "recipients", "fieldtype": "Section Break", "label": "Recipients" + }, + { + "depends_on": "eval: !doc.email_sent", + "fieldname": "schedule_send", + "fieldtype": "Datetime", + "label": "Schedule Send" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" } ], "has_web_view": 1, "icon": "fa fa-envelope", "idx": 1, "is_published_field": "published", + "links": [], "max_attachments": 3, - "modified": "2019-09-06 22:15:55.471254", + "modified": "2020-02-18 09:51:41.090261", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index b0d1756643..c7134eb9b5 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -11,7 +11,7 @@ from frappe.utils.verified_command import get_signed_params, verify_request from frappe.utils.background_jobs import enqueue from frappe.email.queue import send from frappe.email.doctype.email_group.email_group import add_subscribers -from frappe.utils import parse_addr +from frappe.utils import parse_addr, now_datetime from frappe.utils import validate_email_address @@ -250,3 +250,11 @@ def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20 '''.format(','.join(['%s'] * len(email_group_list)), limit_page_length, limit_start), email_group_list, as_dict=1) +def send_scheduled_email(): + ''' Send scheduled newsletter to the recipients ''' + scheduled_newsletter = frappe.get_all('Newsletter', filters = { + 'schedule_send': ('<=', now_datetime()), + 'email_sent': 0 + }, fields = ['name']) + for newsletter in scheduled_newsletter: + send_newsletter(newsletter.name) \ No newline at end of file diff --git a/frappe/hooks.py b/frappe/hooks.py index a132eb69d5..794ef7ef38 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -183,7 +183,8 @@ scheduler_events = { "frappe.desk.page.backups.backups.delete_downloadable_backups", "frappe.deferred_insert.save_to_db", "frappe.desk.form.document_follow.send_hourly_updates", - "frappe.integrations.doctype.google_calendar.google_calendar.sync" + "frappe.integrations.doctype.google_calendar.google_calendar.sync", + "frappe.email.doctype.newsletter.newsletter.send_scheduled_email" ], "daily": [ "frappe.email.queue.clear_outbox", From 7e618550f516997074c83e1edb21ec09fc1de728 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 21 Feb 2020 11:39:46 +0530 Subject: [PATCH 029/408] feat(newsletter): add confirmation email, welcome email for email group --- .../doctype/email_group/email_group.json | 170 +++++++----------- frappe/email/doctype/newsletter/newsletter.js | 31 +++- frappe/email/doctype/newsletter/newsletter.py | 55 ++++-- 3 files changed, 127 insertions(+), 129 deletions(-) diff --git a/frappe/email/doctype/email_group/email_group.json b/frappe/email/doctype/email_group/email_group.json index d02cead0b0..0d784d409a 100644 --- a/frappe/email/doctype/email_group/email_group.json +++ b/frappe/email/doctype/email_group/email_group.json @@ -1,120 +1,70 @@ { - "allow_copy": 0, - "allow_import": 1, - "allow_rename": 0, - "autoname": "field:title", - "beta": 0, - "creation": "2015-03-18 06:08:32.729800", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Setup", - "editable_grid": 0, + "actions": [], + "allow_import": 1, + "autoname": "field:title", + "creation": "2015-03-18 06:08:32.729800", + "doctype": "DocType", + "document_type": "Setup", + "field_order": [ + "title", + "total_subscribers", + "confirmation_email_template", + "welcome_email_template" + ], "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_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Title", - "length": 0, - "no_copy": 1, - "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", + "label": "Title", + "no_copy": 1, + "reqd": 1, + "unique": 1 + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "0", - "fieldname": "total_subscribers", - "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": "Total Subscribers", - "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": "0", + "fieldname": "total_subscribers", + "fieldtype": "Int", + "in_list_view": 1, + "label": "Total Subscribers", + "read_only": 1 + }, + { + "fieldname": "confirmation_email_template", + "fieldtype": "Link", + "label": "Confirmation Email Template", + "options": "Email Template" + }, + { + "fieldname": "welcome_email_template", + "fieldtype": "Link", + "label": "Welcome Email Template", + "options": "Email Template" } - ], - "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-02-27 19:01:17.203845", - "modified_by": "Administrator", - "module": "Email", - "name": "Email Group", - "name_case": "", - "owner": "Administrator", + ], + "links": [], + "modified": "2020-02-21 14:12:48.884738", + "modified_by": "Administrator", + "module": "Email", + "name": "Email Group", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "if_owner": 0, - "import": 1, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 1, - "role": "Newsletter Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "import": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Newsletter Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0 + ], + "quick_entry": 1, + "show_name_in_global_search": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index 7bf0ae6b9a..f25763d79a 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -4,17 +4,40 @@ frappe.ui.form.on('Newsletter', { refresh(frm) { let doc = frm.doc; - if(!doc.__islocal && !cint(doc.email_sent) && !doc.__unsaved + let today = new Date(); + if (!doc.__islocal && !cint(doc.email_sent) && !doc.__unsaved && in_list(frappe.boot.user.can_write, doc.doctype)) { - frm.add_custom_button(__('Send'), function() { - frm.call('send_emails').then(() => { - frm.refresh(); + frm.add_custom_button(__('Send Now'), function() { + frappe.confirm(__("Do you really want to send this email?"), function() { + frm.call('send_emails').then(() => { + frm.refresh(); + }); }); }, "fa fa-play", "btn-success"); } frm.events.setup_dashboard(frm); + // setting datepicker options to set min date & min time + frm.get_field('schedule_send').$input.datepicker({ + maxMinutes: 0, + minDate: today, + timeFormat: 'hh', + onSelect: function (fd, d, picker) { + if (!d) return; + var date = d.getDate(); + if (date === today) { + picker.update({ + minHours: (today.getHours() + 1) + }); + } else { + picker.update({ + minHours: 0 + }); + } + } + }); + if(doc.__islocal && !doc.send_from) { let { fullname, email } = frappe.user_info(doc.owner); frm.set_value('send_from', `${fullname} <${email}>`); diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index c7134eb9b5..819ab96546 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -160,26 +160,40 @@ def create_lead(email_id): @frappe.whitelist(allow_guest=True) -def subscribe(email): +def subscribe(email, email_group=_('Website')): url = frappe.utils.get_url("/api/method/frappe.email.doctype.newsletter.newsletter.confirm_subscription") +\ - "?" + get_signed_params({"email": email}) + "?" + get_signed_params({"email": email, "email_group": email_group}) - messages = ( - _("Thank you for your interest in subscribing to our updates"), - _("Please verify your Email Address"), - url, - _("Click here to verify") - ) + confirmation_template = frappe.db.get_value('Email Group', email_group, ['confirmation_email']) - content = """ -

{0}. {1}.

-

{3}

- """ + if confirmation_email: + args = frappe._dict(dict( + user_email=email, + confirmation_url=url, + email_group=email_group + )) - frappe.sendmail(email, subject=_("Confirm Your Email"), content=content.format(*messages)) + email_template = frappe.get_doc("Email Template", welcome_email) + content = frappe.render_template(email_template.response, args) + + + if not message: + messages = ( + _("Thank you for your interest in subscribing to our updates"), + _("Please verify your Email Address"), + url, + _("Click here to verify") + ) + + content = """ +

{0}. {1}.

+

{3}

+ """.format(*messages) + + frappe.sendmail(email, subject=email_template.subject or _("Confirm Your Email"), content=content) @frappe.whitelist(allow_guest=True) -def confirm_subscription(email): +def confirm_subscription(email, email_group=_('Website')): if not verify_request(): return @@ -189,12 +203,23 @@ def confirm_subscription(email): "title": _("Website") }).insert(ignore_permissions=True) - frappe.flags.ignore_permissions = True add_subscribers(_("Website"), email) frappe.db.commit() + welcome_email = frappe.db.get_value('Email Group', _('Website'), 'welcome_email') + + if welcome_email: + args = frappe._dict(dict( + user_email=email, + email_group=email_group + )) + + email_template = frappe.get_doc("Email Template", welcome_email) + message = frappe.render_template(email_template.response, args) + frappe.sendmail(email, subject=email_template.subject, message=message) + frappe.respond_as_web_page(_("Confirmed"), _("{0} has been successfully added to the Email Group.").format(email), indicator_color='green') From 5afbad89306b6efed8c0fc4e0a837eb698cf7ae2 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 21 Feb 2020 21:17:26 +0530 Subject: [PATCH 030/408] fix: add description to indexing check --- .../doctype/website_settings/website_settings.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/website_settings/website_settings.json b/frappe/website/doctype/website_settings/website_settings.json index b14e8c246d..39ffa2329f 100644 --- a/frappe/website/doctype/website_settings/website_settings.json +++ b/frappe/website/doctype/website_settings/website_settings.json @@ -186,7 +186,7 @@ "collapsible": 1, "fieldname": "integrations", "fieldtype": "Section Break", - "label": "Integrations" + "label": "Google Integrations" }, { "description": "Add Google Analytics ID: eg. UA-89XXX57-1. Please search help on Google Analytics for more information.", @@ -295,20 +295,21 @@ }, { "default": "0", + "description": "To use Google Indexing, enable Google Settings.", "fieldname": "enable_google_indexing", "fieldtype": "Check", "label": "Enable Google Indexing" }, { "fieldname": "indexing_refresh_token", - "fieldtype": "Password", + "fieldtype": "Data", "hidden": 1, "label": "Indexing Refresh Token", "read_only": 1 }, { "fieldname": "indexing_authorization_code", - "fieldtype": "Password", + "fieldtype": "Data", "hidden": 1, "label": "Indexing Authorization Code", "read_only": 1 @@ -325,7 +326,7 @@ "issingle": 1, "links": [], "max_attachments": 10, - "modified": "2020-02-11 09:53:08.872362", + "modified": "2020-02-21 16:46:59.947403", "modified_by": "Administrator", "module": "Website", "name": "Website Settings", From 5bc6da78c16858fecec14d73a5b802c1804ab50a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 27 Feb 2020 19:18:36 +0530 Subject: [PATCH 031/408] fix: send email after adding subscriber --- .../email/doctype/email_group/email_group.py | 22 +++++++++++- frappe/email/doctype/newsletter/newsletter.py | 36 +++++++------------ 2 files changed, 33 insertions(+), 25 deletions(-) diff --git a/frappe/email/doctype/email_group/email_group.py b/frappe/email/doctype/email_group/email_group.py index 70210faf37..359c42171e 100755 --- a/frappe/email/doctype/email_group/email_group.py +++ b/frappe/email/doctype/email_group/email_group.py @@ -66,6 +66,11 @@ def import_from(name, doctype): def add_subscribers(name, email_list): if not isinstance(email_list, (list, tuple)): email_list = email_list.replace(",", "\n").split("\n") + + template = frappe.db.get_value('Email Group', name, 'welcome_email_template') + if template: + welcome_email = frappe.get_doc("Email Template", template) + count = 0 for email in email_list: email = email.strip() @@ -78,7 +83,9 @@ def add_subscribers(name, email_list): "doctype": "Email Group Member", "email_group": name, "email": parsed_email - }).insert(ignore_permissions = frappe.flags.ignore_permissions) + }).insert(ignore_permissions=frappe.flags.ignore_permissions) + + send_welcome_email(welcome_email, parsed_email, name) count += 1 else: @@ -90,3 +97,16 @@ def add_subscribers(name, email_list): return frappe.get_doc("Email Group", name).update_total_subscribers() +def send_welcome_email(welcome_email, email, email_group): + ''' Send welcome email for the subscribers of a given email group ''' + + if not welcome_email: + return + + args = dict( + email=email, + email_group=email_group + ) + + message = frappe.render_template(welcome_email.response, args) + frappe.sendmail(email, subject=welcome_email.subject, message=message) \ No newline at end of file diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 819ab96546..4c9de88aad 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -164,20 +164,20 @@ def subscribe(email, email_group=_('Website')): url = frappe.utils.get_url("/api/method/frappe.email.doctype.newsletter.newsletter.confirm_subscription") +\ "?" + get_signed_params({"email": email, "email_group": email_group}) - confirmation_template = frappe.db.get_value('Email Group', email_group, ['confirmation_email']) + email_template = frappe.db.get_value('Email Group', email_group, ['confirmation_email_template']) - if confirmation_email: - args = frappe._dict(dict( - user_email=email, + content='' + if email_template: + args = dict( + email=email, confirmation_url=url, email_group=email_group - )) + ) - email_template = frappe.get_doc("Email Template", welcome_email) + email_template = frappe.get_doc("Email Template", email_template) content = frappe.render_template(email_template.response, args) - - if not message: + if not content: messages = ( _("Thank you for your interest in subscribing to our updates"), _("Please verify your Email Address"), @@ -190,36 +190,24 @@ def subscribe(email, email_group=_('Website')):

{3}

""".format(*messages) - frappe.sendmail(email, subject=email_template.subject or _("Confirm Your Email"), content=content) + frappe.sendmail(email, subject=getattr('email_template', 'subject', '') or _("Confirm Your Email"), content=content) @frappe.whitelist(allow_guest=True) def confirm_subscription(email, email_group=_('Website')): if not verify_request(): return - if not frappe.db.exists("Email Group", _("Website")): + if not frappe.db.exists("Email Group", email_group): frappe.get_doc({ "doctype": "Email Group", - "title": _("Website") + "title": email_group }).insert(ignore_permissions=True) frappe.flags.ignore_permissions = True - add_subscribers(_("Website"), email) + add_subscribers(email_group, email) frappe.db.commit() - welcome_email = frappe.db.get_value('Email Group', _('Website'), 'welcome_email') - - if welcome_email: - args = frappe._dict(dict( - user_email=email, - email_group=email_group - )) - - email_template = frappe.get_doc("Email Template", welcome_email) - message = frappe.render_template(email_template.response, args) - frappe.sendmail(email, subject=email_template.subject, message=message) - frappe.respond_as_web_page(_("Confirmed"), _("{0} has been successfully added to the Email Group.").format(email), indicator_color='green') From 2696eca30a07f845463740184cc7f9eba7ef3959 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 2 Mar 2020 11:39:22 +0530 Subject: [PATCH 032/408] fix: make the schedule send field read only on sending newsletter --- frappe/email/doctype/newsletter/newsletter.js | 20 ++++++++++++------- .../email/doctype/newsletter/newsletter.json | 3 +-- 2 files changed, 14 insertions(+), 9 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index f25763d79a..76933d1cc4 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -4,20 +4,31 @@ frappe.ui.form.on('Newsletter', { refresh(frm) { let doc = frm.doc; - let today = new Date(); if (!doc.__islocal && !cint(doc.email_sent) && !doc.__unsaved && in_list(frappe.boot.user.can_write, doc.doctype)) { frm.add_custom_button(__('Send Now'), function() { frappe.confirm(__("Do you really want to send this email?"), function() { frm.call('send_emails').then(() => { - frm.refresh(); + frm.set_value('schedule_send', new Date()); + frm.save(); }); }); }, "fa fa-play", "btn-success"); } + if (!doc.__islocal && cint(doc.email_sent)) { + frm.set_df_property('schedule_send', "read_only", 1); + } frm.events.setup_dashboard(frm); + if (doc.__islocal && !doc.send_from) { + let { fullname, email } = frappe.user_info(doc.owner); + frm.set_value('send_from', `${fullname} <${email}>`); + } + }, + + schedule_send(frm) { + let today = new Date(); // setting datepicker options to set min date & min time frm.get_field('schedule_send').$input.datepicker({ maxMinutes: 0, @@ -37,11 +48,6 @@ frappe.ui.form.on('Newsletter', { } } }); - - if(doc.__islocal && !doc.send_from) { - let { fullname, email } = frappe.user_info(doc.owner); - frm.set_value('send_from', `${fullname} <${email}>`); - } }, setup_dashboard(frm) { diff --git a/frappe/email/doctype/newsletter/newsletter.json b/frappe/email/doctype/newsletter/newsletter.json index fcd2d5bfe2..719d51c176 100644 --- a/frappe/email/doctype/newsletter/newsletter.json +++ b/frappe/email/doctype/newsletter/newsletter.json @@ -120,7 +120,6 @@ "label": "Recipients" }, { - "depends_on": "eval: !doc.email_sent", "fieldname": "schedule_send", "fieldtype": "Datetime", "label": "Schedule Send" @@ -136,7 +135,7 @@ "is_published_field": "published", "links": [], "max_attachments": 3, - "modified": "2020-02-18 09:51:41.090261", + "modified": "2020-03-02 06:26:51.622521", "modified_by": "Administrator", "module": "Email", "name": "Newsletter", From 91f023edbe1bd5e0e4b6711768a2ad9fdb71f440 Mon Sep 17 00:00:00 2001 From: Rohan Bansal Date: Mon, 2 Mar 2020 16:06:03 +0530 Subject: [PATCH 033/408] feat: include custom JS and CSS files in web form --- frappe/utils/boilerplate.py | 4 +++ frappe/website/doctype/web_form/web_form.py | 40 ++++++++++++++------- 2 files changed, 32 insertions(+), 12 deletions(-) diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index b81d802a07..e65fa44253 100755 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -151,6 +151,10 @@ app_license = "{app_license}" # web_include_css = "/assets/{app_name}/css/{app_name}.css" # web_include_js = "/assets/{app_name}/js/{app_name}.js" +# include js, css files in header of web form +# webform_include_js = {"doctype": "public/js/doctype.js"} +# webform_include_css = {"doctype": "public/css/doctype.css"} + # include js in page # page_js = {{"page" : "public/js/file.js"}} diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index ae2c27c420..a188503ea3 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -2,18 +2,23 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json, os -from frappe.website.website_generator import WebsiteGenerator + +import json +import os + +from six import iteritems +from six.moves.urllib.parse import urlencode + +import frappe from frappe import _, scrub +from frappe.core.doctype.file.file import get_max_file_size, 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.utils import cstr from frappe.website.utils import get_comment_list -from frappe.custom.doctype.customize_form.customize_form import docfield_properties -from frappe.core.doctype.file.file import get_max_file_size -from frappe.core.doctype.file.file import remove_file_by_url -from frappe.modules.utils import export_module_json, get_doc_module -from six.moves.urllib.parse import urlencode -from frappe.integrations.utils import get_payment_gateway_controller -from six import iteritems +from frappe.website.website_generator import WebsiteGenerator class WebForm(WebsiteGenerator): @@ -237,11 +242,23 @@ def get_context(context): js_path = os.path.join(os.path.dirname(self.web_form_module.__file__), scrub(self.name) + '.js') if os.path.exists(js_path): - context.script = frappe.render_template(open(js_path, 'r').read(), context) + script = frappe.render_template(open(js_path, 'r').read(), context) + + for path in get_code_files_via_hooks("webform_include_js", context.doc_type): + custom_js = frappe.render_template(open(path, 'r').read(), context) + script = "\n\n".join([script, custom_js]) + + context.script = script css_path = os.path.join(os.path.dirname(self.web_form_module.__file__), scrub(self.name) + '.css') if os.path.exists(css_path): - context.style = open(css_path, 'r').read() + style = open(css_path, 'r').read() + + for path in get_code_files_via_hooks("webform_include_css", context.doc_type): + custom_css = open(path, 'r').read() + style = "\n\n".join([style, custom_css]) + + context.style = style def get_layout(self): layout = [] @@ -589,4 +606,3 @@ def get_link_options(web_form_name, doctype, allow_read_on_all_link_options=Fals else: raise frappe.PermissionError('Not Allowed, {0}'.format(doctype)) - From 4e30fecb5f02f1af3670a78c5eb7450b69657f61 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 5 Mar 2020 10:53:09 +0530 Subject: [PATCH 034/408] feat: added prepared report user permission --- .../doctype/prepared_report/prepared_report.json | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.json b/frappe/core/doctype/prepared_report/prepared_report.json index ab6650d9e3..4663dcb463 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.json +++ b/frappe/core/doctype/prepared_report/prepared_report.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "REP.#####", "creation": "2018-06-25 18:39:11.152960", "doctype": "DocType", @@ -101,7 +102,8 @@ } ], "in_create": 1, - "modified": "2019-09-18 04:00:55.644257", + "links": [], + "modified": "2020-03-05 10:52:56.598365", "modified_by": "Administrator", "module": "Core", "name": "Prepared Report", @@ -118,6 +120,15 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Prepared Report User", + "share": 1 } ], "quick_entry": 1, From a88a17e0b8e9e85b08a6056998627f0081bcb50a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 5 Mar 2020 10:54:21 +0530 Subject: [PATCH 035/408] feat: added permission condition for list --- .../doctype/prepared_report/prepared_report.py | 17 +++++++++++++++++ frappe/hooks.py | 3 ++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index bc9a1fcdcd..0d1f43c51f 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -98,3 +98,20 @@ def download_attachment(dn): attached_file = frappe.get_doc("File", attachment.name) frappe.local.response.filecontent = gzip_decompress(attached_file.get_content()) frappe.local.response.type = "binary" + + +def get_permission_query_condition(user): + if not user: user = frappe.session.user + if user == "Administrator": + return None + + from frappe.utils.user import UserPermissions + user = UserPermissions(user) + + if "System Manager" in user.roles: + return None + + reports = [ '"%s"'%report for report in user.get_all_reports().keys() ] + + return """`tabPrepared Report`.ref_report_doctype in ({reports})"""\ + .format(reports=','.join(reports)) diff --git a/frappe/hooks.py b/frappe/hooks.py index a132eb69d5..1d27685de6 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -94,7 +94,8 @@ permission_query_conditions = { "Contact": "frappe.contacts.address_and_contact.get_permission_query_conditions_for_contact", "Address": "frappe.contacts.address_and_contact.get_permission_query_conditions_for_address", "Communication": "frappe.core.doctype.communication.communication.get_permission_query_conditions_for_communication", - "Workflow Action": "frappe.workflow.doctype.workflow_action.workflow_action.get_permission_query_conditions" + "Workflow Action": "frappe.workflow.doctype.workflow_action.workflow_action.get_permission_query_conditions", + "Prepared Report": "frappe.core.doctype.prepared_report.prepared_report.get_permission_query_condition" } has_permission = { From 4b42961ed79a6a49a23dd4b3572a5c2606e1ffab Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 5 Mar 2020 10:54:43 +0530 Subject: [PATCH 036/408] feat: added permission check for doc --- .../doctype/prepared_report/prepared_report.py | 14 ++++++++++++++ frappe/hooks.py | 3 ++- 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index 0d1f43c51f..e8aca6a2c5 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -115,3 +115,17 @@ def get_permission_query_condition(user): return """`tabPrepared Report`.ref_report_doctype in ({reports})"""\ .format(reports=','.join(reports)) + + +def has_permission(doc, user): + if not user: user = frappe.session.user + if user == "Administrator": + return True + + from frappe.utils.user import UserPermissions + user = UserPermissions(user) + + if "System Manager" in user.roles: + return True + + return doc.ref_report_doctype in user.get_all_reports().keys() \ No newline at end of file diff --git a/frappe/hooks.py b/frappe/hooks.py index 1d27685de6..6132618297 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -108,7 +108,8 @@ has_permission = { "Address": "frappe.contacts.address_and_contact.has_permission", "Communication": "frappe.core.doctype.communication.communication.has_permission", "Workflow Action": "frappe.workflow.doctype.workflow_action.workflow_action.has_permission", - "File": "frappe.core.doctype.file.file.has_permission" + "File": "frappe.core.doctype.file.file.has_permission", + "Prepared Report": "frappe.core.doctype.prepared_report.prepared_report.has_permission" } has_website_permission = { From 057dea2f92490f318c59b50ec0f155289c4487ae Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 13 Mar 2020 12:25:24 +0530 Subject: [PATCH 037/408] Update frappe/core/doctype/prepared_report/prepared_report.py Co-Authored-By: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/core/doctype/prepared_report/prepared_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/prepared_report/prepared_report.py b/frappe/core/doctype/prepared_report/prepared_report.py index e8aca6a2c5..4e87bb92dd 100644 --- a/frappe/core/doctype/prepared_report/prepared_report.py +++ b/frappe/core/doctype/prepared_report/prepared_report.py @@ -111,7 +111,7 @@ def get_permission_query_condition(user): if "System Manager" in user.roles: return None - reports = [ '"%s"'%report for report in user.get_all_reports().keys() ] + reports = [frappe.db.escape(report) for report in user.get_all_reports().keys()] return """`tabPrepared Report`.ref_report_doctype in ({reports})"""\ .format(reports=','.join(reports)) @@ -128,4 +128,4 @@ def has_permission(doc, user): if "System Manager" in user.roles: return True - return doc.ref_report_doctype in user.get_all_reports().keys() \ No newline at end of file + return doc.ref_report_doctype in user.get_all_reports().keys() From 739d0cd20fc5171a830869d52ecbac8d4b1ac89c Mon Sep 17 00:00:00 2001 From: Naren Date: Thu, 12 Mar 2020 17:40:32 +0530 Subject: [PATCH 038/408] fix: sticky footer logic implemented --- frappe/public/css/website.css | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/frappe/public/css/website.css b/frappe/public/css/website.css index eecb7005e4..dd317952d9 100644 --- a/frappe/public/css/website.css +++ b/frappe/public/css/website.css @@ -1,7 +1,19 @@ /* the element that this class is applied to, should have a max width for this to work*/ +html { + height: 100%; +} body { + min-height: 100%; + display: flex; + flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; } +body > div { + flex: 1 0 auto; +} +footer { + flex-shrink: 0; +} a { cursor: pointer; } From bdc9893820c5dbc0079d191010f1df52c8744f78 Mon Sep 17 00:00:00 2001 From: Naren Date: Fri, 13 Mar 2020 15:30:02 +0530 Subject: [PATCH 039/408] fix: logic moved from css to scss file --- frappe/public/css/website.css | 12 ------------ frappe/public/scss/website.scss | 15 +++++++++++++++ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/frappe/public/css/website.css b/frappe/public/css/website.css index dd317952d9..eecb7005e4 100644 --- a/frappe/public/css/website.css +++ b/frappe/public/css/website.css @@ -1,19 +1,7 @@ /* the element that this class is applied to, should have a max width for this to work*/ -html { - height: 100%; -} body { - min-height: 100%; - display: flex; - flex-direction: column; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; } -body > div { - flex: 1 0 auto; -} -footer { - flex-shrink: 0; -} a { cursor: pointer; } diff --git a/frappe/public/scss/website.scss b/frappe/public/scss/website.scss index a297054ee2..9901eb7fbf 100644 --- a/frappe/public/scss/website.scss +++ b/frappe/public/scss/website.scss @@ -4,8 +4,23 @@ @import "multilevel-dropdown"; @import "website-image"; +html { + height: 100%; +} + body { + min-height: 100%; + display: flex; + flex-direction: column; font-size: 16px; + + > div { + flex: 1 0 auto; + } +} + +footer { + flex-shrink: 0; } .navbar.bg-dark { From 1f82f051efb258eb8925ab23eea6eb66b05bc927 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 19:56:12 +0530 Subject: [PATCH 040/408] fix: mve email_append_to to a section --- frappe/custom/doctype/customize_form/customize_form.js | 10 ---------- .../custom/doctype/customize_form/customize_form.json | 9 ++++++--- frappe/email/doctype/email_account/email_account.py | 6 +++--- 3 files changed, 9 insertions(+), 16 deletions(-) diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 858cf565be..444d9dc495 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -139,16 +139,6 @@ frappe.ui.form.on("Customize Form", { }, 1000); } - }, - - email_append_to: function(frm) { - frm.set_df_property("subject_field", "reqd", 0); - frm.set_df_property("sender_field", "reqd", 0); - - if (frm.doc.email_append_to) { - frm.set_df_property("subject_field", "reqd", 1); - frm.set_df_property("sender_field", "reqd", 1); - } } }); diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index f719add96c..bfc31d60e6 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -21,7 +21,6 @@ "allow_auto_repeat", "allow_import", "image_view", - "email_append_to", "column_break_5", "title_field", "image_field", @@ -31,6 +30,7 @@ "column_break_10", "sort_order", "section_break_23", + "email_append_to", "subject_field", "cb_01", "sender_field", @@ -182,14 +182,17 @@ "label": "Allow Import (via Data Import Tool)" }, { + "depends_on": "email_append_to", "fieldname": "subject_field", "fieldtype": "Data", "label": "Subject Field" }, { + "depends_on": "email_append_to", "fieldname": "sender_field", "fieldtype": "Data", - "label": "Sender Field" + "label": "Sender Field", + "mandatory_depends_on": "email_append_to" }, { "default": "0", @@ -212,7 +215,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2019-12-27 14:29:49.097980", + "modified": "2020-03-13 19:54:39.652134", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/email/doctype/email_account/email_account.py b/frappe/email/doctype/email_account/email_account.py index cddc5dd25d..c0a198f5e5 100755 --- a/frappe/email/doctype/email_account/email_account.py +++ b/frappe/email/doctype/email_account/email_account.py @@ -678,9 +678,9 @@ def get_append_to(doctype=None, txt=None, searchfield=None, start=None, page_len email_append_to_list = [] # Set Email Append To DocTypes via DocType - for dt in frappe.get_all("DocType", filters={"istable": 0, "issingle": 0}, fields=["name", "email_append_to"]): - if dt.get("email_append_to") and dt.email_append_to: - email_append_to_list.append(dt.name) + filters = {"istable": 0, "issingle": 0, "email_append_to": 1} + for dt in frappe.get_all("DocType", filters=filters, fields=["name", "email_append_to"]): + email_append_to_list.append(dt.name) # Set Email Append To DocTypes set via Customize Form for dt in frappe.get_list("Property Setter", filters={"property": "email_append_to", "value": 1}, fields=["doc_type"]): From 71bb527f8cae2474fa42fade10557b85f5603945 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 17 Mar 2020 12:54:06 +0530 Subject: [PATCH 041/408] fix: add a more descriptive confirmation prompt --- frappe/email/doctype/email_group/email_group.py | 2 +- frappe/email/doctype/newsletter/newsletter.js | 2 +- frappe/email/doctype/newsletter/newsletter.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/email/doctype/email_group/email_group.py b/frappe/email/doctype/email_group/email_group.py index 359c42171e..8aa4893dc9 100755 --- a/frappe/email/doctype/email_group/email_group.py +++ b/frappe/email/doctype/email_group/email_group.py @@ -98,7 +98,7 @@ def add_subscribers(name, email_list): return frappe.get_doc("Email Group", name).update_total_subscribers() def send_welcome_email(welcome_email, email, email_group): - ''' Send welcome email for the subscribers of a given email group ''' + ''' Send welcome email for the subscribers of a given email group. ''' if not welcome_email: return diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index 76933d1cc4..691cdcb18f 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -7,7 +7,7 @@ frappe.ui.form.on('Newsletter', { if (!doc.__islocal && !cint(doc.email_sent) && !doc.__unsaved && in_list(frappe.boot.user.can_write, doc.doctype)) { frm.add_custom_button(__('Send Now'), function() { - frappe.confirm(__("Do you really want to send this email?"), function() { + frappe.confirm(__("Do you really want to send this email newsletter?"), function() { frm.call('send_emails').then(() => { frm.set_value('schedule_send', new Date()); frm.save(); diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 4c9de88aad..fcd5d513b6 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -264,7 +264,7 @@ def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20 limit_page_length, limit_start), email_group_list, as_dict=1) def send_scheduled_email(): - ''' Send scheduled newsletter to the recipients ''' + ''' Send scheduled newsletter to the recipients. ''' scheduled_newsletter = frappe.get_all('Newsletter', filters = { 'schedule_send': ('<=', now_datetime()), 'email_sent': 0 From 94b2d856fcfcb3a2fc27064efd821020d530bfd5 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 21:44:45 +0530 Subject: [PATCH 042/408] feat(ldap): allow resetting ldap password from user settings currently, there is no way to reset password for those logging in through ldap. i understand that this shouldn't really be handled by erpnext, but there are people that have requested resetting the ldap password from with the instance itself, and hence, we'll let that happen now. Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 42 +++++++++++++++++++ .../doctype/ldap_settings/ldap_settings.py | 42 +++++++++++++++++-- 2 files changed, 81 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index c4873ee40e..46332e1893 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -97,6 +97,48 @@ frappe.ui.form.on('User', { }); }, __("Password")); + frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => { + if (value === 1 && frm.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 + }, + { + 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 + }).then(() => done()); + } + }); + d.show(); + }, __("Password")); + } + }); + frm.add_custom_button(__("Reset OTP Secret"), function() { frappe.call({ method: "frappe.core.doctype.user.user.reset_otp_secret", diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index c0f12df04a..63e0b8ff55 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from frappe import _ +from frappe import _, safe_encode from frappe.model.document import Document @@ -19,7 +19,7 @@ class LDAPSettings(Document): else: frappe.throw(_("LDAP Search String needs to end with a placeholder, eg sAMAccountName={0}")) - def connect_to_ldap(self, base_dn, password): + def connect_to_ldap(self, base_dn, password, read_only=True): try: import ldap3 import ssl @@ -44,7 +44,7 @@ class LDAPSettings(Document): user=base_dn, password=password, auto_bind=bind_type, - read_only=True, + read_only=read_only, raise_exceptions=True) return conn @@ -170,6 +170,34 @@ class LDAPSettings(Document): else: 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 = "({0}={1})".format(self.ldap_email_field, user) + + conn = self.connect_to_ldap(self.base_dn, self.get_password(raise_exception=False), + read_only=False) + + if conn.search( + search_base=self.organizational_unit, + search_filter=search_filter, + attributes=self.get_ldap_attributes() + ): + if conn.entries and conn.entries[0]: + entry_dn = conn.entries[0].entry_dn + hashed_password = hashed(HASHED_SALTED_SHA, safe_encode(password)) + changes = {'userPassword': [(MODIFY_REPLACE, [hashed_password])]} + if conn.modify(entry_dn, changes=changes): + if logout_sessions: + from frappe.sessions import clear_sessions + clear_sessions(user=user, force=True) + frappe.msgprint(_("Password changed successfully.")) + else: + frappe.throw(_("Failed to change password.")) + else: + frappe.throw(_("LDAP User does not exist!")) + def convert_ldap_entry_to_dict(self, user_entry): # support multiple email values @@ -211,3 +239,11 @@ def login(): # because of a GET request! frappe.db.commit() + + +@frappe.whitelist() +def reset_password(user, password, logout): + ldap = 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 bc77455d132704f8838ebfa50aee9ff01026661b Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 21:49:58 +0530 Subject: [PATCH 043/408] chore: correct indentation for ldap exceptions Signed-off-by: Chinmay D. Pai --- frappe/integrations/doctype/ldap_settings/ldap_settings.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index 63e0b8ff55..f9dce6800c 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -196,7 +196,9 @@ class LDAPSettings(Document): else: frappe.throw(_("Failed to change password.")) else: - frappe.throw(_("LDAP User does not exist!")) + frappe.throw(_("No Entry for the User {0} found within LDAP!").format(user)) + else: + frappe.throw(_("LDAP User does not exist!")) def convert_ldap_entry_to_dict(self, user_entry): From 5fbf1f8c08249a3c6d724b9832efa5f6264af31a Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 21:53:39 +0530 Subject: [PATCH 044/408] chore: make the exception read slightly better Signed-off-by: Chinmay D. Pai --- frappe/integrations/doctype/ldap_settings/ldap_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/integrations/doctype/ldap_settings/ldap_settings.py b/frappe/integrations/doctype/ldap_settings/ldap_settings.py index f9dce6800c..558f7117c0 100644 --- a/frappe/integrations/doctype/ldap_settings/ldap_settings.py +++ b/frappe/integrations/doctype/ldap_settings/ldap_settings.py @@ -198,7 +198,7 @@ class LDAPSettings(Document): else: frappe.throw(_("No Entry for the User {0} found within LDAP!").format(user)) else: - frappe.throw(_("LDAP User does not exist!")) + frappe.throw(_("No LDAP User found for email: {0}").format(user)) def convert_ldap_entry_to_dict(self, user_entry): From 3b8d76e5a7dc6798c67afef38d38970c489bf602 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 22:02:02 +0530 Subject: [PATCH 045/408] chore: check the correct variable for docname Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 46332e1893..0f474c85f2 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -98,7 +98,7 @@ frappe.ui.form.on('User', { }, __("Password")); frappe.db.get_single_value("LDAP Settings", "enabled").then((value) => { - if (value === 1 && frm.name != "Administrator") { + 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"), From 15801f870880e9b5a7457fb42a988db0325f7da8 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 19 Mar 2020 22:15:01 +0530 Subject: [PATCH 046/408] chore: codacy fixes Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 0f474c85f2..de4dc0c272 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -99,7 +99,7 @@ frappe.ui.form.on('User', { 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(){ + frm.add_custom_button(__("Reset LDAP Password"), function() { const d = new frappe.ui.Dialog({ title: __("Reset LDAP Password"), fields: [ @@ -123,7 +123,7 @@ frappe.ui.form.on('User', { ], primary_action: (values) => { d.hide(); - if(values.new_password !== values.confirm_password) { + if (values.new_password !== values.confirm_password) { frappe.throw(__("Passwords do not match!")); } frappe.call( From daa8d14f91d147cdc17b55ccf1f6dbb8134a10e4 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 20 Mar 2020 18:58:20 +0530 Subject: [PATCH 047/408] fix: setup schedule send onload post render --- frappe/email/doctype/newsletter/newsletter.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index 691cdcb18f..f5ca832fd8 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -27,7 +27,15 @@ frappe.ui.form.on('Newsletter', { } }, + onload_post_render(frm) { + frm.trigger('setup_schedule_send'); + }, + schedule_send(frm) { + frm.trigger('setup_schedule_send'); + }, + + setup_schedule_send(frm) { let today = new Date(); // setting datepicker options to set min date & min time frm.get_field('schedule_send').$input.datepicker({ From 7b7d55860477225e85bfa315eede31980a831274 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 20 Mar 2020 18:10:54 +0530 Subject: [PATCH 048/408] feat: add validate_phone api to window to validate phone numbers --- frappe/public/js/frappe/utils/datatype.js | 4 ++++ frappe/public/js/frappe/utils/utils.js | 3 +++ 2 files changed, 7 insertions(+) diff --git a/frappe/public/js/frappe/utils/datatype.js b/frappe/public/js/frappe/utils/datatype.js index 0f73526e04..16f87b872f 100644 --- a/frappe/public/js/frappe/utils/datatype.js +++ b/frappe/public/js/frappe/utils/datatype.js @@ -44,6 +44,10 @@ window.validate_email = function(txt) { return frappe.utils.validate_type(txt, "email"); }; +window.validate_phone = function(txt) { + return frappe.utils.validate_type(txt, "phone"); +}; + window.nth = function(number) { number = cint(number); var s = 'th'; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 278c80897e..1af37c1f60 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -232,6 +232,9 @@ Object.assign(frappe.utils, { var regExp; switch ( type ) { + case "phone": + regExp = /^([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$/; + break; case "number": regExp = /^-?(?:\d+|\d{1,3}(?:,\d{3})+)?(?:\.\d+)?$/; break; From d61a2ad429d1071df1af62123a9688b621a12705 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 20 Mar 2020 22:32:39 +0530 Subject: [PATCH 049/408] fix: check if website is published --- .../website_settings/google_indexing.py | 20 +++++++------------ frappe/website/website_generator.py | 6 +++--- 2 files changed, 10 insertions(+), 16 deletions(-) diff --git a/frappe/website/doctype/website_settings/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py index dded822c9d..f38dc22743 100644 --- a/frappe/website/doctype/website_settings/google_indexing.py +++ b/frappe/website/doctype/website_settings/google_indexing.py @@ -19,9 +19,7 @@ SCOPES = "https://www.googleapis.com/auth/indexing" @frappe.whitelist() def authorize_access(reauthorize=None): - """ - If no Authorization code get it from Google and then request for Refresh Token. - """ + """ If no Authorization code get it from Google and then request for Refresh Token. """ google_settings = frappe.get_doc("Google Settings") website_settings = frappe.get_doc("Website Settings") @@ -52,8 +50,9 @@ def authorize_access(reauthorize=None): except Exception as e: frappe.throw(e) + def get_authentication_url(client_id, redirect_uri): - ''' Return authentication url with the client id and redirect uri ''' + """ Return authentication url with the client id and redirect uri. """ return { "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) } @@ -61,9 +60,7 @@ def get_authentication_url(client_id, redirect_uri): @frappe.whitelist() def google_callback(code=None): - """ - Authorization code is sent to callback as per the API configuration - """ + """ Authorization code is sent to callback as per the API configuration. """ frappe.db.set_value("Website Settings", None, "indexing_authorization_code", code) frappe.db.commit() @@ -71,9 +68,7 @@ def google_callback(code=None): def get_google_indexing_object(): - """ - Returns an object of Google Indexing object - """ + """ Returns an object of Google Indexing object. """ google_settings = frappe.get_doc("Google Settings") account = frappe.get_doc("Website Settings") @@ -81,8 +76,7 @@ def get_google_indexing_object(): "token": account.get_access_token(), "refresh_token": account.get_password(fieldname="indexing_refresh_token", raise_exception=False), "token_uri": get_auth_url(), - "client_id": google_settings.client_id, - "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "client_id": google_settings.client_i . "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), "scopes": "https://www.googleapis.com/auth/indexing" } @@ -93,7 +87,7 @@ def get_google_indexing_object(): def publish_site(url, operation_type="URL_UPDATED"): - ''' Send an update/remove url request ''' + """ Send an update/remove url request. """ google_indexing = get_google_indexing_object() body = { diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index 3bec631131..d6bdcf3ed0 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -122,10 +122,10 @@ class WebsiteGenerator(Document): return route def send_indexing_request(self, operation_type='URL_UPDATED'): - ''' Send indexing request on update/trash operation ''' + ''' Send indexing request on update/trash operation. ''' - if frappe.db.get_single_value('Website Settings', 'enable_google_indexing')\ - and self.meta.is_published_field and self.meta.allow_guest_to_view: + if frappe.db.get_single_value('Website Settings', 'enable_google_indexing') \ + and self.is_website_published() and self.meta.allow_guest_to_view: url = frappe.utils.get_url(self.route) frappe.enqueue('frappe.website.doctype.website_settings.google_indexing.publish_site', \ From 236d4851e40ba7e5f1a0eb2ee265d427e6a9f496 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 23 Mar 2020 12:35:52 +0530 Subject: [PATCH 050/408] refactor: better customization options --- frappe/core/page/dashboard/dashboard.js | 8 ++++++- .../public/js/frappe/views/desktop/desktop.js | 24 ++++++++++++++++--- 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 511aac7010..69e14f65e8 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -92,7 +92,13 @@ class Dashboard { container: this.container, type: "chart", columns: 2, - allow_sorting: false, + options: { + allow_sorting: false, + allow_create: false, + allow_delete: false, + allow_hiding: false, + allow_edit: false, + } widgets: this.charts, }); }); diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 54a25c3771..c74443bcee 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -229,7 +229,13 @@ class DesktopPage { container: this.page, type: "chart", columns: 1, - allow_sorting: false, + options: { + allow_sorting: false, + allow_create: this.allow_customization, + allow_delete: this.allow_customization, + allow_hiding: false, + allow_edit: this.allow_customization, + }, widgets: this.data.charts.items }); } @@ -240,7 +246,13 @@ class DesktopPage { container: this.page, type: "bookmark", columns: 3, - allow_sorting: this.allow_customization && frappe.is_mobile(), + options: { + allow_sorting: this.allow_customization && frappe.is_mobile(), + allow_create: this.allow_customization, + allow_delete: this.allow_customization, + allow_hiding: false, + allow_edit: this.allow_customization, + }, widgets: this.data.shortcuts.items }); } @@ -251,7 +263,13 @@ class DesktopPage { container: this.page, type: "links", columns: 3, - allow_sorting: this.allow_customization && frappe.is_mobile(), + options: { + allow_sorting: this.allow_customization && frappe.is_mobile(), + allow_create: false, + allow_delete: false, + allow_hiding: this.allow_customization, + allow_edit: false, + }, widgets: this.data.cards.items }); From b71d8eb754211263b16b42b80560f2dccaf85619 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 23 Mar 2020 12:36:45 +0530 Subject: [PATCH 051/408] feat: added customization link --- .../public/js/frappe/views/desktop/desktop.js | 17 +++++++++++++ frappe/public/less/desktop.less | 24 +++++++++++++++++++ 2 files changed, 41 insertions(+) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index c74443bcee..d052d20b39 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -152,6 +152,16 @@ class DesktopPage { this.page.hide(); } + make_customization_link() { + this.customize_link = $(`
Customize Workspace
`); + this.customize_link.on('click', () => { + this.customize(); + }) + + this.customize_link.appendTo(this.page); + this.page.addClass('allow-customization'); + } + make() { this.make_page(); this.get_data().then(res => { @@ -164,6 +174,7 @@ class DesktopPage { } this.allow_customization = this.data.allow_customization || false; + this.allow_customization && this.make_customization_link(); !this.sections["onboarding"] && this.data.charts.items.length && @@ -223,6 +234,12 @@ class DesktopPage { }); } + customize() { + Object.keys(this.sections).forEach(section => { + this.sections[section].customize(); + }) + } + make_charts() { this.sections["charts"] = new frappe.widget.WidgetGroup({ title: this.data.charts.label || `${this.page_name} Dashboard`, diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index 9df4addf61..14d19aae88 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -76,6 +76,20 @@ position: relative; min-height: 1px; padding-right: 15px; + + .desk-page.allow-customization { + .customize-page { + text-align: right; + margin-top: 7px; + color: @text-muted; + cursor: pointer; + z-index: 99; + } + + .widget-group:nth-child(2) { + margin-top: -25px; + } + } } @media (max-width: 768px) { @@ -220,6 +234,16 @@ border-color: @disabled-background } + &.new-widget { + min-height: 65px; + background-color: @disabled-background; + color: @text-muted; + display: flex; + align-content: center; + justify-content: center; + cursor: pointer; + } + // Overrides for each widgets &.dashboard-widget-box { padding: 10px 15px !important; From e27e81e7ed3951dbb8a9efcb4b48533747cae901 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 23 Mar 2020 12:37:34 +0530 Subject: [PATCH 052/408] feat: added new widget class --- frappe/public/js/frappe/widgets/new_widget.js | 45 +++++++++++++++++ .../public/js/frappe/widgets/widget_group.js | 50 ++++++++++++------- 2 files changed, 76 insertions(+), 19 deletions(-) create mode 100644 frappe/public/js/frappe/widgets/new_widget.js diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js new file mode 100644 index 0000000000..47af5c03fa --- /dev/null +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -0,0 +1,45 @@ +export default class NewWidget { + constructor(opts) { + Object.assign(this, opts); + this.make(); + window.wid = this; + } + + refresh() { + // + } + + customize() { + return + } + + make() { + this.make_widget(); + this.widget.appendTo(this.container); + this.setup_events(); + } + + make_widget() { + this.widget = $(`
+ + New +
`); + this.body = this.widget + this.set_body(); + } + + delete() { + this.widget.remove(); + } + + set_actions() { + // + } + + set_body() { + // + } + + setup_events() { + // + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 36fb2c9efc..18a9e3f88d 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -3,6 +3,7 @@ import BaseWidget from "../widgets/base_widget"; import ShortcutWidget from "../widgets/shortcut_widget"; import LinksWidget from "../widgets/links_widget"; import OnboardingWidget from "../widgets/onboarding_widget"; +import NewWidget from "../widgets/new_widget"; frappe.provide('frappe.widget') @@ -11,26 +12,15 @@ const widget_factory = { base: BaseWidget, bookmark: ShortcutWidget, links: LinksWidget, - onboarding: OnboardingWidget + onboarding: OnboardingWidget, + new: NewWidget }; export default class WidgetGroup { constructor(opts) { Object.assign(this, opts); - // opts = { - // title: "CRM Dashboard", - // container: $(''), - // widgets: [ - // {type: "dashboard", width: "Full", options: {}}. - // {type: "dashboard", width: "Full", options: {}} - // ], - // allow_delete: true, - // allow_create: true, - // allow_rearrange: true, - // hide_edit_option: false, - // collapsible: false - // } - window.wid_area = this; + this.widgets_list = []; + this.widgets_dict = {}; this.make(); } @@ -42,7 +32,7 @@ export default class WidgetGroup { refresh() { this.title && this.set_title(this.title); this.widgets && this.make_widgets(); - this.allow_sorting && this.setup_sortable(); + this.options.allow_sorting && this.setup_sortable(); } make_container() { @@ -70,13 +60,35 @@ export default class WidgetGroup { const widget_class = widget_factory[this.type]; this.widgets.forEach(widget => { - new widget_class({ + let widget_object = new widget_class({ ...widget, - container: this.body - }) + container: this.body, + on_delete: (name) => this.on_delete(name) + }); + this.widgets_list.push(widget_object); + this.widgets_dict[widget.name] = widget_object; }); } + customize() { + const options = { + delete: this.options.allow_delete, + sort: this.options.allow_sorting + } + + this.widgets_list.forEach(wid => { + wid.customize(options); + }) + + this.options.allow_create && new NewWidget({ + container: this.body + }) + } + + on_delete(name) { + this.widgets_list = this.widgets_list.filter(wid => name != wid.name) + } + setup_sortable() { const container = this.body[0]; this.sortable = new Sortable(container, { From 040af6844f1d16d524ce59c62a372e67cbf6cbd1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 23 Mar 2020 13:07:33 +0530 Subject: [PATCH 053/408] feat: remove customize override for chart widget --- frappe/public/js/frappe/widgets/chart_widget.js | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 3388890776..bb8e412fa7 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -13,10 +13,6 @@ export default class ChartWidget extends Widget { this.make_chart(); } - customize() { - this.setup_customize_actions(); - } - set_body() { this.widget.addClass("dashboard-widget-box"); if (this.width == "Full") { @@ -79,13 +75,6 @@ export default class ChartWidget extends Widget { }); } - setup_customize_actions() { - this.action_area.empty(); - const buttons = $(` - `); - buttons.appendTo(this.action_area); - } - render_time_series_filters() { let filters = [ { From 3f30dbec053751d136ede3dbf0225e60698c56af Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 23 Mar 2020 15:41:48 +0530 Subject: [PATCH 054/408] feat: enable customize mode --- .../public/js/frappe/views/desktop/desktop.js | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index d052d20b39..a51654f91b 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -141,6 +141,7 @@ class DesktopPage { this.page_name = page_name; this.sections = {}; this.allow_customization = false; + this.in_customize_mode = false; this.make(); } @@ -153,12 +154,27 @@ class DesktopPage { } make_customization_link() { - this.customize_link = $(`
Customize Workspace
`); + this.customize_link = $(`
Customize Workspace
`); + this.customize_link.appendTo(this.page); this.customize_link.on('click', () => { this.customize(); }) - this.customize_link.appendTo(this.page); + this.save_or_discard_link = $(`
+ Save / Discard +
`).hide(); + + this.save_or_discard_link.appendTo(this.page); + this.save_or_discard_link.find(".save-customization").on("click", () => { + console.log("Save Customization"); + }); + + this.save_or_discard_link.find(".discard-customization").on("click", () => { + this.in_customize_mode = false; + this.container.empty(); + this.make(); + }) + this.page.addClass('allow-customization'); } @@ -235,9 +251,17 @@ class DesktopPage { } customize() { + if (this.in_customize_mode) { + return + } + + this.customize_link.hide(); + this.save_or_discard_link.show(); + Object.keys(this.sections).forEach(section => { this.sections[section].customize(); }) + this.in_customize_mode = true; } make_charts() { @@ -264,7 +288,7 @@ class DesktopPage { type: "bookmark", columns: 3, options: { - allow_sorting: this.allow_customization && frappe.is_mobile(), + allow_sorting: this.allow_customization && !frappe.is_mobile(), allow_create: this.allow_customization, allow_delete: this.allow_customization, allow_hiding: false, @@ -281,7 +305,7 @@ class DesktopPage { type: "links", columns: 3, options: { - allow_sorting: this.allow_customization && frappe.is_mobile(), + allow_sorting: this.allow_customization && !frappe.is_mobile(), allow_create: false, allow_delete: false, allow_hiding: this.allow_customization, From c9eb03dce7de1f2cd22ae4d9a9c7707fcc0a2ef5 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 23 Mar 2020 15:42:13 +0530 Subject: [PATCH 055/408] feat: setup customize API for base widget --- .../public/js/frappe/widgets/base_widget.js | 58 +++++- .../public/js/frappe/widgets/widget_group.js | 23 ++- frappe/public/less/desktop.less | 180 ++++++++++++++++-- 3 files changed, 228 insertions(+), 33 deletions(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 7461f2ddc0..4064c34d01 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -10,14 +10,35 @@ export default class Widget { this.set_body(); } - customize() { + customize(options) { + this.action_area.empty(); + options.allow_delete && + this.add_custom_button( + '', + () => this.delete() + ); + options.allow_sorting && + this.add_custom_button( + '', + null, + "drag-handle" + ); + options.allow_hiding && + this.add_custom_button( + '', + () => this.hide() + ); + options.allow_edit && + this.add_custom_button( + '', + () => this.edit() + ); } make() { this.make_widget(); this.widget.appendTo(this.container); - this.setup_events(); } make_widget() { @@ -46,6 +67,33 @@ export default class Widget { this.title_field[0].innerHTML = this.label || this.name; } + add_custom_button(html, action, class_name = "") { + let button = $( + `` + ); + action && button.on("click", () => action()); + button.appendTo(this.action_area); + } + + delete() { + this.widget.addClass("zoomOutDelete"); + // wait for animation + setTimeout(() => { + this.widget.remove(); + this.on_delete && this.on_delete(this.name); + }, 300); + } + + edit() { + this.on_edit && this.on_edit(this.name); + } + + hide() { + this.body.css("opacity", 0.5); + this.title_field.css("opacity", 0.5); + this.footer.css("opacity", 0.5); + } + set_actions() { // } @@ -53,8 +101,4 @@ export default class Widget { set_body() { // } - - setup_events() { - // - } -} \ No newline at end of file +} diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 18a9e3f88d..a34b1dc450 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -32,7 +32,6 @@ export default class WidgetGroup { refresh() { this.title && this.set_title(this.title); this.widgets && this.make_widgets(); - this.options.allow_sorting && this.setup_sortable(); } make_container() { @@ -63,7 +62,10 @@ export default class WidgetGroup { let widget_object = new widget_class({ ...widget, container: this.body, - on_delete: (name) => this.on_delete(name) + options: { + ...this.options, + on_delete: (name) => this.on_delete(name) + } }); this.widgets_list.push(widget_object); this.widgets_dict[widget.name] = widget_object; @@ -71,18 +73,16 @@ export default class WidgetGroup { } customize() { - const options = { - delete: this.options.allow_delete, - sort: this.options.allow_sorting - } - this.widgets_list.forEach(wid => { - wid.customize(options); + wid.customize(this.options); }) this.options.allow_create && new NewWidget({ - container: this.body + container: this.body, + type: this.type }) + + this.options.allow_sorting && this.setup_sortable(); } on_delete(name) { @@ -93,11 +93,10 @@ export default class WidgetGroup { const container = this.body[0]; this.sortable = new Sortable(container, { animation: 150, + handle: ".drag-handle", onEnd: () => { console.log("Sorting") - }, - // onChoose: (evt) => this.sortable_config.on_choose(evt, container), - // onStart: (evt) => this.sortable_config.on_start(evt, container) + }, // onStart: (evt) => this.sortable_config.on_start(evt, container) }); } } diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index 14d19aae88..7d7643ec3e 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -78,15 +78,23 @@ padding-right: 15px; .desk-page.allow-customization { - .customize-page { + .customize-options { text-align: right; margin-top: 7px; color: @text-muted; - cursor: pointer; z-index: 99; + + .save-customization { + cursor: pointer; + color: @text-color; + } + + .discard-customization { + cursor: pointer; + } } - .widget-group:nth-child(2) { + .widget-group:nth-child(3) { margin-top: -25px; } } @@ -104,6 +112,9 @@ .widget-group { margin-bottom: 25px; + // -webkit-animation-name: slideInUp; + // animation-name: slideInUp; + // animation-duration: 0.4s; .widget-group-head { display: flex; @@ -215,6 +226,17 @@ margin-left: 5px; } + .drag-handle { + cursor: all-scroll; + cursor: -webkit-grabbing; + + &:active { + cursor: grabbing; + cursor: -moz-grabbing; + cursor: -webkit-grabbing; + } + } + .dashboard-date-field { .clearfix, .help-box { @@ -381,23 +403,153 @@ border-radius: 10px; } -.pill-green { - background: #71b92c; - // color: #000; +@-webkit-keyframes smallBounce { + from, + 20%, + 53%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 40%, + 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -12px, 0); + transform: translate3d(0, -12px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -6px, 0); + transform: translate3d(0, -6px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -4px, 0); + transform: translate3d(0, -4px, 0); + } } -.pill-red { - background: @red; +@keyframes smallBounce { + from, + 20%, + 53%, + 80%, + to { + -webkit-animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + animation-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } + + 40%, + 43% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -12px, 0); + transform: translate3d(0, -12px, 0); + } + + 70% { + -webkit-animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + animation-timing-function: cubic-bezier(0.755, 0.05, 0.855, 0.06); + -webkit-transform: translate3d(0, -6px, 0); + transform: translate3d(0, -6px, 0); + } + + 90% { + -webkit-transform: translate3d(0, -4px, 0); + transform: translate3d(0, -4px, 0); + } } -.pill-blue { - background: @blue; +.small-bounce { + -webkit-animation-name: smallBounce; + animation-name: smallBounce; + -webkit-transform-origin: center bottom; + transform-origin: center bottom; + animation-duration: 1s; } -.pill-yellow { - background: @yellow; +@-webkit-keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } } -.pill-orange { - background: @orange; +@keyframes slideInUp { + from { + -webkit-transform: translate3d(0, 100%, 0); + transform: translate3d(0, 100%, 0); + visibility: visible; + } + + to { + -webkit-transform: translate3d(0, 0, 0); + transform: translate3d(0, 0, 0); + } +} + +.slide-in-up { + -webkit-animation-name: slideInUp; + animation-name: slideInUp; + animation-duration: 1s; +} + + +@-webkit-keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +@keyframes pulse { + from { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } + + 50% { + -webkit-transform: scale3d(1.05, 1.05, 1.05); + transform: scale3d(1.05, 1.05, 1.05); + } + + to { + -webkit-transform: scale3d(1, 1, 1); + transform: scale3d(1, 1, 1); + } +} + +.zoomOutDelete { + // -webkit-animation-name: zoomOut; + // animation-name: zoomOut; + // animation-duration: 1s; + transition: opacity 0.2s, visibility 0.2s, transform 0.2s; + transform: scale3d(0.5, 0.5, 0.5); + opacity: 0; + visibility: hidden; } \ No newline at end of file From 3c276bb40dfdb19a545bd24d36e1da2a36b5be1e Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 23 Mar 2020 18:55:35 +0530 Subject: [PATCH 056/408] fix(List View): strip html from text editor fields or fetched text and small text fields --- frappe/public/js/frappe/list/list_view.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index f32d541584..af9a3c0221 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -586,7 +586,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (this.settings.formatters && this.settings.formatters[fieldname]) { _value = this.settings.formatters[fieldname](value, df, doc); } else { - _value = typeof value === 'string' ? frappe.utils.escape_html(value) : value; + let strip_html_required = df.fieldtype == 'Text Editor' + || (df.fetch_from && ['Text', 'Small Text'].includes(df.fieldtype)); + if (strip_html_required) { + _value = strip_html(value); + } else { + _value = typeof value === 'string' ? frappe.utils.escape_html(value) : value; + } } if (df.fieldtype === 'Image') { From 60c92d821804903f3aa3b6858400d51d4381bf02 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 23 Mar 2020 18:56:18 +0530 Subject: [PATCH 057/408] fix(Form): strip html from fetched text/small text fields --- frappe/public/js/frappe/form/controls/link.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 0615cea314..7471f59907 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -464,7 +464,11 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ set_fetch_values: function(df, docname, fetch_values) { var fl = this.frm.fetch_dict[df.fieldname].fields; for(var i=0; i < fl.length; i++) { - frappe.model.set_value(df.parent, docname, fl[i], fetch_values[i], df.fieldtype); + const fieldtype = frappe.meta.get_docfield(this.doctype, fl[i]).fieldtype; + let value = ['Text', 'Small Text'].includes(fieldtype) + ? strip_html(fetch_values[i]) + : fetch_values[i]; + frappe.model.set_value(df.parent, docname, fl[i], value, df.fieldtype); } } }); From 3b93b2a456f17f034f27d3205fee0c97131ff673 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 24 Mar 2020 13:28:30 +0530 Subject: [PATCH 058/408] refactor: Commonify transition condition evaluation - This add safe_globals for transition evaluation in workflow_action --- frappe/model/workflow.py | 31 ++++++++++++------- .../workflow_action/workflow_action.py | 8 ++--- 2 files changed, 24 insertions(+), 15 deletions(-) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index b134f2f8dc..3f3711af9d 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -45,20 +45,29 @@ def get_transitions(doc, workflow = None, raise_exception=False): transitions = [] for transition in workflow.transitions: if transition.state == current_state and transition.allowed in roles: - if transition.condition: - # if condition, evaluate - # access to frappe.db.get_value and frappe.db.get_list - success = frappe.safe_eval(transition.condition, - dict(frappe = frappe._dict( - db = frappe._dict(get_value = frappe.db.get_value, get_list=frappe.db.get_list), - session = frappe.session - )), - dict(doc = doc)) - if not success: - continue + if not is_transition_condition_satisfied(transition, doc): + continue transitions.append(transition.as_dict()) return transitions +def get_workflow_safe_globals(): + # access to frappe.db.get_value and frappe.db.get_list + return dict( + frappe=frappe._dict( + db=frappe._dict( + get_value=frappe.db.get_value, + get_list=frappe.db.get_list + ), + session=frappe.session + ) + ) + +def is_transition_condition_satisfied(transition, doc): + if not transition.condition: + return True + else: + return frappe.safe_eval(transition.condition, get_workflow_safe_globals(), dict(doc=doc.as_dict())) + @frappe.whitelist() def apply_workflow(doc, action): '''Allow workflow action on the current doc''' diff --git a/frappe/workflow/doctype/workflow_action/workflow_action.py b/frappe/workflow/doctype/workflow_action/workflow_action.py index 3d4c106f29..68ee7cd2ef 100644 --- a/frappe/workflow/doctype/workflow_action/workflow_action.py +++ b/frappe/workflow/doctype/workflow_action/workflow_action.py @@ -9,8 +9,8 @@ from frappe.utils import get_url, get_datetime from frappe.desk.form.utils import get_pdf_link from frappe.utils.verified_command import get_signed_params, verify_request from frappe import _ -from frappe.model.workflow import apply_workflow, get_workflow_name, \ - has_approval_access, get_workflow_state_field, send_email_alert, get_workflow_field_value +from frappe.model.workflow import apply_workflow, get_workflow_name, has_approval_access, \ + get_workflow_state_field, send_email_alert, get_workflow_field_value, is_transition_condition_satisfied from frappe.desk.notifications import clear_doctype_notifications from frappe.utils.user import get_users_with_role @@ -155,10 +155,10 @@ def get_next_possible_transitions(workflow_name, state, doc=None): for transition in transitions: is_next_state_optional = get_state_optional_field_value(workflow_name, transition.next_state) # skip transition if next state of the transition is optional - if transition.condition and not frappe.safe_eval(transition.condition, None, {'doc': doc.as_dict()}): - continue if is_next_state_optional: continue + if not is_transition_condition_satisfied(transition, doc): + continue transitions_to_return.append(transition) return transitions_to_return From 80c4167a0783132cf9ad55a75fec56652f817730 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 23 Mar 2020 18:10:54 +0530 Subject: [PATCH 059/408] feat: server validations for data field options --- frappe/core/doctype/doctype/doctype.py | 14 +++++++++++++- frappe/exceptions.py | 1 + frappe/model/__init__.py | 1 + frappe/model/meta.py | 3 +++ 4 files changed, 18 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index da1b184cc1..43b375e999 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -15,7 +15,7 @@ import frappe import frappe.website.render from frappe import _ from frappe.utils import now, cint -from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields +from frappe.model import no_value_fields, default_fields, data_fieldtypes, table_fields, data_field_options from frappe.model.document import Document from frappe.custom.doctype.property_setter.property_setter import make_property_setter from frappe.custom.doctype.custom_field.custom_field import create_custom_field @@ -942,6 +942,17 @@ def validate_fields(meta): if hasattr(field, 'fetch_from') and getattr(field, 'fetch_from'): field.fetch_from = field.fetch_from.strip('\n').strip() + + def validate_data_field_type(docfield): + if docfield.fieldtype == "Data": + if docfield.options and (docfield.options not in data_field_options): + docfield_label = frappe.bold(docfield.label) + data_field_str = "
  • " + "
  • ".join(data_field_options) + "
" + text = "{0} is an Invalid Data field.{1} Only Options allowed for Data field are: {2}" + message = _(text).format(docfield_label, "

", data_field_str) + frappe.msgprint(message, raise_exception=True) + + fields = meta.get("fields") fieldname_list = [d.fieldname for d in fields] @@ -972,6 +983,7 @@ def validate_fields(meta): check_table_multiselect_option(d) scrub_options_in_select(d) scrub_fetch_from(d) + validate_data_field_type(d) check_fold(fields) check_search_fields(meta, fields) diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 3d63f4b2b4..732fc39e9a 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -78,6 +78,7 @@ class TimestampMismatchError(ValidationError): pass class EmptyTableError(ValidationError): pass class LinkExistsError(ValidationError): pass class InvalidEmailAddressError(ValidationError): pass +class InvalidPhoneNumberError(ValidationError): pass class TemplateNotFoundError(ValidationError): pass class UniqueValidationError(ValidationError): pass class AppNotInstalledError(ValidationError): pass diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 1fe92d7a67..7af987f4bc 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -48,6 +48,7 @@ table_fields = ('Table', 'Table MultiSelect') core_doctypes_list = ('DocType', 'DocField', 'DocPerm', 'DocType Action', 'DocType Link', 'User', 'Role', 'Has Role', 'Page', 'Module Def', 'Print Format', 'Report', 'Customize Form', 'Customize Form Field', 'Property Setter', 'Custom Field', 'Custom Script') +data_field_options = ('Email', 'Phone') def copytables(srctype, src, srcfield, tartype, tar, tarfield, srcfields, tarfields=[]): if not tarfields: diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 1938a4a96c..47db0829a1 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -128,6 +128,9 @@ class Meta(Document): def get_link_fields(self): return self.get("fields", {"fieldtype": "Link", "options":["!=", "[Select]"]}) + def get_data_fields(self): + return self.get("fields", {"fieldtype": "Data"}) + def get_dynamic_link_fields(self): if not hasattr(self, '_dynamic_link_fields'): self._dynamic_link_fields = self.get("fields", {"fieldtype": "Dynamic Link"}) From d199d8027a86a3926a81f0d083c4f21da709f74a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 24 Mar 2020 08:10:54 +0530 Subject: [PATCH 060/408] feat: added server validations for phone and email for fieldtype "Data" and options set to "Email" or "Phone", updating documents will trigger data validations for email and phone numbers will be made --- frappe/model/base_document.py | 12 ++++++++++++ frappe/model/document.py | 2 ++ frappe/utils/__init__.py | 17 +++++++++++++++-- 3 files changed, 29 insertions(+), 2 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 569cea9d5f..17eb4164a6 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -544,6 +544,18 @@ class BaseDocument(object): frappe.throw(_('{0} {1} cannot be "{2}". It should be one of "{3}"').format(prefix, label, value, comma_options)) + def _validate_data_fields(self): + # data_field options defined in frappe.model.data_field_options + for data_field in self.meta.get_data_fields(): + data = self.get(data_field.fieldname) + + if data_field.options == "Email": + for email_address in frappe.utils.split_emails(data): + frappe.utils.validate_email_address(email_address, throw=True) + + if data_field.options == "Phone": + frappe.utils.validate_phone_number(data, throw=True) + def _validate_constants(self): if frappe.flags.in_import or self.is_new() or self.flags.ignore_validate_constants: return diff --git a/frappe/model/document.py b/frappe/model/document.py index 41f946efd9..38407851ff 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -468,6 +468,7 @@ class Document(BaseDocument): def _validate(self): self._validate_mandatory() + self._validate_data_fields() self._validate_selects() self._validate_length() self._extract_images_from_text_editor() @@ -477,6 +478,7 @@ class Document(BaseDocument): children = self.get_all_children() for d in children: + d._validate_data_fields() d._validate_selects() d._validate_length() d._extract_images_from_text_editor() diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 82e6ea1b45..ccaa47432f 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -3,7 +3,7 @@ # util __init__.py -from __future__ import unicode_literals, print_function +from __future__ import unicode_literals, print_function, annotations from werkzeug.test import Client import os, re, sys, json, hashlib, requests, traceback from .html_utils import sanitize_html @@ -81,6 +81,19 @@ def validate_email_add(email_str, throw=False): """ return validate_email_address(email_str, throw=False) +def validate_phone_number(phone_number: str, throw: bool = False): + """Returns True if valid phone number""" + if not phone_number: + return False + + phone_number = phone_number.strip() + match = re.match("([0-9\ \+\_\-\,\.\*\#\(\)]){1,20}$", phone_number) + + if not match and throw: + frappe.throw(frappe._("{0} is not a valid Phone Number").format(phone_number), frappe.InvalidPhoneNumberError) + + return bool(match) + def validate_email_address(email_str, throw=False): """Validates the email string""" email = email_str = (email_str or "").strip() @@ -691,4 +704,4 @@ def get_html_for_route(route): set_request(method='GET', path=route) response = render.render() html = frappe.safe_decode(response.get_data()) - return html \ No newline at end of file + return html From 2575006816c7420567eca0641108f9c2c23a1188 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 25 Mar 2020 15:53:43 +0530 Subject: [PATCH 061/408] feat: validations for Email and Phone Data options added set_invalid control for docfields. On event of docfield.invalid is set, in a given form, set_invalid will be executed which changes the textbox colour to red --- .../js/frappe/form/controls/base_control.js | 1 + .../js/frappe/form/controls/base_input.js | 3 ++ frappe/public/js/frappe/form/controls/data.js | 45 ++++--------------- 3 files changed, 13 insertions(+), 36 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/base_control.js b/frappe/public/js/frappe/form/controls/base_control.js index 2adb5435e3..c1ba41ab16 100644 --- a/frappe/public/js/frappe/form/controls/base_control.js +++ b/frappe/public/js/frappe/form/controls/base_control.js @@ -152,6 +152,7 @@ frappe.ui.form.Control = Class.extend({ () => me.set_model_value(value), () => { me.set_mandatory && me.set_mandatory(value); + me.set_invalid && me.set_invalid(); if(me.df.change || me.df.onchange) { // onchange event specified in df diff --git a/frappe/public/js/frappe/form/controls/base_input.js b/frappe/public/js/frappe/form/controls/base_input.js index 8a8ac271c7..0dbaaeb63c 100644 --- a/frappe/public/js/frappe/form/controls/base_input.js +++ b/frappe/public/js/frappe/form/controls/base_input.js @@ -179,6 +179,9 @@ frappe.ui.form.ControlInput = frappe.ui.form.Control.extend({ set_mandatory: function(value) { this.$wrapper.toggleClass("has-error", (this.df.reqd && is_null(value)) ? true : false); }, + set_invalid: function () { + this.$wrapper.toggleClass("has-error", (this.df.invalid ? true : false)); + }, set_bold: function() { if(this.$input) { this.$input.toggleClass("bold", !!(this.df.bold || this.df.reqd)); diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 6dc8c3d387..41dee09115 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -87,56 +87,29 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ return val==null ? "" : val; }, validate: function(v) { + if (!v){ + return ''; + } if(this.df.is_filter) { return v; } if(this.df.options == 'Phone') { - if(v+''=='') { - return ''; - } - var v1 = ''; - // phone may start with + and must only have numbers later, '-' and ' ' are stripped - v = v.replace(/ /g, '').replace(/-/g, '').replace(/\(/g, '').replace(/\)/g, ''); - - // allow initial +,0,00 - if(v && v.substr(0,1)=='+') { - v1 = '+'; v = v.substr(1); - } - if(v && v.substr(0,2)=='00') { - v1 += '00'; v = v.substr(2); - } - if(v && v.substr(0,1)=='0') { - v1 += '0'; v = v.substr(1); - } - v1 += cint(v) + ''; - return v1; + this.df.invalid = !validate_phone(v) + return v; } else if(this.df.options == 'Email') { - if(v+''=='') { - return ''; - } - var email_list = frappe.utils.split_emails(v); if (!email_list) { - // invalid email return ''; } else { - var invalid_email = false; + let emph = false; email_list.forEach(function(email) { if (!validate_email(email)) { - frappe.msgprint(__("Invalid Email: {0}", [email])); - invalid_email = true; + emph = emph || true; } }); - - if (invalid_email) { - // at least 1 invalid email - return ''; - } else { - // all good - return v; - } + this.df.invalid = emph; + return v; } - } else { return v; } From c199a3555b4c18e9e7db834d16ecabc8dd5fc475 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 25 Mar 2020 15:59:01 +0530 Subject: [PATCH 062/408] chore: remove deprecated validate_email_add function --- frappe/utils/__init__.py | 6 ------ 1 file changed, 6 deletions(-) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index ccaa47432f..61d2cf9d82 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -75,12 +75,6 @@ def extract_email_id(email): email_id = email_id.decode("utf-8", "ignore") return email_id -def validate_email_add(email_str, throw=False): - """ - validate_email_add will be renamed to the validate_email_address in v12 - """ - return validate_email_address(email_str, throw=False) - def validate_phone_number(phone_number: str, throw: bool = False): """Returns True if valid phone number""" if not phone_number: From dd0d21f04a0c4d2418bc6cd37e9df8e3e1877179 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 25 Mar 2020 16:02:01 +0530 Subject: [PATCH 063/408] style: rename variable emph => email_invalid --- frappe/public/js/frappe/form/controls/data.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 41dee09115..516cd87a1a 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -101,13 +101,13 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ if (!email_list) { return ''; } else { - let emph = false; + let email_invalid = false; email_list.forEach(function(email) { if (!validate_email(email)) { - emph = emph || true; + email_invalid = true; } }); - this.df.invalid = emph; + this.df.invalid = email_invalid; return v; } } else { From f7111e20bec5d75b7d24b0ae36ff2502a7da3610 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 26 Mar 2020 12:10:54 +0530 Subject: [PATCH 064/408] fix: Improper ValidationError string returning empty string if parseaddr returns None shows " is not a valid Email Address" eg: if 'email@' is passed as value, email.utils.parseaddr will return a (None, None) tuple --- frappe/utils/__init__.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 61d2cf9d82..5d0e9ab24b 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -105,15 +105,15 @@ def validate_email_address(email_str, throw=False): _valid = False else: - e = extract_email_id(e) - match = re.match("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", e.lower()) if e else None + email_id = extract_email_id(e) + match = re.match("[a-z0-9!#$%&'*+/=?^_`{|}~-]+(?:\.[a-z0-9!#$%&'*+/=?^_`{|}~-]+)*@(?:[a-z0-9](?:[a-z0-9-]*[a-z0-9])?\.)+[a-z0-9](?:[a-z0-9-]*[a-z0-9])?", email_id.lower()) if email_id else None if not match: _valid = False else: matched = match.group(0) if match: - match = matched==e.lower() + match = matched==email_id.lower() if not _valid: if throw: From 2c5bc5abcf67cfb8c75346bf3e31f20366703f78 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 25 Mar 2020 17:22:12 +0530 Subject: [PATCH 065/408] fix: remove type annotations --- frappe/utils/__init__.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 5d0e9ab24b..649d3bf72c 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -3,7 +3,7 @@ # util __init__.py -from __future__ import unicode_literals, print_function, annotations +from __future__ import unicode_literals, print_function from werkzeug.test import Client import os, re, sys, json, hashlib, requests, traceback from .html_utils import sanitize_html @@ -75,7 +75,7 @@ def extract_email_id(email): email_id = email_id.decode("utf-8", "ignore") return email_id -def validate_phone_number(phone_number: str, throw: bool = False): +def validate_phone_number(phone_number, throw=False): """Returns True if valid phone number""" if not phone_number: return False From 5627981192cb8a2553bcd6a5b7af00089011f34b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 25 Mar 2020 19:50:48 +0530 Subject: [PATCH 066/408] fix: safe check of options in docfield --- frappe/model/base_document.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 17eb4164a6..466824d1d2 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -548,12 +548,13 @@ class BaseDocument(object): # data_field options defined in frappe.model.data_field_options for data_field in self.meta.get_data_fields(): data = self.get(data_field.fieldname) + data_field_options = data_field.get("options") - if data_field.options == "Email": + if data_field_options == "Email": for email_address in frappe.utils.split_emails(data): frappe.utils.validate_email_address(email_address, throw=True) - if data_field.options == "Phone": + if data_field_options == "Phone": frappe.utils.validate_phone_number(data, throw=True) def _validate_constants(self): From 44d6c87335276b53db1f78d3834f74a29b7c85de Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 13:51:03 +0530 Subject: [PATCH 067/408] feat: widget config API for saving customizations --- .../public/js/frappe/widgets/widget_group.js | 35 ++++++++++++++++--- 1 file changed, 31 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index a34b1dc450..249dabf008 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -21,6 +21,7 @@ export default class WidgetGroup { Object.assign(this, opts); this.widgets_list = []; this.widgets_dict = {}; + this.widget_order = []; this.make(); } @@ -86,7 +87,19 @@ export default class WidgetGroup { } on_delete(name) { - this.widgets_list = this.widgets_list.filter(wid => name != wid.name) + this.widgets_list = this.widgets_list.filter(wid => name != wid.name); + delete this.widgets_dict[name]; + this.update_widget_order(); + } + + update_widget_order() { + this.widget_order = []; + this.body.children().each((index, element) => { + let name = element.dataset.widgetName; + if (name) { + this.widget_order.push(name); + } + }) } setup_sortable() { @@ -94,11 +107,25 @@ export default class WidgetGroup { this.sortable = new Sortable(container, { animation: 150, handle: ".drag-handle", - onEnd: () => { - console.log("Sorting") - }, // onStart: (evt) => this.sortable_config.on_start(evt, container) + onEnd: () => this.update_widget_order(), }); } + + get_widget_config() { + this.update_widget_order(); + let prepared_dict = {}; + + this.widgets_list.forEach(wid => { + let config = wid.get_config() + let name = config.docname ? config.docname : config.name + prepared_dict[name] = config + }); + + return { + order: this.widget_order, + widgets: prepared_dict + } + } } frappe.widget.WidgetGroup = WidgetGroup; \ No newline at end of file From b1720604bdf23780bf283846eaff72e996f8aa80 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 13:51:50 +0530 Subject: [PATCH 068/408] refactor: save widget name as data attribute --- frappe/public/js/frappe/widgets/base_widget.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 4064c34d01..dea800e452 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -42,7 +42,9 @@ export default class Widget { } make_widget() { - this.widget = $(`
+ this.widget = $(`
From fb303febe683f9a81bb74566a107a2cbac90c900 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 13:52:22 +0530 Subject: [PATCH 069/408] feat: get_config api to save customizations --- frappe/public/js/frappe/widgets/base_widget.js | 8 ++++++++ frappe/public/js/frappe/widgets/chart_widget.js | 9 +++++++++ frappe/public/js/frappe/widgets/links_widget.js | 10 ++++++++++ frappe/public/js/frappe/widgets/shortcut_widget.js | 14 ++++++++++++++ 4 files changed, 41 insertions(+) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index dea800e452..3e28d664da 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -10,6 +10,14 @@ export default class Widget { this.set_body(); } + get_config() { + return { + name: this.name, + docname: this.docname, + label: this.label, + } + } + customize(options) { this.action_area.empty(); diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index bb8e412fa7..18951689c9 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -9,6 +9,15 @@ export default class ChartWidget extends Widget { this.height = 240; } + get_config() { + return { + name: this.name, + docname: this.docname, + chart_name: this.chart_name, + label: this.label, + } + } + refresh() { this.make_chart(); } diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js index e8012b03d8..951f386e19 100644 --- a/frappe/public/js/frappe/widgets/links_widget.js +++ b/frappe/public/js/frappe/widgets/links_widget.js @@ -10,6 +10,16 @@ export default class LinksWidget extends Widget { // } + get_config() { + return { + name: this.name, + docname: this.docname, + links: JSON.stringify(this.links), + label: this.label, + title: this.title, + } + } + set_body() { this.options = {}; this.options.links = this.links; diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index 81ab4f05e4..ad3ad05fdf 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -18,6 +18,20 @@ export default class ShortcutWidget extends Widget { // } + get_config() { + return { + name: this.name, + docname: this.docname, + icon: this.icon, + label: this.label, + format: this.format, + link_to: this.link_to, + restrict_to_domain: this.restrict_to_domain, + stats_filter: this.stats_filter, + type: this.type + } + } + setup_events() { this.widget.click(() => { let route = generate_route(this) From 3ae487544b239f8b4b194ecde787984291ea5822 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 13:53:05 +0530 Subject: [PATCH 070/408] feat: customization handlers for widgets --- .../public/js/frappe/widgets/base_widget.js | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 3e28d664da..17e0d7d218 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -35,7 +35,7 @@ export default class Widget { options.allow_hiding && this.add_custom_button( '', - () => this.hide() + () => this.hide_or_show() ); options.allow_edit && this.add_custom_button( @@ -90,7 +90,8 @@ export default class Widget { // wait for animation setTimeout(() => { this.widget.remove(); - this.on_delete && this.on_delete(this.name); + this.options.on_delete + && this.options.on_delete(this.name); }, 300); } @@ -98,10 +99,18 @@ export default class Widget { this.on_edit && this.on_edit(this.name); } - hide() { - this.body.css("opacity", 0.5); - this.title_field.css("opacity", 0.5); - this.footer.css("opacity", 0.5); + hide_or_show() { + if (!this.hidden) { + this.body.css("opacity", 0.5); + this.title_field.css("opacity", 0.5); + this.footer.css("opacity", 0.5); + this.hidden = true; + } else { + this.body.css("opacity", 1); + this.title_field.css("opacity", 1); + this.footer.css("opacity", 1); + this.hidden = false; + } } set_actions() { From faeb7a6c3377725bc5565e2c5871021971c7232e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 13:53:43 +0530 Subject: [PATCH 071/408] feat: allow saving customizations --- .../public/js/frappe/views/desktop/desktop.js | 23 ++++++++++++++----- 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index a51654f91b..0b51f07975 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -165,9 +165,7 @@ class DesktopPage {
`).hide(); this.save_or_discard_link.appendTo(this.page); - this.save_or_discard_link.find(".save-customization").on("click", () => { - console.log("Save Customization"); - }); + this.save_or_discard_link.find(".save-customization").on("click", () => this.save_customization()); this.save_or_discard_link.find(".discard-customization").on("click", () => { this.in_customize_mode = false; @@ -264,6 +262,19 @@ class DesktopPage { this.in_customize_mode = true; } + save_customization() { + const config = {}; + + if (this.sections.charts) config.charts = this.sections.charts.get_widget_config(); + if (this.sections.shortcuts) config.shortcuts = this.sections.shortcuts.get_widget_config(); + if (this.sections.cards) config.cards = this.sections.cards.get_widget_config(); + + return frappe.call('frappe.desk.desktop.save_customization', { + page: this.page_name, + config: config + }) + } + make_charts() { this.sections["charts"] = new frappe.widget.WidgetGroup({ title: this.data.charts.label || `${this.page_name} Dashboard`, @@ -275,7 +286,7 @@ class DesktopPage { allow_create: this.allow_customization, allow_delete: this.allow_customization, allow_hiding: false, - allow_edit: this.allow_customization, + allow_edit: false, }, widgets: this.data.charts.items }); @@ -292,7 +303,7 @@ class DesktopPage { allow_create: this.allow_customization, allow_delete: this.allow_customization, allow_hiding: false, - allow_edit: this.allow_customization, + allow_edit: false, }, widgets: this.data.shortcuts.items }); @@ -308,7 +319,7 @@ class DesktopPage { allow_sorting: this.allow_customization && !frappe.is_mobile(), allow_create: false, allow_delete: false, - allow_hiding: this.allow_customization, + allow_hiding: false, allow_edit: false, }, widgets: this.data.cards.items From d1ea9f9044a3a9656d66f411eda740e7fdd4911a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 13:54:16 +0530 Subject: [PATCH 072/408] feat: API to save customization and override default page for user --- frappe/desk/desktop.py | 193 +++++++++---------- frappe/desk/doctype/desk_page/desk_page.json | 26 ++- 2 files changed, 110 insertions(+), 109 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index ef84114745..10f8db5b83 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -import json +from json import loads, dumps from frappe import _, DoesNotExistError from frappe.boot import get_allowed_pages, get_allowed_reports from six import string_types @@ -13,10 +13,24 @@ from frappe.cache_manager import build_domain_restriced_doctype_cache, build_dom class Workspace: def __init__(self, page_name): self.page_name = page_name + self.extended_cards = [] + self.extended_charts = [] + self.extended_shortcuts = [] - def build_cache(self): - self.doc = frappe.get_doc("Desk Page", self.page_name) - self.get_pages_to_extend() + def get_page_for_user(self): + filters = { + 'extends': self.page_name, + 'for_user': frappe.session.user + } + pages = frappe.get_list("Desk Page", filters=filters) + if pages: + return frappe.get_doc("Desk Page", pages[0]) + else: + self.get_pages_to_extend() + return frappe.get_doc("Desk Page", self.page_name) + + def init(self): + self.doc = self.get_page_for_user() user = frappe.get_user() user.build_permissions() @@ -32,13 +46,11 @@ class Workspace: def get_pages_to_extend(self): pages = frappe.get_all("Desk Page", filters={ "extends": self.page_name, - 'restrict_to_domain': ['in', frappe.get_active_domains()] + 'restrict_to_domain': ['in', frappe.get_active_domains()], + 'for_user': '' }) pages = [frappe.get_doc("Desk Page", page['name']) for page in pages] - self.extended_cards = [] - self.extended_charts = [] - self.extended_shortcuts = [] for page in pages: self.extended_cards = self.extended_cards + page.cards @@ -111,7 +123,7 @@ class Workspace: for section in cards: new_items = [] if isinstance(section.links, string_types): - links = json.loads(section.links) + links = loads(section.links) else: links = section.links @@ -134,6 +146,7 @@ class Workspace: new_section = section.as_dict().copy() new_section["links"] = new_items new_section["label"] = section.title + new_section["docname"] = section.name new_data.append(new_section) return new_data @@ -147,6 +160,7 @@ class Workspace: for chart in charts: chart.label = chart.label if chart.label else chart.chart_name + chart.docname = chart.name all_charts.append(chart) return all_charts @@ -166,6 +180,7 @@ class Workspace: for item in shortcuts: new_item = item.as_dict().copy() + new_item['docname'] = item.name new_item['name'] = _(item.link_to) if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item): if item.type == "Page": @@ -193,7 +208,7 @@ def get_desktop_page(page): """ wspace = Workspace(page) try: - wspace.build_cache() + wspace.init() wspace.build_workspace() return { 'charts': wspace.charts, @@ -205,7 +220,7 @@ def get_desktop_page(page): except DoesNotExistError: if frappe.message_log: frappe.message_log.pop() - return None + return Nonee @frappe.whitelist() def get_desk_sidebar_items(): @@ -214,7 +229,9 @@ def get_desk_sidebar_items(): # don't get domain restricted pages filters = { 'restrict_to_domain': ['in', frappe.get_active_domains()], - 'extends_another_page': False + 'extends_another_page': 0, + 'is_standard': 1, + 'for_user': '' } if not frappe.local.conf.developer_mode: @@ -279,104 +296,72 @@ def get_custom_report_list(module): return out -def make_them_pages(): - """Helper function to make pages - """ - pages = [ - ('Desk', 'frappe', 'octicon octicon-calendar'), - ('Settings', 'frappe', 'octicon octicon-settings'), - ('Users and Permissions', 'frappe', 'octicon octicon-settings'), - ('Customization', 'frappe', 'octicon octicon-settings'), - ('Integrations', 'frappe', 'octicon octicon-globe'), - ('Core', 'frappe', 'octicon octicon-circuit-board'), - ('Website', 'frappe', 'octicon octicon-globe'), - ('Getting Started', 'erpnext', 'fa fa-check-square-o'), - ('Accounts', 'erpnext', 'octicon octicon-repo'), - ('Selling', 'erpnext', 'octicon octicon-tag'), - ('Buying', 'erpnext', 'octicon octicon-briefcase'), - ('Stock', 'erpnext', 'octicon octicon-package'), - ('Assets', 'erpnext', 'octicon octicon-database'), - ('Projects', 'erpnext', 'octicon octicon-rocket'), - ('CRM', 'erpnext', 'octicon octicon-broadcast'), - ('Support', 'erpnext', 'fa fa-check-square-o'), - ('HR', 'erpnext', 'octicon octicon-organization'), - ('Quality Management', 'erpnext', 'fa fa-check-square-o'), - ('Manufacturing', 'erpnext', 'octicon octicon-tools'), - ('Retail', 'erpnext', 'octicon octicon-credit-card'), - ('Education', 'erpnext', 'octicon octicon-mortar-board'), - ('Healthcare', 'erpnext', 'fa fa-heartbeat'), - ('Agriculture', 'erpnext', 'octicon octicon-globe'), - ('Non Profit', 'erpnext', 'octicon octicon-heart'), - ('Help', 'erpnext', 'octicon octicon-device-camera-video') - ] - - for page in pages: - print("Processing Page: {0}".format(page[0])) - make_them_cards(page[0], page[2]) - - -def make_them_cards(page_name, from_module=None, to_module=None, icon=None): - from frappe.desk.moduleview import get - - if not from_module: - from_module = page_name - - if not to_module: - to_module = page_name - - try: - modules = get(from_module)['data'] - except: - return - - # Find or make page doc - if frappe.db.exists("Desk Page", page_name): - page = frappe.get_doc("Desk Page", page_name) - print("--- Got Page: {0}".format(page.name)) +def get_custom_workspace_for_user(page): + filters = { + 'extends': page, + 'for_user': frappe.session.user + } + pages = frappe.get_list("Desk Page", filters=filters) + if pages: + return frappe.get_doc("Desk Page", pages[0]) else: - page = frappe.new_doc("Desk Page") - page.label = page_name - page.cards = [] - page.icon = icon - print("--- New Page: {0}".format(page.name)) + doc = frappe.new_doc("Desk Page") + doc.extends = page + doc.for_user = frappe.session.user + return doc - # Guess Which Module - if not to_module and frappe.db.exists("Module Def", page_name): - page.module = page_name - if to_module: - page.module = to_module - elif frappe.db.exists("Module Def", page_name): - page.module = page_name +@frappe.whitelist() +def save_customization(page, config): + original_page = frappe.get_doc("Desk Page", page) + page_doc = get_custom_workspace_for_user(page) - for data in modules: - # Create a New Card Child Doc - card = frappe.new_doc("Desk Card") + # Update field values + page_doc.charts_label = original_page.charts_label + page_doc.cards_label = original_page.cards_label + page_doc.shortcuts_label = original_page.shortcuts_label + page_doc.charts_label = original_page.charts_label + page_doc.icon = original_page.icon + page_doc.module = original_page.module + page_doc.developer_mode_only = original_page.developer_mode_only + page_doc.category = original_page.category - # Data clean up - for item in data['items']: - try: - del item['count'] - del item['incomplete_dependencies'] - except KeyError: - pass - # Set Child doc values - card.title = data['label'] - card.icon = data.get('icon') - # Pretty dump JSON - card.links = json.dumps(data['items'], indent=4, sort_keys=True) + config = frappe._dict(loads(config)) - # Set Parent attributes - card.parent = page.name - card.parenttype = page.doctype - card.parentfield = "cards" + page_doc.charts = prepare_widget(config.charts, "Desk Chart", "charts") + page_doc.shortcuts = prepare_widget(config.shortcuts, "Desk Shortcut", "shortcuts") + page_doc.cards = prepare_widget(config.cards, "Desk Card", "cards") - # Add cards to page doc - print("------- Adding Card: {0}".format(card.title)) - page.cards.append(card) + # Set label + page_doc.label = page + '-' + frappe.session.user - # End it all - page.save() - frappe.db.commit() - return \ No newline at end of file + if page_doc.is_new(): + page_doc.insert() + else: + page_doc.save() + +def prepare_widget(config, doctype, parentfield): + if not config: + return + order = config.get('order') + widgets = config.get('widgets') + prepare_widget_list = [] + for idx, name in enumerate(order): + wid_config = widgets[name].copy() + # Some cleanup + wid_config.pop("name", None) + wid_config.pop("docname", None) + + # New Doc + doc = frappe.new_doc(doctype) + doc.update(wid_config) + + # Manually Set IDX + doc.idx = idx + 1 + + # Set Parent Field + doc.parentfield = parentfield + + prepare_widget_list.append(doc) + return prepare_widget_list diff --git a/frappe/desk/doctype/desk_page/desk_page.json b/frappe/desk/doctype/desk_page/desk_page.json index 6bc33d1326..7e6baf221b 100644 --- a/frappe/desk/doctype/desk_page/desk_page.json +++ b/frappe/desk/doctype/desk_page/desk_page.json @@ -9,6 +9,7 @@ "field_order": [ "label", "extends", + "for_user", "module", "category", "restrict_to_domain", @@ -36,7 +37,6 @@ "fieldname": "label", "fieldtype": "Data", "label": "Name", - "length": 22, "unique": 1 }, { @@ -52,6 +52,7 @@ "options": "Desk Chart" }, { + "depends_on": "eval:!doc.extends_another_page || !doc.is_standard", "fieldname": "shortcuts", "fieldtype": "Table", "label": "Shortcuts", @@ -136,16 +137,19 @@ "search_index": 1 }, { + "depends_on": "eval:!doc.extends_another_page || !doc.is_standard", "fieldname": "charts_label", "fieldtype": "Data", "label": "Label" }, { + "depends_on": "eval:!doc.extends_another_page || !doc.is_standard", "fieldname": "shortcuts_label", "fieldtype": "Data", "label": "Label" }, { + "depends_on": "eval:!doc.extends_another_page || !doc.is_standard", "fieldname": "cards_label", "fieldtype": "Data", "label": "Label" @@ -166,24 +170,36 @@ "default": "0", "fieldname": "is_standard", "fieldtype": "Check", - "label": "Is Standard" + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Is Standard", + "search_index": 1 }, { "default": "0", "fieldname": "extends_another_page", "fieldtype": "Check", - "label": "Extends Another Page" + "label": "Extends Another Page", + "search_index": 1 }, { "depends_on": "eval:doc.extends_another_page == 1", "fieldname": "extends", "fieldtype": "Link", + "in_standard_filter": 1, "label": "Extends", - "options": "Desk Page" + "options": "Desk Page", + "search_index": 1 + }, + { + "fieldname": "for_user", + "fieldtype": "Data", + "label": "For User", + "read_only": 1 } ], "links": [], - "modified": "2020-03-12 16:38:16.206732", + "modified": "2020-03-26 12:35:41.981432", "modified_by": "Administrator", "module": "Desk", "name": "Desk Page", From 1bfb1a3d37daaab255aab1b6c8d9e6ec2b578eea Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 26 Mar 2020 14:50:57 +0530 Subject: [PATCH 073/408] refactor(Dashboard): refactor dashboard permissions --- frappe/core/page/dashboard/dashboard.json | 2 +- frappe/desk/doctype/dashboard/dashboard.json | 23 +++++++++- .../dashboard_chart/dashboard_chart.json | 32 ++++++++++++- .../dashboard_chart/dashboard_chart.py | 45 +++++++++++++++++++ frappe/hooks.py | 2 + frappe/permissions.py | 2 +- 6 files changed, 102 insertions(+), 4 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.json b/frappe/core/page/dashboard/dashboard.json index 891dcb26f8..58fda5a34c 100644 --- a/frappe/core/page/dashboard/dashboard.json +++ b/frappe/core/page/dashboard/dashboard.json @@ -4,7 +4,7 @@ "docstatus": 0, "doctype": "Page", "idx": 0, - "modified": "2019-01-08 19:19:48.073410", + "modified": "2020-03-26 13:30:44.603948", "modified_by": "Administrator", "module": "Core", "name": "dashboard", diff --git a/frappe/desk/doctype/dashboard/dashboard.json b/frappe/desk/doctype/dashboard/dashboard.json index 239f35bea8..c177ee70ac 100644 --- a/frappe/desk/doctype/dashboard/dashboard.json +++ b/frappe/desk/doctype/dashboard/dashboard.json @@ -34,7 +34,7 @@ } ], "links": [], - "modified": "2020-01-26 20:00:10.069817", + "modified": "2020-03-25 21:09:37.080132", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard", @@ -51,6 +51,27 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Dashboard Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1 } ], "quick_entry": 1, diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 0a017a0de2..8c14ea130d 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -215,7 +215,7 @@ } ], "links": [], - "modified": "2020-03-13 19:19:37.162771", + "modified": "2020-03-26 13:41:11.126000", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", @@ -232,6 +232,36 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Dashboard Manager", + "share": 1, + "write": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "Dashboard User", + "share": 1 + }, + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1 } ], "sort_field": "modified", diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index f01c976b9c..634b33cd97 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -10,8 +10,53 @@ import json from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate, get_datetime from frappe.model.naming import append_number_if_name_exists +from frappe.boot import get_allowed_reports from frappe.model.document import Document + +def get_permission_query_conditions(user): + + if not user: + user = frappe.session.user + + if user == 'Administrator': + return + + roles = frappe.get_roles(user) + if "System Manager" in roles or "Dashboard Manager" in roles or "Dashboard User" in roles: + return None + + allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read()) + allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()]) + + return ''' + `tabDashboard Chart`.`chart_type` = 'Custom' + or `tabDashboard Chart`.`document_type` in {allowed_doctypes} + or `tabDashboard Chart`.`report_name` in {allowed_reports} + '''.format( + allowed_doctypes=allowed_doctypes, + allowed_reports=allowed_reports + ) + + +def has_permission(doc, ptype, user): + roles = frappe.get_roles(user) + if "System Manager" in roles or "Dashboard Manager" in roles or "Dashboard User" in roles: + return True + + if doc.chart_type == 'Custom': + return True + elif doc.chart_type == 'Report': + allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()]) + if doc.report_name in allowed_reports: + return True + else: + allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read()) + if doc.document_type in allowed_doctypes: + return True + + return False + @frappe.whitelist() @cache_source def get(chart_name = None, chart = None, no_cache = None, filters = None, from_date = None, diff --git a/frappe/hooks.py b/frappe/hooks.py index c44c05fdf4..733cec7a08 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -87,6 +87,7 @@ permission_query_conditions = { "ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions", "User": "frappe.core.doctype.user.user.get_permission_query_conditions", "Notification Log": "frappe.desk.doctype.notification_log.notification_log.get_permission_query_conditions", + "Dashboard Chart": "frappe.desk.doctype.dashboard_chart.dashboard_chart.get_permission_query_conditions", "Notification Settings": "frappe.desk.doctype.notification_settings.notification_settings.get_permission_query_conditions", "Note": "frappe.desk.doctype.note.note.get_permission_query_conditions", "Kanban Board": "frappe.desk.doctype.kanban_board.kanban_board.get_permission_query_conditions", @@ -101,6 +102,7 @@ has_permission = { "ToDo": "frappe.desk.doctype.todo.todo.has_permission", "User": "frappe.core.doctype.user.user.has_permission", "Note": "frappe.desk.doctype.note.note.has_permission", + "Dashboard Chart": "frappe.desk.doctype.dashboard_chart.dashboard_chart.has_permission", "Kanban Board": "frappe.desk.doctype.kanban_board.kanban_board.has_permission", "Contact": "frappe.contacts.address_and_contact.has_permission", "Address": "frappe.contacts.address_and_contact.has_permission", diff --git a/frappe/permissions.py b/frappe/permissions.py index a0d1677fac..9835724ce0 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -307,7 +307,7 @@ def has_controller_permissions(doc, ptype, user=None): return None def get_doctypes_with_read(): - return list(set([p.parent for p in get_valid_perms()])) + return list(set([p.parent.encode('UTF8') for p in get_valid_perms()])) def get_valid_perms(doctype=None, user=None): '''Get valid permissions for the current user from DocPerm and Custom DocPerm''' From 3a57996b9afc932bbcaa0c569b74a545b7ebdf19 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 14:53:15 +0530 Subject: [PATCH 074/408] feat: make get_desktop_page readonly --- frappe/desk/desktop.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 10f8db5b83..ae1ed35d70 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -196,6 +196,7 @@ class Workspace: return items @frappe.whitelist() +@frappe.read_only() def get_desktop_page(page): """Applies permissions, customizations and returns the configruration for a page on desk. From 5917677251c77e355ed6d0b8e0c4d16ce4f0e6b9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 14:53:33 +0530 Subject: [PATCH 075/408] refactor: rename bookmark to shortcut --- frappe/public/js/frappe/views/desktop/desktop.js | 2 +- frappe/public/js/frappe/widgets/widget_group.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 0b51f07975..f00c053594 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -296,7 +296,7 @@ class DesktopPage { this.sections["shortcuts"] = new frappe.widget.WidgetGroup({ title: this.data.shortcuts.label || `Your Shortcuts`, container: this.page, - type: "bookmark", + type: "shortcut", columns: 3, options: { allow_sorting: this.allow_customization && !frappe.is_mobile(), diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 249dabf008..c61d6d6747 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -10,7 +10,7 @@ frappe.provide('frappe.widget') const widget_factory = { chart: ChartWidget, base: BaseWidget, - bookmark: ShortcutWidget, + shortcut: ShortcutWidget, links: LinksWidget, onboarding: OnboardingWidget, new: NewWidget From 744cedcfcf0cb298dfcb85a8377a05e541867812 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 14:54:04 +0530 Subject: [PATCH 076/408] chore: cleanup --- frappe/public/js/frappe/widgets/onboarding_widget.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index ea9428972e..736af01cc5 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -3,7 +3,6 @@ import Widget from "./base_widget.js"; export default class OnboardingWidget extends Widget { constructor(opts) { super(opts); - window.onb = this; } refresh() { } From cf20d3bf753f51e47691f4db8cc2c4e643b7b4d3 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 26 Mar 2020 17:08:10 +0530 Subject: [PATCH 077/408] fix: minor change --- frappe/website/doctype/website_settings/google_indexing.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/website/doctype/website_settings/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py index f38dc22743..ecf88438eb 100644 --- a/frappe/website/doctype/website_settings/google_indexing.py +++ b/frappe/website/doctype/website_settings/google_indexing.py @@ -76,7 +76,8 @@ def get_google_indexing_object(): "token": account.get_access_token(), "refresh_token": account.get_password(fieldname="indexing_refresh_token", raise_exception=False), "token_uri": get_auth_url(), - "client_id": google_settings.client_i . "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), + "client_id": google_settings.client_id, + "client_secret": google_settings.get_password(fieldname="client_secret", raise_exception=False), "scopes": "https://www.googleapis.com/auth/indexing" } From b4ebaa662bc0c8fca1a4222bc003f00dc1065cc7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 26 Mar 2020 14:54:16 +0530 Subject: [PATCH 078/408] feat: create new widget dialog from child table meta --- frappe/public/js/frappe/widgets/new_widget.js | 53 +++++++++++++++---- .../public/js/frappe/widgets/widget_group.js | 42 +++++++++------ 2 files changed, 69 insertions(+), 26 deletions(-) diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index 47af5c03fa..18cd8fdd56 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -1,8 +1,12 @@ +const WIDGET_DOCTYPE_MAP = { + chart: "Desk Chart", + shortcut: "Desk Shortcut", +} + export default class NewWidget { constructor(opts) { Object.assign(this, opts); this.make(); - window.wid = this; } refresh() { @@ -19,27 +23,54 @@ export default class NewWidget { this.setup_events(); } + get_title() { + return __(`New ${frappe.utils.to_title_case(this.type)}`) + } + make_widget() { this.widget = $(`
- + New + + ${this.get_title()}
`); this.body = this.widget - this.set_body(); + } + + setup_events() { + this.widget.on('click', () => this.open_dialog()) } delete() { this.widget.remove(); } - set_actions() { - // - } + open_dialog() { + let doctype = WIDGET_DOCTYPE_MAP[this.type] - set_body() { - // - } + if (!doctype) { + console.log(`Could not find ${this.type}`) + } - setup_events() { - // + frappe.model.with_doctype(doctype, () => { + let new_dialog = new frappe.ui.Dialog({ + title: this.get_title(), + fields: frappe.get_meta(doctype).fields, + primary_action: (data) => { + if (this.type == 'chart' && !data.label) { + data.label = data.chart_name; + } + + if (this.type == 'shortcut') { + data.label = data.link_to; + } + + data.docname = frappe.utils.get_random(20); + + new_dialog.hide(); + this.on_create(data); + }, + primary_action_label: __("Add"), + }); + + new_dialog.show() + }); } } \ No newline at end of file diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index c61d6d6747..debef4f0e1 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -57,20 +57,25 @@ export default class WidgetGroup { make_widgets() { this.body.empty() + this.widgets.forEach(widget => { + this.add_widget(widget) + }); + } + + add_widget(widget) { const widget_class = widget_factory[this.type]; - this.widgets.forEach(widget => { - let widget_object = new widget_class({ - ...widget, - container: this.body, - options: { - ...this.options, - on_delete: (name) => this.on_delete(name) - } - }); - this.widgets_list.push(widget_object); - this.widgets_dict[widget.name] = widget_object; + let widget_object = new widget_class({ + ...widget, + container: this.body, + options: { + ...this.options, + on_delete: (name) => this.on_delete(name) + } }); + + this.widgets_list.push(widget_object); + this.widgets_dict[widget.name] = widget_object; } customize() { @@ -78,10 +83,17 @@ export default class WidgetGroup { wid.customize(this.options); }) - this.options.allow_create && new NewWidget({ - container: this.body, - type: this.type - }) + if (this.options.allow_create) { + this.new_widget = new NewWidget({ + container: this.body, + type: this.type, + on_create: (config) => { + this.new_widget.delete(); + this.add_widget(config); + this.customize(); + } + }) + } this.options.allow_sorting && this.setup_sortable(); } From 5a00b5d65af1d196babecee61cdf87b5dfbb8475 Mon Sep 17 00:00:00 2001 From: Rohan Date: Thu, 26 Mar 2020 17:59:55 +0530 Subject: [PATCH 079/408] fix: redirect to defined success URL --- frappe/public/js/frappe/web_form/web_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js index 5f4b012c4e..0c45a03788 100644 --- a/frappe/public/js/frappe/web_form/web_form.js +++ b/frappe/public/js/frappe/web_form/web_form.js @@ -174,7 +174,7 @@ export default class WebForm extends frappe.ui.FieldGroup { title: __("Saved Successfully"), secondary_action: () => { if (this.success_url) { - window.location.pathname = this.success_url; + window.location.href = this.success_url; } else if(this.login_required) { window.location.href = window.location.pathname + "?name=" + data.name; From 55a790cd63b27ce8b6d15abcd81f521a878dd44c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 12:12:01 +0530 Subject: [PATCH 080/408] feat: added check for hidden --- frappe/desk/doctype/desk_card/desk_card.json | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/desk_card/desk_card.json b/frappe/desk/doctype/desk_card/desk_card.json index 4ccffd4f58..522911e804 100644 --- a/frappe/desk/doctype/desk_card/desk_card.json +++ b/frappe/desk/doctype/desk_card/desk_card.json @@ -5,8 +5,9 @@ "engine": "InnoDB", "field_order": [ "title", - "column_break_2", "icon", + "column_break_2", + "hidden", "section_break_3", "links" ], @@ -37,11 +38,17 @@ "fieldname": "icon", "fieldtype": "Data", "label": "Icon" + }, + { + "default": "0", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden" } ], "istable": 1, "links": [], - "modified": "2020-02-03 12:40:42.595122", + "modified": "2020-03-27 12:05:00.545808", "modified_by": "Administrator", "module": "Desk", "name": "Desk Card", From 4585bbba006bd8a3e406bd11d08e4ca2fe812f44 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 12:14:33 +0530 Subject: [PATCH 081/408] feat: reload desktop after save --- .../public/js/frappe/views/desktop/desktop.js | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index f00c053594..1d4cdbf83d 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -142,6 +142,7 @@ class DesktopPage { this.sections = {}; this.allow_customization = false; this.in_customize_mode = false; + this.container.empty(); this.make(); } @@ -153,6 +154,12 @@ class DesktopPage { this.page.hide(); } + reload() { + this.in_customize_mode = false; + this.container.empty(); + this.make(); + } + make_customization_link() { this.customize_link = $(`
Customize Workspace
`); this.customize_link.appendTo(this.page); @@ -167,11 +174,7 @@ class DesktopPage { this.save_or_discard_link.appendTo(this.page); this.save_or_discard_link.find(".save-customization").on("click", () => this.save_customization()); - this.save_or_discard_link.find(".discard-customization").on("click", () => { - this.in_customize_mode = false; - this.container.empty(); - this.make(); - }) + this.save_or_discard_link.find(".discard-customization").on("click", () => this.reload()) this.page.addClass('allow-customization'); } @@ -269,9 +272,11 @@ class DesktopPage { if (this.sections.shortcuts) config.shortcuts = this.sections.shortcuts.get_widget_config(); if (this.sections.cards) config.cards = this.sections.cards.get_widget_config(); - return frappe.call('frappe.desk.desktop.save_customization', { + frappe.call('frappe.desk.desktop.save_customization', { page: this.page_name, config: config + }).then(res => { + this.reload(); }) } @@ -319,7 +324,7 @@ class DesktopPage { allow_sorting: this.allow_customization && !frappe.is_mobile(), allow_create: false, allow_delete: false, - allow_hiding: false, + allow_hiding: this.allow_customization, allow_edit: false, }, widgets: this.data.cards.items From 6b4a070ab39f4a086500dd9671e89719c39d22e6 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 12:14:59 +0530 Subject: [PATCH 082/408] feat: allow hiding of link cards --- frappe/public/js/frappe/widgets/base_widget.js | 15 +++++++++++++-- frappe/public/js/frappe/widgets/links_widget.js | 1 + 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 17e0d7d218..c45bd3b5b9 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -26,17 +26,28 @@ export default class Widget { '', () => this.delete() ); + options.allow_sorting && this.add_custom_button( '', null, "drag-handle" ); - options.allow_hiding && + + if (options.allow_hiding) { + if (this.hidden) { + this.widget.removeClass('hidden') + this.body.css("opacity", 0.5); + this.title_field.css("opacity", 0.5); + this.footer.css("opacity", 0.5); + } + this.add_custom_button( '', () => this.hide_or_show() ); + } + options.allow_edit && this.add_custom_button( '', @@ -50,7 +61,7 @@ export default class Widget { } make_widget() { - this.widget = $(`
diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js index 951f386e19..760fc0fd97 100644 --- a/frappe/public/js/frappe/widgets/links_widget.js +++ b/frappe/public/js/frappe/widgets/links_widget.js @@ -17,6 +17,7 @@ export default class LinksWidget extends Widget { links: JSON.stringify(this.links), label: this.label, title: this.title, + hidden: this.hidden, } } From c205eab1e85d9ead23c403df4f9d144fb5c2cbf0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 12:29:42 +0530 Subject: [PATCH 083/408] chore: formatting --- .../public/js/frappe/widgets/base_widget.js | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index c45bd3b5b9..8ea5a66648 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -14,8 +14,8 @@ export default class Widget { return { name: this.name, docname: this.docname, - label: this.label, - } + label: this.label + }; } customize(options) { @@ -36,7 +36,7 @@ export default class Widget { if (options.allow_hiding) { if (this.hidden) { - this.widget.removeClass('hidden') + this.widget.removeClass("hidden"); this.body.css("opacity", 0.5); this.title_field.css("opacity", 0.5); this.footer.css("opacity", 0.5); @@ -44,7 +44,12 @@ export default class Widget { this.add_custom_button( '', - () => this.hide_or_show() + () => this.hide_or_show(), + "show-or-hide-button" + ); + + this.show_or_hide_button = this.action_area.find( + ".show-or-hide-button" ); } @@ -61,9 +66,9 @@ export default class Widget { } make_widget() { - this.widget = $(`
+ this.widget = $(`
@@ -101,8 +106,7 @@ export default class Widget { // wait for animation setTimeout(() => { this.widget.remove(); - this.options.on_delete - && this.options.on_delete(this.name); + this.options.on_delete && this.options.on_delete(this.name); }, 300); } From 3246715afa98cb8f845a1a6bf61dab63e60f730b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 12:29:54 +0530 Subject: [PATCH 084/408] feat: toggle button icon on show or hide --- frappe/public/js/frappe/widgets/base_widget.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 8ea5a66648..693cc24519 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -119,11 +119,19 @@ export default class Widget { this.body.css("opacity", 0.5); this.title_field.css("opacity", 0.5); this.footer.css("opacity", 0.5); + this.show_or_hide_button.empty(); + $(``).appendTo( + this.show_or_hide_button + ); this.hidden = true; } else { this.body.css("opacity", 1); this.title_field.css("opacity", 1); this.footer.css("opacity", 1); + this.show_or_hide_button.empty(); + $(``).appendTo( + this.show_or_hide_button + ); this.hidden = false; } } From 70127a058412d2d608025dd4333c626f37f3d49e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 12:33:15 +0530 Subject: [PATCH 085/408] feat: allow users to save customizations --- frappe/desk/desktop.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index ae1ed35d70..9954d593d7 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -338,9 +338,9 @@ def save_customization(page, config): page_doc.label = page + '-' + frappe.session.user if page_doc.is_new(): - page_doc.insert() + page_doc.insert(ignore_permissions=True) else: - page_doc.save() + page_doc.save(ignore_permissions=True) def prepare_widget(config, doctype, parentfield): if not config: From 102e871e6a988ddc960aecf4237351efcff1b0b8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 12:59:02 +0530 Subject: [PATCH 086/408] feat: allow adding widgets to empty groups --- .../public/js/frappe/views/desktop/desktop.js | 19 ++++++++++++++++--- .../public/js/frappe/widgets/widget_group.js | 6 ++---- frappe/public/less/desktop.less | 4 ---- 3 files changed, 18 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 1d4cdbf83d..2a23d8d8a4 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -144,6 +144,7 @@ class DesktopPage { this.in_customize_mode = false; this.container.empty(); this.make(); + window.page = this; } show() { @@ -194,10 +195,14 @@ class DesktopPage { this.allow_customization && this.make_customization_link(); !this.sections["onboarding"] && - this.data.charts.items.length && + this.data.charts.items && this.make_charts(); - this.data.shortcuts.items.length && this.make_shortcuts(); - this.data.cards.items.length && this.make_cards(); + this.data.shortcuts.items && this.make_shortcuts(); + this.data.cards.items && this.make_cards(); + if (this.allow_customization) { + // Move the widget group up to align with labels if customization is allowed + $('.desk-page .widget-group:visible:first').css('margin-top', '-25px'); + } }); } @@ -256,6 +261,11 @@ class DesktopPage { return } + // It may be possible the chart area is hidden since it has no widgets + // So the margin-top: -25px would be applied to the shortcut group + // We need to remove this as the chart group will be visible during customization + $('.desk-page .widget-group:visible:first').css('margin-top', '0px'); + this.customize_link.hide(); this.save_or_discard_link.show(); @@ -263,6 +273,9 @@ class DesktopPage { this.sections[section].customize(); }) this.in_customize_mode = true; + + // Move the widget group up to align with labels if customization is allowed + $('.desk-page .widget-group:visible:first').css('margin-top', '-25px'); } save_customization() { diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index debef4f0e1..28e47018df 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -27,10 +27,6 @@ export default class WidgetGroup { make() { this.make_container(); - this.refresh(); - } - - refresh() { this.title && this.set_title(this.title); this.widgets && this.make_widgets(); } @@ -48,6 +44,7 @@ export default class WidgetGroup { this.title_area = widget_area.find(".widget-group-title"); this.control_area = widget_area.find(".widget-group-control"); this.body = widget_area.find(".widget-group-body"); + !this.widgets.length && this.widget_area.hide(); widget_area.appendTo(this.container); } @@ -79,6 +76,7 @@ export default class WidgetGroup { } customize() { + this.widget_area.show(); this.widgets_list.forEach(wid => { wid.customize(this.options); }) diff --git a/frappe/public/less/desktop.less b/frappe/public/less/desktop.less index 7d7643ec3e..4699f94ccf 100644 --- a/frappe/public/less/desktop.less +++ b/frappe/public/less/desktop.less @@ -93,10 +93,6 @@ cursor: pointer; } } - - .widget-group:nth-child(3) { - margin-top: -25px; - } } } From d8b2b62b76ac37c8e03dccd92b18f3deab679281 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 13:41:02 +0530 Subject: [PATCH 087/408] refactor: logic for toggling show hide button --- frappe/public/js/frappe/widgets/base_widget.js | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 693cc24519..7528a5a82a 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -41,9 +41,9 @@ export default class Widget { this.title_field.css("opacity", 0.5); this.footer.css("opacity", 0.5); } - + const classname = this.hidden ? 'fa fa-eye' : 'fa fa-eye-slash'; this.add_custom_button( - '', + ``, () => this.hide_or_show(), "show-or-hide-button" ); @@ -119,21 +119,19 @@ export default class Widget { this.body.css("opacity", 0.5); this.title_field.css("opacity", 0.5); this.footer.css("opacity", 0.5); - this.show_or_hide_button.empty(); - $(``).appendTo( - this.show_or_hide_button - ); this.hidden = true; } else { this.body.css("opacity", 1); this.title_field.css("opacity", 1); this.footer.css("opacity", 1); - this.show_or_hide_button.empty(); - $(``).appendTo( - this.show_or_hide_button - ); this.hidden = false; } + this.show_or_hide_button.empty(); + + const classname = this.hidden ? 'fa fa-eye' : 'fa fa-eye-slash'; + $(``).appendTo( + this.show_or_hide_button + ); } set_actions() { From 99f0c082b73970ac7b73e7cc1491919b84bb267d Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 24 Mar 2020 19:03:01 +0530 Subject: [PATCH 088/408] fix: export custom columns in excel --- frappe/desk/query_report.py | 15 +++++++++++---- .../js/frappe/views/reports/query_report.js | 13 +++++++++---- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index d210af02fd..5597de9bbe 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -42,7 +42,7 @@ def get_report_doc(report_name): return doc -def generate_report_result(report, filters=None, user=None): +def generate_report_result(report, custom_columns, filters=None, user=None): status = None if not user: user = frappe.session.user @@ -80,6 +80,11 @@ def generate_report_result(report, filters=None, user=None): if report.custom_columns: columns = json.loads(report.custom_columns) result = add_data_to_custom_columns(columns, result) + elif custom_columns: + result = add_data_to_custom_columns(custom_columns, result) + + for custom_column in custom_columns: + columns.insert(custom_column['insert_after_index'] + 1, custom_column) if result: result = get_filtered_data(report.ref_doctype, columns, result, user) @@ -161,7 +166,7 @@ def get_script(report_name): @frappe.whitelist() @frappe.read_only() -def run(report_name, filters=None, user=None, ignore_prepared_report=False): +def run(report_name, filters=None, user=None, ignore_prepared_report=False, custom_columns=None): report = get_report_doc(report_name) if not user: @@ -183,7 +188,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False): dn = "" result = get_prepared_report_result(report, filters, dn, user) else: - result = generate_report_result(report, filters, user) + result = generate_report_result(report, custom_columns, filters, user) result["add_total_row"] = report.add_total_row and not result.get('skip_total_row', False) @@ -294,6 +299,8 @@ def export_query(): if isinstance(data.get("file_format_type"), string_types): file_format_type = data["file_format_type"] + custom_columns = frappe.parse_json(data["custom_columns"]) + include_indentation = data["include_indentation"] if isinstance(data.get("visible_idx"), string_types): visible_idx = json.loads(data.get("visible_idx")) @@ -301,7 +308,7 @@ def export_query(): visible_idx = None if file_format_type == "Excel": - data = run(report_name, filters) + data = run(report_name, filters, custom_columns=custom_columns) data = frappe._dict(data) if not data.columns: frappe.respond_as_web_page(_("No data to export"), diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 1d7065e70d..85d628adf9 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -616,6 +616,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { prepare_report_data(data) { this.raw_data = data; this.columns = this.prepare_columns(data.columns); + this.custom_columns = []; this.data = this.prepare_data(data.result); this.linked_doctypes = this.get_linked_doctypes(); this.tree_report = this.data.some(d => 'indent' in d); @@ -1110,6 +1111,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { const args = { cmd: 'frappe.desk.query_report.export_query', report_name: this.report_name, + custom_columns: this.custom_columns.length? this.custom_columns: [], file_format_type: file_format, filters: filters, visible_idx, @@ -1275,16 +1277,20 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { primary_action: (values) => { const custom_columns = []; let df = frappe.meta.get_docfield(values.doctype, values.field); + const insert_after_index = this.columns + .findIndex(column => column.label === values.insert_after) custom_columns.push({ fieldname: df.fieldname, fieldtype: df.fieldtype, label: df.label, + insert_after_index: insert_after_index, link_field: this.doctype_field_map[values.doctype], doctype: values.doctype, options: df.fieldtype === "Link" ? df.options : undefined, width: 100 }); + this.custom_columns = this.custom_columns.concat(custom_columns); frappe.call({ method: 'frappe.desk.query_report.get_data_for_custom_field', args: { @@ -1294,7 +1300,8 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { callback: (r) => { const custom_data = r.message; const link_field = this.doctype_field_map[values.doctype]; - this.add_custom_column(custom_columns, custom_data, link_field, values.field, values.insert_after); + + this.add_custom_column(custom_columns, custom_data, link_field, values.field, insert_after_index); d.hide(); } }); @@ -1369,11 +1376,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { } } - add_custom_column(custom_column, custom_data, link_field, column_field, insert_after) { + add_custom_column(custom_column, custom_data, link_field, column_field, insert_after_index) { const column = this.prepare_columns(custom_column); - const insert_after_index = this.columns - .findIndex(column => column.label === insert_after); this.columns.splice(insert_after_index + 1, 0, column[0]); this.data.forEach(row => { From 5651fa2874f188afbf3ddb362671f9caf8c81d4a Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 24 Mar 2020 19:09:44 +0530 Subject: [PATCH 089/408] fix: fix argument order --- frappe/desk/query_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 5597de9bbe..994f816c8a 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -42,7 +42,7 @@ def get_report_doc(report_name): return doc -def generate_report_result(report, custom_columns, filters=None, user=None): +def generate_report_result(report, filters=None, user=None, custom_columns=None): status = None if not user: user = frappe.session.user @@ -188,7 +188,7 @@ def run(report_name, filters=None, user=None, ignore_prepared_report=False, cust dn = "" result = get_prepared_report_result(report, filters, dn, user) else: - result = generate_report_result(report, custom_columns, filters, user) + result = generate_report_result(report, filters, user, custom_columns) result["add_total_row"] = report.add_total_row and not result.get('skip_total_row', False) From 5deb86f46c272447f483ac3f368ea03fa3e54c4b Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 24 Mar 2020 19:55:28 +0530 Subject: [PATCH 090/408] fix: codacy --- frappe/public/js/frappe/views/reports/query_report.js | 2 +- 1 file changed, 1 insertion(+), 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 85d628adf9..2276bc07d6 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1278,7 +1278,7 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { const custom_columns = []; let df = frappe.meta.get_docfield(values.doctype, values.field); const insert_after_index = this.columns - .findIndex(column => column.label === values.insert_after) + .findIndex(column => column.label === values.insert_after); custom_columns.push({ fieldname: df.fieldname, fieldtype: df.fieldtype, From 7b7e4f1aaf5099488fd429bd7692a606e61cab04 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 27 Mar 2020 15:13:20 +0530 Subject: [PATCH 091/408] fix: move sender field --- frappe/core/doctype/doctype/doctype.js | 8 -------- frappe/core/doctype/doctype/doctype.json | 7 ++++--- frappe/custom/doctype/customize_form/customize_form.json | 9 ++------- 3 files changed, 6 insertions(+), 18 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 844d49ed21..b3469abf29 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -53,14 +53,6 @@ frappe.ui.form.on('DocType', { frm.events.autoname(frm); }, - email_append_to: function (frm) { - frm.set_df_property("sender_field", "reqd", 0); - - if (frm.doc.email_append_to) { - frm.set_df_property("sender_field", "reqd", 1); - } - }, - autoname: function(frm) { frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt'); } diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 59fd55c402..379ea227cb 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -56,8 +56,8 @@ "show_name_in_global_search", "email_settings_sb", "email_append_to", - "subject_field", "sender_field", + "subject_field", "sb2", "permissions", "restrict_to_domain", @@ -503,7 +503,8 @@ "depends_on": "email_append_to", "fieldname": "sender_field", "fieldtype": "Data", - "label": "Sender Field" + "label": "Sender Field", + "mandatory_depends_on": "email_append_to" }, { "default": "0", @@ -521,7 +522,7 @@ "icon": "fa fa-bolt", "idx": 6, "links": [], - "modified": "2019-12-27 13:28:06.201814", + "modified": "2020-03-27 14:51:44.581128", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index bfc31d60e6..51a5c0b85f 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -31,9 +31,8 @@ "sort_order", "section_break_23", "email_append_to", - "subject_field", - "cb_01", "sender_field", + "subject_field", "fields_section_break", "fields" ], @@ -204,10 +203,6 @@ "depends_on": "doc_type", "fieldname": "section_break_23", "fieldtype": "Section Break" - }, - { - "fieldname": "cb_01", - "fieldtype": "Column Break" } ], "hide_toolbar": 1, @@ -215,7 +210,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2020-03-13 19:54:39.652134", + "modified": "2020-03-27 15:06:35.443861", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", From 2e68fabe85b3e015fe276a599882838962d1ee31 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 27 Mar 2020 15:31:51 +0530 Subject: [PATCH 092/408] fix: call setup events after rendering --- frappe/public/js/frappe/widgets/base_widget.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 7528a5a82a..0ecb5a1de3 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -87,6 +87,7 @@ export default class Widget { this.set_title(); this.set_actions(); this.set_body(); + this.setup_events(); } set_title() { @@ -134,6 +135,10 @@ export default class Widget { ); } + setup_events() { + // + } + set_actions() { // } From 523f0a1f5710b9b7e2703915007af2ead02cc3f8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 27 Mar 2020 14:27:10 +0530 Subject: [PATCH 093/408] fix: strip html from fetched fields server side if required --- frappe/model/base_document.py | 11 ++++++++++- frappe/public/js/frappe/form/controls/link.js | 7 ++----- frappe/utils/html_utils.py | 9 +++++++++ 3 files changed, 21 insertions(+), 6 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 569cea9d5f..1ce3d62c3c 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -15,6 +15,7 @@ from frappe.model import display_fieldtypes, data_fieldtypes from frappe.utils.password import get_decrypted_password, set_encrypted_password from frappe.utils import (cint, flt, now, cstr, strip_html, getdate, get_datetime, to_timedelta, sanitize_html, sanitize_email, cast_fieldtype) +from frappe.utils.html_utils import unescape_html max_positive_value = { 'smallint': 2 ** 15, @@ -502,7 +503,15 @@ class BaseDocument(object): for _df in fields_to_fetch: if self.is_new() or self.docstatus != 1 or _df.allow_on_submit: - setattr(self, _df.fieldname, values[_df.fetch_from.split('.')[-1]]) + fetch_from_fieldname = _df.fetch_from.split('.')[-1] + value = values[fetch_from_fieldname] + if _df.fieldtype == 'Small Text' or _df.fieldtype == 'Text' or _df.fieldtype == 'Data': + fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname) + fetch_from_ft = fetch_from_df and fetch_from_df.fieldtype + + if fetch_from_ft == 'Text Editor' and value: + value = unescape_html(strip_html(value)) + setattr(self, _df.fieldname, value) notify_link_count(doctype, docname) diff --git a/frappe/public/js/frappe/form/controls/link.js b/frappe/public/js/frappe/form/controls/link.js index 7471f59907..9d8241f5a7 100644 --- a/frappe/public/js/frappe/form/controls/link.js +++ b/frappe/public/js/frappe/form/controls/link.js @@ -461,14 +461,11 @@ frappe.ui.form.ControlLink = frappe.ui.form.ControlData.extend({ }); } }, + set_fetch_values: function(df, docname, fetch_values) { var fl = this.frm.fetch_dict[df.fieldname].fields; for(var i=0; i < fl.length; i++) { - const fieldtype = frappe.meta.get_docfield(this.doctype, fl[i]).fieldtype; - let value = ['Text', 'Small Text'].includes(fieldtype) - ? strip_html(fetch_values[i]) - : fetch_values[i]; - frappe.model.set_value(df.parent, docname, fl[i], value, df.fieldtype); + frappe.model.set_value(df.parent, docname, fl[i], fetch_values[i], df.fieldtype); } } }); diff --git a/frappe/utils/html_utils.py b/frappe/utils/html_utils.py index 67a01296c1..6e133f16d6 100644 --- a/frappe/utils/html_utils.py +++ b/frappe/utils/html_utils.py @@ -103,6 +103,15 @@ def get_icon_html(icon, small=False): else: return "".format(icon=icon) +def unescape_html(value): + try: + from HTMLParser import HTMLParser + h = HTMLParser() + return h.unescape(value) + except ImportError: + import html + return html.unescape(value) + # adapted from https://raw.githubusercontent.com/html5lib/html5lib-python/4aa79f113e7486c7ec5d15a6e1777bfe546d3259/html5lib/sanitizer.py acceptable_elements = [ 'a', 'abbr', 'acronym', 'address', 'area', From 708248baf9ef71cf953f5f0026d9372abeadf9e4 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Fri, 27 Mar 2020 16:04:11 +0530 Subject: [PATCH 094/408] fix: avoid sqli inside global web search Signed-off-by: Chinmay D. Pai --- frappe/utils/global_search.py | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index 4b50745a74..5abb29bab3 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -499,22 +499,29 @@ def web_search(text, scope=None, start=0, limit=20): common_query = ''' SELECT `doctype`, `name`, `content`, `title`, `route` FROM `__global_search` WHERE {conditions} - LIMIT {limit} OFFSET {start}''' + LIMIT %(limit)s OFFSET %(start)s}''' - scope_condition = '`route` like "{}%" AND '.format(scope) if scope else '' + scope_condition = '`route` like "%(scope)s" AND ' if scope else '' published_condition = '`published` = 1 AND ' mariadb_conditions = postgres_conditions = ' '.join([published_condition, scope_condition]) # https://mariadb.com/kb/en/library/full-text-index-overview/#in-boolean-mode text = '"{}"'.format(text) - mariadb_conditions += 'MATCH(`content`) AGAINST ({} IN BOOLEAN MODE)'.format(frappe.db.escape(text)) - postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY({})'.format(frappe.db.escape(text)) + mariadb_conditions += 'MATCH(`content`) AGAINST (%(text)s IN BOOLEAN MODE)' + postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY(%(text)s)' + + values = { + "scope": "".join([scope, "%"]) if scope else '', + "limit": limit, + "start": start, + "text": frappe.db.escape(text) + } result = frappe.db.multisql({ - 'mariadb': common_query.format(conditions=mariadb_conditions, limit=limit, start=start), - 'postgres': common_query.format(conditions=postgres_conditions, limit=limit, start=start) - }, as_dict=True) - tmp_result=[] + 'mariadb': common_query.format(conditions=mariadb_conditions), + 'postgres': common_query.format(conditions=postgres_conditions) + }, values=values, as_dict=True) + tmp_result = [] for i in result: if i in results or not results: tmp_result.append(i) From 063def052e4b2bcdded9b36541e328f270399d48 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Fri, 27 Mar 2020 18:54:18 +0530 Subject: [PATCH 095/408] chore: remove trailing closing bracket Signed-off-by: Chinmay D. Pai --- frappe/utils/global_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index 5abb29bab3..a5fcca8bb8 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -499,7 +499,7 @@ def web_search(text, scope=None, start=0, limit=20): common_query = ''' SELECT `doctype`, `name`, `content`, `title`, `route` FROM `__global_search` WHERE {conditions} - LIMIT %(limit)s OFFSET %(start)s}''' + LIMIT %(limit)s OFFSET %(start)s''' scope_condition = '`route` like "%(scope)s" AND ' if scope else '' published_condition = '`published` = 1 AND ' From 18664430c4c759e420889808f4dddedf05c86156 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Sat, 28 Mar 2020 21:06:56 +0530 Subject: [PATCH 096/408] fix: Do not show create, write and delete perm in role permission manager for doctypes with in_create property set --- frappe/core/page/permission_manager/permission_manager.js | 1 + frappe/core/page/permission_manager/permission_manager.py | 1 + 2 files changed, 2 insertions(+) diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js index 710bb51680..ed3b0d17db 100644 --- a/frappe/core/page/permission_manager/permission_manager.js +++ b/frappe/core/page/permission_manager/permission_manager.js @@ -217,6 +217,7 @@ frappe.PermissionEngine = Class.extend({ me.rights.forEach(r => { if (!d.is_submittable && ['submit', 'cancel', 'amend'].includes(r)) return; + if (d.in_create && ['create', 'write', 'delete'].includes(r)) return; me.add_check(perm_container, d, r); }); diff --git a/frappe/core/page/permission_manager/permission_manager.py b/frappe/core/page/permission_manager/permission_manager.py index 1afd7bb423..637b526d5c 100644 --- a/frappe/core/page/permission_manager/permission_manager.py +++ b/frappe/core/page/permission_manager/permission_manager.py @@ -66,6 +66,7 @@ def get_permissions(doctype=None, role=None): meta = frappe.get_meta(d.parent) if meta: d.is_submittable = meta.is_submittable + d.in_create = meta.in_create return out From e57a5d347dab403ab7225985c5211df6ef211216 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 26 Mar 2020 02:17:54 +0530 Subject: [PATCH 097/408] chore: update .eslintrc globals style: updated data.js spacing format --- .eslintrc | 1 + frappe/public/js/frappe/form/controls/data.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.eslintrc b/.eslintrc index 7e469f7672..e79571f556 100644 --- a/.eslintrc +++ b/.eslintrc @@ -78,6 +78,7 @@ "has_common": true, "has_words": true, "validate_email": true, + "validate_phone": true, "get_number_format": true, "format_number": true, "format_currency": true, diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 516cd87a1a..512c6fbcb0 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -87,7 +87,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ return val==null ? "" : val; }, validate: function(v) { - if (!v){ + if (!v) { return ''; } if(this.df.is_filter) { From 11aefbd8ed4461973d926b3d665c4706aad8c548 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 27 Mar 2020 06:27:34 +0530 Subject: [PATCH 098/408] fix: dont validate email if owner and email_data is in STANDARD_USERS --- frappe/model/base_document.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 466824d1d2..0880c56a22 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -7,6 +7,7 @@ from six import iteritems, string_types import frappe import datetime from frappe import _ +from frappe.core.doctype.user.user import STANDARD_USERS from frappe.model import default_fields, table_fields from frappe.model.naming import set_new_name from frappe.model.utils.link_count import notify_link_count @@ -551,6 +552,8 @@ class BaseDocument(object): data_field_options = data_field.get("options") if data_field_options == "Email": + if (self.owner in STANDARD_USERS) and (data in STANDARD_USERS): + return for email_address in frappe.utils.split_emails(data): frappe.utils.validate_email_address(email_address, throw=True) From cf9440439e758e4b7a1697a224d3a6ad936ea1a1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sat, 28 Mar 2020 08:07:14 +0530 Subject: [PATCH 099/408] fix: translatable string for data field options --- frappe/core/doctype/doctype/doctype.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 43b375e999..d5b293be68 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -946,11 +946,11 @@ def validate_fields(meta): def validate_data_field_type(docfield): if docfield.fieldtype == "Data": if docfield.options and (docfield.options not in data_field_options): - docfield_label = frappe.bold(docfield.label) - data_field_str = "
  • " + "
  • ".join(data_field_options) + "
" - text = "{0} is an Invalid Data field.{1} Only Options allowed for Data field are: {2}" - message = _(text).format(docfield_label, "

", data_field_str) - frappe.msgprint(message, raise_exception=True) + df_str = frappe.bold(_(docfield.label)) + text_str = _("{0} is an invalid Data field.").format(df_str) + "
" * 2 + _("Only Options allowed for Data field are:") + "
" + df_options_str = "
  • " + "
  • ".join([_(x) for x in data_field_options]) + "
" + + frappe.msgprint(text_str + df_options_str, raise_exception=True) fields = meta.get("fields") From a8e1e1c9763ba242ae9217973a7c46cc38383277 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 30 Mar 2020 10:28:59 +0530 Subject: [PATCH 100/408] style: remove extra spaces for consistency --- frappe/core/doctype/doctype/doctype.py | 60 -------------------------- 1 file changed, 60 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index d5b293be68..de340d65b0 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -96,7 +96,6 @@ class DocType(Document): if self.default_print_format and not self.custom: frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) - def set_default_in_list_view(self): '''Set default in-list-view for first 4 mandatory fields''' if not [d.fieldname for d in self.fields if d.in_list_view]: @@ -107,14 +106,12 @@ class DocType(Document): cnt += 1 if cnt == 4: break - def set_default_translatable(self): '''Ensure that non-translatable never will be translatable''' for d in self.fields: if d.translatable and not supports_translation(d.fieldtype): d.translatable = 0 - def check_developer_mode(self): """Throw exception if not developer mode or via patch""" if frappe.flags.in_patch or frappe.flags.in_test: @@ -123,7 +120,6 @@ class DocType(Document): if not frappe.conf.get("developer_mode") and not self.custom: frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError) - def setup_fields_to_fetch(self): '''Setup query to update values for newly set fetch values''' try: @@ -168,21 +164,18 @@ class DocType(Document): ) ) - def update_fields_to_fetch(self): '''Update fetch values based on queries setup''' if self.flags.update_fields_to_fetch_queries and not self.issingle: for query in self.flags.update_fields_to_fetch_queries: frappe.db.sql(query) - def validate_document_type(self): if self.document_type=="Transaction": self.document_type = "Document" if self.document_type=="Master": self.document_type = "Setup" - def validate_website(self): """Ensure that website generator has field 'route'""" if self.has_web_view: @@ -193,7 +186,6 @@ class DocType(Document): # clear website cache frappe.website.render.clear_cache() - def change_modified_of_parent(self): """Change the timestamp of parent DocType if the current one is a child to clear caches.""" if frappe.flags.in_import: @@ -203,7 +195,6 @@ class DocType(Document): for p in parent_list: frappe.db.sql('UPDATE `tabDocType` SET modified=%s WHERE `name`=%s', (now(), p.parent)) - def scrub_field_names(self): """Sluggify fieldnames if not set from Label.""" restricted = ('name','parent','creation','modified','modified_by', @@ -233,7 +224,6 @@ class DocType(Document): # unique is automatically an index if d.unique: d.search_index = 0 - def validate_series(self, autoname=None, name=None): """Validate if `autoname` property is correctly set.""" if not autoname: autoname = self.autoname @@ -270,7 +260,6 @@ class DocType(Document): if used_in: frappe.throw(_("Series {0} already used in {1}").format(prefix, used_in[0][0])) - def on_update(self): """Update database schema, make controller templates if `custom` is not set and clear cache.""" self.delete_duplicate_custom_fields() @@ -324,7 +313,6 @@ class DocType(Document): dt = {0} and fieldname in ({1}) '''.format('%s', ', '.join(['%s'] * len(fields))), tuple([self.name] + fields), as_dict=True) - def sync_global_search(self): '''If global search settings are changed, rebuild search properties for this table''' global_search_fields_before_update = [d.fieldname for d in @@ -342,7 +330,6 @@ class DocType(Document): frappe.enqueue('frappe.utils.global_search.rebuild_for_doctype', now=now, doctype=self.name) - def set_base_class_for_controller(self): '''Updates the controller class to subclass from `WebsiteGenertor`, if it is a subclass of `Document`''' @@ -362,14 +349,12 @@ class DocType(Document): with open(controller_path, 'w') as f: f.write(code) - def run_module_method(self, method): from frappe.modules import load_doctype_module module = load_doctype_module(self.name, self.module) if hasattr(module, method): getattr(module, method)() - def before_rename(self, old, new, merge=False): """Throw exception if merge. DocTypes cannot be merged.""" if not self.custom and frappe.session.user != "Administrator": @@ -385,7 +370,6 @@ class DocType(Document): if not self.custom and not frappe.flags.in_test and not frappe.flags.in_patch: self.rename_files_and_folders(old, new) - def after_rename(self, old, new, merge=False): """Change table name using `RENAME TABLE` if table exists. Or update `doctype` property for Single type.""" @@ -396,7 +380,6 @@ class DocType(Document): else: frappe.db.sql("rename table `tab%s` to `tab%s`" % (old, new)) - def rename_files_and_folders(self, old, new): # move files new_path = get_doc_path(self.module, 'doctype', new) @@ -413,7 +396,6 @@ class DocType(Document): self.rename_inside_controller(new, old, new_path) frappe.msgprint(_('Renamed files and replaced code in controllers, please check!')) - def rename_inside_controller(self, new, old, new_path): for fname in ('{}.js', '{}.py', '{}_list.js', '{}_calendar.js', 'test_{}.py', 'test_{}.js'): fname = os.path.join(new_path, fname.format(frappe.scrub(new))) @@ -439,7 +421,6 @@ class DocType(Document): if not (self.issingle and self.istable): self.preserve_naming_series_options_in_property_setter() - def preserve_naming_series_options_in_property_setter(self): """Preserve naming_series as property setter if it does not exist""" naming_series = self.get("fields", {"fieldname": "naming_series"}) @@ -459,7 +440,6 @@ class DocType(Document): if naming_series[0].default: make_property_setter(self.name, "naming_series", "default", naming_series[0].default, "Text", validate_fields_for_doctype=False) - def before_export(self, docdict): # remove null and empty fields def remove_null_fields(o): @@ -504,7 +484,6 @@ class DocType(Document): except ValueError: pass - @staticmethod def prepare_for_import(docdict): # set order of fields from field_order @@ -527,19 +506,16 @@ class DocType(Document): if "field_order" in docdict: del docdict["field_order"] - def export_doc(self): """Export to standard folder `[module]/doctype/[name]/[name].json`.""" from frappe.modules.export_file import export_to_files export_to_files(record_list=[['DocType', self.name]], create_init=True) - def import_doc(self): """Import from standard folder `[module]/doctype/[name]/[name].json`.""" from frappe.modules.import_module import import_from_files import_from_files(record_list=[[self.module, 'doctype', self.name]]) - def make_controller_template(self): """Make boilerplate controller template.""" make_boilerplate("controller._py", self) @@ -556,7 +532,6 @@ class DocType(Document): make_boilerplate('templates/controller.html', self.as_dict()) make_boilerplate('templates/controller_row.html', self.as_dict()) - def make_amendable(self): """If is_submittable is set, add amended_from docfields.""" if self.is_submittable: @@ -572,7 +547,6 @@ class DocType(Document): "no_copy": 1 }) - def make_repeatable(self): """If allow_auto_repeat is set, add auto_repeat custom field.""" if self.allow_auto_repeat: @@ -641,14 +615,12 @@ class DocType(Document): }) self.nsm_parent_field = parent_field_name - def get_max_idx(self): """Returns the highest `idx`""" max_idx = frappe.db.sql("""select max(idx) from `tabDocField` where parent = %s""", self.name) return max_idx and max_idx[0][0] or 0 - def validate_name(self, name=None): if not name: name = self.name @@ -668,7 +640,6 @@ def validate_fields_for_doctype(doctype): doc.delete_duplicate_custom_fields() validate_fields(frappe.get_meta(doctype, cached=False)) - # this is separate because it is also called via custom field def validate_fields(meta): """Validate doctype fields. Checks @@ -692,29 +663,24 @@ def validate_fields(meta): def check_illegal_characters(fieldname): validate_column_name(fieldname) - def check_invalid_fieldnames(docname, fieldname): invalid_fields = ('doctype',) if fieldname in invalid_fields: frappe.throw(_("{0}: Fieldname cannot be one of {1}") .format(docname, ", ".join([frappe.bold(d) for d in invalid_fields]))) - def check_unique_fieldname(docname, fieldname): duplicates = list(filter(None, map(lambda df: df.fieldname==fieldname and str(df.idx) or None, fields))) if len(duplicates) > 1: frappe.throw(_("{0}: Fieldname {1} appears multiple times in rows {2}").format(docname, fieldname, ", ".join(duplicates)), UniqueFieldnameError) - def check_fieldname_length(fieldname): validate_column_length(fieldname) - def check_illegal_mandatory(docname, d): if (d.fieldtype in no_value_fields) and d.fieldtype not in table_fields and d.reqd: frappe.throw(_("{0}: Field {1} of type {2} cannot be mandatory").format(docname, d.label, d.fieldtype), IllegalMandatoryError) - def check_link_table_options(docname, d): if frappe.flags.in_patch: return if d.fieldtype in ("Link",) + table_fields: @@ -733,28 +699,23 @@ def validate_fields(meta): # fix case d.options = options - def check_hidden_and_mandatory(docname, d): if d.hidden and d.reqd and not d.default: frappe.throw(_("{0}: Field {1} in row {2} cannot be hidden and mandatory without default").format(docname, d.label, d.idx), HiddenAndMandatoryWithoutDefaultError) - def check_width(d): if d.fieldtype == "Currency" and cint(d.width) < 100: frappe.throw(_("Max width for type Currency is 100px in row {0}").format(d.idx)) - def check_in_list_view(d): if d.in_list_view and (d.fieldtype in not_allowed_in_list_view): frappe.throw(_("'In List View' not allowed for type {0} in row {1}").format(d.fieldtype, d.idx)) - def check_in_global_search(d): if d.in_global_search and d.fieldtype in no_value_fields: frappe.throw(_("'In Global Search' not allowed for type {0} in row {1}") .format(d.fieldtype, d.idx)) - def check_dynamic_link_options(d): if d.fieldtype=="Dynamic Link": doctype_pointer = list(filter(lambda df: df.fieldname==d.options, fields)) @@ -762,7 +723,6 @@ def validate_fields(meta): or (doctype_pointer[0].fieldtype=="Link" and doctype_pointer[0].options!="DocType"): frappe.throw(_("Options 'Dynamic Link' type of field must point to another Link Field with options as 'DocType'")) - def check_illegal_default(d): if d.fieldtype == "Check" and not d.default: d.default = '0' @@ -771,12 +731,10 @@ def validate_fields(meta): if d.fieldtype == "Select" and d.default and (d.default not in d.options.split("\n")): frappe.throw(_("Default for {0} must be an option").format(d.fieldname)) - def check_precision(d): if d.fieldtype in ("Currency", "Float", "Percent") and d.precision is not None and not (1 <= cint(d.precision) <= 6): frappe.throw(_("Precision should be between 1 and 6")) - def check_unique_and_text(docname, d): if meta.issingle: d.unique = 0 @@ -798,7 +756,6 @@ def validate_fields(meta): if d.search_index and d.fieldtype in ("Text", "Long Text", "Small Text", "Code", "Text Editor"): frappe.throw(_("{0}:Fieldtype {1} for {2} cannot be indexed").format(docname, d.fieldtype, d.label), CannotIndexedError) - def check_fold(fields): fold_exists = False for i, f in enumerate(fields): @@ -813,7 +770,6 @@ def validate_fields(meta): else: frappe.throw(_("Fold can not be at the end of the form")) - def check_search_fields(meta, fields): """Throw exception if `search_fields` don't contain valid fields.""" if not meta.search_fields: @@ -830,7 +786,6 @@ def validate_fields(meta): (fieldname not in fieldname_list): frappe.throw(_("Search field {0} is not valid").format(fieldname)) - def check_title_field(meta): """Throw exception if `title_field` isn't a valid fieldname.""" if not meta.get("title_field"): @@ -857,7 +812,6 @@ def validate_fields(meta): _validate_title_field_pattern(df.options) _validate_title_field_pattern(df.default) - def check_image_field(meta): '''check image_field exists and is of type "Attach Image"''' if not meta.image_field: @@ -869,7 +823,6 @@ def validate_fields(meta): if df[0].fieldtype != 'Attach Image': frappe.throw(_("Image field must be of type Attach Image"), InvalidFieldNameError) - def check_is_published_field(meta): if not meta.is_published_field: return @@ -877,7 +830,6 @@ def validate_fields(meta): if meta.is_published_field not in fieldname_list: frappe.throw(_("Is Published Field must be a valid fieldname"), InvalidFieldNameError) - def check_timeline_field(meta): if not meta.timeline_field: return @@ -889,7 +841,6 @@ def validate_fields(meta): if df.fieldtype not in ("Link", "Dynamic Link"): frappe.throw(_("Timeline field must be a Link or Dynamic Link"), InvalidFieldNameError) - def check_sort_field(meta): '''Validate that sort_field(s) is a valid field''' if meta.sort_field: @@ -902,7 +853,6 @@ def validate_fields(meta): frappe.throw(_("Sort field {0} must be a valid fieldname").format(fieldname), InvalidFieldNameError) - def check_illegal_depends_on_conditions(docfield): ''' assignment operation should not be allowed in the depends on condition.''' depends_on_fields = ["depends_on", "collapsible_depends_on", "mandatory_depends_on", "read_only_depends_on"] @@ -912,7 +862,6 @@ def validate_fields(meta): re.match("""[\w\.:_]+\s*={1}\s*[\w\.@'"]+""", depends_on): frappe.throw(_("Invalid {0} condition").format(frappe.unscrub(field)), frappe.ValidationError) - def check_table_multiselect_option(docfield): '''check if the doctype provided in Option has atleast 1 Link field''' if not docfield.fieldtype == 'Table MultiSelect': return @@ -925,7 +874,6 @@ def validate_fields(meta): frappe.throw(_('DocType {0} provided for the field {1} must have atleast one Link field') .format(doctype, docfield.fieldname), frappe.ValidationError) - def scrub_options_in_select(field): """Strip options for whitespaces""" @@ -937,12 +885,10 @@ def validate_fields(meta): options_list.append(_option) field.options = '\n'.join(options_list) - def scrub_fetch_from(field): if hasattr(field, 'fetch_from') and getattr(field, 'fetch_from'): field.fetch_from = field.fetch_from.strip('\n').strip() - def validate_data_field_type(docfield): if docfield.fieldtype == "Data": if docfield.options and (docfield.options not in data_field_options): @@ -993,7 +939,6 @@ def validate_fields(meta): check_sort_field(meta) check_image_field(meta) - def validate_permissions_for_doctype(doctype, for_remove=False): """Validates if permissions are set correctly.""" doctype = frappe.get_doc("DocType", doctype) @@ -1005,7 +950,6 @@ def validate_permissions_for_doctype(doctype, for_remove=False): clear_permissions_cache(doctype.name) - def clear_permissions_cache(doctype): frappe.clear_cache(doctype=doctype) delete_notification_count_for(doctype) @@ -1020,7 +964,6 @@ def clear_permissions_cache(doctype): """, doctype): frappe.clear_cache(user=user) - def validate_permissions(doctype, for_remove=False): permissions = doctype.get("permissions") if not permissions: @@ -1114,7 +1057,6 @@ def validate_permissions(doctype, for_remove=False): check_level_zero_is_set(d) remove_rights_for_single(d) - def make_module_and_roles(doc, perm_fieldname="permissions"): """Make `Module Def` and `Role` records if already not made. Called while installing.""" try: @@ -1145,7 +1087,6 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): else: raise - def check_if_fieldname_conflicts_with_methods(doctype, fieldname): doc = frappe.get_doc({"doctype": doctype}) method_list = [method for method in dir(doc) if isinstance(method, str) and callable(getattr(doc, method))] @@ -1153,6 +1094,5 @@ def check_if_fieldname_conflicts_with_methods(doctype, fieldname): if fieldname in method_list: frappe.throw(_("Fieldname {0} conflicting with meta object").format(fieldname)) - def clear_linked_doctype_cache(): frappe.cache().delete_value('linked_doctypes_without_ignore_user_permissions_enabled') From 10e939d0126cde262f58b705e8692acd70792f6f Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 30 Mar 2020 10:46:00 +0530 Subject: [PATCH 101/408] chore: update global_search and add test Signed-off-by: Chinmay D. Pai --- frappe/tests/test_global_search.py | 3 +++ frappe/utils/global_search.py | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py index 01067c85dd..5c3a2df4db 100644 --- a/frappe/tests/test_global_search.py +++ b/frappe/tests/test_global_search.py @@ -191,3 +191,6 @@ class TestGlobalSearch(unittest.TestCase): frappe.db.commit() results = global_search.web_search('unsubscribe') self.assertTrue('Unsubscribe' in results[0].content) + results = global_search.web_search(text='manufacturing', + scope="manufacturing\" UNION ALL SELECT 1,2,3,4,doctype from __global_search") + self.assetTrue(results == []) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index a5fcca8bb8..3c4b9583f8 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -501,7 +501,7 @@ def web_search(text, scope=None, start=0, limit=20): WHERE {conditions} LIMIT %(limit)s OFFSET %(start)s''' - scope_condition = '`route` like "%(scope)s" AND ' if scope else '' + scope_condition = '`route` like %(scope)s AND ' if scope else '' published_condition = '`published` = 1 AND ' mariadb_conditions = postgres_conditions = ' '.join([published_condition, scope_condition]) @@ -514,7 +514,7 @@ def web_search(text, scope=None, start=0, limit=20): "scope": "".join([scope, "%"]) if scope else '', "limit": limit, "start": start, - "text": frappe.db.escape(text) + "text": text } result = frappe.db.multisql({ From 39df9fc8c0a4d07daafde6563362cc7106a80ca5 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 30 Mar 2020 10:49:20 +0530 Subject: [PATCH 102/408] chore: add test assertion for global_search Signed-off-by: Chinmay D. Pai --- frappe/tests/test_global_search.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py index 5c3a2df4db..99e8f0bc58 100644 --- a/frappe/tests/test_global_search.py +++ b/frappe/tests/test_global_search.py @@ -194,3 +194,5 @@ class TestGlobalSearch(unittest.TestCase): results = global_search.web_search(text='manufacturing', scope="manufacturing\" UNION ALL SELECT 1,2,3,4,doctype from __global_search") self.assetTrue(results == []) + results = global_search.web_search('manufacturing') + self.assertTrue('Manufacturing' in results[0].content) From 5cd5d03038c30a5d88b25466b40675d4c334883b Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 30 Mar 2020 11:11:20 +0530 Subject: [PATCH 103/408] chore: fix errors in global_search test Signed-off-by: Chinmay D. Pai --- frappe/tests/test_global_search.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py index 99e8f0bc58..de1ae26845 100644 --- a/frappe/tests/test_global_search.py +++ b/frappe/tests/test_global_search.py @@ -191,8 +191,8 @@ class TestGlobalSearch(unittest.TestCase): frappe.db.commit() results = global_search.web_search('unsubscribe') self.assertTrue('Unsubscribe' in results[0].content) - results = global_search.web_search(text='manufacturing', + results = global_search.web_search(text='open source', scope="manufacturing\" UNION ALL SELECT 1,2,3,4,doctype from __global_search") - self.assetTrue(results == []) - results = global_search.web_search('manufacturing') - self.assertTrue('Manufacturing' in results[0].content) + self.assertTrue(results == []) + results = global_search.web_search('open source') + self.assertTrue('Open Source' in results[0].content) From 6c350f6c7bcef064d1364a0846766472c0c7cf20 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 30 Mar 2020 11:19:15 +0530 Subject: [PATCH 104/408] chore: update msgprint title fix: circular import --- frappe/core/doctype/doctype/doctype.py | 2 +- frappe/model/base_document.py | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index de340d65b0..aa0dffffca 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -896,7 +896,7 @@ def validate_fields(meta): text_str = _("{0} is an invalid Data field.").format(df_str) + "
" * 2 + _("Only Options allowed for Data field are:") + "
" df_options_str = "
  • " + "
  • ".join([_(x) for x in data_field_options]) + "
" - frappe.msgprint(text_str + df_options_str, raise_exception=True) + frappe.msgprint(text_str + df_options_str, title="Invalid Data Field", raise_exception=True) fields = meta.get("fields") diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 0880c56a22..4af502f844 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -7,7 +7,6 @@ from six import iteritems, string_types import frappe import datetime from frappe import _ -from frappe.core.doctype.user.user import STANDARD_USERS from frappe.model import default_fields, table_fields from frappe.model.naming import set_new_name from frappe.model.utils.link_count import notify_link_count @@ -546,6 +545,8 @@ class BaseDocument(object): value, comma_options)) def _validate_data_fields(self): + from frappe.core.doctype.user.user import STANDARD_USERS + # data_field options defined in frappe.model.data_field_options for data_field in self.meta.get_data_fields(): data = self.get(data_field.fieldname) From a5fc3d82ddd58844b5ea4ca4bd50c0c490c60b3a Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 30 Mar 2020 11:58:55 +0530 Subject: [PATCH 105/408] fix: Get valid columns from meta only if table exists (for doctype action) --- frappe/model/meta.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 5065684311..84c96d0566 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -68,7 +68,7 @@ def load_doctype_from_file(doctype): class Meta(Document): _metaclass = True default_fields = list(default_fields)[1:] - special_doctypes = ("DocField", "DocPerm", "Role", "DocType", "Module Def") + special_doctypes = ("DocField", "DocPerm", "Role", "DocType", "Module Def", 'DocType Action', 'DocType Link') def __init__(self, doctype): self._fields = {} @@ -165,7 +165,8 @@ class Meta(Document): def get_valid_columns(self): if not hasattr(self, "_valid_columns"): - if self.name in ("DocType", "DocField", "DocPerm", 'DocType Action', 'DocType Link'): + table_exists = frappe.db.table_exists(self.name) + if self.name in self.special_doctypes and table_exists: self._valid_columns = get_table_columns(self.name) else: self._valid_columns = self.default_fields + \ From 737ecae59d3811ac870ac18bf09564b65c8c60af Mon Sep 17 00:00:00 2001 From: prafful1234 <43948551+prafful1234@users.noreply.github.com> Date: Mon, 30 Mar 2020 12:53:21 +0530 Subject: [PATCH 106/408] feat(website): fetch email comments in issue portal (#9657) * feat(website): fetch email comments in issue portal * fix: deepsource issues Signed-off-by: Chinmay D. Pai * chore: remove unnecessary import not sure how i missed it Signed-off-by: Chinmay D. Pai Co-authored-by: prafful1234 Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Co-authored-by: Chinmay D. Pai --- frappe/desk/form/utils.py | 16 ++++--- .../public/js/frappe/form/footer/timeline.js | 3 +- frappe/website/utils.py | 47 ++++++++++++++----- 3 files changed, 45 insertions(+), 21 deletions(-) diff --git a/frappe/desk/form/utils.py b/frappe/desk/form/utils.py index 498ab50645..4c3bab2e23 100644 --- a/frappe/desk/form/utils.py +++ b/frappe/desk/form/utils.py @@ -56,18 +56,20 @@ def validate_link(): frappe.response['valid_value'] = valid_value frappe.response['message'] = 'Ok' + @frappe.whitelist() -def add_comment(reference_doctype, reference_name, content, comment_email): +def add_comment(reference_doctype, reference_name, content, comment_email, comment_by): """allow any logged user to post a comment""" doc = frappe.get_doc(dict( - doctype = 'Comment', - reference_doctype = reference_doctype, - reference_name = reference_name, - comment_email = comment_email, - comment_type = 'Comment' + doctype='Comment', + reference_doctype=reference_doctype, + reference_name=reference_name, + comment_email=comment_email, + comment_type='Comment', + comment_by=comment_by )) doc.content = extract_images_from_html(doc, content) - doc.insert(ignore_permissions = True) + doc.insert(ignore_permissions=True) follow_document(doc.reference_doctype, doc.reference_name, frappe.session.user) return doc.as_dict() diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index 593f987a9a..d27c65548d 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -703,7 +703,8 @@ frappe.ui.form.Timeline = class Timeline { reference_doctype: this.frm.doctype, reference_name: this.frm.docname, content: comment, - comment_email: frappe.session.user + comment_email: frappe.session.user, + comment_by: frappe.session.user_fullname }, btn: btn, callback: function(r) { diff --git a/frappe/website/utils.py b/frappe/website/utils.py index 495c36c92a..f5e35976eb 100644 --- a/frappe/website/utils.py +++ b/frappe/website/utils.py @@ -3,11 +3,15 @@ from __future__ import unicode_literals import functools -import frappe, re, os +import re +import os +import frappe + from six import iteritems from past.builtins import cmp from frappe.utils import markdown + def delete_page_cache(path): cache = frappe.cache() cache.delete_value('full_index') @@ -20,7 +24,7 @@ def delete_page_cache(path): cache.delete_key(name) def find_first_image(html): - m = re.finditer("""]*src\s?=\s?['"]([^'"]*)['"]""", html) + m = re.finditer(r"""]*src\s?=\s?['"]([^'"]*)['"]""", html) try: return next(m).groups()[0] except StopIteration: @@ -33,16 +37,34 @@ def can_cache(no_cache=False): return False return not no_cache + def get_comment_list(doctype, name): - return frappe.get_all('Comment', - fields = ['name', 'creation', 'owner', 'comment_email', 'comment_by', 'content'], - filters = dict( - reference_doctype = doctype, - reference_name = name, - comment_type = 'Comment', - published = 1 - ), - order_by = 'creation asc') + comments = frappe.get_all('Comment', + fields=['name', 'creation', 'owner', + 'comment_email', 'comment_by', 'content'], + filters=dict( + reference_doctype=doctype, + reference_name=name, + comment_type='Comment', + ), + or_filters=[ + ['owner', '=', frappe.session.user], + ['published', '=', 1]]) + + communications = frappe.get_all("Communication", + fields=['name', 'creation', 'owner', 'owner as comment_email', + 'sender_full_name as comment_by', 'content', 'recipients'], + filters=dict( + reference_doctype=doctype, + reference_name=name, + ), + or_filters=[ + ['recipients', 'like', '%{0}%'.format(frappe.session.user)], + ['cc', 'like', '%{0}%'.format(frappe.session.user)], + ['bcc', 'like', '%{0}%'.format(frappe.session.user)]]) + + return sorted((comments + communications), key=lambda comment: comment['creation'], reverse=True) + def get_home_page(): if frappe.local.flags.home_page: @@ -92,7 +114,7 @@ def cleanup_page_name(title): return '' name = title.lower() - name = re.sub('[~!@#$%^&*+()<>,."\'\?]', '', name) + name = re.sub(r'[~!@#$%^&*+()<>,."\'\?]', '', name) name = re.sub('[:/]', '-', name) name = '-'.join(name.split()) @@ -194,7 +216,6 @@ def abs_url(path): def get_toc(route, url_prefix=None, app=None): '''Insert full index (table of contents) for {index} tag''' - from frappe.website.utils import get_full_index full_index = get_full_index(app=app) From 751d38436e8ed6ef0145c76ac573e95fc1c9c6be Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 30 Mar 2020 16:18:47 +0530 Subject: [PATCH 107/408] chore: remove whitespaces from docstrings --- .../doctype/website_settings/google_indexing.py | 10 +++++----- .../doctype/website_settings/test_website_settings.py | 10 ---------- frappe/website/website_generator.py | 2 +- 3 files changed, 6 insertions(+), 16 deletions(-) delete mode 100644 frappe/website/doctype/website_settings/test_website_settings.py diff --git a/frappe/website/doctype/website_settings/google_indexing.py b/frappe/website/doctype/website_settings/google_indexing.py index ecf88438eb..d6db8541ef 100644 --- a/frappe/website/doctype/website_settings/google_indexing.py +++ b/frappe/website/doctype/website_settings/google_indexing.py @@ -19,7 +19,7 @@ SCOPES = "https://www.googleapis.com/auth/indexing" @frappe.whitelist() def authorize_access(reauthorize=None): - """ If no Authorization code get it from Google and then request for Refresh Token. """ + """If no Authorization code get it from Google and then request for Refresh Token.""" google_settings = frappe.get_doc("Google Settings") website_settings = frappe.get_doc("Website Settings") @@ -52,7 +52,7 @@ def authorize_access(reauthorize=None): def get_authentication_url(client_id, redirect_uri): - """ Return authentication url with the client id and redirect uri. """ + """Return authentication url with the client id and redirect uri.""" return { "url": "https://accounts.google.com/o/oauth2/v2/auth?access_type=offline&response_type=code&prompt=consent&client_id={}&include_granted_scopes=true&scope={}&redirect_uri={}".format(client_id, SCOPES, redirect_uri) } @@ -60,7 +60,7 @@ def get_authentication_url(client_id, redirect_uri): @frappe.whitelist() def google_callback(code=None): - """ Authorization code is sent to callback as per the API configuration. """ + """Authorization code is sent to callback as per the API configuration.""" frappe.db.set_value("Website Settings", None, "indexing_authorization_code", code) frappe.db.commit() @@ -68,7 +68,7 @@ def google_callback(code=None): def get_google_indexing_object(): - """ Returns an object of Google Indexing object. """ + """Returns an object of Google Indexing object.""" google_settings = frappe.get_doc("Google Settings") account = frappe.get_doc("Website Settings") @@ -88,7 +88,7 @@ def get_google_indexing_object(): def publish_site(url, operation_type="URL_UPDATED"): - """ Send an update/remove url request. """ + """Send an update/remove url request.""" google_indexing = get_google_indexing_object() body = { diff --git a/frappe/website/doctype/website_settings/test_website_settings.py b/frappe/website/doctype/website_settings/test_website_settings.py deleted file mode 100644 index 9eca957713..0000000000 --- a/frappe/website/doctype/website_settings/test_website_settings.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestWebsiteSettings(unittest.TestCase): - pass diff --git a/frappe/website/website_generator.py b/frappe/website/website_generator.py index d6bdcf3ed0..1a32ef9ffa 100644 --- a/frappe/website/website_generator.py +++ b/frappe/website/website_generator.py @@ -122,7 +122,7 @@ class WebsiteGenerator(Document): return route def send_indexing_request(self, operation_type='URL_UPDATED'): - ''' Send indexing request on update/trash operation. ''' + """Send indexing request on update/trash operation.""" if frappe.db.get_single_value('Website Settings', 'enable_google_indexing') \ and self.is_website_published() and self.meta.allow_guest_to_view: From e5dacb6e1a9693b44d7626413061c2dc565c60a2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 30 Mar 2020 18:33:58 +0530 Subject: [PATCH 108/408] fix: add scroll to grid form --- frappe/public/less/form_grid.less | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/less/form_grid.less b/frappe/public/less/form_grid.less index 28f08635ba..ed457a9ce8 100644 --- a/frappe/public/less/form_grid.less +++ b/frappe/public/less/form_grid.less @@ -262,6 +262,11 @@ border-bottom: 1px solid @border-color; } +.grid-form-body { + max-height: 75vh; + overflow-y: auto; +} + .grid-header-toolbar { display: flow-root; } From 8f4a958b3d82561018759feb1b812f1c9b550e0a Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 30 Mar 2020 18:34:46 +0530 Subject: [PATCH 109/408] fix: scroll back to row in case body is scrolled --- frappe/public/js/frappe/form/grid_row.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 0e36e671cc..7f6039a2a2 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -550,6 +550,7 @@ export default class GridRow { hide_form() { frappe.dom.unfreeze(); this.row.toggle(true); + frappe.utils.scroll_to(this.row); this.refresh(); if(cur_frm) cur_frm.cur_grid = null; this.wrapper.removeClass("grid-row-open"); From bf92c8a4040ba698fdaa0fd35fca8979dd2490f9 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 30 Mar 2020 19:08:24 +0530 Subject: [PATCH 110/408] refactor: add element_to_be_scrolled as parameter to frappe.utils.scroll_to --- frappe/public/js/frappe/form/grid_row_form.js | 1 + frappe/public/js/frappe/utils/utils.js | 14 ++++++++------ 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/grid_row_form.js b/frappe/public/js/frappe/form/grid_row_form.js index 73f0856c08..47da96f4d7 100644 --- a/frappe/public/js/frappe/form/grid_row_form.js +++ b/frappe/public/js/frappe/form/grid_row_form.js @@ -9,6 +9,7 @@ export default class GridRowForm { var me = this; this.make_form(); this.form_area.empty(); + frappe.utils.scroll_to(0, false, 0, this.wrapper.find('.grid-form-body')) this.layout = new frappe.ui.form.Layout({ fields: this.row.docfields, diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 278c80897e..dd2db371a1 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -122,9 +122,11 @@ Object.assign(frappe.utils, {

'); return content.html(); }, - scroll_to: function(element, animate, additional_offset) { + scroll_to: function(element, animate, additional_offset, element_to_be_scrolled) { + element_to_be_scrolled = element_to_be_scrolled || $("html, body"); + var y = 0; - if(element && typeof element==='number') { + if (element && typeof element==="number") { y = element; } else if(element) { var header_offset = $(".navbar").height() + $(".page-head").height(); @@ -136,14 +138,14 @@ Object.assign(frappe.utils, { } // already there - if(y==$('html, body').scrollTop()) { + if (y == element_to_be_scrolled.scrollTop()) { return; } - if (animate!==false) { - $("html, body").animate({ scrollTop: y }); + if (animate !== false) { + element_to_be_scrolled.animate({ scrollTop: y }); } else { - $(window).scrollTop(y); + element_to_be_scrolled.scrollTop(y); } }, From 10d2931a19215ae57d3b00e12f50952003f2ffc4 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 30 Mar 2020 19:08:47 +0530 Subject: [PATCH 111/408] fix: add offset --- frappe/public/js/frappe/form/grid_row.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 7f6039a2a2..8cb7d7d664 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -550,7 +550,7 @@ export default class GridRow { hide_form() { frappe.dom.unfreeze(); this.row.toggle(true); - frappe.utils.scroll_to(this.row); + frappe.utils.scroll_to(this.row, false, 15); this.refresh(); if(cur_frm) cur_frm.cur_grid = null; this.wrapper.removeClass("grid-row-open"); From 006609f59ecce628704d0b7258cb2d1f1d851a10 Mon Sep 17 00:00:00 2001 From: Nabin Hait Date: Mon, 30 Mar 2020 18:35:45 +0530 Subject: [PATCH 112/408] fix: Duplicate check before inserting in contcat email table --- frappe/contacts/doctype/contact/contact.py | 28 ++++++++++--------- .../move_email_and_phone_to_child_table.py | 5 +++- 2 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frappe/contacts/doctype/contact/contact.py b/frappe/contacts/doctype/contact/contact.py index b9239dc1f6..82311b19d4 100644 --- a/frappe/contacts/doctype/contact/contact.py +++ b/frappe/contacts/doctype/contact/contact.py @@ -69,23 +69,25 @@ class Contact(Document): return True def add_email(self, email_id, is_primary=0, autosave=False): - self.append("email_ids", { - "email_id": email_id, - "is_primary": is_primary - }) + if not frappe.db.exists("Contact Email", {"email_id": email_id, "parent": self.name}): + self.append("email_ids", { + "email_id": email_id, + "is_primary": is_primary + }) - if autosave: - self.save(ignore_permissions=True) + if autosave: + self.save(ignore_permissions=True) def add_phone(self, phone, is_primary_phone=0, is_primary_mobile_no=0, autosave=False): - self.append("phone_nos", { - "phone": phone, - "is_primary_phone": is_primary_phone, - "is_primary_mobile_no": is_primary_mobile_no - }) + if not frappe.db.exists("Contact Phone", {"phone": phone, "parent": self.name}): + self.append("phone_nos", { + "phone": phone, + "is_primary_phone": is_primary_phone, + "is_primary_mobile_no": is_primary_mobile_no + }) - if autosave: - self.save(ignore_permissions=True) + if autosave: + self.save(ignore_permissions=True) def set_primary_email(self): if not self.email_ids: diff --git a/frappe/patches/v12_0/move_email_and_phone_to_child_table.py b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py index b18a7487f3..4388d3c849 100644 --- a/frappe/patches/v12_0/move_email_and_phone_to_child_table.py +++ b/frappe/patches/v12_0/move_email_and_phone_to_child_table.py @@ -5,7 +5,11 @@ def execute(): SELECT `name`, `email_id`, `phone`, `mobile_no`, `modified_by`, `creation`, `modified` FROM `tabContact` + where not exists (select * from `tabContact Email` + where `tabContact Email`.parent=`tabContact`.name + and `tabContact Email`.email_id=`tabContact`.email_id) """, as_dict=True) + frappe.reload_doc("contacts", "doctype", "contact_email") frappe.reload_doc("contacts", "doctype", "contact_phone") frappe.reload_doc("contacts", "doctype", "contact") @@ -15,7 +19,6 @@ def execute(): for count, contact_detail in enumerate(contact_details): phone_counter = 1 is_primary = 1 - if contact_detail.email_id: email_values.append(( 1, From 705758cc010f09e3b2746d269966ff93a28b8441 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Tue, 31 Mar 2020 10:15:40 +0530 Subject: [PATCH 113/408] fix(patch): set parent and parenttype as None for print formats (#9801) * fix(patch): set parent and parenttype as None for print formats * fix: remove empty file Co-authored-by: Himanshu --- frappe/patches.txt | 1 + ...ove_parent_and_parenttype_from_print_formats.py | 14 ++++++++++++++ 2 files changed, 15 insertions(+) create mode 100644 frappe/patches/v12_0/remove_parent_and_parenttype_from_print_formats.py diff --git a/frappe/patches.txt b/frappe/patches.txt index a33b4d68b0..fc4f3ae998 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -270,3 +270,4 @@ execute:frappe.delete_doc_if_exists('DocType', 'GSuite Settings') execute:frappe.delete_doc_if_exists('DocType', 'GSuite Templates') execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account') execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings') +frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats diff --git a/frappe/patches/v12_0/remove_parent_and_parenttype_from_print_formats.py b/frappe/patches/v12_0/remove_parent_and_parenttype_from_print_formats.py new file mode 100644 index 0000000000..1a3c56da59 --- /dev/null +++ b/frappe/patches/v12_0/remove_parent_and_parenttype_from_print_formats.py @@ -0,0 +1,14 @@ +import frappe + +def execute(): + frappe.db.sql(""" + UPDATE + `tabPrint Format` + SET + `tabPrint Format`.`parent`='', + `tabPrint Format`.`parenttype`='', + `tabPrint Format`.parentfield='' + WHERE + `tabPrint Format`.parent != '' + OR `tabPrint Format`.parenttype != '' + """) \ No newline at end of file From 5fac84d613bb9ea17c70efe6a19f21cae29b126f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 13:31:03 +0530 Subject: [PATCH 114/408] feat: enable edit API for widgets --- .../public/js/frappe/views/desktop/desktop.js | 6 +-- .../public/js/frappe/widgets/base_widget.js | 39 ++++++++++++++++--- .../public/js/frappe/widgets/chart_widget.js | 1 + .../public/js/frappe/widgets/links_widget.js | 4 -- .../js/frappe/widgets/shortcut_widget.js | 4 -- 5 files changed, 37 insertions(+), 17 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 2a23d8d8a4..66c7ec8043 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -300,11 +300,11 @@ class DesktopPage { type: "chart", columns: 1, options: { - allow_sorting: false, + allow_sorting: this.allow_customization && !frappe.is_mobile(), allow_create: this.allow_customization, allow_delete: this.allow_customization, allow_hiding: false, - allow_edit: false, + allow_edit: true, }, widgets: this.data.charts.items }); @@ -321,7 +321,7 @@ class DesktopPage { allow_create: this.allow_customization, allow_delete: this.allow_customization, allow_hiding: false, - allow_edit: false, + allow_edit: true, }, widgets: this.data.shortcuts.items }); diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 0ecb5a1de3..eaad7b75d5 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -8,6 +8,7 @@ export default class Widget { this.set_title(); this.set_actions(); this.set_body(); + this.setup_events(); } get_config() { @@ -84,10 +85,7 @@ export default class Widget { this.action_area = this.widget.find(".widget-control"); this.head = this.widget.find(".widget-head"); this.footer = this.widget.find(".widget-footer"); - this.set_title(); - this.set_actions(); - this.set_body(); - this.setup_events(); + this.refresh(); } set_title() { @@ -98,7 +96,10 @@ export default class Widget { let button = $( `` ); - action && button.on("click", () => action()); + button.click(event => { + event.stopPropagation(); + action && action(); + }); button.appendTo(this.action_area); } @@ -112,7 +113,33 @@ export default class Widget { } edit() { - this.on_edit && this.on_edit(this.name); + frappe.model.with_doctype(this.doctype, () => { + let new_dialog = new frappe.ui.Dialog({ + title: __("Edit"), + fields: frappe.get_meta(this.doctype).fields, + primary_action: (data) => { + if (this.doctype == 'Desk Chart' && !data.label) { + data.label = data.chart_name; + } + + if (this.doctype == 'Desk Shortcut') { + data.label = data.link_to; + } + + new_dialog.hide(); + console.log("DATA", data) + Object.assign(this, data); + this.set_title(); + this.set_actions(); + this.set_body(); + this.setup_events(); + }, + primary_action_label: __("Save"), + }); + + new_dialog.show(); + new_dialog.set_values(this.get_config()); + }); } hide_or_show() { diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 18951689c9..382b0df746 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -19,6 +19,7 @@ export default class ChartWidget extends Widget { } refresh() { + super.refresh(); this.make_chart(); } diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js index 760fc0fd97..dc2dc9e7f8 100644 --- a/frappe/public/js/frappe/widgets/links_widget.js +++ b/frappe/public/js/frappe/widgets/links_widget.js @@ -6,10 +6,6 @@ export default class LinksWidget extends Widget { super(opts); } - refresh() { - // - } - get_config() { return { name: this.name, diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index ad3ad05fdf..3b4aee668d 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -14,10 +14,6 @@ export default class ShortcutWidget extends Widget { super(opts); } - refresh() { - // - } - get_config() { return { name: this.name, From 481e292e416414ca6b330eecc8ba328c7e04d2bc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 13:31:23 +0530 Subject: [PATCH 115/408] refactor: use console.warn --- frappe/public/js/frappe/widgets/new_widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index 18cd8fdd56..2c2ae1466d 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -46,7 +46,7 @@ export default class NewWidget { let doctype = WIDGET_DOCTYPE_MAP[this.type] if (!doctype) { - console.log(`Could not find ${this.type}`) + console.warn(`Could not find ${this.type}`) } frappe.model.with_doctype(doctype, () => { From 5f5ab02260b39ad6ef724e94cd8f2e5ab0fa7daf Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Tue, 31 Mar 2020 13:35:11 +0530 Subject: [PATCH 116/408] feat: Add ljust_list utility method --- frappe/core/utils.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/frappe/core/utils.py b/frappe/core/utils.py index 55767ffe34..abab9e6160 100644 --- a/frappe/core/utils.py +++ b/frappe/core/utils.py @@ -67,3 +67,18 @@ def find_all(list_of_dict, match_function): if match_function(entry): found.append(entry) return found + +def ljust_list(_list, length, fill_word=None): + '''Similar to ljust but for list + + Usage: + $ ljust_list([1, 2, 3], 5) + > [1, 2, 3, None, None] + ''' + # make a copy to avoid mutation of passed list + _list = list(_list) + fill_length = length - len(_list) + if fill_length > 0: + _list.extend([fill_word] * fill_length) + + return _list \ No newline at end of file From a1259b4ada47cd452597d79743023d5c69c2d43a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 13:43:02 +0530 Subject: [PATCH 117/408] feat: use label field across widgets --- frappe/desk/doctype/desk_card/desk_card.json | 19 ++++++++++--------- .../desk/doctype/desk_chart/desk_chart.json | 2 +- .../doctype/desk_shortcut/desk_shortcut.json | 8 +++++++- .../public/js/frappe/widgets/base_widget.js | 2 +- 4 files changed, 19 insertions(+), 12 deletions(-) diff --git a/frappe/desk/doctype/desk_card/desk_card.json b/frappe/desk/doctype/desk_card/desk_card.json index 522911e804..8877ccd2bd 100644 --- a/frappe/desk/doctype/desk_card/desk_card.json +++ b/frappe/desk/doctype/desk_card/desk_card.json @@ -4,7 +4,7 @@ "doctype": "DocType", "engine": "InnoDB", "field_order": [ - "title", + "label", "icon", "column_break_2", "hidden", @@ -15,17 +15,11 @@ { "fieldname": "links", "fieldtype": "Code", + "in_list_view": 1, "label": "Links", "options": "JSON", "reqd": 1 }, - { - "fieldname": "title", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Title", - "reqd": 1 - }, { "fieldname": "section_break_3", "fieldtype": "Section Break" @@ -44,11 +38,18 @@ "fieldname": "hidden", "fieldtype": "Check", "label": "Hidden" + }, + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-03-27 12:05:00.545808", + "modified": "2020-03-31 13:40:14.993872", "modified_by": "Administrator", "module": "Desk", "name": "Desk Card", diff --git a/frappe/desk/doctype/desk_chart/desk_chart.json b/frappe/desk/doctype/desk_chart/desk_chart.json index c3c9231353..09deefd59d 100644 --- a/frappe/desk/doctype/desk_chart/desk_chart.json +++ b/frappe/desk/doctype/desk_chart/desk_chart.json @@ -26,7 +26,7 @@ ], "istable": 1, "links": [], - "modified": "2020-03-20 10:04:13.992228", + "modified": "2020-03-31 13:33:13.128804", "modified_by": "Administrator", "module": "Desk", "name": "Desk Chart", diff --git a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json index 6b57f250a9..9d2469f7fb 100644 --- a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json +++ b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json @@ -9,6 +9,7 @@ "icon", "column_break_4", "link_to", + "label", "restrict_to_domain", "is_query_report", "section_break_5", @@ -81,11 +82,16 @@ "fieldtype": "Link", "label": "Restrict to Domain", "options": "Domain" + }, + { + "fieldname": "label", + "fieldtype": "Data", + "label": "Label" } ], "istable": 1, "links": [], - "modified": "2020-03-11 13:09:00.180528", + "modified": "2020-03-31 13:41:49.802777", "modified_by": "Administrator", "module": "Desk", "name": "Desk Shortcut", diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index eaad7b75d5..4ac464c22e 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -89,7 +89,7 @@ export default class Widget { } set_title() { - this.title_field[0].innerHTML = this.label || this.name; + this.title_field[0].innerHTML = this.label; } add_custom_button(html, action, class_name = "") { From ed3e2a9a3926405b6a956a7b7fcd431919d884eb Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 13:50:23 +0530 Subject: [PATCH 118/408] feat: get rid of docname hack --- frappe/desk/desktop.py | 6 ------ frappe/public/js/frappe/widgets/base_widget.js | 9 ++------- frappe/public/js/frappe/widgets/chart_widget.js | 1 - frappe/public/js/frappe/widgets/links_widget.js | 2 -- frappe/public/js/frappe/widgets/shortcut_widget.js | 8 ++++++-- 5 files changed, 8 insertions(+), 18 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 9954d593d7..39369599c2 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -145,8 +145,6 @@ class Workspace: else: new_section = section.as_dict().copy() new_section["links"] = new_items - new_section["label"] = section.title - new_section["docname"] = section.name new_data.append(new_section) return new_data @@ -160,7 +158,6 @@ class Workspace: for chart in charts: chart.label = chart.label if chart.label else chart.chart_name - chart.docname = chart.name all_charts.append(chart) return all_charts @@ -180,8 +177,6 @@ class Workspace: for item in shortcuts: new_item = item.as_dict().copy() - new_item['docname'] = item.name - new_item['name'] = _(item.link_to) if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item): if item.type == "Page": page = self.allowed_pages[item.link_to] @@ -352,7 +347,6 @@ def prepare_widget(config, doctype, parentfield): wid_config = widgets[name].copy() # Some cleanup wid_config.pop("name", None) - wid_config.pop("docname", None) # New Doc doc = frappe.new_doc(doctype) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 4ac464c22e..759c49feb2 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -14,7 +14,6 @@ export default class Widget { get_config() { return { name: this.name, - docname: this.docname, label: this.label }; } @@ -69,7 +68,7 @@ export default class Widget { make_widget() { this.widget = $(`
+ }" data-widget-name=${this.name}>
@@ -127,12 +126,8 @@ export default class Widget { } new_dialog.hide(); - console.log("DATA", data) Object.assign(this, data); - this.set_title(); - this.set_actions(); - this.set_body(); - this.setup_events(); + this.refresh(); }, primary_action_label: __("Save"), }); diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 382b0df746..e5cc879974 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -12,7 +12,6 @@ export default class ChartWidget extends Widget { get_config() { return { name: this.name, - docname: this.docname, chart_name: this.chart_name, label: this.label, } diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js index dc2dc9e7f8..b93b592117 100644 --- a/frappe/public/js/frappe/widgets/links_widget.js +++ b/frappe/public/js/frappe/widgets/links_widget.js @@ -9,10 +9,8 @@ export default class LinksWidget extends Widget { get_config() { return { name: this.name, - docname: this.docname, links: JSON.stringify(this.links), label: this.label, - title: this.title, hidden: this.hidden, } } diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index 3b4aee668d..ed5cbf5e5a 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -17,7 +17,6 @@ export default class ShortcutWidget extends Widget { get_config() { return { name: this.name, - docname: this.docname, icon: this.icon, label: this.label, format: this.format, @@ -30,7 +29,12 @@ export default class ShortcutWidget extends Widget { setup_events() { this.widget.click(() => { - let route = generate_route(this) + let route = generate_route({ + route: this.route, + name: this.link_to, + type: this.type + }) + frappe.set_route(route) }) } From fe0bc6feff9ea93e08e429cc47d140eea52c8ba7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 14:08:13 +0530 Subject: [PATCH 119/408] feat: move settings to modules section --- frappe/core/desk_page/settings/settings.json | 41 ++++++++++++-------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/frappe/core/desk_page/settings/settings.json b/frappe/core/desk_page/settings/settings.json index 41d1765684..ac8310454d 100644 --- a/frappe/core/desk_page/settings/settings.json +++ b/frappe/core/desk_page/settings/settings.json @@ -1,37 +1,43 @@ { "cards": [ { + "hidden": 0, "icon": "fa fa-th", - "links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]", - "title": "Data" + "label": "Data", + "links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]" }, { + "hidden": 0, "icon": "fa fa-envelope", - "links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]", - "title": "Email / Notifications" + "label": "Email / Notifications", + "links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]" }, { + "hidden": 0, "icon": "fa fa-globe", - "links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]", - "title": "Website" + "label": "Website", + "links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]" }, { + "hidden": 0, "icon": "fa fa-wrench", - "links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]", - "title": "Core" + "label": "Core", + "links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]" }, { + "hidden": 0, "icon": "fa fa-print", - "links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]", - "title": "Printing" + "label": "Printing", + "links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]" }, { + "hidden": 0, "icon": "fa fa-random", - "links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]", - "title": "Workflow" + "label": "Workflow", + "links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]" } ], - "category": "Administration", + "category": "Modules", "charts": [], "creation": "2020-03-02 15:09:40.527211", "developer_mode_only": 0, @@ -42,29 +48,32 @@ "idx": 0, "is_standard": 1, "label": "Settings", - "modified": "2020-03-12 16:30:43.510434", + "modified": "2020-03-31 13:56:46.807568", "modified_by": "Administrator", "module": "Core", "name": "Settings", "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 1, + "pin_to_bottom": 1, + "pin_to_top": 0, "shortcuts": [ { "icon": "octicon octicon-settings", "is_query_report": 0, + "label": "System Settings", "link_to": "System Settings", "type": "DocType" }, { "icon": "fa fa-print", "is_query_report": 0, + "label": "Print Settings", "link_to": "Print Settings", "type": "DocType" }, { "icon": "fa fa-globe", "is_query_report": 0, + "label": "Website Settings", "link_to": "Website Settings", "type": "DocType" } From 25d260d5c476c7875fb625eda41862124a24d47b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 30 Mar 2020 18:16:04 +0530 Subject: [PATCH 120/408] test(DocType): added test_data_field_options --- frappe/core/doctype/doctype/test_doctype.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 969a71ab7d..85381f40d0 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -113,6 +113,22 @@ class TestDocType(unittest.TestCase): if condition: self.assertFalse(re.match(pattern, condition)) + def test_data_field_options(self): + for field in frappe.model.data_field_options: + test_doctype = frappe.get_doc({ + "doctype": "DocType", + "name": "Test Data Fields", + "module": "Core", + "custom": 1, + "fields": [{ + "fieldname": "{0}_field".format(field), + "fieldtype": "Data", + "options": field + ".invalid" + }] + }) + # assert that only data options in frappe.model.data_field_options are valid + self.assertRaises(frappe.ValidationError, test_doctype.insert) + def test_sync_field_order(self): from frappe.modules.import_file import get_file_path import os From e8434806ec6041ca0357e18df09844e01597ce31 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 14:30:30 +0530 Subject: [PATCH 121/408] feat: removed unused fields from widget docs --- frappe/desk/doctype/desk_card/desk_card.json | 8 +------ frappe/desk/doctype/desk_page/desk_page.js | 24 +++++++++++-------- .../doctype/desk_shortcut/desk_shortcut.json | 20 +++++++--------- 3 files changed, 23 insertions(+), 29 deletions(-) diff --git a/frappe/desk/doctype/desk_card/desk_card.json b/frappe/desk/doctype/desk_card/desk_card.json index 8877ccd2bd..775681b4e2 100644 --- a/frappe/desk/doctype/desk_card/desk_card.json +++ b/frappe/desk/doctype/desk_card/desk_card.json @@ -5,7 +5,6 @@ "engine": "InnoDB", "field_order": [ "label", - "icon", "column_break_2", "hidden", "section_break_3", @@ -28,11 +27,6 @@ "fieldname": "column_break_2", "fieldtype": "Column Break" }, - { - "fieldname": "icon", - "fieldtype": "Data", - "label": "Icon" - }, { "default": "0", "fieldname": "hidden", @@ -49,7 +43,7 @@ ], "istable": 1, "links": [], - "modified": "2020-03-31 13:40:14.993872", + "modified": "2020-03-31 14:30:06.122112", "modified_by": "Administrator", "module": "Desk", "name": "Desk Card", diff --git a/frappe/desk/doctype/desk_page/desk_page.js b/frappe/desk/doctype/desk_page/desk_page.js index 7af3e0e98c..67def55521 100644 --- a/frappe/desk/doctype/desk_page/desk_page.js +++ b/frappe/desk/doctype/desk_page/desk_page.js @@ -2,17 +2,21 @@ // For license information, please see license.txt frappe.ui.form.on('Desk Page', { - refresh: function(frm) { + setup: function(frm) { frm.get_field("is_standard").toggle(frappe.boot.developer_mode); frm.get_field("extends_another_page").toggle(frappe.boot.developer_mode); - if (!frappe.boot.developer_mode) { - frm.set_read_only(); - frm.fields - .filter(field => field.has_input) - .forEach(field => { - frm.set_df_property(field.df.fieldname, "read_only", "1"); - }); - frm.disable_save(); + if (!frappe.boot.developer_mode || frm.doc.for_user) { + frm.trigger('disable_form') } + }, + + disable_form: function(frm) { + frm.set_read_only(); + frm.fields + .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/doctype/desk_shortcut/desk_shortcut.json b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json index 9d2469f7fb..d008a94834 100644 --- a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json +++ b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json @@ -6,12 +6,11 @@ "engine": "InnoDB", "field_order": [ "type", - "icon", - "column_break_4", "link_to", + "column_break_4", "label", + "icon", "restrict_to_domain", - "is_query_report", "section_break_5", "stats_filter", "column_break_3", @@ -52,6 +51,7 @@ "label": "Format" }, { + "depends_on": "eval:doc.type == \"DocType\" && frappe.boot.developer_mode", "fieldname": "section_break_5", "fieldtype": "Section Break", "label": "Count Filter" @@ -62,13 +62,7 @@ "label": "Color" }, { - "default": "0", - "depends_on": "eval:doc.type === \"Report\"", - "fieldname": "is_query_report", - "fieldtype": "Check", - "label": "Is Query Report" - }, - { + "depends_on": "eval:frappe.boot.developer_mode", "fieldname": "icon", "fieldtype": "Data", "label": "Icon" @@ -78,6 +72,7 @@ "fieldtype": "Column Break" }, { + "depends_on": "eval:frappe.boot.developer_mode", "fieldname": "restrict_to_domain", "fieldtype": "Link", "label": "Restrict to Domain", @@ -86,12 +81,13 @@ { "fieldname": "label", "fieldtype": "Data", - "label": "Label" + "label": "Label", + "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-03-31 13:41:49.802777", + "modified": "2020-03-31 14:29:37.021121", "modified_by": "Administrator", "module": "Desk", "name": "Desk Shortcut", From 63a6cd0c1b96a010109f14e502975e7f9510b325 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 31 Mar 2020 14:37:55 +0530 Subject: [PATCH 122/408] fix: datepicker options for min hours --- .../email/doctype/email_group/email_group.py | 3 +-- frappe/email/doctype/newsletter/newsletter.js | 18 ++++++++++++------ frappe/email/doctype/newsletter/newsletter.py | 2 +- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/frappe/email/doctype/email_group/email_group.py b/frappe/email/doctype/email_group/email_group.py index 8aa4893dc9..23495b2d70 100755 --- a/frappe/email/doctype/email_group/email_group.py +++ b/frappe/email/doctype/email_group/email_group.py @@ -98,8 +98,7 @@ def add_subscribers(name, email_list): return frappe.get_doc("Email Group", name).update_total_subscribers() def send_welcome_email(welcome_email, email, email_group): - ''' Send welcome email for the subscribers of a given email group. ''' - + """Send welcome email for the subscribers of a given email group.""" if not welcome_email: return diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index f5ca832fd8..ef4b88dbdf 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -31,21 +31,19 @@ frappe.ui.form.on('Newsletter', { frm.trigger('setup_schedule_send'); }, - schedule_send(frm) { - frm.trigger('setup_schedule_send'); - }, - setup_schedule_send(frm) { let today = new Date(); + // setting datepicker options to set min date & min time + today.setHours(today.getHours() + 1 ); frm.get_field('schedule_send').$input.datepicker({ maxMinutes: 0, minDate: today, timeFormat: 'hh', onSelect: function (fd, d, picker) { if (!d) return; - var date = d.getDate(); - if (date === today) { + var date = d.toDateString(); + if (date === today.toDateString()) { picker.update({ minHours: (today.getHours() + 1) }); @@ -54,8 +52,16 @@ frappe.ui.form.on('Newsletter', { minHours: 0 }); } + frm.get_field('schedule_send').$input.trigger('change'); } }); + + + const $tp = frm.get_field('schedule_send').datepicker.timepicker; + $tp.$minutes.parent().css('display', 'none'); + $tp.$minutesText.css('display', 'none'); + $tp.$minutesText.prev().css('display', 'none'); + $tp.$seconds.parent().css('display', 'none'); }, setup_dashboard(frm) { diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index fcd5d513b6..401e305ae0 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -264,7 +264,7 @@ def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20 limit_page_length, limit_start), email_group_list, as_dict=1) def send_scheduled_email(): - ''' Send scheduled newsletter to the recipients. ''' + """Send scheduled newsletter to the recipients.""" scheduled_newsletter = frappe.get_all('Newsletter', filters = { 'schedule_send': ('<=', now_datetime()), 'email_sent': 0 From 50492786313e26b5a17e6eb17aa972302e4f81f9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 14:38:21 +0530 Subject: [PATCH 123/408] feat: cleanup --- frappe/desk/desktop.py | 19 ++++++++++--------- frappe/desk/doctype/desk_card/desk_card.json | 5 +++-- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 39369599c2..13e0dbf7b4 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -313,15 +313,16 @@ def save_customization(page, config): page_doc = get_custom_workspace_for_user(page) # Update field values - page_doc.charts_label = original_page.charts_label - page_doc.cards_label = original_page.cards_label - page_doc.shortcuts_label = original_page.shortcuts_label - page_doc.charts_label = original_page.charts_label - page_doc.icon = original_page.icon - page_doc.module = original_page.module - page_doc.developer_mode_only = original_page.developer_mode_only - page_doc.category = original_page.category - + page_doc.update({ + "charts_label": original_page.charts_label, + "cards_label": original_page.cards_label, + "shortcuts_label": original_page.shortcuts_label, + "charts_label": original_page.charts_label, + "icon": original_page.icon, + "module": original_page.module, + "developer_mode_only": original_page.developer_mode_only, + "category": original_page.category + }) config = frappe._dict(loads(config)) diff --git a/frappe/desk/doctype/desk_card/desk_card.json b/frappe/desk/doctype/desk_card/desk_card.json index 775681b4e2..dbcb4b0d5c 100644 --- a/frappe/desk/doctype/desk_card/desk_card.json +++ b/frappe/desk/doctype/desk_card/desk_card.json @@ -2,6 +2,7 @@ "actions": [], "creation": "2020-01-29 14:45:54.383089", "doctype": "DocType", + "editable_grid": 1, "engine": "InnoDB", "field_order": [ "label", @@ -14,7 +15,6 @@ { "fieldname": "links", "fieldtype": "Code", - "in_list_view": 1, "label": "Links", "options": "JSON", "reqd": 1 @@ -31,6 +31,7 @@ "default": "0", "fieldname": "hidden", "fieldtype": "Check", + "in_list_view": 1, "label": "Hidden" }, { @@ -43,7 +44,7 @@ ], "istable": 1, "links": [], - "modified": "2020-03-31 14:30:06.122112", + "modified": "2020-03-31 14:38:06.303847", "modified_by": "Administrator", "module": "Desk", "name": "Desk Card", From 0027781cb058b199d27f92bfd426942f189825cc Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 14:39:25 +0530 Subject: [PATCH 124/408] fix: label for custom card --- frappe/desk/desktop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 13e0dbf7b4..d2049faeb5 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -255,7 +255,7 @@ def get_table_with_counts(): def get_custom_reports_and_doctypes(module): return [ frappe._dict({ - "title": "Custom", + "label": "Custom", "links": get_custom_doctype_list(module) + get_custom_report_list(module) }) ] From 626f41078878cffc092dd99022fceb19994bf5f9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 15:02:06 +0530 Subject: [PATCH 125/408] fix: custom links card bug --- frappe/public/js/frappe/widgets/base_widget.js | 2 +- frappe/public/js/frappe/widgets/new_widget.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 759c49feb2..992be13438 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -68,7 +68,7 @@ export default class Widget { make_widget() { this.widget = $(`
+ }" data-widget-name=${this.name ? this.name : ''}>
diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index 2c2ae1466d..d0dabe6678 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -58,11 +58,11 @@ export default class NewWidget { data.label = data.chart_name; } - if (this.type == 'shortcut') { + if (this.type == 'shortcut' && !data.label) { data.label = data.link_to; } - data.docname = frappe.utils.get_random(20); + data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`; new_dialog.hide(); this.on_create(data); From 34f2a0ada13d3e542d6e19540d7fe1d5aa63437d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 31 Mar 2020 09:38:12 +0000 Subject: [PATCH 126/408] chore(deps): bump bleach from 3.1.2 to 3.1.4 Bumps [bleach](https://github.com/mozilla/bleach) from 3.1.2 to 3.1.4. - [Release notes](https://github.com/mozilla/bleach/releases) - [Changelog](https://github.com/mozilla/bleach/blob/master/CHANGES) - [Commits](https://github.com/mozilla/bleach/compare/v3.1.2...v3.1.4) Signed-off-by: dependabot[bot] --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 55a4910056..bcf3760cd8 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ Babel==2.6.0 beautifulsoup4==4.8.2 bleach-whitelist==0.0.10 -bleach==3.1.2 +bleach==3.1.4 boto3==1.10.18 braintree==3.57.1 chardet==3.0.4 From 01326a3d20d26fc939ab68ff3cd8f1c4e55e2e5b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 16:01:04 +0530 Subject: [PATCH 127/408] feat: added toggleFullwidth event to body --- frappe/public/js/frappe/ui/toolbar/toolbar.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index eed36615fc..e774550a69 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -203,6 +203,7 @@ $.extend(frappe.ui.toolbar, { fullwidth = !fullwidth; localStorage.container_fullwidth = fullwidth; frappe.ui.toolbar.set_fullwidth_if_enabled(); + $(document.body).trigger('toggleFullWidth'); }, set_fullwidth_if_enabled() { let fullwidth = JSON.parse(localStorage.container_fullwidth || 'false'); From b1d7e8fb05bc46ce7de5d9f56aafff1abe6abdc2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 16:01:33 +0530 Subject: [PATCH 128/408] refactor: clean up --- .../public/js/frappe/views/desktop/desktop.js | 83 +++++-------------- 1 file changed, 23 insertions(+), 60 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 66c7ec8043..2ae65ed1a4 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -1,7 +1,6 @@ export default class Desktop { constructor({ wrapper }) { this.wrapper = wrapper; - window.desk = this; this.pages = {}; this.sidebar_items = {}; this.sidebar_categories = [ @@ -131,8 +130,6 @@ export default class Desktop { this.pages[page] = $page; return $page; } - - setup_events() {} } class DesktopPage { @@ -141,10 +138,7 @@ class DesktopPage { this.page_name = page_name; this.sections = {}; this.allow_customization = false; - this.in_customize_mode = false; - this.container.empty(); - this.make(); - window.page = this; + this.reload(); } show() { @@ -159,6 +153,7 @@ class DesktopPage { this.in_customize_mode = false; this.container.empty(); this.make(); + this.setup_events(); } make_customization_link() { @@ -181,7 +176,9 @@ class DesktopPage { } make() { - this.make_page(); + this.page = $(`
`); + this.page.appendTo(this.container); + this.get_data().then(res => { this.data = res.message; // this.make_onboarding(); @@ -191,26 +188,24 @@ class DesktopPage { return; } - this.allow_customization = this.data.allow_customization || false; - this.allow_customization && this.make_customization_link(); - - !this.sections["onboarding"] && - this.data.charts.items && - this.make_charts(); - this.data.shortcuts.items && this.make_shortcuts(); - this.data.cards.items && this.make_cards(); - if (this.allow_customization) { - // Move the widget group up to align with labels if customization is allowed - $('.desk-page .widget-group:visible:first').css('margin-top', '-25px'); - } + this.refresh(); }); } - make_page() { - this.page = $( - `
` - ); - this.page.appendTo(this.container); + refresh() { + this.page.empty(); + this.allow_customization = this.data.allow_customization || false; + this.allow_customization && this.make_customization_link(); + + !this.sections["onboarding"] && + this.data.charts.items && + this.make_charts(); + this.data.shortcuts.items && this.make_shortcuts(); + this.data.cards.items && this.make_cards(); + if (this.allow_customization) { + // Move the widget group up to align with labels if customization is allowed + $('.desk-page .widget-group:visible:first').css('margin-top', '-25px'); + } } get_data() { @@ -219,41 +214,8 @@ class DesktopPage { }); } - make_onboarding() { - this.sections["onboarding"] = new frappe.widget.WidgetGroup({ - title: `Getting Started`, - container: this.page, - type: "onboarding", - columns: 1, - widgets: [ - { - label: "Unlock Great Customer Experience", - subtitle: "Just a few steps, and you’re good to go.", - steps: [ - { - label: "Configure Lead Sources", - completed: true - }, - { - label: "Add Your Leads", - completed: false - }, - { - label: "Create Your First Opportunity", - completed: false - }, - { - label: "Onboard your Sales Team", - completed: false - }, - { - label: "Assign Territories", - completed: false - } - ] - } - ] - }); + setup_events() { + $(document.body).on('toggleFullWidth', () => this.refresh()); } customize() { @@ -289,6 +251,7 @@ class DesktopPage { page: this.page_name, config: config }).then(res => { + frappe.msgprint(__("Customizations Saved Successfully")) this.reload(); }) } From 5600d84d3c6e5fdd901dc4e41c37156b82c9d5d1 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 31 Mar 2020 16:01:57 +0530 Subject: [PATCH 129/408] fix: don't show dashboard chart if not permitted --- frappe/core/page/dashboard/dashboard.js | 17 +++++++++++------ frappe/desk/desktop.py | 5 +++-- frappe/desk/doctype/dashboard/dashboard.py | 9 +++++++++ .../dashboard_chart/dashboard_chart.json | 11 +---------- .../doctype/dashboard_chart/dashboard_chart.py | 12 +++++------- 5 files changed, 29 insertions(+), 25 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 511aac7010..92e7e48a8c 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -59,7 +59,7 @@ class Dashboard { } show_dashboard(current_dashboard_name) { - if(this.dashboard_name !== current_dashboard_name) { + 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'))) { @@ -76,9 +76,8 @@ class Dashboard { } refresh() { - this.get_dashboard_doc().then((doc) => { - this.dashboard_doc = doc; - this.charts = this.dashboard_doc.charts + this.get_permitted_dashboard_charts().then(charts => { + this.charts = charts .map(chart => { return { chart_name: chart.chart, @@ -98,8 +97,14 @@ class Dashboard { }); } - get_dashboard_doc() { - return frappe.model.with_doc('Dashboard', this.dashboard_name); + get_permitted_dashboard_charts() { + return frappe.xcall( + 'frappe.desk.doctype.dashboard.dashboard.get_permitted_charts', + { + dashboard_name: this.dashboard_name + }).then(charts => { + return charts; + }); } set_dropdown() { diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index ef84114745..1cb03355c6 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -146,8 +146,9 @@ class Workspace: charts = charts + self.extended_charts for chart in charts: - chart.label = chart.label if chart.label else chart.chart_name - all_charts.append(chart) + if frappe.has_permission('Dashboard Chart', doc=chart.chart_name): + chart.label = chart.label if chart.label else chart.chart_name + all_charts.append(chart) return all_charts diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index c8f22d29b9..5c344956bf 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -12,3 +12,12 @@ class Dashboard(Document): # make all other dashboards non-default frappe.db.sql('''update tabDashboard set is_default = 0 where name != %s''', self.name) + +@frappe.whitelist() +def get_permitted_charts(dashboard_name): + permitted_charts = [] + dashboard = frappe.get_doc('Dashboard', dashboard_name) + for chart in dashboard.charts: + if frappe.has_permission('Dashboard Chart', doc=chart.chart): + permitted_charts.append(chart) + return permitted_charts diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 8c14ea130d..9652ae3945 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -215,7 +215,7 @@ } ], "links": [], - "modified": "2020-03-26 13:41:11.126000", + "modified": "2020-03-31 16:00:01.987059", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", @@ -245,15 +245,6 @@ "share": 1, "write": 1 }, - { - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "Dashboard User", - "share": 1 - }, { "email": 1, "export": 1, diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 634b33cd97..b2a6f0a0ff 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -23,15 +23,14 @@ def get_permission_query_conditions(user): return roles = frappe.get_roles(user) - if "System Manager" in roles or "Dashboard Manager" in roles or "Dashboard User" in roles: + if "System Manager" in roles: return None allowed_doctypes = tuple(frappe.permissions.get_doctypes_with_read()) allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()]) return ''' - `tabDashboard Chart`.`chart_type` = 'Custom' - or `tabDashboard Chart`.`document_type` in {allowed_doctypes} + `tabDashboard Chart`.`document_type` in {allowed_doctypes} or `tabDashboard Chart`.`report_name` in {allowed_reports} '''.format( allowed_doctypes=allowed_doctypes, @@ -41,12 +40,11 @@ def get_permission_query_conditions(user): def has_permission(doc, ptype, user): roles = frappe.get_roles(user) - if "System Manager" in roles or "Dashboard Manager" in roles or "Dashboard User" in roles: + if "System Manager" in roles: return True - if doc.chart_type == 'Custom': - return True - elif doc.chart_type == 'Report': + + if doc.chart_type == 'Report': allowed_reports = tuple([key.encode('UTF8') for key in get_allowed_reports()]) if doc.report_name in allowed_reports: return True From 15f80cc4b078d901c4f52c9340bad2c181277da1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 16:16:57 +0530 Subject: [PATCH 130/408] feat: set max count for widget group --- frappe/public/js/frappe/views/desktop/desktop.js | 1 + frappe/public/js/frappe/widgets/widget_group.js | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 2ae65ed1a4..c8c99295e2 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -268,6 +268,7 @@ class DesktopPage { allow_delete: this.allow_customization, allow_hiding: false, allow_edit: true, + max_widget_count: 2, }, widgets: this.data.charts.items }); diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 28e47018df..7acb9b63de 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -81,7 +81,11 @@ export default class WidgetGroup { wid.customize(this.options); }) - if (this.options.allow_create) { + const max = this.options + ? this.options.max_widget_count || Number.POSITIVE_INFINITY + : Number.POSITIVE_INFINITY; + + if (this.options.allow_create && this.widgets_list.length < max) { this.new_widget = new NewWidget({ container: this.body, type: this.type, From 27963b774839e975913c7da0badd971f847d8553 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 16:18:31 +0530 Subject: [PATCH 131/408] feat: add in_customize_mode flag --- frappe/public/js/frappe/widgets/base_widget.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 992be13438..93093de84f 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -19,6 +19,7 @@ export default class Widget { } customize(options) { + this.in_customize_mode = true; this.action_area.empty(); options.allow_delete && @@ -58,9 +59,11 @@ export default class Widget { '', () => this.edit() ); + } make() { + this.in_customize_mode = false; this.make_widget(); this.widget.appendTo(this.container); } From 01245091453589c80d49dade4da4c2194458ea26 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 16:18:44 +0530 Subject: [PATCH 132/408] fix: actions not visible on adding widget bug --- .../public/js/frappe/widgets/chart_widget.js | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index e5cc879974..2dd87e9a50 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -18,7 +18,8 @@ export default class ChartWidget extends Widget { } refresh() { - super.refresh(); + this.set_title(); + this.set_body(); this.make_chart(); } @@ -69,17 +70,18 @@ export default class ChartWidget extends Widget { this.get_settings().then(() => { this.setup_container(); this.prepare_chart_object(); - this.action_area.empty(); - this.prepare_chart_actions(); - this.setup_filter_button(); + if (!this.in_customize_mode) { + this.action_area.empty(); + this.prepare_chart_actions(); + this.setup_filter_button(); - if ( - this.chart_doc.timeseries && - this.chart_doc.chart_type !== "Custom" - ) { - this.render_time_series_filters(); + if ( + this.chart_doc.timeseries && + this.chart_doc.chart_type !== "Custom" + ) { + this.render_time_series_filters(); + } } - this.fetch_and_update_chart(); }); } From d9f1a85d002bdc1759f0e53e8f0c6f770fa298fc Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 31 Mar 2020 16:23:12 +0530 Subject: [PATCH 133/408] fix: only encode if type is not str --- frappe/permissions.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/permissions.py b/frappe/permissions.py index 9835724ce0..0d766aec8d 100644 --- a/frappe/permissions.py +++ b/frappe/permissions.py @@ -307,7 +307,7 @@ def has_controller_permissions(doc, ptype, user=None): return None def get_doctypes_with_read(): - return list(set([p.parent.encode('UTF8') for p in get_valid_perms()])) + return list(set([p.parent if type(p.parent) == str else p.parent.encode('UTF8') for p in get_valid_perms()])) def get_valid_perms(doctype=None, user=None): '''Get valid permissions for the current user from DocPerm and Custom DocPerm''' From d0aa01367b947b7dea3fad18ec2bc80b6b3cdea2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 31 Mar 2020 16:28:08 +0530 Subject: [PATCH 134/408] fix: show message if there are no permitted charts on a dashboard --- frappe/core/page/dashboard/dashboard.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 92e7e48a8c..8705804014 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -77,6 +77,9 @@ class Dashboard { refresh() { this.get_permitted_dashboard_charts().then(charts => { + if (!charts.length) { + frappe.msgprint(__('No Permitted Charts on this Dashboard'), __('No Permitted Charts')) + } this.charts = charts .map(chart => { return { From 249de2c7d142bd7db984a32559335788bb0a0767 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 31 Mar 2020 16:32:19 +0530 Subject: [PATCH 135/408] fix: chart edit bug --- frappe/public/js/frappe/widgets/chart_widget.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 2dd87e9a50..2aa7209d0d 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -18,6 +18,7 @@ export default class ChartWidget extends Widget { } refresh() { + delete this.dashboard_chart; this.set_title(); this.set_body(); this.make_chart(); From 1d4a14802ee0260964bbd1d25813a2ab1243c8a0 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Tue, 31 Mar 2020 16:36:15 +0530 Subject: [PATCH 136/408] fix: navigate between role profiles not changes the roles --- frappe/core/doctype/role_profile/role_profile.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/role_profile/role_profile.js b/frappe/core/doctype/role_profile/role_profile.js index 09aead670a..d31618cc4a 100644 --- a/frappe/core/doctype/role_profile/role_profile.js +++ b/frappe/core/doctype/role_profile/role_profile.js @@ -2,7 +2,7 @@ // For license information, please see license.txt frappe.ui.form.on('Role Profile', { - setup: function(frm) { + refresh: function(frm) { if(has_common(frappe.user_roles, ["Administrator", "System Manager"])) { if(!frm.roles_editor) { var role_area = $('
') From 256428572ffb81146c056b6fcda1f08be3fceca3 Mon Sep 17 00:00:00 2001 From: marty Date: Tue, 31 Mar 2020 12:54:30 +0000 Subject: [PATCH 137/408] resolve error redis_wrapper --- frappe/utils/redis_wrapper.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index b0c0990e85..385bac8e4a 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -43,7 +43,7 @@ class RedisWrapper(redis.Redis): try: if expires_in_sec: - self.setex(key, expires_in_sec, pickle.dumps(val)) + self.setex(name=key, time=expires_in_sec, value=pickle.dumps(val)) else: self.set(key, pickle.dumps(val)) From 9393c05b13990ae427084a5ddcee00cace685716 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 1 Apr 2020 10:12:35 +0530 Subject: [PATCH 138/408] tests: updated tests to cover more data field options --- frappe/core/doctype/doctype/test_doctype.py | 37 +++++++++++++-------- 1 file changed, 23 insertions(+), 14 deletions(-) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 85381f40d0..17a56b5336 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -114,20 +114,29 @@ class TestDocType(unittest.TestCase): self.assertFalse(re.match(pattern, condition)) def test_data_field_options(self): - for field in frappe.model.data_field_options: - test_doctype = frappe.get_doc({ - "doctype": "DocType", - "name": "Test Data Fields", - "module": "Core", - "custom": 1, - "fields": [{ - "fieldname": "{0}_field".format(field), - "fieldtype": "Data", - "options": field + ".invalid" - }] - }) - # assert that only data options in frappe.model.data_field_options are valid - self.assertRaises(frappe.ValidationError, test_doctype.insert) + valid_data_field_options = frappe.model.data_field_options + ("",) + invalid_data_field_options = ("Invalid Option 1", "Invalid Option 2", frappe.utils.random_string(5)) + + for options_set in [valid_data_field_options, invalid_data_field_options]: + for field in options_set: + test_doctype = frappe.get_doc({ + "doctype": "DocType", + "name": "Test Data Fields", + "module": "Core", + "custom": 1, + "fields": [{ + "fieldname": "{0}_field".format(field), + "fieldtype": "Data", + "options": field + }] + }) + if options_set == invalid_data_field_options: + # assert that only data options in frappe.model.data_field_options are valid + self.assertRaises(frappe.ValidationError, test_doctype.insert) + else: + test_doctype.insert() + self.assertEqual(test_doctype.name, doctype_name) + test_doctype.delete() def test_sync_field_order(self): from frappe.modules.import_file import get_file_path From 0af7c90cce8e21794282cc209f4af72c946bb8c9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 1 Apr 2020 10:49:17 +0530 Subject: [PATCH 139/408] fix: fix typo --- frappe/desk/desktop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index d2049faeb5..a639400670 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -216,7 +216,7 @@ def get_desktop_page(page): except DoesNotExistError: if frappe.message_log: frappe.message_log.pop() - return Nonee + return None @frappe.whitelist() def get_desk_sidebar_items(): From c67ecbc6ba28c27a10155ee8be7c87eda400ecb7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 1 Apr 2020 11:25:15 +0530 Subject: [PATCH 140/408] feat: update desk pages --- frappe/automation/desk_page/tools/tools.json | 34 ++++++++-------- frappe/core/desk_page/settings/settings.json | 11 +----- frappe/core/desk_page/users/users.json | 28 ++++++------- .../customization/customization.json | 24 ++++++------ .../desk_page/integrations/integrations.json | 28 +++++++------ frappe/website/desk_page/website/website.json | 39 ++++++++++--------- 6 files changed, 83 insertions(+), 81 deletions(-) diff --git a/frappe/automation/desk_page/tools/tools.json b/frappe/automation/desk_page/tools/tools.json index 3cfaa0bd97..235498724d 100644 --- a/frappe/automation/desk_page/tools/tools.json +++ b/frappe/automation/desk_page/tools/tools.json @@ -1,22 +1,24 @@ { "cards": [ { - "icon": "octicon octicon-briefcase", - "links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]", - "title": "Tools" + "hidden": 0, + "label": "Tools", + "links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]" }, { - "links": "[\n {\n \"description\": \"Newsletters to contacts, leads.\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Group List\",\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]", - "title": "Email" + "hidden": 0, + "label": "Email", + "links": "[\n {\n \"description\": \"Newsletters to contacts, leads.\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Group List\",\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]" }, { - "icon": "fa fa-cog", - "links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Assignment Rule\",\n \"description\": \"Set up rules for user assignments.\",\n \"label\": \"Assignment Rule\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Milestone\",\n \"description\": \"Tracks milestones on the lifecycle of a document if it undergoes multiple stages.\",\n \"label\": \"Milestone\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Auto Repeat\",\n \"description\": \"Automatically generates recurring documents.\",\n \"label\": \"Auto Repeat\"\n }\n]", - "title": "Automation" + "hidden": 0, + "label": "Automation", + "links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Assignment Rule\",\n \"description\": \"Set up rules for user assignments.\",\n \"label\": \"Assignment Rule\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Milestone\",\n \"description\": \"Tracks milestones on the lifecycle of a document if it undergoes multiple stages.\",\n \"label\": \"Milestone\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Auto Repeat\",\n \"description\": \"Automatically generates recurring documents.\",\n \"label\": \"Auto Repeat\"\n }\n]" }, { - "links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Event Producer\",\n \"description\": \"The site you want to subscribe to for consuming events.\",\n \"label\": \"Event Producer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Consumer\",\n \"description\": \"The site which is consuming your events.\",\n \"label\": \"Event Consumer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Update Log\",\n \"description\": \"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.\",\n \"label\": \"Event Update Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Sync Log\",\n \"description\": \"Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.\",\n \"label\": \"Event Sync Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Document Type Mapping\",\n \"description\": \"The mapping configuration between two doctypes.\",\n \"label\": \"Document Type Mapping\"\n }\n]", - "title": "Event Streaming" + "hidden": 0, + "label": "Event Streaming", + "links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Event Producer\",\n \"description\": \"The site you want to subscribe to for consuming events.\",\n \"label\": \"Event Producer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Consumer\",\n \"description\": \"The site which is consuming your events.\",\n \"label\": \"Event Consumer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Update Log\",\n \"description\": \"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.\",\n \"label\": \"Event Update Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Sync Log\",\n \"description\": \"Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.\",\n \"label\": \"Event Sync Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Document Type Mapping\",\n \"description\": \"The mapping configuration between two doctypes.\",\n \"label\": \"Document Type Mapping\"\n }\n]" } ], "category": "Administration", @@ -30,7 +32,7 @@ "idx": 0, "is_standard": 1, "label": "Tools", - "modified": "2020-03-12 16:30:41.841895", + "modified": "2020-04-01 11:24:40.804346", "modified_by": "Administrator", "module": "Automation", "name": "Tools", @@ -39,27 +41,27 @@ "pin_to_top": 0, "shortcuts": [ { - "is_query_report": 0, + "label": "ToDo", "link_to": "ToDo", "type": "DocType" }, { - "is_query_report": 0, + "label": "Note", "link_to": "Note", "type": "DocType" }, { - "is_query_report": 0, + "label": "File", "link_to": "File", "type": "DocType" }, { - "is_query_report": 0, + "label": "Assignment Rule", "link_to": "Assignment Rule", "type": "DocType" }, { - "is_query_report": 0, + "label": "Auto Repeat", "link_to": "Auto Repeat", "type": "DocType" } diff --git a/frappe/core/desk_page/settings/settings.json b/frappe/core/desk_page/settings/settings.json index ac8310454d..6569b2fb20 100644 --- a/frappe/core/desk_page/settings/settings.json +++ b/frappe/core/desk_page/settings/settings.json @@ -2,37 +2,31 @@ "cards": [ { "hidden": 0, - "icon": "fa fa-th", "label": "Data", "links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "icon": "fa fa-envelope", "label": "Email / Notifications", "links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "icon": "fa fa-globe", "label": "Website", "links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "icon": "fa fa-wrench", "label": "Core", "links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "icon": "fa fa-print", "label": "Printing", "links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]" }, { "hidden": 0, - "icon": "fa fa-random", "label": "Workflow", "links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]" } @@ -48,7 +42,7 @@ "idx": 0, "is_standard": 1, "label": "Settings", - "modified": "2020-03-31 13:56:46.807568", + "modified": "2020-04-01 11:24:40.636747", "modified_by": "Administrator", "module": "Core", "name": "Settings", @@ -58,21 +52,18 @@ "shortcuts": [ { "icon": "octicon octicon-settings", - "is_query_report": 0, "label": "System Settings", "link_to": "System Settings", "type": "DocType" }, { "icon": "fa fa-print", - "is_query_report": 0, "label": "Print Settings", "link_to": "Print Settings", "type": "DocType" }, { "icon": "fa fa-globe", - "is_query_report": 0, "label": "Website Settings", "link_to": "Website Settings", "type": "DocType" diff --git a/frappe/core/desk_page/users/users.json b/frappe/core/desk_page/users/users.json index dc9619314e..30455b86e6 100644 --- a/frappe/core/desk_page/users/users.json +++ b/frappe/core/desk_page/users/users.json @@ -1,19 +1,19 @@ { "cards": [ { - "icon": "fa fa-group", - "links": "[\n {\n \"description\": \"System and Website Users\",\n \"label\": \"User\",\n \"name\": \"User\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User Roles\",\n \"label\": \"Role\",\n \"name\": \"Role\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Role Profile\",\n \"label\": \"Role Profile\",\n \"name\": \"Role Profile\",\n \"type\": \"doctype\"\n }\n]", - "title": "Users" + "hidden": 0, + "label": "Users", + "links": "[\n {\n \"description\": \"System and Website Users\",\n \"label\": \"User\",\n \"name\": \"User\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User Roles\",\n \"label\": \"Role\",\n \"name\": \"Role\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Role Profile\",\n \"label\": \"Role Profile\",\n \"name\": \"Role Profile\",\n \"type\": \"doctype\"\n }\n]" }, { - "icon": "fa fa-group", - "links": "[\n {\n \"description\": \"Activity Log by \",\n \"label\": \"Activity Log\",\n \"name\": \"Activity Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"View Log of all print, download and export events\",\n \"label\": \"Access Log\",\n \"name\": \"Access Log\",\n \"type\": \"doctype\"\n }\n]", - "title": "Logs" + "hidden": 0, + "label": "Logs", + "links": "[\n {\n \"description\": \"Activity Log by \",\n \"label\": \"Activity Log\",\n \"name\": \"Activity Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"View Log of all print, download and export events\",\n \"label\": \"Access Log\",\n \"name\": \"Access Log\",\n \"type\": \"doctype\"\n }\n]" }, { - "icon": "fa fa-lock", - "links": "[\n {\n \"description\": \"Set Permissions on Document Types and Roles\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"Role Permissions Manager\",\n \"name\": \"permission-manager\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restrict user for specific document\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"User Permissions\",\n \"name\": \"User Permission\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Set custom roles for page and report\",\n \"label\": \"Role Permission for Page and Report\",\n \"name\": \"Role Permission for Page and Report\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"User\"\n ],\n \"description\": \"Check which Documents are readable by a User\",\n \"doctype\": \"User\",\n \"icon\": \"fa fa-eye-open\",\n \"is_query_report\": true,\n \"label\": \"Permitted Documents For User\",\n \"name\": \"Permitted Documents For User\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"DocShare\"\n ],\n \"description\": \"Report of all document shares\",\n \"doctype\": \"DocShare\",\n \"icon\": \"fa fa-share\",\n \"label\": \"Document Share Report\",\n \"name\": \"Document Share Report\",\n \"type\": \"report\"\n }\n]", - "title": "Permissions" + "hidden": 0, + "label": "Permissions", + "links": "[\n {\n \"description\": \"Set Permissions on Document Types and Roles\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"Role Permissions Manager\",\n \"name\": \"permission-manager\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restrict user for specific document\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"User Permissions\",\n \"name\": \"User Permission\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Set custom roles for page and report\",\n \"label\": \"Role Permission for Page and Report\",\n \"name\": \"Role Permission for Page and Report\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"User\"\n ],\n \"description\": \"Check which Documents are readable by a User\",\n \"doctype\": \"User\",\n \"icon\": \"fa fa-eye-open\",\n \"is_query_report\": true,\n \"label\": \"Permitted Documents For User\",\n \"name\": \"Permitted Documents For User\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"DocShare\"\n ],\n \"description\": \"Report of all document shares\",\n \"doctype\": \"DocShare\",\n \"icon\": \"fa fa-share\",\n \"label\": \"Document Share Report\",\n \"name\": \"Document Share Report\",\n \"type\": \"report\"\n }\n]" } ], "category": "Administration", @@ -27,7 +27,7 @@ "idx": 0, "is_standard": 1, "label": "Users", - "modified": "2020-03-12 16:30:42.483376", + "modified": "2020-04-01 11:24:40.767676", "modified_by": "Administrator", "module": "Core", "name": "Users", @@ -36,22 +36,22 @@ "pin_to_top": 0, "shortcuts": [ { - "is_query_report": 0, + "label": "User", "link_to": "User", "type": "DocType" }, { - "is_query_report": 0, + "label": "Role", "link_to": "Role", "type": "DocType" }, { - "is_query_report": 0, + "label": "permission-manager", "link_to": "permission-manager", "type": "Page" }, { - "is_query_report": 0, + "label": "user-profile", "link_to": "user-profile", "type": "Page" } diff --git a/frappe/custom/desk_page/customization/customization.json b/frappe/custom/desk_page/customization/customization.json index dedfcaeeec..29f4cb745f 100644 --- a/frappe/custom/desk_page/customization/customization.json +++ b/frappe/custom/desk_page/customization/customization.json @@ -1,17 +1,19 @@ { "cards": [ { - "links": "[\n {\n \"label\": \"Dashboard\",\n \"name\": \"Dashboard\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart\",\n \"name\": \"Dashboard Chart\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart Source\",\n \"name\": \"Dashboard Chart Source\",\n \"type\": \"doctype\"\n }\n]", - "title": "Dashboards" + "hidden": 0, + "label": "Dashboards", + "links": "[\n {\n \"label\": \"Dashboard\",\n \"name\": \"Dashboard\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart\",\n \"name\": \"Dashboard Chart\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart Source\",\n \"name\": \"Dashboard Chart Source\",\n \"type\": \"doctype\"\n }\n]" }, { - "icon": "fa fa-glass", - "links": "[\n {\n \"description\": \"Change field properties (hide, readonly, permission etc.)\",\n \"label\": \"Customize Form\",\n \"name\": \"Customize Form\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add fields to forms.\",\n \"label\": \"Custom Field\",\n \"name\": \"Custom Field\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom javascript to forms.\",\n \"label\": \"Custom Script\",\n \"name\": \"Custom Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom forms.\",\n \"label\": \"DocType\",\n \"name\": \"DocType\",\n \"type\": \"doctype\"\n }\n]", - "title": "Form Customization" + "hidden": 0, + "label": "Form Customization", + "links": "[\n {\n \"description\": \"Change field properties (hide, readonly, permission etc.)\",\n \"label\": \"Customize Form\",\n \"name\": \"Customize Form\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add fields to forms.\",\n \"label\": \"Custom Field\",\n \"name\": \"Custom Field\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom javascript to forms.\",\n \"label\": \"Custom Script\",\n \"name\": \"Custom Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom forms.\",\n \"label\": \"DocType\",\n \"name\": \"DocType\",\n \"type\": \"doctype\"\n }\n]" }, { - "links": "[\n {\n \"description\": \"Add your own translations\",\n \"label\": \"Custom Translations\",\n \"name\": \"Translation\",\n \"type\": \"doctype\"\n }\n]", - "title": "Other" + "hidden": 0, + "label": "Other", + "links": "[\n {\n \"description\": \"Add your own translations\",\n \"label\": \"Custom Translations\",\n \"name\": \"Translation\",\n \"type\": \"doctype\"\n }\n]" } ], "category": "Administration", @@ -25,7 +27,7 @@ "idx": 0, "is_standard": 1, "label": "Customization", - "modified": "2020-03-12 16:30:42.155206", + "modified": "2020-04-01 11:24:40.787109", "modified_by": "Administrator", "module": "Custom", "name": "Customization", @@ -34,17 +36,17 @@ "pin_to_top": 0, "shortcuts": [ { - "is_query_report": 0, + "label": "Customize Form", "link_to": "Customize Form", "type": "DocType" }, { - "is_query_report": 0, + "label": "Custom Role", "link_to": "Custom Role", "type": "DocType" }, { - "is_query_report": 0, + "label": "Custom Script", "link_to": "Custom Script", "type": "DocType" } diff --git a/frappe/integrations/desk_page/integrations/integrations.json b/frappe/integrations/desk_page/integrations/integrations.json index 6ea871cd90..9201e223f8 100644 --- a/frappe/integrations/desk_page/integrations/integrations.json +++ b/frappe/integrations/desk_page/integrations/integrations.json @@ -1,25 +1,29 @@ { "cards": [ { - "links": "[\n {\n \"description\": \"Dropbox backup settings\",\n \"label\": \"Dropbox Settings\",\n \"name\": \"Dropbox Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"S3 Backup Settings\",\n \"label\": \"S3 Backup Settings\",\n \"name\": \"S3 Backup Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Backup.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]", - "title": "Backup" + "hidden": 0, + "label": "Backup", + "links": "[\n {\n \"description\": \"Dropbox backup settings\",\n \"label\": \"Dropbox Settings\",\n \"name\": \"Dropbox Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"S3 Backup Settings\",\n \"label\": \"S3 Backup Settings\",\n \"name\": \"S3 Backup Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Backup.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]" }, { - "links": "[\n {\n \"description\": \"Google API Settings.\",\n \"label\": \"Google Settings\",\n \"name\": \"Google Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Contacts Integration.\",\n \"label\": \"Google Contacts\",\n \"name\": \"Google Contacts\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Calendar Integration.\",\n \"label\": \"Google Calendar\",\n \"name\": \"Google Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Integration.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]", - "title": "Google Services" + "hidden": 0, + "label": "Google Services", + "links": "[\n {\n \"description\": \"Google API Settings.\",\n \"label\": \"Google Settings\",\n \"name\": \"Google Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Contacts Integration.\",\n \"label\": \"Google Contacts\",\n \"name\": \"Google Contacts\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Calendar Integration.\",\n \"label\": \"Google Calendar\",\n \"name\": \"Google Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Integration.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]" }, { - "links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n }\n]", - "title": "Webhook" + "hidden": 0, + "label": "Webhook", + "links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n }\n]" }, { - "links": "[\n {\n \"description\": \"Enter keys to enable login via Facebook, Google, GitHub.\",\n \"label\": \"Social Login Key\",\n \"name\": \"Social Login Key\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Ldap settings\",\n \"label\": \"LDAP Settings\",\n \"name\": \"LDAP Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Register OAuth Client App\",\n \"label\": \"OAuth Client\",\n \"name\": \"OAuth Client\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for OAuth Provider\",\n \"label\": \"OAuth Provider Settings\",\n \"name\": \"OAuth Provider Settings\",\n \"type\": \"doctype\"\n }\n]", - "title": "Authentication" + "hidden": 0, + "label": "Authentication", + "links": "[\n {\n \"description\": \"Enter keys to enable login via Facebook, Google, GitHub.\",\n \"label\": \"Social Login Key\",\n \"name\": \"Social Login Key\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Ldap settings\",\n \"label\": \"LDAP Settings\",\n \"name\": \"LDAP Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Register OAuth Client App\",\n \"label\": \"OAuth Client\",\n \"name\": \"OAuth Client\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for OAuth Provider\",\n \"label\": \"OAuth Provider Settings\",\n \"name\": \"OAuth Provider Settings\",\n \"type\": \"doctype\"\n }\n]" }, { - "icon": "fa fa-star", - "links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n }\n]", - "title": "Payments" + "hidden": 0, + "label": "Payments", + "links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n }\n]" } ], "category": "Administration", @@ -34,7 +38,7 @@ "idx": 0, "is_standard": 1, "label": "Integrations", - "modified": "2020-03-12 16:30:42.823316", + "modified": "2020-04-01 11:24:40.751651", "modified_by": "Administrator", "module": "Integrations", "name": "Integrations", diff --git a/frappe/website/desk_page/website/website.json b/frappe/website/desk_page/website/website.json index 915a9d2554..9171018523 100644 --- a/frappe/website/desk_page/website/website.json +++ b/frappe/website/desk_page/website/website.json @@ -1,26 +1,29 @@ { "cards": [ { - "icon": "fa fa-cog", - "links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]", - "title": "Setup" + "hidden": 0, + "label": "Setup", + "links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]" }, { - "links": "[\n {\n \"description\": \"Single Post (article).\",\n \"label\": \"Blog Post\",\n \"name\": \"Blog Post\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"A user who posts blogs.\",\n \"label\": \"Blogger\",\n \"name\": \"Blogger\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Categorize blog posts.\",\n \"label\": \"Blog Category\",\n \"name\": \"Blog Category\",\n \"type\": \"doctype\"\n }\n]", - "title": "Blog" + "hidden": 0, + "label": "Blog", + "links": "[\n {\n \"description\": \"Single Post (article).\",\n \"label\": \"Blog Post\",\n \"name\": \"Blog Post\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"A user who posts blogs.\",\n \"label\": \"Blogger\",\n \"name\": \"Blogger\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Categorize blog posts.\",\n \"label\": \"Blog Category\",\n \"name\": \"Blog Category\",\n \"type\": \"doctype\"\n }\n]" }, { - "icon": "fa fa-star", - "links": "[\n {\n \"description\": \"Content web page.\",\n \"label\": \"Web Page\",\n \"name\": \"Web Page\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User editable form on Website.\",\n \"label\": \"Web Form\",\n \"name\": \"Web Form\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Website Sidebar\",\n \"name\": \"Website Sidebar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Embed image slideshows in website pages.\",\n \"label\": \"Website Slideshow\",\n \"name\": \"Website Slideshow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add meta tags to your web pages\",\n \"label\": \"Website Route Meta\",\n \"name\": \"Website Route Meta\",\n \"type\": \"doctype\"\n }\n]", - "title": "Web Site" + "hidden": 0, + "label": "Web Site", + "links": "[\n {\n \"description\": \"Content web page.\",\n \"label\": \"Web Page\",\n \"name\": \"Web Page\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User editable form on Website.\",\n \"label\": \"Web Form\",\n \"name\": \"Web Form\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Website Sidebar\",\n \"name\": \"Website Sidebar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Embed image slideshows in website pages.\",\n \"label\": \"Website Slideshow\",\n \"name\": \"Website Slideshow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add meta tags to your web pages\",\n \"label\": \"Website Route Meta\",\n \"name\": \"Website Route Meta\",\n \"type\": \"doctype\"\n }\n]" }, { - "links": "[\n {\n \"label\": \"Portal Settings\",\n \"name\": \"Portal Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]", - "title": "Portal" + "hidden": 0, + "label": "Portal", + "links": "[\n {\n \"label\": \"Portal Settings\",\n \"name\": \"Portal Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n }\n]" }, { - "links": "[\n {\n \"label\": \"Help Category\",\n \"name\": \"Help Category\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Help Article\",\n \"name\": \"Help Article\",\n \"type\": \"doctype\"\n }\n]", - "title": "Knowledge Base" + "hidden": 0, + "label": "Knowledge Base", + "links": "[\n {\n \"label\": \"Help Category\",\n \"name\": \"Help Category\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Help Article\",\n \"name\": \"Help Article\",\n \"type\": \"doctype\"\n }\n]" } ], "category": "Modules", @@ -34,7 +37,7 @@ "idx": 0, "is_standard": 1, "label": "Website", - "modified": "2020-03-12 16:30:43.092622", + "modified": "2020-04-01 11:24:40.726934", "modified_by": "Administrator", "module": "Website", "name": "Website", @@ -45,30 +48,30 @@ { "color": "", "format": "{} Published", - "is_query_report": 0, + "label": "Blog Post", "link_to": "Blog Post", "stats_filter": "{\"published\":\"1\"}", "type": "DocType" }, { "format": "{} Active", - "is_query_report": 0, + "label": "Blogger", "link_to": "Blogger", "stats_filter": "{\"disabled\": 0}", "type": "DocType" }, { - "is_query_report": 0, + "label": "Web Page", "link_to": "Web Page", "type": "DocType" }, { - "is_query_report": 0, + "label": "Web Form", "link_to": "Web Form", "type": "DocType" }, { - "is_query_report": 0, + "label": "Website Settings", "link_to": "Website Settings", "type": "DocType" } From ea52e5176671a8b698a34b91682f4c2d1b01186a Mon Sep 17 00:00:00 2001 From: marination Date: Wed, 1 Apr 2020 11:55:42 +0530 Subject: [PATCH 141/408] fix: Change 'Errored' to 'Failed' Transactions in Bulk Workflow Status prompt --- frappe/model/workflow.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index b134f2f8dc..c63b4337f0 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -185,7 +185,7 @@ def bulk_workflow_approval(docnames, doctype, action): from collections import defaultdict # dictionaries for logging - errored_transactions = defaultdict(list) + failed_transactions = defaultdict(list) successful_transactions = defaultdict(list) # WARN: message log is cleared @@ -206,7 +206,7 @@ def bulk_workflow_approval(docnames, doctype, action): if e.args: message += " : {0}".format(e.args[0]) message_dict = {"docname": docname, "message": message} - errored_transactions[docname].append(message_dict) + failed_transactions[docname].append(message_dict) frappe.db.rollback() frappe.log_error(frappe.get_traceback(), "Workflow {0} threw an error for {1} {2}".format(action, doctype, docname)) @@ -219,20 +219,20 @@ def bulk_workflow_approval(docnames, doctype, action): message_dict = {"docname": docname, "message": message.get("message")} if message.get("raise_exception", False): - errored_transactions[docname].append(message_dict) + failed_transactions[docname].append(message_dict) else: successful_transactions[docname].append(message_dict) else: successful_transactions[docname].append({"docname": docname, "message": None}) - if errored_transactions and successful_transactions: + if failed_transactions and successful_transactions: indicator = "orange" - elif errored_transactions: + elif failed_transactions: indicator = "red" else: indicator = "green" - print_workflow_log(errored_transactions, _("Errored Transactions"), doctype, indicator) + print_workflow_log(failed_transactions, _("Failed Transactions"), doctype, indicator) print_workflow_log(successful_transactions, _("Successful Transactions"), doctype, indicator) def print_workflow_log(messages, title, doctype, indicator): From b5a9250574f808db067faebf81372834dcf8bd47 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 1 Apr 2020 10:59:11 +0530 Subject: [PATCH 142/408] tests: simplifying test_data_field_options Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/core/doctype/doctype/test_doctype.py | 45 +++++++++++---------- 1 file changed, 23 insertions(+), 22 deletions(-) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 17a56b5336..c766295ee2 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -114,29 +114,30 @@ class TestDocType(unittest.TestCase): self.assertFalse(re.match(pattern, condition)) def test_data_field_options(self): + doctype_name = "Test Data Fields" valid_data_field_options = frappe.model.data_field_options + ("",) - invalid_data_field_options = ("Invalid Option 1", "Invalid Option 2", frappe.utils.random_string(5)) + invalid_data_field_options = ("Invalid Option 1", frappe.utils.random_string(5)) - for options_set in [valid_data_field_options, invalid_data_field_options]: - for field in options_set: - test_doctype = frappe.get_doc({ - "doctype": "DocType", - "name": "Test Data Fields", - "module": "Core", - "custom": 1, - "fields": [{ - "fieldname": "{0}_field".format(field), - "fieldtype": "Data", - "options": field - }] - }) - if options_set == invalid_data_field_options: - # assert that only data options in frappe.model.data_field_options are valid - self.assertRaises(frappe.ValidationError, test_doctype.insert) - else: - test_doctype.insert() - self.assertEqual(test_doctype.name, doctype_name) - test_doctype.delete() + for field_option in (valid_data_field_options + invalid_data_field_options): + test_doctype = frappe.get_doc({ + "doctype": "DocType", + "name": doctype_name, + "module": "Core", + "custom": 1, + "fields": [{ + "fieldname": "{0}_field".format(field_option), + "fieldtype": "Data", + "options": field_option + }] + }) + + if field_option in invalid_data_field_options: + # assert that only data options in frappe.model.data_field_options are valid + self.assertRaises(frappe.ValidationError, test_doctype.insert) + else: + test_doctype.insert() + self.assertEqual(test_doctype.name, doctype_name) + test_doctype.delete() def test_sync_field_order(self): from frappe.modules.import_file import get_file_path @@ -374,4 +375,4 @@ class TestDocType(unittest.TestCase): # delete doctype link_doc.delete() doc.delete() - frappe.db.commit() \ No newline at end of file + frappe.db.commit() From 8f3209c5e9677990e0316d5c7ab42417e03cdf91 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 1 Apr 2020 12:04:10 +0530 Subject: [PATCH 143/408] fix: fix HTMLParser import in unescape_html --- frappe/utils/html_utils.py | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/frappe/utils/html_utils.py b/frappe/utils/html_utils.py index 6e133f16d6..2fbe0cc196 100644 --- a/frappe/utils/html_utils.py +++ b/frappe/utils/html_utils.py @@ -104,13 +104,9 @@ def get_icon_html(icon, small=False): return "".format(icon=icon) def unescape_html(value): - try: - from HTMLParser import HTMLParser - h = HTMLParser() - return h.unescape(value) - except ImportError: - import html - return html.unescape(value) + from six.moves.html_parser import HTMLParser + h = HTMLParser() + return h.unescape(value) # adapted from https://raw.githubusercontent.com/html5lib/html5lib-python/4aa79f113e7486c7ec5d15a6e1777bfe546d3259/html5lib/sanitizer.py acceptable_elements = [ From fe54fb67c975d8c88645d8b34dc63263184e940a Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 1 Apr 2020 12:34:18 +0530 Subject: [PATCH 144/408] refactor: generate_report_result method - to fix codacy complexity issue --- frappe/core/doctype/report/report.py | 14 +++++++++- frappe/core/utils.py | 7 +++-- frappe/desk/query_report.py | 42 +++++++++------------------- 3 files changed, 30 insertions(+), 33 deletions(-) diff --git a/frappe/core/doctype/report/report.py b/frappe/core/doctype/report/report.py index 2d49915f59..967b28b8b2 100644 --- a/frappe/core/doctype/report/report.py +++ b/frappe/core/doctype/report/report.py @@ -6,7 +6,7 @@ import frappe import json, datetime from frappe import _, scrub import frappe.desk.query_report -from frappe.utils import cint +from frappe.utils import cint, cstr from frappe.model.document import Document from frappe.modules.export_file import export_to_files from frappe.modules import make_boilerplate @@ -92,6 +92,18 @@ class Report(Document): make_boilerplate("controller.py", self, {"name": self.name}) make_boilerplate("controller.js", self, {"name": self.name}) + def execute_query_report(self, filters): + if not self.query: + frappe.throw(_("Must specify a Query to run"), title=_('Report Document Error')) + + if not self.query.lower().startswith("select"): + frappe.throw(_("Query must be a SELECT"), title=_('Report Document Error')) + + result = [list(t) for t in frappe.db.sql(self.query, filters)] + columns = [cstr(c[0]) for c in frappe.db.get_description()] + + return [columns, result] + def execute_script_report(self, filters): # save the timestamp to automatically set to prepared threshold = 30 diff --git a/frappe/core/utils.py b/frappe/core/utils.py index abab9e6160..55cfbc34d7 100644 --- a/frappe/core/utils.py +++ b/frappe/core/utils.py @@ -69,16 +69,17 @@ def find_all(list_of_dict, match_function): return found def ljust_list(_list, length, fill_word=None): - '''Similar to ljust but for list + """ + Similar to ljust but for list. Usage: $ ljust_list([1, 2, 3], 5) > [1, 2, 3, None, None] - ''' + """ # make a copy to avoid mutation of passed list _list = list(_list) fill_length = length - len(_list) if fill_length > 0: _list.extend([fill_word] * fill_length) - return _list \ No newline at end of file + return _list diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 994f816c8a..aaf859e7fd 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -8,7 +8,7 @@ import os, json from frappe import _ from frappe.modules import scrub, get_module_path -from frappe.utils import flt, cint, get_html_format, cstr, get_url_to_form +from frappe.utils import flt, cint, get_html_format, get_url_to_form from frappe.model.utils import render_include from frappe.translate import send_translations import frappe.desk.reportview @@ -16,6 +16,7 @@ from frappe.permissions import get_role_permissions from six import string_types, iteritems from datetime import timedelta from frappe.utils import gzip_decompress +from frappe.core.utils import ljust_list def get_report_doc(report_name): doc = frappe.get_doc("Report", report_name) @@ -43,44 +44,27 @@ def get_report_doc(report_name): def generate_report_result(report, filters=None, user=None, custom_columns=None): - status = None - if not user: - user = frappe.session.user - if not filters: - filters = [] + user = user or frappe.session.user + filters = filters or [] if filters and isinstance(filters, string_types): filters = json.loads(filters) - columns, result, message, chart, report_summary, skip_total_row = [], [], None, None, None, 0 + + res = [] + if report.report_type == "Query Report": - if not report.query: - status = "error" - frappe.msgprint(_("Must specify a Query to run"), raise_exception=True) - - if not report.query.lower().startswith("select"): - status = "error" - frappe.msgprint(_("Query must be a SELECT"), raise_exception=True) - - result = [list(t) for t in frappe.db.sql(report.query, filters)] - columns = [cstr(c[0]) for c in frappe.db.get_description()] + res = report.execute_query_report(filters) elif report.report_type == 'Script Report': res = report.execute_script_report(filters) - columns, result = res[0], res[1] - if len(res) > 2: - message = res[2] - if len(res) > 3: - chart = res[3] - if len(res) > 4: - report_summary = res[4] - if len(res) > 5: - skip_total_row = cint(res[5]) + columns, result, message, chart, report_summary, skip_total_row = \ + ljust_list(res, 6) if report.custom_columns: columns = json.loads(report.custom_columns) result = add_data_to_custom_columns(columns, result) - elif custom_columns: + if custom_columns: result = add_data_to_custom_columns(custom_columns, result) for custom_column in custom_columns: @@ -98,8 +82,8 @@ def generate_report_result(report, filters=None, user=None, custom_columns=None) "message": message, "chart": chart, "report_summary": report_summary, - "skip_total_row": skip_total_row, - "status": status, + "skip_total_row": skip_total_row or 0, + "status": None, "execution_time": frappe.cache().hget('report_execution_time', report.name) or 0 } From a8af9e91bc62c488fc4413c4c119548264763b95 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 1 Apr 2020 11:05:40 +0530 Subject: [PATCH 145/408] style: Fix indentation --- frappe/core/doctype/doctype/test_doctype.py | 16 ++++++++-------- frappe/public/js/frappe/form/controls/data.js | 2 +- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index c766295ee2..fe9f88b9b3 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -120,14 +120,14 @@ class TestDocType(unittest.TestCase): for field_option in (valid_data_field_options + invalid_data_field_options): test_doctype = frappe.get_doc({ - "doctype": "DocType", - "name": doctype_name, - "module": "Core", - "custom": 1, - "fields": [{ - "fieldname": "{0}_field".format(field_option), - "fieldtype": "Data", - "options": field_option + "doctype": "DocType", + "name": doctype_name, + "module": "Core", + "custom": 1, + "fields": [{ + "fieldname": "{0}_field".format(field_option), + "fieldtype": "Data", + "options": field_option }] }) diff --git a/frappe/public/js/frappe/form/controls/data.js b/frappe/public/js/frappe/form/controls/data.js index 512c6fbcb0..a7f0050d65 100644 --- a/frappe/public/js/frappe/form/controls/data.js +++ b/frappe/public/js/frappe/form/controls/data.js @@ -94,7 +94,7 @@ frappe.ui.form.ControlData = frappe.ui.form.ControlInput.extend({ return v; } if(this.df.options == 'Phone') { - this.df.invalid = !validate_phone(v) + this.df.invalid = !validate_phone(v); return v; } else if(this.df.options == 'Email') { var email_list = frappe.utils.split_emails(v); From bf8630b6bdba1ccc40673b3a30ea3d84709ae85d Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 1 Apr 2020 13:27:22 +0530 Subject: [PATCH 146/408] fix(Report): fix report group by field if not in format tabdoctype.fieldname --- frappe/public/js/frappe/ui/group_by/group_by.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) 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 0214339ee7..6936f25c18 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -93,6 +93,10 @@ frappe.ui.GroupBy = class { apply_settings(settings) { + if (!settings.group_by.startsWith('`tab')) { + settings.group_by = '`tab' + this.doctype + '`.`' + settings.group_by + '`'; + } + // Extract fieldname from `tabdoctype`.`fieldname` let group_by_fieldname = settings.group_by.split('.')[1].replace(/`/g, ''); @@ -160,7 +164,7 @@ frappe.ui.GroupBy = class { if (this.aggregate_function === 'count') { aggregate_column = 'count(`tab'+ this.doctype + '`.`name`)'; } else { - aggregate_column = + aggregate_column = `${this.aggregate_function}(\`tab${this.aggregate_on_doctype}\`.\`${this.aggregate_on}\`)`; aggregate_on_field = '`tab' + this.aggregate_on_doctype + '`.`' + this.aggregate_on + '`'; } From 27074e95da97c61d38beca35e445a2df972b0f5d Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Wed, 1 Apr 2020 18:56:30 +0530 Subject: [PATCH 147/408] improve error message for logged out user --- frappe/www/desk.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/www/desk.py b/frappe/www/desk.py index 689bf725f0..78a7512b7e 100644 --- a/frappe/www/desk.py +++ b/frappe/www/desk.py @@ -12,8 +12,10 @@ from frappe import _ import frappe.sessions def get_context(context): - if (frappe.session.user == "Guest" or - frappe.db.get_value("User", frappe.session.user, "user_type")=="Website User"): + if (frappe.session.user == "Guest"): + frappe.throw(_("Log in to access this page."), frappe.PermissionError) + + elif (frappe.db.get_value("User", frappe.session.user, "user_type")=="Website User")): frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError) hooks = frappe.get_hooks() From abbd92032089d9d14bac83ae731e8de9ca1d15d6 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Wed, 1 Apr 2020 20:37:00 +0530 Subject: [PATCH 148/408] add comments for separation logic --- frappe/www/desk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/www/desk.py b/frappe/www/desk.py index 78a7512b7e..3bd7b68032 100644 --- a/frappe/www/desk.py +++ b/frappe/www/desk.py @@ -12,10 +12,10 @@ from frappe import _ import frappe.sessions def get_context(context): - if (frappe.session.user == "Guest"): + if (frappe.session.user == "Guest"): #show message if user is logged out frappe.throw(_("Log in to access this page."), frappe.PermissionError) - elif (frappe.db.get_value("User", frappe.session.user, "user_type")=="Website User")): + elif (frappe.db.get_value("User", frappe.session.user, "user_type")=="Website User")): #show message if user is website user frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError) hooks = frappe.get_hooks() From 14135054227e406706772a49b95938657e0fa083 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Wed, 1 Apr 2020 20:57:24 +0530 Subject: [PATCH 149/408] fix syntax --- frappe/www/desk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/desk.py b/frappe/www/desk.py index 3bd7b68032..b89b688f7a 100644 --- a/frappe/www/desk.py +++ b/frappe/www/desk.py @@ -15,7 +15,7 @@ def get_context(context): if (frappe.session.user == "Guest"): #show message if user is logged out frappe.throw(_("Log in to access this page."), frappe.PermissionError) - elif (frappe.db.get_value("User", frappe.session.user, "user_type")=="Website User")): #show message if user is website user + elif (frappe.db.get_value("User", frappe.session.user, "user_type")=="Website User"): #show message if user is website user frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError) hooks = frappe.get_hooks() From d73f8d85dbb714eddb3ba04b94af734d1c32d2bb Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Wed, 1 Apr 2020 22:18:23 +0530 Subject: [PATCH 150/408] style: Fix formatting & remove unnecessary comment --- frappe/www/desk.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/www/desk.py b/frappe/www/desk.py index b89b688f7a..6cb7c8a077 100644 --- a/frappe/www/desk.py +++ b/frappe/www/desk.py @@ -12,10 +12,9 @@ from frappe import _ import frappe.sessions def get_context(context): - if (frappe.session.user == "Guest"): #show message if user is logged out + if frappe.session.user == "Guest": frappe.throw(_("Log in to access this page."), frappe.PermissionError) - - elif (frappe.db.get_value("User", frappe.session.user, "user_type")=="Website User"): #show message if user is website user + elif frappe.db.get_value("User", frappe.session.user, "user_type") == "Website User": frappe.throw(_("You are not permitted to access this page."), frappe.PermissionError) hooks = frappe.get_hooks() From f79a43d5f30bb2ec0f5894465dce4def5952168e Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 1 Apr 2020 00:09:40 +0530 Subject: [PATCH 151/408] feat: save dashboard chart config for user --- frappe/core/page/dashboard/dashboard.js | 36 +++++++------ .../doctype/dashboard_settings/__init__.py | 0 .../dashboard_settings/dashboard_settings.js | 8 +++ .../dashboard_settings.json | 51 +++++++++++++++++++ .../dashboard_settings/dashboard_settings.py | 39 ++++++++++++++ frappe/hooks.py | 1 + .../public/js/frappe/utils/dashboard_utils.js | 21 ++++++++ .../public/js/frappe/views/desktop/desktop.js | 40 ++++++++++----- .../public/js/frappe/widgets/chart_widget.js | 49 ++++++++++++------ 9 files changed, 202 insertions(+), 43 deletions(-) create mode 100644 frappe/desk/doctype/dashboard_settings/__init__.py create mode 100644 frappe/desk/doctype/dashboard_settings/dashboard_settings.js create mode 100644 frappe/desk/doctype/dashboard_settings/dashboard_settings.json create mode 100644 frappe/desk/doctype/dashboard_settings/dashboard_settings.py diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 8705804014..88bfba9e84 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -80,23 +80,27 @@ class Dashboard { if (!charts.length) { frappe.msgprint(__('No Permitted Charts on this Dashboard'), __('No Permitted Charts')) } - this.charts = charts - .map(chart => { - return { - chart_name: chart.chart, - label: chart.chart, - ...chart - } - }); - this.chart_group = new frappe.widget.WidgetGroup({ - title: null, - container: this.container, - type: "chart", - columns: 2, - allow_sorting: false, - widgets: this.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 + } + }); + this.chart_group = new frappe.widget.WidgetGroup({ + title: null, + container: this.container, + type: "chart", + columns: 2, + allow_sorting: false, + widgets: this.charts, + }); + }) }); } diff --git a/frappe/desk/doctype/dashboard_settings/__init__.py b/frappe/desk/doctype/dashboard_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.js b/frappe/desk/doctype/dashboard_settings/dashboard_settings.js new file mode 100644 index 0000000000..8e7966366d --- /dev/null +++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Dashboard Settings', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.json b/frappe/desk/doctype/dashboard_settings/dashboard_settings.json new file mode 100644 index 0000000000..e1eb75db47 --- /dev/null +++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.json @@ -0,0 +1,51 @@ +{ + "actions": [], + "autoname": "Prompt", + "creation": "2020-03-31 19:41:45.785014", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "user", + "chart_config" + ], + "fields": [ + { + "fieldname": "user", + "fieldtype": "Link", + "label": "User", + "options": "User", + "read_only": 1 + }, + { + "fieldname": "chart_config", + "fieldtype": "Code", + "label": "Chart Configuration", + "options": "JSON", + "read_only": 1 + } + ], + "in_create": 1, + "links": [], + "modified": "2020-04-01 00:07:26.489561", + "modified_by": "Administrator", + "module": "Desk", + "name": "Dashboard Settings", + "owner": "Administrator", + "permissions": [ + { + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 + } + ], + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py new file mode 100644 index 0000000000..ce7f2979c7 --- /dev/null +++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document +import frappe +import json + +class DashboardSettings(Document): + pass + + +@frappe.whitelist() +def create_dashboard_settings(user): + if not frappe.db.exists("Dashboard Settings", user): + doc = frappe.new_doc('Dashboard Settings') + doc.name = user + doc.insert(ignore_permissions=True) + frappe.db.commit() + return doc + +def get_permission_query_conditions(user): + if not user: user = frappe.session.user + + return '''(`tabDashboard Settings`.name = '{user}')'''.format(user=user) + +@frappe.whitelist() +def save_chart_config(config, chart_name): + config = frappe.parse_json(config) + doc = frappe.get_doc('Dashboard Settings', frappe.session.user) + chart_config = frappe.parse_json(doc.chart_config) or {} + + if not chart_name in chart_config: + chart_config[chart_name] = {} + + chart_config[chart_name].update(config) + frappe.db.set_value('Dashboard Settings', frappe.session.user, 'chart_config', json.dumps(chart_config)) \ No newline at end of file diff --git a/frappe/hooks.py b/frappe/hooks.py index 733cec7a08..4f65303be9 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -86,6 +86,7 @@ permission_query_conditions = { "Event": "frappe.desk.doctype.event.event.get_permission_query_conditions", "ToDo": "frappe.desk.doctype.todo.todo.get_permission_query_conditions", "User": "frappe.core.doctype.user.user.get_permission_query_conditions", + "Dashboard Settings": "frappe.desk.doctype.dashboard_settings.dashboard_settings.get_permission_query_conditions", "Notification Log": "frappe.desk.doctype.notification_log.notification_log.get_permission_query_conditions", "Dashboard Chart": "frappe.desk.doctype.dashboard_chart.dashboard_chart.get_permission_query_conditions", "Notification Settings": "frappe.desk.doctype.notification_settings.notification_settings.get_permission_query_conditions", diff --git a/frappe/public/js/frappe/utils/dashboard_utils.js b/frappe/public/js/frappe/utils/dashboard_utils.js index 7e64f5c143..66f3d5e21f 100644 --- a/frappe/public/js/frappe/utils/dashboard_utils.js +++ b/frappe/public/js/frappe/utils/dashboard_utils.js @@ -54,5 +54,26 @@ frappe.dashboard_utils = { } else { return Promise.resolve(); } + }, + + get_dashboard_settings() { + return frappe.model.with_doc('Dashboard Settings', frappe.session.user).then(settings => { + if (!settings) { + return this.create_dashboard_settings().then(settings => { + return settings; + }); + } else { + return settings; + } + }); + }, + + create_dashboard_settings() { + return frappe.xcall( + 'frappe.desk.doctype.dashboard_settings.dashboard_settings.create_dashboard_settings', + {user: frappe.session.user} + ).then(settings => { + return settings; + }); } }; \ No newline at end of file diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 54a25c3771..3a95ec1d90 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -165,11 +165,18 @@ class DesktopPage { this.allow_customization = this.data.allow_customization || false; - !this.sections["onboarding"] && - this.data.charts.items.length && - this.make_charts(); - this.data.shortcuts.items.length && this.make_shortcuts(); - this.data.cards.items.length && this.make_cards(); + let create_shortcuts_and_cards = () => { + this.data.shortcuts.items.length && this.make_shortcuts(); + this.data.cards.items.length && this.make_cards(); + } + + if (!this.sections["onboarding"] && this.data.charts.items.length) { + this.make_charts().then(() => { + create_shortcuts_and_cards(); + }); + } else { + create_shortcuts_and_cards(); + } }); } @@ -224,13 +231,22 @@ class DesktopPage { } make_charts() { - this.sections["charts"] = new frappe.widget.WidgetGroup({ - title: this.data.charts.label || `${this.page_name} Dashboard`, - container: this.page, - type: "chart", - columns: 1, - allow_sorting: false, - widgets: this.data.charts.items + return frappe.dashboard_utils.get_dashboard_settings().then(settings => { + let chart_config = settings.chart_config? JSON.parse(settings.chart_config): {}; + if (this.data.charts.items) { + this.data.charts.items.map(chart => { + chart.chart_settings = chart_config[chart.chart_name] || {} + }); + } + + this.sections["charts"] = new frappe.widget.WidgetGroup({ + title: this.data.charts.label || `${this.page_name} Dashboard`, + container: this.page, + type: "chart", + columns: 1, + allow_sorting: false, + widgets: this.data.charts.items + }); }); } diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 3388890776..4fc00ef9c9 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -62,6 +62,9 @@ export default class ChartWidget extends Widget { make_chart() { this.get_settings().then(() => { + if (!this.chart_settings) { + this.chart_settings = {}; + } this.setup_container(); this.prepare_chart_object(); this.action_area.empty(); @@ -89,7 +92,7 @@ export default class ChartWidget extends Widget { render_time_series_filters() { let filters = [ { - label: this.chart_doc.timespan, + label: this.chart_settings.timespan || this.chart_doc.timespan, options: [ "Select Date Range", "Last Year", @@ -116,13 +119,16 @@ export default class ChartWidget extends Widget { this.fetch_and_update_chart(); } + this.save_chart_config_for_user({'timespan': this.selected_timespan}) } }, { - label: this.chart_doc.time_interval, + label: this.chart_settings.time_interval || this.chart_doc.time_interval, options: ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily"], action: selected_item => { this.selected_time_interval = selected_item; + + this.save_chart_config_for_user({'time_interval': this.selected_time_interval}) this.fetch_and_update_chart(); } } @@ -138,10 +144,10 @@ export default class ChartWidget extends Widget { fetch_and_update_chart() { this.args = { - timespan: this.selected_timespan, - time_interval: this.selected_time_interval, - from_date: this.selected_from_date, - to_date: this.selected_to_date + timespan: this.selected_timespan || this.chart_settings.timespan, + time_interval: this.selected_time_interval || this.chart_settings.time_interval, + from_date: this.selected_from_date || this.chart_settings.from_date, + to_date: this.selected_to_date || this.chart_settings.to_date }; this.fetch(this.filters, true, this.args).then(data => { @@ -176,16 +182,18 @@ export default class ChartWidget extends Widget { fieldname: "from_date", placeholder: "Date Range", input_class: "input-xs", + default: [this.chart_settings.from_date, this.chart_settings.to_date], reqd: 1, change: () => { let selected_date_range = this.date_range_field.get_value(); this.selected_from_date = selected_date_range[0]; this.selected_to_date = selected_date_range[1]; - if ( - selected_date_range && - selected_date_range.length == 2 - ) { + if (selected_date_range && selected_date_range.length == 2) { + this.save_chart_config_for_user({ + 'from_date': this.selected_from_date, + 'to_date': this.selected_to_date, + }); this.fetch_and_update_chart(); } } @@ -334,6 +342,7 @@ export default class ChartWidget extends Widget { } else { me.filters = values; } + me.save_chart_config_for_user({'filters': me.filters}); me.fetch_and_update_chart(); } }, @@ -350,6 +359,15 @@ export default class ChartWidget extends Widget { dialog.set_values(this.filters); } + save_chart_config_for_user(config) { + Object.assign(this.chart_settings, config); + frappe.xcall('frappe.desk.doctype.dashboard_settings.dashboard_settings.save_chart_config', + { + 'config': this.chart_settings, + 'chart_name': this.chart_doc.chart_name + }); + } + create_filter_group_and_add_filters(parent) { this.filter_group = new frappe.ui.FilterGroup({ parent: parent, @@ -406,10 +424,10 @@ export default class ChartWidget extends Widget { filters: filters, refresh: refresh ? 1 : 0, time_interval: - args && args.time_interval ? args.time_interval : null, - timespan: args && args.timespan ? args.timespan : null, - from_date: args && args.from_date ? args.from_date : null, - to_date: args && args.to_date ? args.to_date : null + args && args.time_interval? args.time_interval: null, + timespan: args && args.timespan? args.timespan: null, + from_date: args && args.from_date? args.from_date: null, + to_date: args && args.to_date? args.to_date: null }; } return frappe.xcall(method, args); @@ -481,8 +499,9 @@ export default class ChartWidget extends Widget { } prepare_chart_object() { + let saved_filters = this.chart_settings.filters || null; this.filters = - this.filters || JSON.parse(this.chart_doc.filters_json || "[]"); + saved_filters || this.filters || JSON.parse(this.chart_doc.filters_json || "[]"); } get_settings() { From a50492eae410a540d47de19c8c1ef52fcd3f8fdf Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 2 Apr 2020 11:35:55 +0530 Subject: [PATCH 152/408] feat: add button to reset default chart config --- .../dashboard_settings/dashboard_settings.py | 12 +++++++---- .../public/js/frappe/widgets/chart_widget.js | 20 +++++++++++++++++-- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py index ce7f2979c7..4697d897fc 100644 --- a/frappe/desk/doctype/dashboard_settings/dashboard_settings.py +++ b/frappe/desk/doctype/dashboard_settings/dashboard_settings.py @@ -27,13 +27,17 @@ def get_permission_query_conditions(user): return '''(`tabDashboard Settings`.name = '{user}')'''.format(user=user) @frappe.whitelist() -def save_chart_config(config, chart_name): - config = frappe.parse_json(config) +def save_chart_config(reset, config, chart_name): + reset = frappe.parse_json(reset) doc = frappe.get_doc('Dashboard Settings', frappe.session.user) chart_config = frappe.parse_json(doc.chart_config) or {} - if not chart_name in chart_config: + if reset: chart_config[chart_name] = {} + else: + config = frappe.parse_json(config) + if not chart_name in chart_config: + chart_config[chart_name] = {} + chart_config[chart_name].update(config) - chart_config[chart_name].update(config) frappe.db.set_value('Dashboard Settings', frappe.session.user, 'chart_config', json.dumps(chart_config)) \ No newline at end of file diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 4fc00ef9c9..0799b3a072 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -243,7 +243,7 @@ export default class ChartWidget extends Widget { } }, { - label: __("Edit..."), + label: __("Edit"), action: "action-edit", handler: () => { frappe.set_route( @@ -252,6 +252,15 @@ export default class ChartWidget extends Widget { this.chart_doc.name ); } + }, + { + label: __("Reset Chart"), + action: "action-list", + handler: () => { + this.reset_chart(); + delete this.dashboard_chart; + this.make_chart(); + } } ]; @@ -359,10 +368,17 @@ export default class ChartWidget extends Widget { dialog.set_values(this.filters); } - save_chart_config_for_user(config) { + reset_chart() { + this.save_chart_config_for_user(null, 1); + this.chart_settings = {}; + this.filters = null; + } + + save_chart_config_for_user(config, reset=0) { Object.assign(this.chart_settings, config); frappe.xcall('frappe.desk.doctype.dashboard_settings.dashboard_settings.save_chart_config', { + 'reset': reset, 'config': this.chart_settings, 'chart_name': this.chart_doc.chart_name }); From 40d8947ddf038f3d3cdac8b21783b55cdd9dbb7b Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 2 Apr 2020 12:04:37 +0530 Subject: [PATCH 153/408] fix: set from date and do date as null when timespan is select date range --- frappe/public/js/frappe/widgets/chart_widget.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 0799b3a072..686c7d1e3e 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -117,9 +117,14 @@ export default class ChartWidget extends Widget { this.head.css('flex-direction', "row"); } + this.save_chart_config_for_user({ + 'timespan': this.selected_timespan, + 'from_date': null, + 'to_date': null + + }); this.fetch_and_update_chart(); } - this.save_chart_config_for_user({'timespan': this.selected_timespan}) } }, { @@ -191,6 +196,7 @@ export default class ChartWidget extends Widget { if (selected_date_range && selected_date_range.length == 2) { this.save_chart_config_for_user({ + 'timespan': this.selected_timespan, 'from_date': this.selected_from_date, 'to_date': this.selected_to_date, }); From ae8dd80dc0dd36fb98845c06a78690d73114442f Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 2 Apr 2020 12:14:31 +0530 Subject: [PATCH 154/408] fix: code formatting --- frappe/public/js/frappe/views/desktop/desktop.js | 4 ++-- frappe/public/js/frappe/widgets/chart_widget.js | 6 ++---- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 3a95ec1d90..d0993e2e8c 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -168,7 +168,7 @@ class DesktopPage { let create_shortcuts_and_cards = () => { this.data.shortcuts.items.length && this.make_shortcuts(); this.data.cards.items.length && this.make_cards(); - } + }; if (!this.sections["onboarding"] && this.data.charts.items.length) { this.make_charts().then(() => { @@ -235,7 +235,7 @@ class DesktopPage { let chart_config = settings.chart_config? JSON.parse(settings.chart_config): {}; if (this.data.charts.items) { this.data.charts.items.map(chart => { - chart.chart_settings = chart_config[chart.chart_name] || {} + chart.chart_settings = chart_config[chart.chart_name] || {}; }); } diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 686c7d1e3e..174f0ae666 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -132,8 +132,7 @@ export default class ChartWidget extends Widget { options: ["Yearly", "Quarterly", "Monthly", "Weekly", "Daily"], action: selected_item => { this.selected_time_interval = selected_item; - - this.save_chart_config_for_user({'time_interval': this.selected_time_interval}) + this.save_chart_config_for_user({'time_interval': this.selected_time_interval}); this.fetch_and_update_chart(); } } @@ -382,8 +381,7 @@ export default class ChartWidget extends Widget { save_chart_config_for_user(config, reset=0) { Object.assign(this.chart_settings, config); - frappe.xcall('frappe.desk.doctype.dashboard_settings.dashboard_settings.save_chart_config', - { + frappe.xcall('frappe.desk.doctype.dashboard_settings.dashboard_settings.save_chart_config', { 'reset': reset, 'config': this.chart_settings, 'chart_name': this.chart_doc.chart_name From 0bd0188addf0c10d109f83bfa83a78f01fe6449c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 2 Apr 2020 13:19:43 +0530 Subject: [PATCH 155/408] fix: new_widget shows if max count not exceeded --- .../public/js/frappe/widgets/widget_group.js | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 7acb9b63de..41ea7fae71 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -73,6 +73,8 @@ export default class WidgetGroup { this.widgets_list.push(widget_object); this.widgets_dict[widget.name] = widget_object; + + return widget_object } customize() { @@ -81,29 +83,43 @@ export default class WidgetGroup { wid.customize(this.options); }) + this.options.allow_create && this.setup_new_widget(); + this.options.allow_sorting && this.setup_sortable(); + } + + setup_new_widget() { const max = this.options ? this.options.max_widget_count || Number.POSITIVE_INFINITY : Number.POSITIVE_INFINITY; - if (this.options.allow_create && this.widgets_list.length < max) { + if (this.widgets_list.length < max) { this.new_widget = new NewWidget({ container: this.body, type: this.type, on_create: (config) => { + // Remove new widget this.new_widget.delete(); - this.add_widget(config); - this.customize(); + delete this.new_widget; + + // Add new widget and customize it + let wid = this.add_widget(config); + wid.customize(this.options); + + // Put back the new widget if required + if (this.widgets_list.length < max) { + this.setup_new_widget(); + } } }) } - - this.options.allow_sorting && this.setup_sortable(); } on_delete(name) { this.widgets_list = this.widgets_list.filter(wid => name != wid.name); delete this.widgets_dict[name]; this.update_widget_order(); + + if (!this.new_widget) this.setup_new_widget(); } update_widget_order() { From 277de227e5e791cb2b79d1a205ef84f81e55d6ac Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Thu, 2 Apr 2020 20:49:13 +0530 Subject: [PATCH 156/408] chore: Add CODEOWNERS file - To automate reviewer assignment --- .github/CODEOWNERS | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS new file mode 100644 index 0000000000..d0df8e6210 --- /dev/null +++ b/.github/CODEOWNERS @@ -0,0 +1,30 @@ +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, +# @global-owner1 and @global-owner2 will be requested for +# review when someone opens a pull request. +* @surajshetty3416, @netchampfaris + +# Website +website/ @scmmishra +templates/ @scmmishra +www/ @scmmishra + +integrations/ @Mangesh-Khairnar + +patches/ @surajshetty3416 @sahil28297 + +dashboard/ @prssanna + +email/ @Thunderbottom + +event_streaming/ @ruchamahabal + +data_import* @netchampfaris + +core/ @surajshetty3416 + +requirements.txt @gavindsouza + +chat/ @god \ No newline at end of file From 12c16ff518dc03bcebf325c1cab52d9babc18ef4 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 3 Apr 2020 00:51:48 +0530 Subject: [PATCH 157/408] fix: Variable undefined issue in timeline --- frappe/public/js/frappe/form/footer/timeline.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/form/footer/timeline.js b/frappe/public/js/frappe/form/footer/timeline.js index d27c65548d..beec168dfd 100644 --- a/frappe/public/js/frappe/form/footer/timeline.js +++ b/frappe/public/js/frappe/form/footer/timeline.js @@ -561,10 +561,9 @@ frappe.ui.form.Timeline = class Timeline { } let updater_reference_link = null; - - if (!$.isEmptyObject(data.updater_reference)) { + let updater_reference = data.updater_reference; + if (!$.isEmptyObject(updater_reference)) { let label = updater_reference.label || __('via {0}', [updater_reference.doctype]); - let updater_reference = data.updater_reference; updater_reference_link = frappe.utils.get_form_link( updater_reference.doctype, updater_reference.docname, From a16159b67fd18951174440f85cbc30ded9332d05 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 3 Apr 2020 08:33:42 +0530 Subject: [PATCH 158/408] chore: Move CODEOWNERS file to root folder --- .github/CODEOWNERS | 30 ------------------------------ CODEOWNERS | 17 +++++++++++++++++ 2 files changed, 17 insertions(+), 30 deletions(-) delete mode 100644 .github/CODEOWNERS create mode 100644 CODEOWNERS diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS deleted file mode 100644 index d0df8e6210..0000000000 --- a/.github/CODEOWNERS +++ /dev/null @@ -1,30 +0,0 @@ -# Each line is a file pattern followed by one or more owners. - -# These owners will be the default owners for everything in -# the repo. Unless a later match takes precedence, -# @global-owner1 and @global-owner2 will be requested for -# review when someone opens a pull request. -* @surajshetty3416, @netchampfaris - -# Website -website/ @scmmishra -templates/ @scmmishra -www/ @scmmishra - -integrations/ @Mangesh-Khairnar - -patches/ @surajshetty3416 @sahil28297 - -dashboard/ @prssanna - -email/ @Thunderbottom - -event_streaming/ @ruchamahabal - -data_import* @netchampfaris - -core/ @surajshetty3416 - -requirements.txt @gavindsouza - -chat/ @god \ No newline at end of file diff --git a/CODEOWNERS b/CODEOWNERS new file mode 100644 index 0000000000..2ff8752871 --- /dev/null +++ b/CODEOWNERS @@ -0,0 +1,17 @@ +# Each line is a file pattern followed by one or more owners. + +# These owners will be the default owners for everything in +# the repo. Unless a later match takes precedence, + +* @surajshetty3416, @netchampfaris +website/ @scmmishra +templates/ @scmmishra +www/ @scmmishra +integrations/ @Mangesh-Khairnar +patches/ @surajshetty3416 @sahil28297 +dashboard/ @prssanna +email/ @Thunderbottom +event_streaming/ @ruchamahabal +data_import* @netchampfaris +core/ @surajshetty3416 +requirements.txt @gavindsouza \ No newline at end of file From 5a4251bce83d508dc6ff7feb9b5d096d92cd4fd6 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Fri, 3 Apr 2020 09:54:34 +0530 Subject: [PATCH 159/408] chore: add test to check null result Signed-off-by: Chinmay D. Pai --- frappe/tests/test_global_search.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frappe/tests/test_global_search.py b/frappe/tests/test_global_search.py index de1ae26845..5cffbaaf64 100644 --- a/frappe/tests/test_global_search.py +++ b/frappe/tests/test_global_search.py @@ -191,8 +191,6 @@ class TestGlobalSearch(unittest.TestCase): frappe.db.commit() results = global_search.web_search('unsubscribe') self.assertTrue('Unsubscribe' in results[0].content) - results = global_search.web_search(text='open source', + results = global_search.web_search(text='unsubscribe', scope="manufacturing\" UNION ALL SELECT 1,2,3,4,doctype from __global_search") self.assertTrue(results == []) - results = global_search.web_search('open source') - self.assertTrue('Open Source' in results[0].content) From 1f3f8b924606cfaa0db3c2b48ac9bf961c620b1c Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Fri, 3 Apr 2020 09:57:11 +0530 Subject: [PATCH 160/408] chore: fix codacy issues Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/user/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index de4dc0c272..b17548d994 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -131,7 +131,7 @@ frappe.ui.form.on('User', { user: frm.doc.email, password: values.new_password, logout: values.logout_sessions - }).then(() => done()); + }); } }); d.show(); From 379ecf5952c34e12bf37b32c39a7eb0ed8609661 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 2 Apr 2020 13:04:37 +0530 Subject: [PATCH 161/408] fix: don't send energy points summary if email notifications for energy points are disabled --- frappe/desk/doctype/notification_log/notification_log.py | 8 ++------ .../notification_settings/notification_settings.py | 3 +++ .../social/doctype/energy_point_log/energy_point_log.py | 6 ++++++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 398a3de351..17eb6371b1 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -6,14 +6,13 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.model.document import Document -from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled, - is_email_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value) +from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value) class NotificationLog(Document): def after_insert(self): frappe.publish_realtime('notification', after_commit=True, user=self.for_user) set_notifications_as_unseen(self.for_user) - if is_email_notifications_enabled(self.for_user): + if is_email_notifications_enabled_for_type(self.for_user, self.type): send_notification_email(self) @@ -73,9 +72,6 @@ def make_notification_logs(doc, users): _doc.insert(ignore_permissions=True) def send_notification_email(doc): - is_type_enabled = is_email_notifications_enabled_for_type(doc.for_user, doc.type) - if not is_type_enabled: - return if doc.type == 'Energy Point' and doc.email_content is None: return diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 295b4c8afd..233d15e3ce 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -25,6 +25,9 @@ def is_email_notifications_enabled(user): return enabled def is_email_notifications_enabled_for_type(user, notification_type): + if not is_email_notifications_enabled(user): + return False + fieldname = 'enable_email_' + frappe.scrub(notification_type) enabled = frappe.db.get_value('Notification Settings', user, fieldname) if enabled is None: diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index 8353c3aa5f..31de1b8a60 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -9,6 +9,8 @@ import json from frappe.model.document import Document 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 is_email_notifications_enabled_for_type, is_email_notifications_enabled from frappe.utils import cint, get_fullname, getdate, get_link_to_form class EnergyPointLog(Document): @@ -300,6 +302,10 @@ def send_summary(timespan): if not is_energy_point_enabled(): return + + if not is_email_notifications_enabled_for_type(frappe.session.user, 'Energy Point'): + return + from_date = frappe.utils.add_to_date(None, weeks=-1) if timespan == 'Monthly': from_date = frappe.utils.add_to_date(None, months=-1) From 0e087cf1b458a43024cc2a042781a58f59f7f3fd Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 3 Apr 2020 18:06:32 +0530 Subject: [PATCH 162/408] fix: handle case in which email account does not exists --- frappe/email/doctype/newsletter/newsletter.js | 5 ++--- frappe/email/doctype/newsletter/newsletter.py | 4 ++-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.js b/frappe/email/doctype/newsletter/newsletter.js index ef4b88dbdf..0f1e8dc57c 100644 --- a/frappe/email/doctype/newsletter/newsletter.js +++ b/frappe/email/doctype/newsletter/newsletter.js @@ -9,8 +9,7 @@ frappe.ui.form.on('Newsletter', { frm.add_custom_button(__('Send Now'), function() { frappe.confirm(__("Do you really want to send this email newsletter?"), function() { frm.call('send_emails').then(() => { - frm.set_value('schedule_send', new Date()); - frm.save(); + frm.refresh(); }); }); }, "fa fa-play", "btn-success"); @@ -39,7 +38,7 @@ frappe.ui.form.on('Newsletter', { frm.get_field('schedule_send').$input.datepicker({ maxMinutes: 0, minDate: today, - timeFormat: 'hh', + timeFormat: 'hh:00:00', onSelect: function (fd, d, picker) { if (!d) return; var date = d.toDateString(); diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 401e305ae0..2d40ffd800 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -42,7 +42,6 @@ class Newsletter(WebsiteGenerator): if self.recipients: if getattr(frappe.local, "is_ajax", False): self.validate_send() - # using default queue with a longer timeout as this isn't a scheduled task enqueue(send_newsletter, queue='default', timeout=6000, event='send_newsletter', newsletter=self.name) @@ -53,6 +52,7 @@ class Newsletter(WebsiteGenerator): frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients))) frappe.db.set(self, "email_sent", 1) + frappe.db.set(self, "schedule_send", now_datetime()) frappe.db.set(self, 'scheduled_to_send', len(self.recipients)) else: frappe.msgprint(_("Newsletter should have atleast one recipient")) @@ -225,7 +225,7 @@ def send_newsletter(newsletter): doc.db_set("email_sent", 0) frappe.db.commit() - frappe.log_error("send_newsletter") + frappe.log_error(title='Send Newsletter') raise From 6624f3a1af1c6328279c1d74303d479fd4ae41ba Mon Sep 17 00:00:00 2001 From: Saurabh Date: Fri, 3 Apr 2020 18:12:32 +0530 Subject: [PATCH 163/408] enhancement: provision to scheduled server scripts execution --- .../scheduled_job_type.json | 9 +++++++- .../scheduled_job_type/scheduled_job_type.py | 7 +++++- .../doctype/server_script/server_script.json | 14 +++++++++--- .../doctype/server_script/server_script.py | 22 +++++++++++++++++++ .../server_script/server_script_utils.py | 3 ++- frappe/utils/safe_exec.py | 3 +++ 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json index e2ec921679..48c98c0bc4 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json @@ -12,6 +12,7 @@ "engine": "InnoDB", "field_order": [ "stopped", + "execute_via_server_script", "method", "frequency", "cron_format", @@ -63,6 +64,12 @@ "options": "All\nHourly\nHourly Long\nDaily\nDaily Long\nWeekly\nWeekly Long\nMonthly\nMonthly Long\nCron\nYearly\nAnnual", "read_only": 1, "reqd": 1 + }, + { + "default": "0", + "fieldname": "execute_via_server_script", + "fieldtype": "Check", + "label": "Execute Via Server Script" } ], "in_create": 1, @@ -72,7 +79,7 @@ "link_fieldname": "scheduled_job_type" } ], - "modified": "2019-12-09 11:10:21.259929", + "modified": "2020-04-03 17:14:13.899031", "modified_by": "Administrator", "module": "Core", "name": "Scheduled Job Type", diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index 3cd994ebfa..b1b867d527 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -70,7 +70,12 @@ class ScheduledJobType(Document): self.scheduler_log = None try: self.log_status('Start') - frappe.get_attr(self.method)() + if self.execute_via_server_script: + script_name = frappe.db.get_value("Server Script", {'api_method': self.method}) + if script_name: + frappe.get_doc('Server Script', script_name).execute_scheduled_method() + else: + frappe.get_attr(self.method)() frappe.db.commit() self.log_status('Complete') except Exception: diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index 36c297cc26..aeed982162 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -11,6 +11,7 @@ "column_break_3", "reference_doctype", "doctype_event", + "scheduler_event", "api_method", "allow_guest", "section_break_8", @@ -22,7 +23,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Script Type", - "options": "DocType Event\nAPI", + "options": "DocType Event\nScheduler Event\nAPI", "reqd": 1 }, { @@ -47,7 +48,7 @@ "options": "Before Insert\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)" }, { - "depends_on": "eval:doc.script_type==='API'", + "depends_on": "eval:doc.script_type==='API' || doc.script_type==='Scheduler Event'", "fieldname": "api_method", "fieldtype": "Data", "label": "API Method" @@ -72,10 +73,17 @@ { "fieldname": "section_break_8", "fieldtype": "Section Break" + }, + { + "depends_on": "eval:doc.script_type==='Scheduler Event'", + "fieldname": "scheduler_event", + "fieldtype": "Select", + "label": "Scheduler Event", + "options": "All\nHourly\nHourly Long\nDaily\nDaily Long\nWeekly\nWeekly Long\nMonthly\nMonthly Long\nCron\nYearly\nAnnual" } ], "links": [], - "modified": "2019-12-17 12:55:07.389775", + "modified": "2020-04-03 16:33:08.765621", "modified_by": "Administrator", "module": "Core", "name": "Server Script", diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index e2c6d3b7b0..c651da2269 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -8,6 +8,7 @@ import frappe from frappe.model.document import Document from frappe.utils.safe_exec import safe_exec + class ServerScript(Document): @staticmethod def validate(): @@ -15,6 +16,7 @@ class ServerScript(Document): @staticmethod def on_update(): + setup_scheduler_events() frappe.cache().delete_value('server_script_map') def execute_method(self): @@ -31,3 +33,23 @@ class ServerScript(Document): # execute event safe_exec(self.script, None, dict(doc = doc)) + def execute_scheduled_method(self): + if self.script_type == 'Scheduler Event': + safe_exec(self.script) + else: + # wrong report type! + raise frappe.DoesNotExistError + +def setup_scheduler_events(): + enabled_server_scripts = frappe.get_all('Server Script', + fields=('name', 'doctype_event','api_method', 'scheduler_event'), + filters={'disabled': 0, 'script_type': 'Scheduler Event'}) + + for script in enabled_server_scripts: + if not frappe.db.exists('Scheduled Job Type', dict(method=script.api_method)): + frappe.get_doc(dict( + doctype = 'Scheduled Job Type', + method = script.api_method, + frequency = script.scheduler_event, + has_server_script=1 + )).insert() \ No newline at end of file diff --git a/frappe/core/doctype/server_script/server_script_utils.py b/frappe/core/doctype/server_script/server_script_utils.py index 2e1a5ae8bb..e03504f30b 100644 --- a/frappe/core/doctype/server_script/server_script_utils.py +++ b/frappe/core/doctype/server_script/server_script_utils.py @@ -66,6 +66,7 @@ def get_server_script_map(): script_map.setdefault(script.reference_doctype, {}).setdefault(script.doctype_event, []).append(script.name) else: script_map.setdefault('_api', {})[script.api_method] = script.name + frappe.cache().set_value('server_script_map', script_map) - return script_map + return script_map \ No newline at end of file diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index 62d0286e03..ed949d8a01 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -11,6 +11,7 @@ from frappe.website.utils import (get_shade, get_toc, get_next_link) from frappe.modules import scrub from frappe.www.printview import get_visible_columns import frappe.exceptions +import frappe.integrations.utils class ServerScriptNotEnabled(frappe.PermissionError): pass @@ -79,6 +80,8 @@ def get_safe_globals(): user=user, csrf_token=frappe.local.session.data.csrf_token if getattr(frappe.local, "session", None) else '' ), + make_get_request = frappe.integrations.utils.make_get_request, + make_post_request = frappe.integrations.utils.make_post_request, socketio_port=frappe.conf.socketio_port, get_hooks=frappe.get_hooks, ), From 4381850b61320cbfd3043293160bd226fa7453a0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 3 Apr 2020 18:17:55 +0530 Subject: [PATCH 164/408] test: schedule_send --- .../doctype/newsletter/test_newsletter.py | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/frappe/email/doctype/newsletter/test_newsletter.py b/frappe/email/doctype/newsletter/test_newsletter.py index 04af44f78a..5a13f99e56 100644 --- a/frappe/email/doctype/newsletter/test_newsletter.py +++ b/frappe/email/doctype/newsletter/test_newsletter.py @@ -3,8 +3,9 @@ from __future__ import unicode_literals import frappe, unittest +from frappe.utils import getdate, add_days -from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe +from frappe.email.doctype.newsletter.newsletter import confirmed_unsubscribe, send_scheduled_email from six.moves.urllib.parse import unquote test_dependencies = ["Email Group"] @@ -58,7 +59,7 @@ class TestNewsletter(unittest.TestCase): self.assertTrue(email in recipients) @staticmethod - def send_newsletter(published=0): + def send_newsletter(published=0, schedule_send=None): frappe.db.sql("delete from `tabEmail Queue`") frappe.db.sql("delete from `tabEmail Queue Recipient`") frappe.db.sql("delete from `tabNewsletter`") @@ -67,11 +68,16 @@ class TestNewsletter(unittest.TestCase): "subject": "_Test Newsletter", "send_from": "Test Sender ", "message": "Testing my news.", - "published": published + "published": published, + "schedule_send": schedule_send }).insert(ignore_permissions=True) newsletter.append("email_group", {"email_group": "_Test Email Group"}) newsletter.save() + if schedule_send: + send_scheduled_email() + return + newsletter.send_emails() return newsletter.name @@ -89,4 +95,13 @@ class TestNewsletter(unittest.TestCase): doc = frappe.get_doc("Newsletter", newsletter_name) doc.get_context(context) self.assertEqual(context.no_cache, 1) - self.assertTrue("attachments" not in list(context)) \ No newline at end of file + self.assertTrue("attachments" not in list(context)) + + def test_schedule_send(self): + self.send_newsletter(schedule_send=add_days(getdate(), -1)) + + email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")] + self.assertEqual(len(email_queue_list), 4) + recipients = [e.recipients[0].recipient for e in email_queue_list] + for email in emails: + self.assertTrue(email in recipients) \ No newline at end of file From ab6d6f3f6593b5ec8fe00e093280f23dce940c4f Mon Sep 17 00:00:00 2001 From: Fumin Date: Sat, 4 Apr 2020 00:17:46 +0800 Subject: [PATCH 165/408] HTTP headers should be URI escaped This is needed for reports containing Chinese characters. --- frappe/public/js/frappe/request.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/request.js b/frappe/public/js/frappe/request.js index d11e12c523..ea4de99249 100644 --- a/frappe/public/js/frappe/request.js +++ b/frappe/public/js/frappe/request.js @@ -211,7 +211,7 @@ frappe.request.call = function(opts) { }; if (opts.args && opts.args.doctype) { - ajax_args.headers["X-Frappe-Doctype"] = opts.args.doctype; + ajax_args.headers["X-Frappe-Doctype"] = encodeURIComponent(opts.args.doctype); } frappe.last_request = ajax_args.data; From f51c975759d58f42e81f8b33df94b6d6860e5c78 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Sat, 4 Apr 2020 19:09:06 +0530 Subject: [PATCH 166/408] refactor: Remove unnecessary code in query_report get_user_settings is useless after following refactor https://github.com/frappe/frappe/pull/5473 https://github.com/frappe/frappe/pull/5834 https://github.com/frappe/frappe/pull/5834/commits/53193d313052d5c935bd8e1861c858c8533e97b1 --- frappe/public/js/frappe/views/reports/query_report.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 2276bc07d6..08da956072 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -242,7 +242,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { () => this.setup_filters(), () => this.set_route_filters(), () => this.report_settings.onload && this.report_settings.onload(this), - () => this.get_user_settings(), () => this.refresh() ]); } @@ -850,13 +849,6 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { setTimeout(preview_chart, 500); } - get_user_settings() { - return frappe.model.user_settings.get(this.report_name) - .then(user_settings => { - this.user_settings = user_settings; - }); - } - prepare_columns(columns) { return columns.map(column => { column = frappe.report_utils.prepare_field_from_column(column); From 8fd991880e6b6f728765a3ae9759a3846873e6d1 Mon Sep 17 00:00:00 2001 From: Fumin Date: Sun, 5 Apr 2020 03:53:52 +0800 Subject: [PATCH 167/408] fix: web form not submitting data in grid views Web forms work nicely with vanilla type fields. However, for child tables fields in grid views, the data is not submitted. The below instructions show how to replicate this issue: --- frappe/public/js/frappe/web_form/web_form.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js index 0c45a03788..71a34d9888 100644 --- a/frappe/public/js/frappe/web_form/web_form.js +++ b/frappe/public/js/frappe/web_form/web_form.js @@ -112,6 +112,7 @@ export default class WebForm extends frappe.ui.FieldGroup { if (window.saving) return; let for_payment = Boolean(this.accept_payment && !this.doc.paid); + this.doc = isvalid; this.doc.doctype = this.doc_type; this.doc.web_form_name = this.name; @@ -187,4 +188,4 @@ export default class WebForm extends frappe.ui.FieldGroup { this.success_message || __("Your information has been submitted"); success_dialog.set_message(success_message); } -} \ No newline at end of file +} From 4479405872d2ae80a501fc247eb3efaa336092b5 Mon Sep 17 00:00:00 2001 From: Felipe Orellana Date: Sun, 5 Apr 2020 21:27:32 -0400 Subject: [PATCH 168/408] fix: hooks.py boilerplate escape chars Creating new app currently breaks due to comments in hooks.py do not properly escape sample configuration on webform hooks. --- frappe/utils/boilerplate.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index e65fa44253..96ed3303ed 100755 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -152,8 +152,8 @@ app_license = "{app_license}" # web_include_js = "/assets/{app_name}/js/{app_name}.js" # include js, css files in header of web form -# webform_include_js = {"doctype": "public/js/doctype.js"} -# webform_include_css = {"doctype": "public/css/doctype.css"} +# webform_include_js = {{"doctype": "public/js/doctype.js"}} +# webform_include_css = {{"doctype": "public/css/doctype.css"}} # include js in page # page_js = {{"page" : "public/js/file.js"}} From 30e7d9e1512c2d9d774844c15dd30b41beee5797 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 12:05:48 +0530 Subject: [PATCH 169/408] feat: link server scripts in scheduler --- .../scheduled_job_type.json | 14 ++++--- .../scheduled_job_type/scheduled_job_type.py | 4 +- .../doctype/server_script/server_script.js | 42 ++++++++++++++++++- .../doctype/server_script/server_script.json | 12 +----- .../doctype/server_script/server_script.py | 42 +++++++++++++------ 5 files changed, 81 insertions(+), 33 deletions(-) diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json index 48c98c0bc4..2a9c1a4573 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.json @@ -12,8 +12,8 @@ "engine": "InnoDB", "field_order": [ "stopped", - "execute_via_server_script", "method", + "server_script", "frequency", "cron_format", "last_execution", @@ -66,10 +66,12 @@ "reqd": 1 }, { - "default": "0", - "fieldname": "execute_via_server_script", - "fieldtype": "Check", - "label": "Execute Via Server Script" + "fieldname": "server_script", + "fieldtype": "Link", + "label": "Server Script", + "options": "Server Script", + "read_only": 1, + "search_index": 1 } ], "in_create": 1, @@ -79,7 +81,7 @@ "link_fieldname": "scheduled_job_type" } ], - "modified": "2020-04-03 17:14:13.899031", + "modified": "2020-04-05 17:27:33.480562", "modified_by": "Administrator", "module": "Core", "name": "Scheduled Job Type", diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index b1b867d527..c179054550 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -70,8 +70,8 @@ class ScheduledJobType(Document): self.scheduler_log = None try: self.log_status('Start') - if self.execute_via_server_script: - script_name = frappe.db.get_value("Server Script", {'api_method': self.method}) + if self.server_script: + script_name = frappe.db.get_value("Server Script", self.server_script) if script_name: frappe.get_doc('Server Script', script_name).execute_scheduled_method() else: diff --git a/frappe/core/doctype/server_script/server_script.js b/frappe/core/doctype/server_script/server_script.js index eea8558456..d7f4c3e536 100644 --- a/frappe/core/doctype/server_script/server_script.js +++ b/frappe/core/doctype/server_script/server_script.js @@ -2,7 +2,45 @@ // For license information, please see license.txt frappe.ui.form.on('Server Script', { - // refresh: function(frm) { + refresh: function(frm) { + if(frm.doc.script_type === 'Scheduler Event' && !frm.doc.disabled){ + frm.add_custom_button('Schedule Script', function() { + var d = new frappe.ui.Dialog({ + title: "Schedule Script Execution", + fields: [ + { + fieldname: "event_type", + label: __('Select Event Type'), + fieldtype: "Select", + options: "All\nHourly\nDaily\nWeekly\nMonthly\nYearly\nHourly Long\nDaily Long\nWeekly Long\nMonthly Long" + }, + ], + primary_action_label: __('Schedule Script'), + primary_action: () => { + d.get_primary_btn().attr('disabled', true); + var data = d.get_values(); + d.hide(); + if(data) { + frm.events.schedule_script(frm, data); + } + + } + }); + + d.show(); + + }); + } + }, + + schedule_script(frm, data){ + frm.call({ + method: "frappe.core.doctype.server_script.server_script.setup_scheduler_events", + args: { + 'script_name': frm.doc.name, + 'frequency': data.event_type + } + }) + } - // } }); diff --git a/frappe/core/doctype/server_script/server_script.json b/frappe/core/doctype/server_script/server_script.json index aeed982162..bef3dfc60c 100644 --- a/frappe/core/doctype/server_script/server_script.json +++ b/frappe/core/doctype/server_script/server_script.json @@ -11,7 +11,6 @@ "column_break_3", "reference_doctype", "doctype_event", - "scheduler_event", "api_method", "allow_guest", "section_break_8", @@ -48,7 +47,7 @@ "options": "Before Insert\nBefore Save\nAfter Save\nBefore Submit\nAfter Submit\nBefore Cancel\nAfter Cancel\nBefore Delete\nAfter Delete\nBefore Save (Submitted Document)\nAfter Save (Submitted Document)" }, { - "depends_on": "eval:doc.script_type==='API' || doc.script_type==='Scheduler Event'", + "depends_on": "eval:doc.script_type==='API'", "fieldname": "api_method", "fieldtype": "Data", "label": "API Method" @@ -73,17 +72,10 @@ { "fieldname": "section_break_8", "fieldtype": "Section Break" - }, - { - "depends_on": "eval:doc.script_type==='Scheduler Event'", - "fieldname": "scheduler_event", - "fieldtype": "Select", - "label": "Scheduler Event", - "options": "All\nHourly\nHourly Long\nDaily\nDaily Long\nWeekly\nWeekly Long\nMonthly\nMonthly Long\nCron\nYearly\nAnnual" } ], "links": [], - "modified": "2020-04-03 16:33:08.765621", + "modified": "2020-04-06 11:24:38.161555", "modified_by": "Administrator", "module": "Core", "name": "Server Script", diff --git a/frappe/core/doctype/server_script/server_script.py b/frappe/core/doctype/server_script/server_script.py index c651da2269..9522b77b4b 100644 --- a/frappe/core/doctype/server_script/server_script.py +++ b/frappe/core/doctype/server_script/server_script.py @@ -7,6 +7,7 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils.safe_exec import safe_exec +from frappe import _ class ServerScript(Document): @@ -16,7 +17,6 @@ class ServerScript(Document): @staticmethod def on_update(): - setup_scheduler_events() frappe.cache().delete_value('server_script_map') def execute_method(self): @@ -40,16 +40,32 @@ class ServerScript(Document): # wrong report type! raise frappe.DoesNotExistError -def setup_scheduler_events(): - enabled_server_scripts = frappe.get_all('Server Script', - fields=('name', 'doctype_event','api_method', 'scheduler_event'), - filters={'disabled': 0, 'script_type': 'Scheduler Event'}) +@frappe.whitelist() +def setup_scheduler_events(script_name, frequency): + method = frappe.scrub(script_name) + '_' + frequency.lower() + scheduled_script = frappe.db.get_value('Scheduled Job Type', + dict(method=method)) - for script in enabled_server_scripts: - if not frappe.db.exists('Scheduled Job Type', dict(method=script.api_method)): - frappe.get_doc(dict( - doctype = 'Scheduled Job Type', - method = script.api_method, - frequency = script.scheduler_event, - has_server_script=1 - )).insert() \ No newline at end of file + if not scheduled_script: + doc = frappe.get_doc(dict( + doctype = 'Scheduled Job Type', + method = method, + frequency = frequency, + server_script = script_name + )) + + doc.insert() + + frappe.msgprint(_('Enabled scheduled execution for script {0}').format(script_name)) + + else: + doc = frappe.get_doc('Scheduled Job Type', scheduled_script) + doc.update(dict( + doctype = 'Scheduled Job Type', + method = method, + frequency = frequency, + server_script = script_name + )) + doc.save() + + frappe.msgprint(_('Scheduled execution for script {0} has updated').format(script_name)) From 3dd2c756f13e95776c95d7788481b9d3e637da19 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 14:00:43 +0530 Subject: [PATCH 170/408] fix: added comma --- frappe/core/page/dashboard/dashboard.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 69e14f65e8..30941f94c0 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -98,7 +98,7 @@ class Dashboard { allow_delete: false, allow_hiding: false, allow_edit: false, - } + }, widgets: this.charts, }); }); From aca5bdb89fbbc80543f3d11070ca4d7b5765723d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 15:19:32 +0530 Subject: [PATCH 171/408] feat: don't rebuild count cache on every insert --- frappe/cache_manager.py | 11 +---------- frappe/hooks.py | 3 +-- 2 files changed, 2 insertions(+), 12 deletions(-) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 9959ba97bb..50c4c396a2 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -121,7 +121,7 @@ def clear_doctype_map(doctype, name): cache_key = frappe.scrub(doctype) + '_map' frappe.cache().hdel(cache_key, name) -def build_table_count_cache(doc=None, method=None, *args, **kwargs): +def build_table_count_cache(): if (frappe.flags.in_patch or frappe.flags.in_install or frappe.flags.in_migrate @@ -129,15 +129,6 @@ def build_table_count_cache(doc=None, method=None, *args, **kwargs): or frappe.flags.in_setup_wizard): return - if doc and isinstance(doc, Document): - doctype = doc.doctype - - if doc.meta.istable: - return - - if doctype in count_cache_blacklist: - return - _cache = frappe.cache() data = frappe.db.multisql({ "mariadb": """ diff --git a/frappe/hooks.py b/frappe/hooks.py index 1b12b26c3a..03b2a2a62e 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -143,8 +143,7 @@ doc_events = { ], "on_change": [ "frappe.social.doctype.energy_point_rule.energy_point_rule.process_energy_points" - ], - "after_insert": "frappe.cache_manager.build_table_count_cache" + ] }, "Event": { "after_insert": "frappe.integrations.doctype.google_calendar.google_calendar.insert_event_in_google_calendar", From 5c83ad9b97a14d53eb81413cf069147487b462c4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 15:20:11 +0530 Subject: [PATCH 172/408] feat: delete domain cache on frappe.clear_cache() --- frappe/__init__.py | 1 + frappe/cache_manager.py | 12 +++++++----- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index e19327fcff..b72d24029c 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -587,6 +587,7 @@ def clear_cache(user=None, doctype=None): else: # everything from frappe import translate frappe.cache_manager.clear_user_cache() + frappe.cache_manager.clear_domain_cache() translate.clear_cache() reset_metadata_version() local.cache = {} diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 50c4c396a2..0c5b5f94b4 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -14,7 +14,8 @@ common_default_keys = ["__default", "__global"] global_cache_keys = ("app_hooks", "installed_apps", "app_modules", "module_app", "system_settings", 'scheduler_events', 'time_zone', 'webhooks', 'active_domains', - 'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version') + 'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version', + 'domain_restricted_doctypes', 'domain_restricted_pages', 'information_schema:counts') user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", "defaults", "user_permissions", "home_page", "linked_with", @@ -23,10 +24,6 @@ user_cache_keys = ("bootinfo", "user_recent", "roles", "user_doc", "lang", doctype_cache_keys = ("meta", "form_meta", "table_columns", "last_modified", "linked_doctypes", 'notifications', 'workflow' ,'energy_point_rule_map') -count_cache_blacklist = ["Version", "Tag", "ToDo", "List Filter", "Note Seen By", "Notification Log", - "Document Follow", "Communication", "Email Queue", "Deleted Document", "File", "Email Queue Recipient" - "Comment", "Has Role", "Attendance", "Route History"] - def clear_user_cache(user=None): cache = frappe.cache() @@ -46,6 +43,11 @@ def clear_user_cache(user=None): clear_defaults_cache() clear_global_cache() +def clear_domain_cache(user=None): + cache = frappe.cache() + domain_cache_keys = ('domain_restricted_doctypes', 'domain_restricted_pages') + cache.delete_value(domain_cache_keys) + def clear_global_cache(): from frappe.website.render import clear_cache as clear_website_cache From 947dc83ddb3d6dfeeebeac5910e34c75fa489243 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 15:20:25 +0530 Subject: [PATCH 173/408] feat: use domain restrictions from cache --- frappe/desk/desktop.py | 4 ++-- frappe/hooks.py | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 1cb03355c6..0688d77cb6 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -26,8 +26,8 @@ class Workspace: self.allowed_reports = get_allowed_reports() self.table_counts = get_table_with_counts() - self.restricted_doctypes = build_domain_restriced_doctype_cache() - self.restricted_pages = build_domain_restriced_page_cache() + self.restricted_doctypes = frappe.cache().get_value("domain_restricted_doctypes") or build_domain_restriced_doctype_cache() + self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache() def get_pages_to_extend(self): pages = frappe.get_all("Desk Page", filters={ diff --git a/frappe/hooks.py b/frappe/hooks.py index 03b2a2a62e..5997263c32 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -155,9 +155,11 @@ 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", }, "Page": { + "after_insert": "frappe.cache_manager.build_domain_restriced_page_cache", "after_save": "frappe.cache_manager.build_domain_restriced_page_cache", }, "Event Update Log": { From 62b48aeca4306c28c45912854a10932b37c159a4 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 6 Apr 2020 17:52:44 +0530 Subject: [PATCH 174/408] fix: remove field if no docfield exists --- frappe/public/js/frappe/list/list_sidebar_group_by.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_sidebar_group_by.js b/frappe/public/js/frappe/list/list_sidebar_group_by.js index bd37b71ae4..f72ff33e4c 100644 --- a/frappe/public/js/frappe/list/list_sidebar_group_by.js +++ b/frappe/public/js/frappe/list/list_sidebar_group_by.js @@ -53,14 +53,17 @@ frappe.views.ListGroupBy = class ListGroupBy { render_group_by_items() { let get_item_html = (fieldname) => { - let label; - let fieldtype; + let label, fieldtype; if (fieldname === 'assigned_to') { label = __('Assigned To'); } else if (fieldname === 'owner') { label = __('Created By'); } else { label = frappe.meta.get_label(this.doctype, fieldname); + let docfield = frappe.meta.get_docfield(this.doctype, fieldname); + if (!docfield) { + return; + } fieldtype = frappe.meta.get_docfield(this.doctype, fieldname).fieldtype; } From 6199f980205e97d3978902b79743d4fc2f28bf8c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 18:48:42 +0530 Subject: [PATCH 175/408] feat: disable shortcut events in customize mode --- frappe/public/js/frappe/widgets/shortcut_widget.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index ed5cbf5e5a..8bedc556ac 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -29,6 +29,8 @@ export default class ShortcutWidget extends Widget { setup_events() { this.widget.click(() => { + if (this.in_customize_mode) return + let route = generate_route({ route: this.route, name: this.link_to, From 95a789763b67989f6c2dd04a8db796e7dc353ec6 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 18:51:12 +0530 Subject: [PATCH 176/408] feat: disable link events in customize mode --- frappe/public/js/frappe/widgets/links_widget.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js index b93b592117..1f186aa6c2 100644 --- a/frappe/public/js/frappe/widgets/links_widget.js +++ b/frappe/public/js/frappe/widgets/links_widget.js @@ -80,21 +80,22 @@ export default class LinksWidget extends Widget { const popover = link.find(".module-link-popover"); link_label.mouseover(() => { + if (this.in_customize_mode) return popover.show(); }); link_label.mouseout(() => popover.hide()); } else { - if (link_label.hasClass("help-video-link")) { - link_label.click(event => { + link_label.click(event => { + if (this.in_customize_mode) return + + if (link_label.hasClass("help-video-link")) { let yt_id = event.target.dataset.youtubeid; frappe.help.show_video(yt_id); - }); - } else { - link_label.click(event => { + } else { let route = event.target.dataset.route; frappe.set_route(route); - }); - } + } + }); } }); } From ed9fd709adb8a9296f7d94fce6c213cf0d06bbfa Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 6 Apr 2020 19:02:30 +0530 Subject: [PATCH 177/408] fix: update newsletter status on sending sets email_sent, schedule_send, and scheduled_to_send on newsletter being sent also switch to raw sql query to avoid default set by coalesce Signed-off-by: Chinmay D. Pai --- frappe/email/doctype/newsletter/newsletter.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 2d40ffd800..056f3a0b02 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -51,9 +51,6 @@ class Newsletter(WebsiteGenerator): frappe.msgprint(_("Scheduled to send to {0} recipients").format(len(self.recipients))) - frappe.db.set(self, "email_sent", 1) - frappe.db.set(self, "schedule_send", now_datetime()) - frappe.db.set(self, 'scheduled_to_send', len(self.recipients)) else: frappe.msgprint(_("Newsletter should have atleast one recipient")) @@ -217,6 +214,9 @@ def send_newsletter(newsletter): try: doc = frappe.get_doc("Newsletter", newsletter) doc.queue_all() + doc.db_set("email_sent", 1) + doc.db_set("schedule_send", now_datetime()) + doc.db_set("scheduled_to_send", len(self.recipients)) except: frappe.db.rollback() @@ -265,9 +265,10 @@ def get_newsletter_list(doctype, txt, filters, limit_start, limit_page_length=20 def send_scheduled_email(): """Send scheduled newsletter to the recipients.""" - scheduled_newsletter = frappe.get_all('Newsletter', filters = { - 'schedule_send': ('<=', now_datetime()), - 'email_sent': 0 - }, fields = ['name']) + scheduled_newsletter = frappe.db.sql(""" +SELECT name from `tabNewsletter` +WHERE schedule_send <= %s +AND email_sent = 0 +""", now_datetime(), as_dict=1) for newsletter in scheduled_newsletter: - send_newsletter(newsletter.name) \ No newline at end of file + send_newsletter(newsletter.name) From 27b193914641cbe23d416d529a97808c9dc35bdd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 19:20:53 +0530 Subject: [PATCH 178/408] fix: retain container on reload of page --- frappe/public/js/frappe/views/desktop/desktop.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index c8c99295e2..3311af301f 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -105,8 +105,6 @@ export default class Desktop { } this.current_page = page; localStorage.current_desk_page = page; - frappe.set_route("workspace", page); - this.pages[page] ? this.pages[page].show() : this.make_page(page); } @@ -134,6 +132,7 @@ export default class Desktop { class DesktopPage { constructor({ container, page_name }) { + frappe.desk_page = this; this.container = container; this.page_name = page_name; this.sections = {}; @@ -142,6 +141,8 @@ class DesktopPage { } show() { + console.log("Showing ", this.page_name) + frappe.desk_page = this; this.page.show(); } @@ -151,7 +152,7 @@ class DesktopPage { reload() { this.in_customize_mode = false; - this.container.empty(); + this.page && this.page.remove(); this.make(); this.setup_events(); } From 73623547fc28405835d2e13fe38f17e5f9d7a29c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 6 Apr 2020 19:28:20 +0530 Subject: [PATCH 179/408] feat: filter blocked modules --- frappe/desk/desktop.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index a639400670..1edc2e57ec 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -30,15 +30,20 @@ class Workspace: return frappe.get_doc("Desk Page", self.page_name) def init(self): - self.doc = self.get_page_for_user() - user = frappe.get_user() user.build_permissions() - self.user = user + self.blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules() + self.doc = self.get_page_for_user() + + if self.doc.module in self.blocked_modules: + raise frappe.PermissionError + + self.user = user self.allowed_pages = get_allowed_pages() self.allowed_reports = get_allowed_reports() + self.table_counts = get_table_with_counts() self.restricted_doctypes = build_domain_restriced_doctype_cache() self.restricted_pages = build_domain_restriced_page_cache() @@ -47,7 +52,8 @@ class Workspace: pages = frappe.get_all("Desk Page", filters={ "extends": self.page_name, 'restrict_to_domain': ['in', frappe.get_active_domains()], - 'for_user': '' + 'for_user': '', + 'module': ['not in', self.blocked_modules] }) pages = [frappe.get_doc("Desk Page", page['name']) for page in pages] @@ -223,11 +229,14 @@ def get_desk_sidebar_items(): """Get list of sidebar items for desk """ # don't get domain restricted pages + blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules() + filters = { 'restrict_to_domain': ['in', frappe.get_active_domains()], 'extends_another_page': 0, 'is_standard': 1, - 'for_user': '' + 'for_user': '', + 'module': ['not in', blocked_modules] } if not frappe.local.conf.developer_mode: From a1e297ecb1754551eeece7e79ab1345b45b8ba7a Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 6 Apr 2020 19:46:57 +0530 Subject: [PATCH 180/408] chore: fix codacy issue Signed-off-by: Chinmay D. Pai --- frappe/email/doctype/newsletter/newsletter.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 056f3a0b02..ccb2e87264 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -216,7 +216,7 @@ def send_newsletter(newsletter): doc.queue_all() doc.db_set("email_sent", 1) doc.db_set("schedule_send", now_datetime()) - doc.db_set("scheduled_to_send", len(self.recipients)) + doc.db_set("scheduled_to_send", len(doc.recipients)) except: frappe.db.rollback() From 1bc97e8f8a7e625f5981b1b510488f59ef744cdb Mon Sep 17 00:00:00 2001 From: Saurabh Date: Tue, 7 Apr 2020 00:06:27 +0530 Subject: [PATCH 181/408] feat: Offsite backup (#8241) * feat: utility to get file size * feat: if backup file is greater than 1gb then consider latest backup site instead of taking new * fix: remove unwanted import * chore: fix condition inside offsite backup utils Signed-off-by: Chinmay D. Pai * chore: change email field to notify_email Signed-off-by: Chinmay D. Pai * chore: fix deepsource issues Signed-off-by: Chinmay D. Pai * feat: add offsite_backup support for google drive Signed-off-by: Chinmay D. Pai * chore: get recipients from within send_email pass email_field to send_email instead of calling two functions Signed-off-by: Chinmay D. Pai * chore: set flag within validate_file_size Signed-off-by: Chinmay D. Pai * feat: get latest file backup when specified Signed-off-by: Chinmay D. Pai * chore: fix deepsource issues Signed-off-by: Chinmay D. Pai * chore: fix incorrectly spelled dropbox settings Signed-off-by: Chinmay D. Pai * chore: implement file backup logic for aws s3 Signed-off-by: Chinmay D. Pai * chore: fix deepsource issues Signed-off-by: Chinmay D. Pai * chore: fix deepsource issues Signed-off-by: Chinmay D. Pai Co-authored-by: Chinmay D. Pai Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- .../dropbox_settings/dropbox_settings.json | 576 ++++-------------- .../dropbox_settings/dropbox_settings.py | 51 +- .../dropbox_settings/test_dropbox_settings.py | 10 + .../doctype/google_drive/google_drive.py | 52 +- .../s3_backup_settings.json | 471 +++----------- .../s3_backup_settings/s3_backup_settings.py | 54 +- frappe/integrations/offsite_backup_utils.py | 83 +++ frappe/integrations/utils.py | 2 +- 8 files changed, 346 insertions(+), 953 deletions(-) create mode 100644 frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py create mode 100644 frappe/integrations/offsite_backup_utils.py diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json index 33d34e0210..858469647a 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.json @@ -1,487 +1,129 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-09-21 10:12:57.399174", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "System", - "editable_grid": 1, + "creation": "2016-09-21 10:12:57.399174", + "doctype": "DocType", + "document_type": "System", + "editable_grid": 1, + "field_order": [ + "enabled", + "send_notifications_to", + "send_email_for_successful_backup", + "backup_frequency", + "limit_no_of_backups", + "no_of_backups", + "file_backup", + "app_access_key", + "app_secret_key", + "allow_dropbox_access", + "dropbox_access_key", + "dropbox_access_secret", + "dropbox_access_token" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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": 0, - "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, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fieldname": "enabled", + "fieldtype": "Check", + "label": "Enabled" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "send_notifications_to", - "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": "Send Notifications To", - "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 - }, + "fieldname": "send_notifications_to", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Send Notifications To", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "description": "Note: By default emails for failed backups are sent.", - "fieldname": "send_email_for_successful_backup", - "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": "Send Email for Successful Backup", - "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, - "translatable": 0, - "unique": 0 - }, + "default": "1", + "description": "Note: By default emails for failed backups are sent.", + "fieldname": "send_email_for_successful_backup", + "fieldtype": "Check", + "label": "Send Email for Successful Backup" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "backup_frequency", - "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": "Backup Frequency", - "length": 0, - "no_copy": 0, - "options": "\nDaily\nWeekly", - "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": "backup_frequency", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Backup Frequency", + "options": "\nDaily\nWeekly", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "limit_no_of_backups", - "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": "Limit Number of DB Backups", - "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": "limit_no_of_backups", + "fieldtype": "Check", + "label": "Limit Number of DB Backups" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "5", - "depends_on": "eval:doc.limit_no_of_backups", - "fieldname": "no_of_backups", - "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": "Number of DB Backups", - "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": "5", + "depends_on": "eval:doc.limit_no_of_backups", + "fieldname": "no_of_backups", + "fieldtype": "Int", + "label": "Number of DB Backups" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "fieldname": "file_backup", - "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": "File Backup", - "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": "1", + "fieldname": "file_backup", + "fieldtype": "Check", + "label": "File Backup" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "app_access_key", - "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 Access 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "app_access_key", + "fieldtype": "Data", + "label": "App Access Key" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "depends_on": "", - "fieldname": "app_secret_key", - "fieldtype": "Password", - "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 Secret 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "app_secret_key", + "fieldtype": "Password", + "label": "App Secret Key" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "allow_dropbox_access", - "fieldtype": "Button", - "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 Dropbox Access", - "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": "allow_dropbox_access", + "fieldtype": "Button", + "label": "Allow Dropbox Access" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dropbox_access_key", - "fieldtype": "Password", - "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": "Dropbox Access Key", - "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": "dropbox_access_key", + "fieldtype": "Password", + "hidden": 1, + "label": "Dropbox Access Key", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dropbox_access_secret", - "fieldtype": "Password", - "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": "Dropbox Access 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, - "translatable": 0, - "unique": 0 - }, + "fieldname": "dropbox_access_secret", + "fieldtype": "Password", + "hidden": 1, + "label": "Dropbox Access Secret", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "dropbox_access_token", - "fieldtype": "Password", - "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": "Dropbox Access Token", - "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": "dropbox_access_token", + "fieldtype": "Password", + "hidden": 1, + "label": "Dropbox Access Token" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 1, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2019-01-03 05:44:40.520943", - "modified_by": "Administrator", - "module": "Integrations", - "name": "Dropbox Settings", - "name_case": "", - "owner": "Administrator", + ], + "in_create": 1, + "issingle": 1, + "modified": "2019-08-22 16:26:44.468391", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Dropbox Settings", + "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": 0, - "role": "System Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 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", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} + ], + "read_only": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py index dd4768f8b3..2a036f4838 100644 --- a/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py +++ b/frappe/integrations/doctype/dropbox_settings/dropbox_settings.py @@ -3,22 +3,25 @@ # For license information, please see license.txt from __future__ import unicode_literals +import dropbox +import json import frappe import os from frappe import _ from frappe.model.document import Document -import dropbox, json +from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size +from frappe.integrations.utils import make_post_request +from frappe.utils import (cint, get_request_site_address, + get_files_path, get_backups_path, get_url, encode) from frappe.utils.backups import new_backup from frappe.utils.background_jobs import enqueue from six.moves.urllib.parse import urlparse, parse_qs -from frappe.integrations.utils import make_post_request from rq.timeouts import JobTimeoutException -from frappe.utils import (cint, split_emails, get_request_site_address, - get_files_path, get_backups_path, get_url, encode) from six import text_type ignore_list = [".DS_Store"] + class DropboxSettings(Document): def onload(self): if not self.app_access_key and frappe.conf.dropbox_access_key: @@ -48,10 +51,12 @@ 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")): + validate_file_size() + did_not_upload, error_log = backup_to_dropbox(upload_db_backup) if did_not_upload: raise Exception - send_email(True, "Dropbox") + send_email(True, "Dropbox", "Dropbox Settings", "send_notifications_to") except JobTimeoutException: if retry_count < 2: args = { @@ -66,34 +71,8 @@ def take_backup_to_dropbox(retry_count=0, upload_db_backup=True): else: file_and_error = [" - ".join(f) for f in zip(did_not_upload, error_log)] error_message = ("\n".join(file_and_error) + "\n" + frappe.get_traceback()) - frappe.errprint(error_message) - send_email(False, "Dropbox", error_message) -def send_email(success, service_name, error_status=None): - if success: - if frappe.db.get_value("Dropbox Settings", None, "send_email_for_successful_backup") == '0': - return - - subject = "Backup Upload Successful" - message ="""

Backup Uploaded Successfully

Hi there, this is just to inform you - that your backup was successfully uploaded to your %s account. So relax!

- """ % service_name - - else: - subject = "[Warning] Backup Upload Failed" - message ="""

Backup Upload Failed

Oops, your automated backup to %s - failed.

-

Error message:
-

%s
-

-

Please contact your system manager for more information.

- """ % (service_name, error_status) - - if not frappe.db: - frappe.connect() - - recipients = split_emails(frappe.db.get_value("Dropbox Settings", None, "send_notifications_to")) - frappe.sendmail(recipients=recipients, subject=subject, message=message) + send_email(False, "Dropbox", "Dropbox Settings", "send_notifications_to", error_message) def backup_to_dropbox(upload_db_backup=True): if not frappe.db: @@ -114,8 +93,12 @@ def backup_to_dropbox(upload_db_backup=True): dropbox_client = dropbox.Dropbox(dropbox_settings['access_token']) if upload_db_backup: - backup = new_backup(ignore_files=True) - filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db)) + if frappe.flags.create_new_backup: + backup = new_backup(ignore_files=True) + filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db)) + else: + filename = get_latest_backup_file() + upload_file_to_dropbox(filename, "/database", dropbox_client) # delete older databases diff --git a/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py b/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py new file mode 100644 index 0000000000..539fc417f2 --- /dev/null +++ b/frappe/integrations/doctype/dropbox_settings/test_dropbox_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDropboxSettings(unittest.TestCase): + pass diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py index 8078c702c0..60ee173bbf 100644 --- a/frappe/integrations/doctype/google_drive/google_drive.py +++ b/frappe/integrations/doctype/google_drive/google_drive.py @@ -19,6 +19,7 @@ from apiclient.http import MediaFileUpload from frappe.utils import get_backups_path, get_bench_path from frappe.utils.backups import new_backup from frappe.integrations.doctype.google_settings.google_settings import get_auth_url +from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size SCOPES = "https://www.googleapis.com/auth/drive" @@ -183,13 +184,16 @@ def upload_system_backup_to_google_drive(): check_for_folder_in_google_drive() account.load_from_db() - progress(1, "Backing up Data.") - backup = new_backup() - - fileurl_backup = os.path.basename(backup.backup_path_db) - fileurl_public_files = os.path.basename(backup.backup_path_files) - fileurl_private_files = os.path.basename(backup.backup_path_private_files) + validate_file_size() + if frappe.flags.create_new_backup: + set_progress(1, "Backing up Data.") + backup = new_backup() + fileurl_backup = os.path.basename(backup.backup_path_db) + fileurl_public_files = os.path.basename(backup.backup_path_files) + fileurl_private_files = os.path.basename(backup.backup_path_private_files) + else: + fileurl_backup, fileurl_public_files, fileurl_private_files = get_latest_backup_file(with_files=True) for fileurl in [fileurl_backup, fileurl_public_files, fileurl_private_files]: file_metadata = { @@ -203,15 +207,14 @@ def upload_system_backup_to_google_drive(): frappe.throw(_("Google Drive - Could not locate locate - {0}").format(e)) try: - progress(2, "Uploading backup to Google Drive.") + set_progress(2, "Uploading backup to Google Drive.") google_drive.files().create(body=file_metadata, media_body=media, fields="id").execute() except HttpError as e: - send_email(success=False, error=e) - frappe.msgprint(_("Google Drive - Could not upload backup - Error {0}").format(e)) + send_email(False, "Google Drive", "Google Drive", "email", error_status=e) - progress(3, "Uploading successful.") + set_progress(3, "Uploading successful.") frappe.db.set_value("Google Drive", None, "last_backup_on", frappe.utils.now_datetime()) - send_email(success=True) + send_email(True, "Google Drive", "Google Drive", "email") return _("Google Drive Backup Successful.") def daily_backup(): @@ -226,30 +229,5 @@ def get_absolute_path(filename): file_path = os.path.join(get_backups_path()[2:], filename) return "{0}/sites/{1}".format(get_bench_path(), file_path) -def progress(progress, message): +def set_progress(progress, message): frappe.publish_realtime("upload_to_google_drive", dict(progress=progress, total=3, message=message), user=frappe.session.user) - -def send_email(success, error=None): - if success: - if not frappe.db.get_single_value("Google Drive", "send_email_for_successful_backup"): - return - - subject = "Backup Upload Successful" - message = """

Backup Uploaded Successfully

Hi there, this is just to inform you - that your backup was successfully uploaded to Google Drive.

- """ - else: - subject = "[Warning] Backup Upload Failed" - message = """

Backup Upload Failed

Oops, your automated backup to Google Drive - failed.

-

Error message:
-

{0}
-

-

Please contact your system manager for more information.

- """.format(error) - - frappe.sendmail( - recipients=frappe.db.get_single_value("Google Drive", "email"), - subject=subject, - message=message - ) \ No newline at end of file diff --git a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json index 93fff995f0..bbdbf74a67 100755 --- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.json @@ -1,397 +1,110 @@ { - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2017-09-04 20:57:20.129205", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "creation": "2017-09-04 20:57:20.129205", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "enabled", + "notify_email", + "send_email_for_successful_backup", + "frequency", + "access_key_id", + "secret_access_key", + "region", + "endpoint_url", + "bucket", + "backup_limit" + ], "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": "enabled", - "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": "Enable Automatic Backup", - "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": "enabled", + "fieldtype": "Check", + "label": "Enable Automatic Backup" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "notify_email", - "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": "Send Notifications To", - "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 - }, + "fieldname": "notify_email", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Send Notifications To", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "1", - "description": "Note: By default emails for failed backups are sent.", - "fetch_if_empty": 0, - "fieldname": "send_email_for_successful_backup", - "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": "Send Email for Successful Backup", - "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": "1", + "description": "Note: By default emails for failed backups are sent.", + "fieldname": "send_email_for_successful_backup", + "fieldtype": "Check", + "label": "Send Email for Successful Backup" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "frequency", - "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": "Backup Frequency", - "length": 0, - "no_copy": 0, - "options": "Daily\nWeekly\nMonthly\nNone", - "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": "frequency", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Backup Frequency", + "options": "Daily\nWeekly\nMonthly\nNone", + "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": "access_key_id", - "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": "Access Key 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "access_key_id", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Access Key ID", + "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": "secret_access_key", - "fieldtype": "Password", - "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": "Secret Access 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 - }, + "fieldname": "secret_access_key", + "fieldtype": "Password", + "in_list_view": 1, + "label": "Secret Access Key", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "default": "us-east-1", - "description": "See https://docs.aws.amazon.com/de_de/general/latest/gr/rande.html#s3_region for details.", - "fetch_if_empty": 0, - "fieldname": "region", - "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": "Region", - "length": 0, - "no_copy": 0, - "options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-north-1\nsa-east-1", - "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": "us-east-1", + "description": "See https://docs.aws.amazon.com/de_de/general/latest/gr/rande.html#s3_region for details.", + "fieldname": "region", + "fieldtype": "Select", + "label": "Region", + "options": "us-east-1\nus-east-2\nus-west-1\nus-west-2\nap-south-1\nap-southeast-1\nap-southeast-2\nap-northeast-1\nap-northeast-2\nap-northeast-3\nca-central-1\ncn-north-1\ncn-northwest-1\neu-central-1\neu-west-1\neu-west-2\neu-west-3\neu-north-1\nsa-east-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": "endpoint_url", - "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": "Endpoint URL", - "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": "endpoint_url", + "fieldtype": "Data", + "label": "Endpoint URL" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_if_empty": 0, - "fieldname": "bucket", - "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": "Bucket", - "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 - }, + "fieldname": "bucket", + "fieldtype": "Data", + "label": "Bucket", + "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": "backup_limit", - "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": "Backup 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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "backup_limit", + "fieldtype": "Int", + "label": "Backup Limit", + "reqd": 1 } - ], - "has_web_view": 0, - "hide_toolbar": 1, - "idx": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "menu_index": 0, - "modified": "2019-04-10 03:56:55.632017", - "modified_by": "Administrator", - "module": "Integrations", - "name": "S3 Backup Settings", - "name_case": "", - "owner": "Administrator", + ], + "hide_toolbar": 1, + "issingle": 1, + "modified": "2019-08-22 16:26:04.774571", + "modified_by": "Administrator", + "module": "Integrations", + "name": "S3 Backup Settings", + "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, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file 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 55b9e63a4d..7e69da922c 100755 --- a/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py +++ b/frappe/integrations/doctype/s3_backup_settings/s3_backup_settings.py @@ -8,12 +8,14 @@ import os.path import frappe import boto3 from frappe import _ +from frappe.integrations.offsite_backup_utils import get_latest_backup_file, send_email, validate_file_size from frappe.model.document import Document -from frappe.utils import cint, split_emails +from frappe.utils import cint from frappe.utils.background_jobs import enqueue from rq.timeouts import JobTimeoutException from botocore.exceptions import ClientError + class S3BackupSettings(Document): def validate(self): @@ -49,7 +51,7 @@ class S3BackupSettings(Document): @frappe.whitelist() def take_backup(): - "Enqueue longjob for taking backup to s3" + """Enqueue longjob for taking backup to s3""" enqueue("frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3", queue='long', timeout=1500) frappe.msgprint(_("Queued for backup. It may take a few minutes to an hour.")) @@ -65,22 +67,21 @@ def take_backups_weekly(): def take_backups_monthly(): take_backups_if("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: take_backups_s3() - @frappe.whitelist() def take_backups_s3(retry_count=0): try: + validate_file_size() backup_to_s3() - send_email(True, "S3 Backup Settings") + send_email(True, "Amazon S3", "S3 Backup Settings", "notify_email") except JobTimeoutException: if retry_count < 2: args = { - "retry_count" :retry_count + 1 + "retry_count": retry_count + 1 } enqueue("frappe.integrations.doctype.s3_backup_settings.s3_backup_settings.take_backups_s3", queue='long', timeout=1500, **args) @@ -89,31 +90,10 @@ def take_backups_s3(retry_count=0): except Exception: notify() + def notify(): error_message = frappe.get_traceback() - frappe.errprint(error_message) - send_email(False, "S3 Backup Settings", error_message) - -def send_email(success, service_name, error_status=None): - if success: - if frappe.db.get_value("S3 Backup Settings", None, "send_email_for_successful_backup") == '0': - return - - subject = "Backup Upload Successful" - message = """

Backup Uploaded Successfully!

Hi there, this is just to inform you - that your backup was successfully uploaded to your Amazon S3 bucket. So relax!

""" - - else: - subject = "[Warning] Backup Upload Failed" - message = """

Backup Upload Failed!

Oops, your automated backup to Amazon S3 failed. -

Error message: %s

Please contact your system manager - for more information.

""" % error_status - - if not frappe.db: - frappe.connect() - - recipients = split_emails(frappe.db.get_value("S3 Backup Settings", None, "notify_email")) - frappe.sendmail(recipients=recipients, subject=subject, message=message) + send_email(False, 'Amazon S3', "S3 Backup Settings", "notify_email", error_message) def backup_to_s3(): @@ -130,11 +110,15 @@ def backup_to_s3(): endpoint_url=doc.endpoint_url or 'https://s3.amazonaws.com' ) - backup = new_backup(ignore_files=False, backup_path_db=None, + if frappe.flags.create_new_backup: + backup = new_backup(ignore_files=False, backup_path_db=None, backup_path_files=None, backup_path_private_files=None, force=True) - db_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db)) - files_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_files)) - private_files = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_private_files)) + db_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_db)) + files_filename = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_files)) + private_files = os.path.join(get_backups_path(), os.path.basename(backup.backup_path_private_files)) + else: + db_filename, files_filename, private_files = get_latest_backup_file(with_files=True) + folder = os.path.basename(db_filename)[:15] + '/' # for adding datetime to folder name @@ -143,8 +127,8 @@ def backup_to_s3(): upload_file_to_s3(files_filename, folder, conn, bucket) delete_old_backups(doc.backup_limit, bucket) -def upload_file_to_s3(filename, folder, conn, bucket): +def upload_file_to_s3(filename, folder, conn, bucket): destpath = os.path.join(folder, os.path.basename(filename)) try: print("Uploading file:", filename) @@ -156,7 +140,7 @@ def upload_file_to_s3(filename, folder, conn, bucket): def delete_old_backups(limit, bucket): - all_backups = list() + all_backups = [] doc = frappe.get_single("S3 Backup Settings") backup_limit = int(limit) diff --git a/frappe/integrations/offsite_backup_utils.py b/frappe/integrations/offsite_backup_utils.py new file mode 100644 index 0000000000..c280a1d9dd --- /dev/null +++ b/frappe/integrations/offsite_backup_utils.py @@ -0,0 +1,83 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import glob +import os +from frappe.utils import split_emails, get_backups_path + + +def send_email(success, service_name, doctype, email_field, error_status=None): + recipients = get_recipients(service_name, email_field) + if not recipients: + frappe.log_error("No Email Recipient found for {0}".format(service_name), + "{0}: Failed to send backup status email".format(service_name)) + return + + if success: + if not frappe.db.get_value(doctype, None, "send_email_for_successful_backup"): + return + + subject = "Backup Upload Successful" + message = """ +

Backup Uploaded Successfully!

+

Hi there, this is just to inform you that your backup was successfully uploaded to your {0} bucket. So relax!

""".format(service_name) + + else: + subject = "[Warning] Backup Upload Failed" + message = """ +

Backup Upload Failed!

+

Oops, your automated backup to {0} failed.

+

Error message: {1}

+

Please contact your system manager for more information.

""".format(service_name, error_status) + + frappe.sendmail(recipients=recipients, subject=subject, message=message) + + +def get_recipients(service_name, email_field): + if not frappe.db: + frappe.connect() + + return split_emails(frappe.db.get_value(service_name, None, email_field)) + + +def get_latest_backup_file(with_files=False): + + def get_latest(file_ext): + file_list = glob.glob(os.path.join(get_backups_path(), file_ext)) + return max(file_list, key=os.path.getctime) + + latest_file = get_latest('*.sql.gz') + + if with_files: + latest_public_file_bak = get_latest('*-files.tar') + latest_private_file_bak = get_latest('*-private-files.tar') + return latest_file, latest_public_file_bak, latest_private_file_bak + + return latest_file + + +def get_file_size(file_path, unit): + if not unit: + unit = 'MB' + + file_size = os.path.getsize(file_path) + + memory_size_unit_mapper = {'KB': 1, 'MB': 2, 'GB': 3, 'TB': 4} + i = 0 + while i < memory_size_unit_mapper[unit]: + file_size = file_size / 1000.0 + i += 1 + + return file_size + + +def validate_file_size(): + frappe.flags.create_new_backup = True + latest_file = get_latest_backup_file() + file_size = get_file_size(latest_file, unit='GB') + + if file_size > 1: + frappe.flags.create_new_backup = False diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py index 811b007131..808affe47a 100644 --- a/frappe/integrations/utils.py +++ b/frappe/integrations/utils.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2015, Frappe Technologies and contributors +# Copyright (c) 2019, Frappe Technologies and contributors # For license information, please see license.txt from __future__ import unicode_literals From 17418166583dcd29d0773f7f9060ae6f7deaf8bb Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 12:12:07 +0530 Subject: [PATCH 182/408] feat: added wiggle effect for invalid login --- frappe/templates/includes/login/login.css | 26 +++++++++++++++++++++++ frappe/templates/includes/login/login.js | 10 ++++++++- 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/frappe/templates/includes/login/login.css b/frappe/templates/includes/login/login.css index 21dee49010..3a4b00fca5 100644 --- a/frappe/templates/includes/login/login.css +++ b/frappe/templates/includes/login/login.css @@ -137,3 +137,29 @@ p { position: absolute; z-index: 2; } + +.invalid-login { + -webkit-animation: wiggle 0.5s linear; +} + +@-webkit-keyframes wiggle { + 8%, + 41% { + -webkit-transform: translateX(-10px); + } + 25%, + 58% { + -webkit-transform: translateX(10px); + } + 75% { + -webkit-transform: translateX(-5px); + } + 92% { + -webkit-transform: translateX(5px); + } + 0%, + 100% { + -webkit-transform: translateX(0); + } +} + diff --git a/frappe/templates/includes/login/login.js b/frappe/templates/includes/login/login.js index ed6542ccdf..74d6337f74 100644 --- a/frappe/templates/includes/login/login.js +++ b/frappe/templates/includes/login/login.js @@ -141,6 +141,14 @@ login.set_indicator = function(message, color) { .removeClass().addClass('indicator').addClass(color).text(message) } +login.set_invalid = function(message) { + $(".login-content.page-card").addClass('invalid-login'); + setTimeout(() => { + $(".login-content.page-card").removeClass('invalid-login'); + }, 500) + login.set_indicator(message, 'red'); +} + login.login_handlers = (function() { var get_error_handler = function(default_message) { return function(xhr, data) { @@ -161,7 +169,7 @@ login.login_handlers = (function() { } if(message===default_message) { - login.set_indicator(message, 'red'); + login.set_invalid(message); } else { login.reset_sections(false); } From 074be8124d90339a13cf4f4e531d070b2fd681a6 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 7 Apr 2020 15:47:20 +0530 Subject: [PATCH 183/408] fix: set email_sent after enqueuing newsletters Signed-off-by: Chinmay D. Pai --- frappe/email/doctype/newsletter/newsletter.py | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index ccb2e87264..a847940448 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -68,8 +68,8 @@ class Newsletter(WebsiteGenerator): attachments = [] if self.send_attachements: - files = frappe.get_all("File", fields = ["name"], filters = {"attached_to_doctype": "Newsletter", - "attached_to_name":self.name}, order_by="creation desc") + files = frappe.get_all("File", fields=["name"], filters={"attached_to_doctype": "Newsletter", + "attached_to_name": self.name}, order_by="creation desc") for file in files: try: @@ -79,17 +79,21 @@ class Newsletter(WebsiteGenerator): except IOError: frappe.throw(_("Unable to find attachment {0}").format(file.name)) - send(recipients = self.recipients, sender = sender, - subject = self.subject, message = self.message, - reference_doctype = self.doctype, reference_name = self.name, - add_unsubscribe_link = self.send_unsubscribe_link, attachments=attachments, - unsubscribe_method = "/unsubscribe", - unsubscribe_params = {"name": self.name}, - send_priority = 0, queue_separately=True) + send(recipients=self.recipients, sender=sender, + subject=self.subject, message=self.message, + reference_doctype=self.doctype, reference_name=self.name, + add_unsubscribe_link=self.send_unsubscribe_link, attachments=attachments, + unsubscribe_method="/unsubscribe", + unsubscribe_params={"name": self.name}, + send_priority=0, queue_separately=True) if not frappe.flags.in_test: frappe.db.auto_commit_on_many_writes = False + self.db_set("email_sent", 1) + self.db_set("schedule_send", now_datetime()) + self.db_set("scheduled_to_send", len(self.recipients)) + def get_recipients(self): """Get recipients from Email Group""" recipients_list = [] From dd8fe186568e8b15d4093820ffa3ca4ea86cb664 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 15:47:58 +0530 Subject: [PATCH 184/408] feat: add allowed sidebar items to bootinfo --- frappe/boot.py | 6 ++++-- frappe/desk/desktop.py | 4 +++- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 7fc071b6b8..e6d1199b19 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -41,7 +41,7 @@ def get_bootinfo(): bootinfo.modules = {} bootinfo.module_list = [] - load_desktop_icons(bootinfo) + load_desktop_data(bootinfo) bootinfo.letter_heads = get_letter_heads() bootinfo.active_domains = frappe.get_active_domains() bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")] @@ -99,9 +99,11 @@ def load_conf_settings(bootinfo): for key in ('developer_mode', 'socketio_port', 'file_watcher_port'): if key in conf: bootinfo[key] = conf.get(key) -def load_desktop_icons(bootinfo): +def load_desktop_data(bootinfo): from frappe.config import get_modules_from_all_apps_for_user + from frappe.desk.desktop import get_desk_sidebar_items bootinfo.allowed_modules = get_modules_from_all_apps_for_user() + bootinfo.allowed_workspaces = get_desk_sidebar_items(True) def get_allowed_pages(): return get_user_pages_or_reports('Page') diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 1cb03355c6..c55691fe91 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -209,7 +209,7 @@ def get_desktop_page(page): return None @frappe.whitelist() -def get_desk_sidebar_items(): +def get_desk_sidebar_items(flatten=False): """Get list of sidebar items for desk """ # don't get domain restricted pages @@ -224,6 +224,8 @@ def get_desk_sidebar_items(): # pages sorted based on pinned to top and then by name order_by = "pin_to_top desc, pin_to_bottom asc, name asc" pages = frappe.get_all("Desk Page", fields=["name", "category"], filters=filters, order_by=order_by, ignore_permissions=True) + if flatten: + return pages from collections import defaultdict sidebar_items = defaultdict(list) From 2e0d5bc522ef1735ce00c831f2e51b8e2b40291a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 15:48:15 +0530 Subject: [PATCH 185/408] feat: Open desk page from awesomebar --- .../js/frappe/ui/toolbar/awesome_bar.js | 2 +- .../js/frappe/ui/toolbar/search_utils.js | 31 ++++++------------- 2 files changed, 11 insertions(+), 22 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js index bfcc5a2d77..0595fb6219 100644 --- a/frappe/public/js/frappe/ui/toolbar/awesome_bar.js +++ b/frappe/public/js/frappe/ui/toolbar/awesome_bar.js @@ -176,7 +176,7 @@ frappe.search.AwesomeBar = Class.extend({ frappe.search.utils.get_doctypes(txt), frappe.search.utils.get_reports(txt), frappe.search.utils.get_pages(txt), - frappe.search.utils.get_modules(txt), + frappe.search.utils.get_workspaces(txt), frappe.search.utils.get_recent_pages(txt || ""), frappe.search.utils.get_executables(txt) ); diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index 153a4dfa67..9a03360d59 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -283,31 +283,20 @@ frappe.search.utils = { return out; }, - get_modules: function(keywords) { + get_workspaces: function(keywords) { var me = this; var out = []; - Object.keys(frappe.modules).forEach(function(item) { - var level = me.fuzzy_search(keywords, item); + frappe.boot.allowed_workspaces.forEach(function(item) { + var level = me.fuzzy_search(keywords, item.name); if(level > 0) { - var module = frappe.modules[item]; - if (module._doctype) return; - - // disallow restricted modules - if (frappe.boot.user.allow_modules && - !frappe.boot.user.allow_modules.includes(module.module_name)) { - return; - } var ret = { - type: "Module", - label: __("Open {0}", [me.bolden_match_part(__(item), keywords)]), - value: __("Open {0}", [__(item)]), + type: "Workspace", + label: __("Open {0}", [me.bolden_match_part(__(item.name), keywords)]), + value: __("Open {0}", [__(item.name)]), index: level, + route: ["workspace", item.name] }; - if(module.link) { - ret.route = [module.link]; - } else { - ret.route = ["Module", item]; - } + out.push(ret); } }); @@ -497,9 +486,9 @@ frappe.search.utils = { results: sort_uniques(this.get_pages(keywords)) }, { - title: "Modules", + title: "Workspace", fetch_type: "Nav", - results: sort_uniques(this.get_modules(keywords)) + results: sort_uniques(this.get_workspaces(keywords)) }, { title: "Setup", From 8e0c9201a566f2276602f5cf12159b8f686cc2e0 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Tue, 7 Apr 2020 16:08:55 +0530 Subject: [PATCH 186/408] chore: remove duplicate db set --- frappe/email/doctype/newsletter/newsletter.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index a847940448..b022655699 100755 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -218,9 +218,6 @@ def send_newsletter(newsletter): try: doc = frappe.get_doc("Newsletter", newsletter) doc.queue_all() - doc.db_set("email_sent", 1) - doc.db_set("schedule_send", now_datetime()) - doc.db_set("scheduled_to_send", len(doc.recipients)) except: frappe.db.rollback() From 3d772709672a43a7546153f25cfb4a87b2a51e7a Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 7 Apr 2020 18:05:17 +0530 Subject: [PATCH 187/408] fix: set animate as true --- frappe/public/js/frappe/form/grid_row.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index 8cb7d7d664..31d62dc445 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -550,7 +550,7 @@ export default class GridRow { hide_form() { frappe.dom.unfreeze(); this.row.toggle(true); - frappe.utils.scroll_to(this.row, false, 15); + frappe.utils.scroll_to(this.row, true, 15); this.refresh(); if(cur_frm) cur_frm.cur_grid = null; this.wrapper.removeClass("grid-row-open"); From 0fdcce62a5ba3b67f9b31c4b4695ec34c1882694 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 7 Apr 2020 18:07:27 +0530 Subject: [PATCH 188/408] style: missing semicolon --- frappe/public/js/frappe/form/grid_row_form.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid_row_form.js b/frappe/public/js/frappe/form/grid_row_form.js index 47da96f4d7..f93640936f 100644 --- a/frappe/public/js/frappe/form/grid_row_form.js +++ b/frappe/public/js/frappe/form/grid_row_form.js @@ -9,7 +9,7 @@ export default class GridRowForm { var me = this; this.make_form(); this.form_area.empty(); - frappe.utils.scroll_to(0, false, 0, this.wrapper.find('.grid-form-body')) + frappe.utils.scroll_to(0, false, 0, this.wrapper.find('.grid-form-body')); this.layout = new frappe.ui.form.Layout({ fields: this.row.docfields, From 5b5d8031ce4978e0f0416b7d1716ef5c6f609772 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 7 Apr 2020 18:11:39 +0530 Subject: [PATCH 189/408] fix: use .get() to set fieldtype --- 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 1ce3d62c3c..d2c695c33d 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -507,7 +507,7 @@ class BaseDocument(object): value = values[fetch_from_fieldname] if _df.fieldtype == 'Small Text' or _df.fieldtype == 'Text' or _df.fieldtype == 'Data': fetch_from_df = frappe.get_meta(doctype).get_field(fetch_from_fieldname) - fetch_from_ft = fetch_from_df and fetch_from_df.fieldtype + fetch_from_ft = fetch_from_df.get('fieldtype') if fetch_from_ft == 'Text Editor' and value: value = unescape_html(strip_html(value)) From 10a9f3111da2c9db07bf10b459547ec1c97a567e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 19:07:35 +0530 Subject: [PATCH 190/408] feat: remove label as mandatory field --- .../doctype/desk_shortcut/desk_shortcut.json | 6 +-- frappe/public/js/frappe/widgets/new_widget.js | 37 ++++++++++++------- 2 files changed, 27 insertions(+), 16 deletions(-) diff --git a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json index d008a94834..9f8990732a 100644 --- a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json +++ b/frappe/desk/doctype/desk_shortcut/desk_shortcut.json @@ -6,9 +6,9 @@ "engine": "InnoDB", "field_order": [ "type", - "link_to", - "column_break_4", "label", + "column_break_4", + "link_to", "icon", "restrict_to_domain", "section_break_5", @@ -87,7 +87,7 @@ ], "istable": 1, "links": [], - "modified": "2020-03-31 14:29:37.021121", + "modified": "2020-04-07 19:04:23.645198", "modified_by": "Administrator", "module": "Desk", "name": "Desk Shortcut", diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index d0dabe6678..73ad401256 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -1,7 +1,7 @@ const WIDGET_DOCTYPE_MAP = { chart: "Desk Chart", shortcut: "Desk Shortcut", -} +}; export default class NewWidget { constructor(opts) { @@ -14,7 +14,7 @@ export default class NewWidget { } customize() { - return + return; } make() { @@ -24,18 +24,18 @@ export default class NewWidget { } get_title() { - return __(`New ${frappe.utils.to_title_case(this.type)}`) + return __(`New ${frappe.utils.to_title_case(this.type)}`); } make_widget() { this.widget = $(`
+ ${this.get_title()}
`); - this.body = this.widget + this.body = this.widget; } setup_events() { - this.widget.on('click', () => this.open_dialog()) + this.widget.on("click", () => this.open_dialog()); } delete() { @@ -43,26 +43,37 @@ export default class NewWidget { } open_dialog() { - let doctype = WIDGET_DOCTYPE_MAP[this.type] + let doctype = WIDGET_DOCTYPE_MAP[this.type]; if (!doctype) { - console.warn(`Could not find ${this.type}`) + console.warn(`Could not find ${this.type}`); } frappe.model.with_doctype(doctype, () => { + let fields = frappe.get_meta(doctype).fields; + + if (this.type == "shortcut") { + let fields_to_remove = ["label"]; + fields = fields.filter( + (df) => !fields_to_remove.includes(df.fieldname) + ); + } + let new_dialog = new frappe.ui.Dialog({ title: this.get_title(), - fields: frappe.get_meta(doctype).fields, + fields: fields, primary_action: (data) => { - if (this.type == 'chart' && !data.label) { + if (this.type == "chart" && !data.label) { data.label = data.chart_name; } - if (this.type == 'shortcut' && !data.label) { + if (this.type == "shortcut" && !data.label) { data.label = data.link_to; } - data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`; + data.name = `${this.type}-${ + this.label + }-${frappe.utils.get_random(20)}`; new_dialog.hide(); this.on_create(data); @@ -70,7 +81,7 @@ export default class NewWidget { primary_action_label: __("Add"), }); - new_dialog.show() + new_dialog.show(); }); } -} \ No newline at end of file +} From 009c2cad949421622c92737dbaac5afbc4a94ce2 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 20:21:57 +0530 Subject: [PATCH 191/408] feat: cleanup --- frappe/public/js/frappe/views/desktop/desktop.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 3311af301f..249308a79f 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -141,7 +141,6 @@ class DesktopPage { } show() { - console.log("Showing ", this.page_name) frappe.desk_page = this; this.page.show(); } From 749f3f60216d42cf95295e0e3551ede4251cc437 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 20:22:17 +0530 Subject: [PATCH 192/408] feat: object oriented new widget --- frappe/public/js/frappe/widgets/new_widget.js | 202 ++++++++++++++---- .../public/js/frappe/widgets/widget_group.js | 6 +- 2 files changed, 163 insertions(+), 45 deletions(-) diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index 73ad401256..1fd019cb73 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -1,18 +1,9 @@ -const WIDGET_DOCTYPE_MAP = { - chart: "Desk Chart", - shortcut: "Desk Shortcut", -}; - -export default class NewWidget { +export class NewWidget { constructor(opts) { Object.assign(this, opts); this.make(); } - refresh() { - // - } - customize() { return; } @@ -42,46 +33,173 @@ export default class NewWidget { this.widget.remove(); } + get_fields() { + // + } + + process_data(data) { + return data + } + + setup_dialog_events() { + // + } + open_dialog() { - let doctype = WIDGET_DOCTYPE_MAP[this.type]; + this.dialog = new frappe.ui.Dialog({ + title: this.get_title(), + fields: this.get_fields(), + primary_action: (data) => { + data = this.process_data(data); + this.dialog.hide(); + this.on_create(data); + }, + primary_action_label: __("Add"), + }); - if (!doctype) { - console.warn(`Could not find ${this.type}`); - } + this.setup_dialog_events(); - frappe.model.with_doctype(doctype, () => { - let fields = frappe.get_meta(doctype).fields; + this.dialog.show(); + } - if (this.type == "shortcut") { - let fields_to_remove = ["label"]; - fields = fields.filter( - (df) => !fields_to_remove.includes(df.fieldname) - ); - } + hide_field(fieldname) { + this.dialog.set_df_property(fieldname, "hidden", true); + } - let new_dialog = new frappe.ui.Dialog({ - title: this.get_title(), - fields: fields, - primary_action: (data) => { - if (this.type == "chart" && !data.label) { - data.label = data.chart_name; + show_field(fieldname) { + this.dialog.set_df_property(fieldname, "hidden", false); + } +} + +export class NewChartWidget extends NewWidget { + constructor(opts) { + super(opts); + } + + get_fields() { + return [ + { + fieldtype: "Link", + fieldname: "chart_name", + label: "Chart Name", + options: "Dashboard Chart", + reqd: 1, + }, + { + fieldtype: "Data", + fieldname: "label", + label: "Label" + }, + ]; + } + + process_data(data) { + data.label = data.chart_name; + return data + } +} + +export class NewShortcutWidget extends NewWidget { + constructor(opts) { + super(opts); + window.neww = this; + } + + get_fields() { + return [ + { + fieldtype: "Select", + fieldname: "type", + label: "Type", + reqd: 1, + options: "DocType\nReport\nPage", + onchange: () => { + if (this.dialog.get_value("type") == "DocType") { + this.dialog.fields_dict.link_to.get_query = () => { + return { filters: { "istable": false }} + } } - - if (this.type == "shortcut" && !data.label) { - data.label = data.link_to; - } - - data.name = `${this.type}-${ - this.label - }-${frappe.utils.get_random(20)}`; - - new_dialog.hide(); - this.on_create(data); }, - primary_action_label: __("Add"), - }); + }, + { + fieldtype: "Column Break", + fieldname: "column_break_4", + }, + { + fieldtype: "Dynamic Link", + fieldname: "link_to", + label: "Link To", + reqd: 1, + options: "type", + onchange: () => { + let dg = this.dialog; + if (this.dialog.get_value("type") == "DocType") { + this.show_field('count_section_break'); + this.show_field('filters_section_break'); + this.setup_filter(); + } else { + this.hide_field('count_section_break'); + this.hide_field('filters_section_break'); + } + }, + }, + { + fieldtype: "Section Break", + fieldname: "count_section_break", + label: "Count Filter", + hidden: 1, + }, + { + fieldtype: "Color", + fieldname: "color", + label: "Color", + }, + { + fieldtype: "Column Break", + fieldname: "column_break_3", + }, + { + fieldtype: "Data", + fieldname: "format", + label: "Format", + description: "For Example: {} Open", + }, + { + fieldtype: "Section Break", + fieldname: "filters_section_break", + hidden: 1, + }, + { + fieldtype: "HTML", + fieldname: "filter_area", + }, + ]; + } - new_dialog.show(); + process_data(data) { + data.label = data.link_to; + + return data + } + + setup_filter() { + if (this.filter_group) { + this.filter_group.wrapper.empty(); + delete this.filter_group; + } + this.filter_group = new frappe.ui.FilterGroup({ + parent: this.dialog.get_field('filter_area').$wrapper, + doctype: this.dialog.get_value('link_to'), + on_change: () => {}, }); } } + +export function get_new_widget_class(type) { + const widget_map = { + chart: NewChartWidget, + shortcut: NewShortcutWidget, + }; + + return widget_map[type] || NewWidget; +} diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 41ea7fae71..cc57b66666 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -3,7 +3,7 @@ import BaseWidget from "../widgets/base_widget"; import ShortcutWidget from "../widgets/shortcut_widget"; import LinksWidget from "../widgets/links_widget"; import OnboardingWidget from "../widgets/onboarding_widget"; -import NewWidget from "../widgets/new_widget"; +import { get_new_widget_class } from "../widgets/new_widget"; frappe.provide('frappe.widget') @@ -13,7 +13,6 @@ const widget_factory = { shortcut: ShortcutWidget, links: LinksWidget, onboarding: OnboardingWidget, - new: NewWidget }; export default class WidgetGroup { @@ -93,7 +92,8 @@ export default class WidgetGroup { : Number.POSITIVE_INFINITY; if (this.widgets_list.length < max) { - this.new_widget = new NewWidget({ + const new_widget_class = get_new_widget_class(this.type) + this.new_widget = new new_widget_class({ container: this.body, type: this.type, on_create: (config) => { From 069db06b724705aa5c9677a256da6d24177d0b08 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 21:08:13 +0530 Subject: [PATCH 193/408] chore: cleanup --- frappe/desk/desktop.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 1edc2e57ec..1f3a83735b 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -326,7 +326,6 @@ def save_customization(page, config): "charts_label": original_page.charts_label, "cards_label": original_page.cards_label, "shortcuts_label": original_page.shortcuts_label, - "charts_label": original_page.charts_label, "icon": original_page.icon, "module": original_page.module, "developer_mode_only": original_page.developer_mode_only, @@ -334,7 +333,6 @@ def save_customization(page, config): }) config = frappe._dict(loads(config)) - page_doc.charts = prepare_widget(config.charts, "Desk Chart", "charts") page_doc.shortcuts = prepare_widget(config.shortcuts, "Desk Shortcut", "shortcuts") page_doc.cards = prepare_widget(config.cards, "Desk Card", "cards") From 33cdd41b101448250f83f57d5c4672fbd1f7d805 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 21:08:30 +0530 Subject: [PATCH 194/408] feat: set a random name --- frappe/public/js/frappe/widgets/new_widget.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index 1fd019cb73..2a4999fd0f 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -51,6 +51,8 @@ export class NewWidget { fields: this.get_fields(), primary_action: (data) => { data = this.process_data(data); + data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`; + this.dialog.hide(); this.on_create(data); }, From 4ceed892d02928112f7330f80d2352f494fe8866 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 21:09:15 +0530 Subject: [PATCH 195/408] feat: setup new widget class for charts and shortcut --- frappe/public/js/frappe/widgets/new_widget.js | 32 ++++++++++++++++--- 1 file changed, 27 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index 2a4999fd0f..1fbe3384ad 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -136,9 +136,17 @@ export class NewShortcutWidget extends NewWidget { onchange: () => { let dg = this.dialog; if (this.dialog.get_value("type") == "DocType") { - this.show_field('count_section_break'); - this.show_field('filters_section_break'); - this.setup_filter(); + let doctype = this.dialog.get_value("link_to") + frappe.db.get_value("DocType", doctype, "issingle").then(res => { + if (res.message.issingle) { + this.hide_field('count_section_break'); + this.hide_field('filters_section_break'); + } else { + this.setup_filter(doctype); + this.show_field('count_section_break'); + this.show_field('filters_section_break'); + } + }) } else { this.hide_field('count_section_break'); this.hide_field('filters_section_break'); @@ -179,21 +187,35 @@ export class NewShortcutWidget extends NewWidget { } process_data(data) { + let stats_filter = {}; + let filters = this.filter_group.get_filters(); + filters.forEach(arr => { + stats_filter[arr[1]] = [arr[2], arr[3]] + }); + + data.stats_filter = JSON.stringify(stats_filter); data.label = data.link_to; return data } - setup_filter() { + setup_filter(doctype) { if (this.filter_group) { this.filter_group.wrapper.empty(); delete this.filter_group; } + + this.filters = [] + this.filter_group = new frappe.ui.FilterGroup({ parent: this.dialog.get_field('filter_area').$wrapper, - doctype: this.dialog.get_value('link_to'), + doctype: doctype, on_change: () => {}, }); + + frappe.model.with_doctype(doctype, () => { + this.filter_group.add_filters_to_filter_group(this.filters); + }); } } From 24e0174fcfe9535c8e0e2320c3228a33835b8924 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 8 Apr 2020 12:35:33 +0530 Subject: [PATCH 196/408] fix: enable validations for email and phone data fields --- frappe/contacts/doctype/contact/contact.json | 6 +- .../doctype/contact_phone/contact_phone.json | 5 +- frappe/core/doctype/user/user.json | 6 +- .../core/doctype/user_email/user_email.json | 244 ++----- .../doctype/email_account/email_account.json | 3 +- .../contact_us_settings.json | 651 ++++-------------- 6 files changed, 185 insertions(+), 730 deletions(-) diff --git a/frappe/contacts/doctype/contact/contact.json b/frappe/contacts/doctype/contact/contact.json index 7dd5aad4ce..2e2fb6df67 100644 --- a/frappe/contacts/doctype/contact/contact.json +++ b/frappe/contacts/doctype/contact/contact.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_events_in_timeline": 1, "allow_import": 1, "allow_rename": 1, @@ -115,6 +116,7 @@ "label": "Phone", "oldfieldname": "contact_no", "oldfieldtype": "Data", + "options": "Phone", "read_only": 1 }, { @@ -200,6 +202,7 @@ "fieldname": "mobile_no", "fieldtype": "Data", "label": "Mobile No", + "options": "Phone", "read_only": 1 }, { @@ -245,7 +248,8 @@ "icon": "fa fa-user", "idx": 1, "image_field": "image", - "modified": "2019-10-10 22:04:41.070479", + "links": [], + "modified": "2020-04-06 18:25:28.223693", "modified_by": "Administrator", "module": "Contacts", "name": "Contact", diff --git a/frappe/contacts/doctype/contact_phone/contact_phone.json b/frappe/contacts/doctype/contact_phone/contact_phone.json index 3fb203ed69..5412e4a1b7 100644 --- a/frappe/contacts/doctype/contact_phone/contact_phone.json +++ b/frappe/contacts/doctype/contact_phone/contact_phone.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-08-02 13:10:37.890214", "doctype": "DocType", "editable_grid": 1, @@ -14,6 +15,7 @@ "fieldtype": "Data", "in_list_view": 1, "label": "Number", + "options": "Phone", "reqd": 1 }, { @@ -34,7 +36,8 @@ } ], "istable": 1, - "modified": "2019-09-24 17:47:50.375326", + "links": [], + "modified": "2020-04-06 18:28:10.486220", "modified_by": "Administrator", "module": "Contacts", "name": "Contact Phone", diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 5ebde7e7bd..7ed14e094c 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -238,12 +238,14 @@ { "fieldname": "phone", "fieldtype": "Data", - "label": "Phone" + "label": "Phone", + "options": "Phone" }, { "fieldname": "mobile_no", "fieldtype": "Data", "label": "Mobile No", + "options": "Phone", "unique": 1 }, { @@ -588,7 +590,7 @@ "image_field": "user_image", "links": [], "max_attachments": 5, - "modified": "2020-03-23 22:59:26.154985", + "modified": "2020-04-08 12:27:36.716490", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user_email/user_email.json b/frappe/core/doctype/user_email/user_email.json index 16e6b5a24e..b106ed4a19 100644 --- a/frappe/core/doctype/user_email/user_email.json +++ b/frappe/core/doctype/user_email/user_email.json @@ -1,201 +1,63 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2016-03-30 10:04:25.828742", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, + "actions": [], + "creation": "2016-03-30 10:04:25.828742", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "email_account", + "email_id", + "column_break_3", + "awaiting_password", + "enable_outgoing" + ], "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": 1, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "fieldname": "email_account", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Email Account", + "options": "Email Account", + "reqd": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, "fetch_from": "email_account.email_id", - "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": "", - "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": "email_id", + "fieldtype": "Data", + "label": "Email ID", + "options": "Email", + "read_only": 1 + }, { - "allow_bulk_edit": 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_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "email_account.awaiting_password", - "fieldname": "awaiting_password", - "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": "Awaiting Password", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "default": "0", + "fetch_from": "email_account.awaiting_password", + "fieldname": "awaiting_password", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Awaiting Password", + "read_only": 1 + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fetch_from": "email_account.enable_outgoing", - "fieldname": "enable_outgoing", - "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": "Enable Outgoing", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 1, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "default": "0", + "fetch_from": "email_account.enable_outgoing", + "fieldname": "enable_outgoing", + "fieldtype": "Check", + "label": "Enable Outgoing", + "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": "2018-05-25 22:43:34.045787", - "modified_by": "Administrator", - "module": "Core", - "name": "User Email", - "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": "2020-04-06 19:19:12.130246", + "modified_by": "Administrator", + "module": "Core", + "name": "User Email", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index e724102fdf..5c57a7f35d 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -66,6 +66,7 @@ "fieldtype": "Data", "in_global_search": 1, "label": "Email Address", + "options": "Email", "reqd": 1 }, { @@ -410,7 +411,7 @@ ], "icon": "fa fa-inbox", "links": [], - "modified": "2019-12-18 15:56:39.744520", + "modified": "2020-04-06 19:20:50.491146", "modified_by": "Administrator", "module": "Email", "name": "Email Account", diff --git a/frappe/website/doctype/contact_us_settings/contact_us_settings.json b/frappe/website/doctype/contact_us_settings/contact_us_settings.json index 3f61032c63..4e7d1cff1b 100644 --- a/frappe/website/doctype/contact_us_settings/contact_us_settings.json +++ b/frappe/website/doctype/contact_us_settings/contact_us_settings.json @@ -1,559 +1,142 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-02-21 20:12:42", - "custom": 0, - "description": "Settings for Contact Us Page", - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, + "actions": [], + "creation": "2013-02-21 20:12:42", + "description": "Settings for Contact Us Page", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "introduction_section", + "forward_to_email", + "heading", + "introduction", + "query_options", + "address", + "address_title", + "address_line1", + "address_line2", + "city", + "state", + "pincode", + "country", + "column_break_14", + "phone", + "email_id", + "skype" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "introduction_section", - "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": "Introduction", - "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": "introduction_section", + "fieldtype": "Section Break", + "label": "Introduction" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Send enquiries to this email address", - "fieldname": "forward_to_email", - "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": "Forward To Email Address", - "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": "Send enquiries to this email address", + "fieldname": "forward_to_email", + "fieldtype": "Data", + "label": "Forward To Email Address", + "options": "Email" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Default: \"Contact Us\"", - "fieldname": "heading", - "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": "Heading", - "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: \"Contact Us\"", + "fieldname": "heading", + "fieldtype": "Data", + "label": "Heading" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Introductory information for the Contact Us Page", - "fieldname": "introduction", - "fieldtype": "Text 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": "Introduction", - "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": "Introductory information for the Contact Us Page", + "fieldname": "introduction", + "fieldtype": "Text Editor", + "label": "Introduction" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Contact options, like \"Sales Query, Support Query\" etc each on a new line or separated by commas.", - "fieldname": "query_options", - "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": "Query Options", - "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": "Contact options, like \"Sales Query, Support Query\" etc each on a new line or separated by commas.", + "fieldname": "query_options", + "fieldtype": "Small Text", + "label": "Query Options" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address", - "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": "Address", - "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": "address", + "fieldtype": "Section Break", + "label": "Address" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 1, - "collapsible": 0, - "columns": 0, - "fieldname": "address_title", - "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": "Address Title", - "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 - }, + "bold": 1, + "fieldname": "address_title", + "fieldtype": "Data", + "label": "Address Title" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_line1", - "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": "Address Line 1", - "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": "address_line1", + "fieldtype": "Data", + "label": "Address Line 1" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "address_line2", - "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": "Address Line 2", - "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": "address_line2", + "fieldtype": "Data", + "label": "Address Line 2" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "city", - "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": "City", - "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": "city", + "fieldtype": "Data", + "label": "City" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "state", - "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": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "state", + "fieldtype": "Data", + "label": "State" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "pincode", - "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": "Pincode", - "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": "pincode", + "fieldtype": "Data", + "label": "Pincode" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "country", - "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": "Country", - "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": "country", + "fieldtype": "Data", + "label": "Country" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "column_break_14", - "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_14", + "fieldtype": "Column Break" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "phone", - "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": "Phone", - "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": "phone", + "fieldtype": "Data", + "label": "Phone", + "options": "Phone" + }, { - "allow_bulk_edit": 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, - "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_id", + "fieldtype": "Data", + "label": "Email Id", + "options": "Email" + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "skype", - "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": "Skype", - "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": "skype", + "fieldtype": "Data", + "label": "Skype" } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2017-09-04 21:41:55.580325", - "modified_by": "Administrator", - "module": "Website", - "name": "Contact Us Settings", - "owner": "Administrator", + ], + "icon": "fa fa-cog", + "idx": 1, + "issingle": 1, + "links": [], + "modified": "2020-04-06 19:17:46.083764", + "modified_by": "Administrator", + "module": "Website", + "name": "Contact Us Settings", + "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": "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, - "show_name_in_global_search": 0, - "track_changes": 1, - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file From 57055b462ef10b5dc9b101e4b252ce0f5b281ca8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 7 Apr 2020 21:09:27 +0530 Subject: [PATCH 197/408] feat: set customize mode for new widgets by default --- frappe/public/js/frappe/widgets/base_widget.js | 1 - frappe/public/js/frappe/widgets/shortcut_widget.js | 2 ++ frappe/public/js/frappe/widgets/widget_group.js | 2 ++ 3 files changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 93093de84f..63fc49a166 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -63,7 +63,6 @@ export default class Widget { } make() { - this.in_customize_mode = false; this.make_widget(); this.widget.appendTo(this.container); } diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index 8bedc556ac..d96f135728 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -42,6 +42,8 @@ export default class ShortcutWidget extends Widget { } set_actions() { + if (this.in_customize_mode) return + this.widget.addClass('shortcut-widget-box'); const get_filter = new Function(`return ${this.stats_filter}`) if (this.type == "DocType" && this.stats_filter) { diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index cc57b66666..f121939748 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -101,6 +101,8 @@ export default class WidgetGroup { this.new_widget.delete(); delete this.new_widget; + config.in_customize_mode = 1; + // Add new widget and customize it let wid = this.add_widget(config); wid.customize(this.options); From 040abe2e2855a8e435fd37df1e7662737e285db3 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 13:53:49 +0530 Subject: [PATCH 198/408] refactor: layout for new shortcut widget --- frappe/public/js/frappe/widgets/new_widget.js | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index 1fbe3384ad..e85ecf1718 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -155,10 +155,20 @@ export class NewShortcutWidget extends NewWidget { }, { fieldtype: "Section Break", - fieldname: "count_section_break", + fieldname: "filters_section_break", label: "Count Filter", hidden: 1, }, + { + fieldtype: "HTML", + fieldname: "filter_area", + }, + { + fieldtype: "Section Break", + fieldname: "count_section_break", + label: "Count Customizations", + hidden: 1, + }, { fieldtype: "Color", fieldname: "color", @@ -174,15 +184,6 @@ export class NewShortcutWidget extends NewWidget { label: "Format", description: "For Example: {} Open", }, - { - fieldtype: "Section Break", - fieldname: "filters_section_break", - hidden: 1, - }, - { - fieldtype: "HTML", - fieldname: "filter_area", - }, ]; } From bd8c55110d999f11a74210e35bd2c1967826c66b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 13:54:01 +0530 Subject: [PATCH 199/408] fix: add color option to get_config --- frappe/public/js/frappe/widgets/shortcut_widget.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index d96f135728..a492aaf89b 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -21,6 +21,7 @@ export default class ShortcutWidget extends Widget { label: this.label, format: this.format, link_to: this.link_to, + color: this.color, restrict_to_domain: this.restrict_to_domain, stats_filter: this.stats_filter, type: this.type @@ -42,6 +43,7 @@ export default class ShortcutWidget extends Widget { } set_actions() { + console.log(this.in_customize_mode); if (this.in_customize_mode) return this.widget.addClass('shortcut-widget-box'); From 284c70809b2cf03314aedb960889d1561e51cb52 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 8 Apr 2020 14:20:52 +0530 Subject: [PATCH 200/408] fix: Translatable strings with trailing spaces --- .../customize_form_field.json | 4 +- .../doctype/oauth_client/oauth_client.json | 956 +++++++++--------- frappe/model/naming.py | 6 +- frappe/public/js/frappe/form/workflow.js | 26 +- .../emails/delete_data_confirmation.html | 3 +- 5 files changed, 499 insertions(+), 496 deletions(-) diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index 57b4cec23b..34778a76e9 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -358,7 +358,7 @@ "default": "0", "fieldname": "allow_in_quick_entry", "fieldtype": "Check", - "label": " Allow in Quick Entry " + "label": "Allow in Quick Entry" }, { "fieldname": "property_depends_on_section", @@ -385,7 +385,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-03-16 14:53:40.619043", + "modified": "2020-04-07 14:53:40.619043", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/integrations/doctype/oauth_client/oauth_client.json b/frappe/integrations/doctype/oauth_client/oauth_client.json index 47ede6e280..d0d45c36ab 100644 --- a/frappe/integrations/doctype/oauth_client/oauth_client.json +++ b/frappe/integrations/doctype/oauth_client/oauth_client.json @@ -1,517 +1,517 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "", - "beta": 0, - "creation": "2016-08-24 14:07:21.955052", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "Document", - "editable_grid": 1, - "engine": "InnoDB", + "allow_copy": 0, + "allow_guest_to_view": 0, + "allow_import": 0, + "allow_rename": 0, + "autoname": "", + "beta": 0, + "creation": "2016-08-24 14:07:21.955052", + "custom": 0, + "docstatus": 0, + "doctype": "DocType", + "document_type": "Document", + "editable_grid": 1, + "engine": "InnoDB", "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "allow_bulk_edit": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 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, + "allow_bulk_edit": 0, + "allow_on_submit": 0, + "bold": 0, + "collapsible": 0, + "columns": 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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 - }, + }, { - "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, + "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 } - ], - "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-10-05 21:07:39.476360", - "modified_by": "Administrator", - "module": "Integrations", - "name": "OAuth Client", - "name_case": "", - "owner": "Administrator", + ], + "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", + "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, + "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", - "title_field": "app_name", - "track_changes": 1, + ], + "quick_entry": 0, + "read_only": 0, + "read_only_onload": 0, + "show_name_in_global_search": 0, + "sort_field": "modified", + "sort_order": "DESC", + "title_field": "app_name", + "track_changes": 1, "track_seen": 0 } \ No newline at end of file diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 78d2c462e1..ffaf84e2b3 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -110,7 +110,11 @@ def make_autoname(key="", doctype="", doc=""): if "#" not in key: key = key + ".#####" elif "." not in key: - frappe.throw(_("Invalid naming series (. missing)") + (_(" for {0}").format(doctype) if doctype else "")) + error_message = _("Invalid naming series (. missing)") + if doctype: + error_message = _("Invalid naming series (. missing) for {0}").format(doctype) + + frappe.throw(error_message) parts = key.split('.') n = parse_naming_series(parts, doctype, doc) diff --git a/frappe/public/js/frappe/form/workflow.js b/frappe/public/js/frappe/form/workflow.js index 4eb33a5f28..4c59e8219b 100644 --- a/frappe/public/js/frappe/form/workflow.js +++ b/frappe/public/js/frappe/form/workflow.js @@ -29,20 +29,18 @@ frappe.ui.form.States = Class.extend({ }); frappe.workflow.get_transitions(me.frm.doc).then((transitions) => { - var next_html = $.map(transitions, - function(d) { - return d.action.bold() + __(" by Role ") + d.allowed; - }).join(", ") || __("None: End of Workflow").bold(); + const next_actions = $.map(transitions, d => `${d.action.bold()} ${__("by Role")} ${d.allowed}`) + .join(", ") || __("None: End of Workflow").bold(); + + const document_editable_by = frappe.workflow.get_document_state(me.frm.doctype, state).allow_edit.bold(); + + $(d.body).html(` +

${__("Current status")}: ${state.bold()}

+

${__("Document is only editable by users with role")}: ${document_editable_by}

+

${__("Next actions")}: ${next_actions}

+

${__("{0}: Other permission rules may also apply", [__('Note').bold()])}

+ `).css({padding: '15px'}); - $(d.body).html("

"+__("Current status")+": " + state.bold() + "

" - + "

"+__("Document is only editable by users of role")+": " - + frappe.workflow.get_document_state(me.frm.doctype, - state).allow_edit.bold() + "

" - + "

"+__("Next actions")+": "+ next_html +"

" - + (me.frm.doc.__islocal ? ("
" - +__("Workflow will start after saving.")+"
") : "") - + "

"+__("Note: Other permission rules may also apply")+"

" - ).css({padding: '15px'}); d.show(); }); }, true); @@ -115,7 +113,7 @@ frappe.ui.form.States = Class.extend({ } else { this.setup_btn(added); } - + }); }, diff --git a/frappe/templates/emails/delete_data_confirmation.html b/frappe/templates/emails/delete_data_confirmation.html index e483bd59ed..126d8bcb4b 100644 --- a/frappe/templates/emails/delete_data_confirmation.html +++ b/frappe/templates/emails/delete_data_confirmation.html @@ -7,5 +7,6 @@ {{ _("Confirm Request") }}

- {{_("You can also copy-paste this ")}} {{_("Verification Link")}}{{_(" to your browser")}} + {% set verification_link = '{{ _("Verification Link") }}' %} + {{_("You can also copy-paste this {0} to your browser").format(verification_link) }}

\ No newline at end of file From 58fe724428f3020949a90edefd667f3fae5354db Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 8 Apr 2020 14:24:44 +0530 Subject: [PATCH 201/408] refactor: allow system managers to view notification settings list for all users --- .../notification_settings/notification_settings.js | 9 +++++++++ .../notification_settings/notification_settings.py | 9 ++++++++- 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_settings/notification_settings.js b/frappe/desk/doctype/notification_settings/notification_settings.js index d4e3b08def..863840d155 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.js +++ b/frappe/desk/doctype/notification_settings/notification_settings.js @@ -8,5 +8,14 @@ frappe.ui.form.on('Notification Settings', { route: '#modules/Settings', type: 'Custom' }); + }, + + refresh: (frm) => { + if (frappe.user.has_role(['System Manager', 'Administrator'])) { + frm.add_custom_button('Go to Notification Settings List', () => { + frappe.set_route('List', 'Notification Settings'); + }); + } } + }); diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 233d15e3ce..6b5a13ee27 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -62,7 +62,14 @@ def get_subscribed_documents(): def get_permission_query_conditions(user): if not user: user = frappe.session.user - return '''(`tabNotification Settings`.user = '{user}')'''.format(user=user) + if user == 'Administrator': + return + + roles = frappe.get_roles(user) + if "System Manager" in roles: + return '''(`tabNotification Settings`.name != 'Administrator')''' + + return '''(`tabNotification Settings`.name = '{user}')'''.format(user=user) @frappe.whitelist() def set_seen_value(value, user): From 65abe3d73458090e7e5fe93feada7c5a02c7c18d Mon Sep 17 00:00:00 2001 From: PriyankaGangar Date: Mon, 2 Mar 2020 11:10:34 +0530 Subject: [PATCH 202/408] fix: update gender and update salutations --- .../desk/page/setup_wizard/install_fixtures.py | 16 +++++++++++----- frappe/patches.txt | 1 + .../patches/v8_0/update_gender_and_salutation.py | 5 +++-- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index e7e147fb7d..c857bd077f 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -8,16 +8,22 @@ from frappe import _ from frappe.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes def install(): - update_genders_and_salutations() + update_genders() + update_salutations() update_global_search_doctypes() setup_email_linking() @frappe.whitelist() -def update_genders_and_salutations(): - default_genders = [_("Male"), _("Female"), _("Other")] - default_salutations = [_("Mr"), _("Ms"), _('Mx'), _("Dr"), _("Mrs"), _("Madam"), _("Miss"), _("Master"), _("Prof")] +def update_genders(): + default_genders = [_("Male"), _("Female"), _("Other"),_("Transgender"), _("Genderqueer"), _("Non-Conforming"),_("Prefer not to say")] records = [{'doctype': 'Gender', 'gender': d} for d in default_genders] - records += [{'doctype': 'Salutation', 'salutation': d} for d in default_salutations] + for record in records: + frappe.get_doc(record).insert(ignore_permissions=True, ignore_if_duplicate=True) + +@frappe.whitelist() +def update_salutations(): + default_salutations = [_("Mr"), _("Ms"), _('Mx'), _("Dr"), _("Mrs"), _("Madam"), _("Miss"), _("Master"), _("Prof")] + records = [{'doctype': 'Salutation', 'salutation': d} for d in default_salutations] for record in records: doc = frappe.new_doc(record.get("doctype")) doc.update(record) diff --git a/frappe/patches.txt b/frappe/patches.txt index fc4f3ae998..0e02423639 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -271,3 +271,4 @@ execute:frappe.delete_doc_if_exists('DocType', 'GSuite Templates') execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Account') execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings') frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats +execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders() diff --git a/frappe/patches/v8_0/update_gender_and_salutation.py b/frappe/patches/v8_0/update_gender_and_salutation.py index c990e9c4aa..bcd9d4cbd7 100644 --- a/frappe/patches/v8_0/update_gender_and_salutation.py +++ b/frappe/patches/v8_0/update_gender_and_salutation.py @@ -2,7 +2,7 @@ from __future__ import unicode_literals import frappe -from frappe.desk.page.setup_wizard.install_fixtures import update_genders_and_salutations +from frappe.desk.page.setup_wizard.install_fixtures import update_genders, update_salutations def execute(): frappe.db.set_value("DocType", "Contact", "module", "Contacts") @@ -11,4 +11,5 @@ def execute(): frappe.reload_doc('contacts', 'doctype', 'gender') frappe.reload_doc('contacts', 'doctype', 'salutation') - update_genders_and_salutations() \ No newline at end of file + update_genders() + update_salutations() \ No newline at end of file From a440b954d5e2465d47f698e03af13fd9b098c9e9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 18:18:18 +0530 Subject: [PATCH 203/408] fix: code cleanup --- frappe/public/js/frappe/widgets/shortcut_widget.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index a492aaf89b..e36120ebda 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -43,7 +43,6 @@ export default class ShortcutWidget extends Widget { } set_actions() { - console.log(this.in_customize_mode); if (this.in_customize_mode) return this.widget.addClass('shortcut-widget-box'); From 7a7f7af38056fbfa3ba82a0db78ad417ad6f09bb Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 18:18:32 +0530 Subject: [PATCH 204/408] fix: set margin after widgets are added --- frappe/public/js/frappe/views/desktop/desktop.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 2eb619d910..8ff7d7e167 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -200,6 +200,11 @@ class DesktopPage { let create_shortcuts_and_cards = () => { this.data.shortcuts.items.length && this.make_shortcuts(); this.data.cards.items.length && this.make_cards(); + + if (this.allow_customization) { + // Move the widget group up to align with labels if customization is allowed + $('.desk-page .widget-group:visible:first').css('margin-top', '-25px'); + } }; if (!this.sections["onboarding"] && this.data.charts.items.length) { @@ -209,11 +214,6 @@ class DesktopPage { } else { create_shortcuts_and_cards(); } - - if (this.allow_customization) { - // Move the widget group up to align with labels if customization is allowed - $('.desk-page .widget-group:visible:first').css('margin-top', '-25px'); - } } get_data() { From d0b0b68311f6586b21e81b5030d48085e8afcba6 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 18:33:44 +0530 Subject: [PATCH 205/408] feat: separate new widget and widget dialog --- frappe/public/js/frappe/widgets/new_widget.js | 213 ++---------------- .../public/js/frappe/widgets/widget_dialog.js | 204 +++++++++++++++++ .../public/js/frappe/widgets/widget_group.js | 5 +- 3 files changed, 222 insertions(+), 200 deletions(-) create mode 100644 frappe/public/js/frappe/widgets/widget_dialog.js diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index e85ecf1718..d7b05869b1 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -1,4 +1,6 @@ -export class NewWidget { +import get_dialog_constructor from './widget_dialog.js' + +export default class NewWidget { constructor(opts) { Object.assign(this, opts); this.make(); @@ -29,202 +31,19 @@ export class NewWidget { this.widget.on("click", () => this.open_dialog()); } + open_dialog() { + const dialog_class = get_dialog_constructor(this.type) + this.dialog = new dialog_class({ + label: this.label, + type: this.type, + values: false, + on_create: this.on_create, + }) + + this.dialog.make(); + } + delete() { this.widget.remove(); } - - get_fields() { - // - } - - process_data(data) { - return data - } - - setup_dialog_events() { - // - } - - open_dialog() { - this.dialog = new frappe.ui.Dialog({ - title: this.get_title(), - fields: this.get_fields(), - primary_action: (data) => { - data = this.process_data(data); - data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`; - - this.dialog.hide(); - this.on_create(data); - }, - primary_action_label: __("Add"), - }); - - this.setup_dialog_events(); - - this.dialog.show(); - } - - hide_field(fieldname) { - this.dialog.set_df_property(fieldname, "hidden", true); - } - - show_field(fieldname) { - this.dialog.set_df_property(fieldname, "hidden", false); - } -} - -export class NewChartWidget extends NewWidget { - constructor(opts) { - super(opts); - } - - get_fields() { - return [ - { - fieldtype: "Link", - fieldname: "chart_name", - label: "Chart Name", - options: "Dashboard Chart", - reqd: 1, - }, - { - fieldtype: "Data", - fieldname: "label", - label: "Label" - }, - ]; - } - - process_data(data) { - data.label = data.chart_name; - return data - } -} - -export class NewShortcutWidget extends NewWidget { - constructor(opts) { - super(opts); - window.neww = this; - } - - get_fields() { - return [ - { - fieldtype: "Select", - fieldname: "type", - label: "Type", - reqd: 1, - options: "DocType\nReport\nPage", - onchange: () => { - if (this.dialog.get_value("type") == "DocType") { - this.dialog.fields_dict.link_to.get_query = () => { - return { filters: { "istable": false }} - } - } - }, - }, - { - fieldtype: "Column Break", - fieldname: "column_break_4", - }, - { - fieldtype: "Dynamic Link", - fieldname: "link_to", - label: "Link To", - reqd: 1, - options: "type", - onchange: () => { - let dg = this.dialog; - if (this.dialog.get_value("type") == "DocType") { - let doctype = this.dialog.get_value("link_to") - frappe.db.get_value("DocType", doctype, "issingle").then(res => { - if (res.message.issingle) { - this.hide_field('count_section_break'); - this.hide_field('filters_section_break'); - } else { - this.setup_filter(doctype); - this.show_field('count_section_break'); - this.show_field('filters_section_break'); - } - }) - } else { - this.hide_field('count_section_break'); - this.hide_field('filters_section_break'); - } - }, - }, - { - fieldtype: "Section Break", - fieldname: "filters_section_break", - label: "Count Filter", - hidden: 1, - }, - { - fieldtype: "HTML", - fieldname: "filter_area", - }, - { - fieldtype: "Section Break", - fieldname: "count_section_break", - label: "Count Customizations", - hidden: 1, - }, - { - fieldtype: "Color", - fieldname: "color", - label: "Color", - }, - { - fieldtype: "Column Break", - fieldname: "column_break_3", - }, - { - fieldtype: "Data", - fieldname: "format", - label: "Format", - description: "For Example: {} Open", - }, - ]; - } - - process_data(data) { - let stats_filter = {}; - let filters = this.filter_group.get_filters(); - filters.forEach(arr => { - stats_filter[arr[1]] = [arr[2], arr[3]] - }); - - data.stats_filter = JSON.stringify(stats_filter); - data.label = data.link_to; - - return data - } - - setup_filter(doctype) { - if (this.filter_group) { - this.filter_group.wrapper.empty(); - delete this.filter_group; - } - - this.filters = [] - - this.filter_group = new frappe.ui.FilterGroup({ - parent: this.dialog.get_field('filter_area').$wrapper, - doctype: doctype, - on_change: () => {}, - }); - - frappe.model.with_doctype(doctype, () => { - this.filter_group.add_filters_to_filter_group(this.filters); - }); - } -} - -export function get_new_widget_class(type) { - const widget_map = { - chart: NewChartWidget, - shortcut: NewShortcutWidget, - }; - - return widget_map[type] || NewWidget; -} +} \ No newline at end of file diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js new file mode 100644 index 0000000000..2a4bb864a0 --- /dev/null +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -0,0 +1,204 @@ +class WidgetDialog { + constructor(opts) { + Object.assign(this, opts); + } + + make() { + this.dialog = new frappe.ui.Dialog({ + title: this.get_title(), + fields: this.get_fields(), + primary_action: (data) => { + data = this.process_data(data); + data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`; + + this.dialog.hide(); + this.on_create(data); + }, + primary_action_label: __("Add"), + }); + + this.setup_dialog_events(); + + this.dialog.show(); + } + + get_title() { + return __(`New ${frappe.utils.to_title_case(this.type)}`); + } + + get_fields() { + // + } + + process_data(data) { + return data + } + + setup_dialog_events() { + // + } + + hide_field(fieldname) { + this.dialog.set_df_property(fieldname, "hidden", true); + } + + show_field(fieldname) { + this.dialog.set_df_property(fieldname, "hidden", false); + } +} + +class ChartDialog extends WidgetDialog { + constructor(opts) { + super(opts); + } + + get_fields() { + return [ + { + fieldtype: "Link", + fieldname: "chart_name", + label: "Chart Name", + options: "Dashboard Chart", + reqd: 1, + }, + { + fieldtype: "Data", + fieldname: "label", + label: "Label" + }, + ]; + } + + process_data(data) { + data.label = data.chart_name; + return data + } +} + +class ShortcutDialog extends WidgetDialog { + constructor(opts) { + super(opts); + window.neww = this; + } + + get_fields() { + return [ + { + fieldtype: "Select", + fieldname: "type", + label: "Type", + reqd: 1, + options: "DocType\nReport\nPage", + onchange: () => { + if (this.dialog.get_value("type") == "DocType") { + this.dialog.fields_dict.link_to.get_query = () => { + return { filters: { "istable": false }} + } + } + }, + }, + { + fieldtype: "Column Break", + fieldname: "column_break_4", + }, + { + fieldtype: "Dynamic Link", + fieldname: "link_to", + label: "Link To", + reqd: 1, + options: "type", + onchange: () => { + let dg = this.dialog; + if (this.dialog.get_value("type") == "DocType") { + let doctype = this.dialog.get_value("link_to") + frappe.db.get_value("DocType", doctype, "issingle").then(res => { + if (res.message.issingle) { + this.hide_field('count_section_break'); + this.hide_field('filters_section_break'); + } else { + this.setup_filter(doctype); + this.show_field('count_section_break'); + this.show_field('filters_section_break'); + } + }) + } else { + this.hide_field('count_section_break'); + this.hide_field('filters_section_break'); + } + }, + }, + { + fieldtype: "Section Break", + fieldname: "filters_section_break", + label: "Count Filter", + hidden: 1, + }, + { + fieldtype: "HTML", + fieldname: "filter_area", + }, + { + fieldtype: "Section Break", + fieldname: "count_section_break", + label: "Count Customizations", + hidden: 1, + }, + { + fieldtype: "Color", + fieldname: "color", + label: "Color", + }, + { + fieldtype: "Column Break", + fieldname: "column_break_3", + }, + { + fieldtype: "Data", + fieldname: "format", + label: "Format", + description: "For Example: {} Open", + }, + ]; + } + + process_data(data) { + let stats_filter = {}; + let filters = this.filter_group.get_filters(); + filters.forEach(arr => { + stats_filter[arr[1]] = [arr[2], arr[3]] + }); + + data.stats_filter = JSON.stringify(stats_filter); + data.label = data.link_to; + + return data + } + + setup_filter(doctype) { + if (this.filter_group) { + this.filter_group.wrapper.empty(); + delete this.filter_group; + } + + this.filters = [] + + this.filter_group = new frappe.ui.FilterGroup({ + parent: this.dialog.get_field('filter_area').$wrapper, + doctype: doctype, + on_change: () => {}, + }); + + frappe.model.with_doctype(doctype, () => { + this.filter_group.add_filters_to_filter_group(this.filters); + }); + } +} + +export default function get_dialog_constructor(type) { + const widget_map = { + chart: ChartDialog, + shortcut: ShortcutDialog, + }; + + return widget_map[type] || WidgetDialog; +} \ No newline at end of file diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index f121939748..99f3a8edbd 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -3,7 +3,7 @@ import BaseWidget from "../widgets/base_widget"; import ShortcutWidget from "../widgets/shortcut_widget"; import LinksWidget from "../widgets/links_widget"; import OnboardingWidget from "../widgets/onboarding_widget"; -import { get_new_widget_class } from "../widgets/new_widget"; +import NewWidget from "../widgets/new_widget"; frappe.provide('frappe.widget') @@ -92,8 +92,7 @@ export default class WidgetGroup { : Number.POSITIVE_INFINITY; if (this.widgets_list.length < max) { - const new_widget_class = get_new_widget_class(this.type) - this.new_widget = new new_widget_class({ + this.new_widget = new NewWidget({ container: this.body, type: this.type, on_create: (config) => { From 3713a53a96e073fa0c28c6eace10e41d7b026f62 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 8 Apr 2020 18:55:51 +0530 Subject: [PATCH 206/408] fix: allow dashboard chart type to be set only once --- frappe/desk/doctype/dashboard_chart/dashboard_chart.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 9652ae3945..676cdbe24a 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -49,7 +49,8 @@ "fieldname": "chart_type", "fieldtype": "Select", "label": "Chart Type", - "options": "Count\nSum\nAverage\nGroup By\nCustom\nReport" + "options": "Count\nSum\nAverage\nGroup By\nCustom\nReport", + "set_only_once": 1 }, { "depends_on": "eval:doc.chart_type === 'Custom'", @@ -215,7 +216,7 @@ } ], "links": [], - "modified": "2020-03-31 16:00:01.987059", + "modified": "2020-04-08 18:54:36.739183", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", From afe9ec3ed669e5c10c23753342b86b68f0999f9f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 20:12:20 +0530 Subject: [PATCH 207/408] feat: return a promise of set_values --- frappe/public/js/frappe/ui/field_group.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index 9c04789544..e467e2e65e 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -125,11 +125,14 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({ return this.set_value(key, val); }, set_values: function(dict) { + let promises = [] for(var key in dict) { if(this.fields_dict[key]) { - this.set_value(key, dict[key]); + promises.push(this.set_value(key, dict[key])); } } + + return Promise.all(promises) }, clear: function() { for(var key in this.fields_dict) { From f9686f1a8536d6ed25d4f1c0a6fb5cf10ada0cfa Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 20:12:54 +0530 Subject: [PATCH 208/408] feat: allow editing of widgets --- .../public/js/frappe/widgets/base_widget.js | 34 ++++----- frappe/public/js/frappe/widgets/new_widget.js | 3 +- .../js/frappe/widgets/shortcut_widget.js | 10 +-- .../public/js/frappe/widgets/widget_dialog.js | 71 ++++++++++++++++--- .../public/js/frappe/widgets/widget_group.js | 1 + 5 files changed, 78 insertions(+), 41 deletions(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index 63fc49a166..faa79da76d 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -1,3 +1,5 @@ +import get_dialog_constructor from './widget_dialog.js' + export default class Widget { constructor(opts) { Object.assign(this, opts); @@ -114,29 +116,21 @@ export default class Widget { } edit() { - frappe.model.with_doctype(this.doctype, () => { - let new_dialog = new frappe.ui.Dialog({ - title: __("Edit"), - fields: frappe.get_meta(this.doctype).fields, - primary_action: (data) => { - if (this.doctype == 'Desk Chart' && !data.label) { - data.label = data.chart_name; - } + const dialog_class = get_dialog_constructor(this.widget_type) - if (this.doctype == 'Desk Shortcut') { - data.label = data.link_to; - } + this.edit_dialog = new dialog_class({ + label: this.label, + type: this.widget_type, + values: this.get_config(), + primary_action: (data) => { + Object.assign(this, data); + data.name = this.name; - new_dialog.hide(); - Object.assign(this, data); - this.refresh(); - }, - primary_action_label: __("Save"), - }); + this.refresh(); + }, + }) - new_dialog.show(); - new_dialog.set_values(this.get_config()); - }); + this.edit_dialog.make(); } hide_or_show() { diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index d7b05869b1..d3d887bf20 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -33,11 +33,12 @@ export default class NewWidget { open_dialog() { const dialog_class = get_dialog_constructor(this.type) + this.dialog = new dialog_class({ label: this.label, type: this.type, values: false, - on_create: this.on_create, + primary_action: this.on_create, }) this.dialog.make(); diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index e36120ebda..16f9f32718 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -1,13 +1,5 @@ import Widget from "./base_widget.js"; import { generate_route } from "./utils"; -// import { get_luminosity, shadeColor } from "./utils"; - -String.prototype.format = function () { - var i = 0, args = arguments; - return this.replace(/{}/g, function () { - return typeof args[i] != 'undefined' ? args[i++] : ''; - }); -}; export default class ShortcutWidget extends Widget { constructor(opts) { @@ -69,7 +61,7 @@ export default class ShortcutWidget extends Widget { set_count(count) { const get_label = () => { if (this.format) { - return this.format.format(count); + return this.format.replace(/{}/g, count); } return count } diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 2a4bb864a0..1fa1c08a13 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -4,22 +4,33 @@ class WidgetDialog { } make() { + this.make_dialog(); + this.setup_dialog_events(); + this.dialog.show(); + + if (this.values && Object.keys(this.values).length) { + this.set_default_values(); + } + } + + make_dialog() { this.dialog = new frappe.ui.Dialog({ title: this.get_title(), fields: this.get_fields(), primary_action: (data) => { data = this.process_data(data); - data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`; + + if (this.values && this.values.name) { + data.name = this.values.name; + } else { + data.name = `${this.type}-${this.label}-${frappe.utils.get_random(20)}`; + } this.dialog.hide(); - this.on_create(data); + this.primary_action(data); }, primary_action_label: __("Add"), }); - - this.setup_dialog_events(); - - this.dialog.show(); } get_title() { @@ -30,6 +41,10 @@ class WidgetDialog { // } + set_default_values() { + return this.dialog.set_values(this.values); + } + process_data(data) { return data } @@ -70,7 +85,12 @@ class ChartDialog extends WidgetDialog { } process_data(data) { - data.label = data.chart_name; + if (this.values && this.values.label) { + data.label = this.values.label; + } else { + data.label = data.chart_name; + } + return data } } @@ -110,9 +130,9 @@ class ShortcutDialog extends WidgetDialog { onchange: () => { let dg = this.dialog; if (this.dialog.get_value("type") == "DocType") { - let doctype = this.dialog.get_value("link_to") - frappe.db.get_value("DocType", doctype, "issingle").then(res => { - if (res.message.issingle) { + let doctype = this.dialog.get_value("link_to"); + doctype &&frappe.db.get_value("DocType", doctype, "issingle").then(res => { + if (res.message && res.message.issingle) { this.hide_field('count_section_break'); this.hide_field('filters_section_break'); } else { @@ -133,9 +153,14 @@ class ShortcutDialog extends WidgetDialog { label: "Count Filter", hidden: 1, }, + { + fieldtype: "HTML", + fieldname: "filter_area_loading", + }, { fieldtype: "HTML", fieldname: "filter_area", + hidden: 1, }, { fieldtype: "Section Break", @@ -161,6 +186,12 @@ class ShortcutDialog extends WidgetDialog { ]; } + set_default_values() { + super.set_default_values().then(() => { + this.dialog.fields_dict.link_to.df.onchange(); + }); + } + process_data(data) { let stats_filter = {}; let filters = this.filter_group.get_filters(); @@ -169,7 +200,12 @@ class ShortcutDialog extends WidgetDialog { }); data.stats_filter = JSON.stringify(stats_filter); - data.label = data.link_to; + + if (this.values && this.values.label) { + data.label = this.values.label; + } else { + data.label = data.link_to; + } return data } @@ -180,8 +216,19 @@ class ShortcutDialog extends WidgetDialog { delete this.filter_group; } + let $loading = this.dialog.get_field('filter_area_loading').$wrapper + $(`Loading Filters...`).appendTo($loading) + this.filters = [] + if (this.values && this.values.stats_filter) { + const filters_json = JSON.parse(this.values.stats_filter); + this.filters = Object.keys(filters_json).map(filter => { + let val = filters_json[filter]; + return [this.values.link_to, filter, val[0], val[1], false] + }) + } + this.filter_group = new frappe.ui.FilterGroup({ parent: this.dialog.get_field('filter_area').$wrapper, doctype: doctype, @@ -190,6 +237,8 @@ class ShortcutDialog extends WidgetDialog { frappe.model.with_doctype(doctype, () => { this.filter_group.add_filters_to_filter_group(this.filters); + this.hide_field('filter_area_loading'); + this.show_field('filter_area'); }); } } diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index 99f3a8edbd..b1759f3d93 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -63,6 +63,7 @@ export default class WidgetGroup { let widget_object = new widget_class({ ...widget, + widget_type: this.type, container: this.body, options: { ...this.options, From bf47bc8ba3fc292beeed4ae690f6654b1cec2f0b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 8 Apr 2020 20:16:51 +0530 Subject: [PATCH 209/408] feat: custom primary action label for new widget dialog --- frappe/public/js/frappe/widgets/base_widget.js | 1 + frappe/public/js/frappe/widgets/widget_dialog.js | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index faa79da76d..d436d07731 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -128,6 +128,7 @@ export default class Widget { this.refresh(); }, + primary_action_label: __("Save") }) this.edit_dialog.make(); diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 1fa1c08a13..1584c59ed4 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -29,7 +29,7 @@ class WidgetDialog { this.dialog.hide(); this.primary_action(data); }, - primary_action_label: __("Add"), + primary_action_label: this.primary_action_label || __("Add"), }); } From 24a8b036393c6aa71db16cb0fccf17064505c403 Mon Sep 17 00:00:00 2001 From: Kenneth Sequeira Date: Wed, 8 Apr 2020 20:27:38 +0530 Subject: [PATCH 210/408] chore: improve error message on delete --- frappe/model/delete_doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index d77898020d..b6db154bd4 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -210,7 +210,7 @@ def check_permission_and_not_submitted(doc): # check if submitted if doc.docstatus == 1: - frappe.msgprint(_("{0} {1}: Submitted Record cannot be deleted.").format(_(doc.doctype), doc.name), + frappe.msgprint(_("{0} {1}: Submitted Record cannot be deleted. You must Cancel it first.").format(_(doc.doctype), doc.name), raise_exception=True) def check_if_doc_is_linked(doc, method="Delete"): From 1fc2f0d27caa5570f7a8751c7ab607a8c626673d Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Thu, 9 Apr 2020 09:31:36 +0530 Subject: [PATCH 211/408] fix: bread word if too long in link preview popover (#9869) --- frappe/public/less/link_preview.less | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/less/link_preview.less b/frappe/public/less/link_preview.less index 5bc9767815..22a61a79e7 100644 --- a/frappe/public/less/link_preview.less +++ b/frappe/public/less/link_preview.less @@ -37,6 +37,7 @@ padding-bottom: 5px; max-width: 330px; min-width: 200px; + overflow-wrap: break-word; .preview-field { padding-bottom: 10px; From 029b8f0a35af32c220dd411a6efc90c94c1dc1cf Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 9 Apr 2020 09:40:42 +0530 Subject: [PATCH 212/408] fix: Remove Admin role from check --- .../desk/doctype/notification_settings/notification_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_settings/notification_settings.js b/frappe/desk/doctype/notification_settings/notification_settings.js index 863840d155..fc8be15965 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.js +++ b/frappe/desk/doctype/notification_settings/notification_settings.js @@ -11,7 +11,7 @@ frappe.ui.form.on('Notification Settings', { }, refresh: (frm) => { - if (frappe.user.has_role(['System Manager', 'Administrator'])) { + if (frappe.user.has_role('System Manager')) { frm.add_custom_button('Go to Notification Settings List', () => { frappe.set_route('List', 'Notification Settings'); }); From ab383b5bee68784822bdb2613ef8ecee0034a393 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 9 Apr 2020 10:59:27 +0530 Subject: [PATCH 213/408] style: fix indent --- .../desk/doctype/notification_settings/notification_settings.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_settings/notification_settings.js b/frappe/desk/doctype/notification_settings/notification_settings.js index fc8be15965..b8b7f37a4f 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.js +++ b/frappe/desk/doctype/notification_settings/notification_settings.js @@ -13,7 +13,7 @@ frappe.ui.form.on('Notification Settings', { refresh: (frm) => { if (frappe.user.has_role('System Manager')) { frm.add_custom_button('Go to Notification Settings List', () => { - frappe.set_route('List', 'Notification Settings'); + frappe.set_route('List', 'Notification Settings'); }); } } From bb87ce2b118f92316db90330bb115ba62f111b41 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 9 Apr 2020 11:10:33 +0530 Subject: [PATCH 214/408] chore(CODEOWNERS): Remove comma --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index 2ff8752871..2c8891ce86 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,7 +3,7 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -* @surajshetty3416, @netchampfaris +* @surajshetty3416 @netchampfaris website/ @scmmishra templates/ @scmmishra www/ @scmmishra @@ -14,4 +14,4 @@ email/ @Thunderbottom event_streaming/ @ruchamahabal data_import* @netchampfaris core/ @surajshetty3416 -requirements.txt @gavindsouza \ No newline at end of file +requirements.txt @gavindsouza From 9dbaf7932a38d4e35596165bac0a73751898cb0d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 9 Apr 2020 11:29:10 +0530 Subject: [PATCH 215/408] fix (linting): remove unused import --- frappe/desk/desktop.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 175bfa90ff..b367aea455 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -from json import loads, dumps +from json import loads from frappe import _, DoesNotExistError from frappe.boot import get_allowed_pages, get_allowed_reports from six import string_types From 0b80c44796f8b91f299dde75f84b161dc12910ed Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 9 Apr 2020 11:35:42 +0530 Subject: [PATCH 216/408] style: remove unnecessary else --- frappe/desk/desktop.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index b367aea455..305edc0615 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -25,9 +25,9 @@ class Workspace: pages = frappe.get_list("Desk Page", filters=filters) if pages: return frappe.get_doc("Desk Page", pages[0]) - else: - self.get_pages_to_extend() - return frappe.get_doc("Desk Page", self.page_name) + + self.get_pages_to_extend() + return frappe.get_doc("Desk Page", self.page_name) def init(self): user = frappe.get_user() @@ -310,11 +310,10 @@ def get_custom_workspace_for_user(page): pages = frappe.get_list("Desk Page", filters=filters) if pages: return frappe.get_doc("Desk Page", pages[0]) - else: - doc = frappe.new_doc("Desk Page") - doc.extends = page - doc.for_user = frappe.session.user - return doc + doc = frappe.new_doc("Desk Page") + doc.extends = page + doc.for_user = frappe.session.user + return doc @frappe.whitelist() From 953d0f273eb70578d61f7d1f6221d83de3d9f29a Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 9 Apr 2020 11:45:03 +0530 Subject: [PATCH 217/408] chore(CODEOWNERS): Update owners --- CODEOWNERS | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/CODEOWNERS b/CODEOWNERS index 2c8891ce86..e27856b8ae 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,8 +3,9 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -* @surajshetty3416 @netchampfaris +* @surajshetty3416 website/ @scmmishra +web_form/ @scmmishra templates/ @scmmishra www/ @scmmishra integrations/ @Mangesh-Khairnar From d99f68a230a6bbaad6fc4592f388a699954efab6 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 9 Apr 2020 12:01:10 +0530 Subject: [PATCH 218/408] style: load data on object initializing --- frappe/desk/desktop.py | 29 +++++++++++++---------------- 1 file changed, 13 insertions(+), 16 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 305edc0615..b616c2838d 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -17,19 +17,6 @@ class Workspace: self.extended_charts = [] self.extended_shortcuts = [] - def get_page_for_user(self): - filters = { - 'extends': self.page_name, - 'for_user': frappe.session.user - } - pages = frappe.get_list("Desk Page", filters=filters) - if pages: - return frappe.get_doc("Desk Page", pages[0]) - - self.get_pages_to_extend() - return frappe.get_doc("Desk Page", self.page_name) - - def init(self): user = frappe.get_user() user.build_permissions() @@ -43,11 +30,22 @@ class Workspace: self.allowed_pages = get_allowed_pages() self.allowed_reports = get_allowed_reports() - self.table_counts = get_table_with_counts() self.restricted_doctypes = build_domain_restriced_doctype_cache() self.restricted_pages = build_domain_restriced_page_cache() + def get_page_for_user(self): + filters = { + 'extends': self.page_name, + 'for_user': frappe.session.user + } + pages = frappe.get_list("Desk Page", filters=filters) + if pages: + return frappe.get_doc("Desk Page", pages[0]) + + self.get_pages_to_extend() + return frappe.get_doc("Desk Page", self.page_name) + def get_pages_to_extend(self): pages = frappe.get_all("Desk Page", filters={ "extends": self.page_name, @@ -209,9 +207,8 @@ def get_desktop_page(page): Returns: dict: dictionary of cards, charts and shortcuts to be displayed on website """ - wspace = Workspace(page) try: - wspace.init() + wspace = Workspace(page) wspace.build_workspace() return { 'charts': wspace.charts, From 6417c87afe2746bab4b1a1cb861b25a172f228a7 Mon Sep 17 00:00:00 2001 From: Felipe Orellana Date: Wed, 25 Mar 2020 22:12:48 +0000 Subject: [PATCH 219/408] feat: Adds small improvements to chat notifications --- frappe/public/js/frappe/chat.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index 810de89874..9d82b4d593 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -1566,7 +1566,11 @@ class extends Component { const alert = // TODO: ellipses content ` - ${frappe.user.first_name(r.user)}: ${r.content} + + + + + ${frappe.user.first_name(r.user)}: ${r.content} ` frappe.show_alert(alert, 15, { @@ -1575,6 +1579,11 @@ class extends Component { this.base.firstChild._component.toggle() }.bind(this, r) }) + frappe.notify(`${frappe.user.first_name(r.user)}`, { + body: r.content, + icon: frappe.user.image(r.user), + requireInteraction: true + }) } if ( r.room === state.room.name ) { From 945ee717920432614bbceced721642f9ea09d509 Mon Sep 17 00:00:00 2001 From: Felipe Orellana Date: Wed, 25 Mar 2020 22:38:14 +0000 Subject: [PATCH 220/408] feat: adds notification tag to avoid spamming recipient --- frappe/public/js/frappe/chat.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/chat.js b/frappe/public/js/frappe/chat.js index 9d82b4d593..04633bd920 100644 --- a/frappe/public/js/frappe/chat.js +++ b/frappe/public/js/frappe/chat.js @@ -1582,7 +1582,7 @@ class extends Component { frappe.notify(`${frappe.user.first_name(r.user)}`, { body: r.content, icon: frappe.user.image(r.user), - requireInteraction: true + tag: r.user }) } From e06114ea54a86230162448705fa10ee0293fccb6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 9 Apr 2020 13:23:33 +0530 Subject: [PATCH 221/408] chore(CODEOWNERS): Add frappe-review-team as fallback owner --- CODEOWNERS | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CODEOWNERS b/CODEOWNERS index e27856b8ae..8e6f8eb5e9 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -3,13 +3,13 @@ # These owners will be the default owners for everything in # the repo. Unless a later match takes precedence, -* @surajshetty3416 +* @frappe/frappe-review-team website/ @scmmishra web_form/ @scmmishra templates/ @scmmishra www/ @scmmishra integrations/ @Mangesh-Khairnar -patches/ @surajshetty3416 @sahil28297 +patches/ @sahil28297 dashboard/ @prssanna email/ @Thunderbottom event_streaming/ @ruchamahabal From b7270d82ea6e0b5abb3df52bbc793f11ab1f800a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 9 Apr 2020 13:47:35 +0530 Subject: [PATCH 222/408] fix (linting): fixes from codacy --- frappe/public/js/frappe/ui/field_group.js | 4 +- .../public/js/frappe/views/desktop/desktop.js | 10 ++-- .../public/js/frappe/widgets/base_widget.js | 6 +-- .../public/js/frappe/widgets/chart_widget.js | 2 +- .../public/js/frappe/widgets/links_widget.js | 6 +-- frappe/public/js/frappe/widgets/new_widget.js | 8 +-- .../js/frappe/widgets/shortcut_widget.js | 52 +++++++++++-------- .../public/js/frappe/widgets/widget_group.js | 44 ++++++++-------- 8 files changed, 68 insertions(+), 64 deletions(-) diff --git a/frappe/public/js/frappe/ui/field_group.js b/frappe/public/js/frappe/ui/field_group.js index e467e2e65e..4d827354d8 100644 --- a/frappe/public/js/frappe/ui/field_group.js +++ b/frappe/public/js/frappe/ui/field_group.js @@ -125,14 +125,14 @@ frappe.ui.FieldGroup = frappe.ui.form.Layout.extend({ return this.set_value(key, val); }, set_values: function(dict) { - let promises = [] + let promises = []; for(var key in dict) { if(this.fields_dict[key]) { promises.push(this.set_value(key, dict[key])); } } - return Promise.all(promises) + return Promise.all(promises); }, clear: function() { for(var key in this.fields_dict) { diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index 8ff7d7e167..e0f400ed9a 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -169,9 +169,7 @@ class DesktopPage { this.save_or_discard_link.appendTo(this.page); this.save_or_discard_link.find(".save-customization").on("click", () => this.save_customization()); - - this.save_or_discard_link.find(".discard-customization").on("click", () => this.reload()) - + this.save_or_discard_link.find(".discard-customization").on("click", () => this.reload()); this.page.addClass('allow-customization'); } @@ -228,7 +226,7 @@ class DesktopPage { customize() { if (this.in_customize_mode) { - return + return; } // It may be possible the chart area is hidden since it has no widgets @@ -259,9 +257,9 @@ class DesktopPage { page: this.page_name, config: config }).then(res => { - frappe.msgprint(__("Customizations Saved Successfully")) + frappe.msgprint(__("Customizations Saved Successfully")); this.reload(); - }) + }); } make_charts() { diff --git a/frappe/public/js/frappe/widgets/base_widget.js b/frappe/public/js/frappe/widgets/base_widget.js index d436d07731..700966ecb5 100644 --- a/frappe/public/js/frappe/widgets/base_widget.js +++ b/frappe/public/js/frappe/widgets/base_widget.js @@ -1,4 +1,4 @@ -import get_dialog_constructor from './widget_dialog.js' +import get_dialog_constructor from './widget_dialog.js'; export default class Widget { constructor(opts) { @@ -116,7 +116,7 @@ export default class Widget { } edit() { - const dialog_class = get_dialog_constructor(this.widget_type) + const dialog_class = get_dialog_constructor(this.widget_type); this.edit_dialog = new dialog_class({ label: this.label, @@ -129,7 +129,7 @@ export default class Widget { this.refresh(); }, primary_action_label: __("Save") - }) + }); this.edit_dialog.make(); } diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 2bfd701db1..6f83be39c7 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -14,7 +14,7 @@ export default class ChartWidget extends Widget { name: this.name, chart_name: this.chart_name, label: this.label, - } + }; } refresh() { diff --git a/frappe/public/js/frappe/widgets/links_widget.js b/frappe/public/js/frappe/widgets/links_widget.js index 1f186aa6c2..f7bca23c47 100644 --- a/frappe/public/js/frappe/widgets/links_widget.js +++ b/frappe/public/js/frappe/widgets/links_widget.js @@ -12,7 +12,7 @@ export default class LinksWidget extends Widget { links: JSON.stringify(this.links), label: this.label, hidden: this.hidden, - } + }; } set_body() { @@ -80,13 +80,13 @@ export default class LinksWidget extends Widget { const popover = link.find(".module-link-popover"); link_label.mouseover(() => { - if (this.in_customize_mode) return + if (this.in_customize_mode) return; popover.show(); }); link_label.mouseout(() => popover.hide()); } else { link_label.click(event => { - if (this.in_customize_mode) return + if (this.in_customize_mode) return; if (link_label.hasClass("help-video-link")) { let yt_id = event.target.dataset.youtubeid; diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index d3d887bf20..ffd96c87da 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -1,4 +1,4 @@ -import get_dialog_constructor from './widget_dialog.js' +import get_dialog_constructor from "./widget_dialog.js"; export default class NewWidget { constructor(opts) { @@ -32,14 +32,14 @@ export default class NewWidget { } open_dialog() { - const dialog_class = get_dialog_constructor(this.type) + const dialog_class = get_dialog_constructor(this.type); this.dialog = new dialog_class({ label: this.label, type: this.type, values: false, primary_action: this.on_create, - }) + }); this.dialog.make(); } @@ -47,4 +47,4 @@ export default class NewWidget { delete() { this.widget.remove(); } -} \ No newline at end of file +} diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index 16f9f32718..8df604cf97 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -16,44 +16,47 @@ export default class ShortcutWidget extends Widget { color: this.color, restrict_to_domain: this.restrict_to_domain, stats_filter: this.stats_filter, - type: this.type - } + type: this.type, + }; } setup_events() { this.widget.click(() => { - if (this.in_customize_mode) return + if (this.in_customize_mode) return; let route = generate_route({ route: this.route, name: this.link_to, - type: this.type - }) + type: this.type, + }); - frappe.set_route(route) - }) + frappe.set_route(route); + }); } set_actions() { - if (this.in_customize_mode) return + if (this.in_customize_mode) return; - this.widget.addClass('shortcut-widget-box'); - const get_filter = new Function(`return ${this.stats_filter}`) + this.widget.addClass("shortcut-widget-box"); + const get_filter = new Function(`return ${this.stats_filter}`); if (this.type == "DocType" && this.stats_filter) { - frappe.db.count(this.link_to, { - filters: get_filter() - }).then(count => this.set_count(count)) + frappe.db + .count(this.link_to, { + filters: get_filter(), + }) + .then((count) => this.set_count(count)); } } set_title() { if (this.icon) { this.title_field[0].innerHTML = `
- + ${this.label || this.name} -
` - } - else { +
`; + } else { super.set_title(); } } @@ -63,17 +66,20 @@ export default class ShortcutWidget extends Widget { if (this.format) { return this.format.replace(/{}/g, count); } - return count - } + return count; + }; this.action_area.empty(); const label = get_label(); const buttons = $(`
${label}
`); - if(this.color) { - buttons.css('background-color', this.color); - buttons.css('color', frappe.ui.color.get_contrast_color(this.color)) + if (this.color) { + buttons.css("background-color", this.color); + buttons.css( + "color", + frappe.ui.color.get_contrast_color(this.color) + ); } buttons.appendTo(this.action_area); } -} \ No newline at end of file +} diff --git a/frappe/public/js/frappe/widgets/widget_group.js b/frappe/public/js/frappe/widgets/widget_group.js index b1759f3d93..bfbdb7393b 100644 --- a/frappe/public/js/frappe/widgets/widget_group.js +++ b/frappe/public/js/frappe/widgets/widget_group.js @@ -5,7 +5,7 @@ import LinksWidget from "../widgets/links_widget"; import OnboardingWidget from "../widgets/onboarding_widget"; import NewWidget from "../widgets/new_widget"; -frappe.provide('frappe.widget') +frappe.provide("frappe.widget"); const widget_factory = { chart: ChartWidget, @@ -52,9 +52,9 @@ export default class WidgetGroup { } make_widgets() { - this.body.empty() - this.widgets.forEach(widget => { - this.add_widget(widget) + this.body.empty(); + this.widgets.forEach((widget) => { + this.add_widget(widget); }); } @@ -67,21 +67,21 @@ export default class WidgetGroup { container: this.body, options: { ...this.options, - on_delete: (name) => this.on_delete(name) - } + on_delete: (name) => this.on_delete(name), + }, }); this.widgets_list.push(widget_object); this.widgets_dict[widget.name] = widget_object; - return widget_object + return widget_object; } customize() { this.widget_area.show(); - this.widgets_list.forEach(wid => { + this.widgets_list.forEach((wid) => { wid.customize(this.options); - }) + }); this.options.allow_create && this.setup_new_widget(); this.options.allow_sorting && this.setup_sortable(); @@ -89,8 +89,8 @@ export default class WidgetGroup { setup_new_widget() { const max = this.options - ? this.options.max_widget_count || Number.POSITIVE_INFINITY - : Number.POSITIVE_INFINITY; + ? this.options.max_widget_count || Number.POSITIVE_INFINITY + : Number.POSITIVE_INFINITY; if (this.widgets_list.length < max) { this.new_widget = new NewWidget({ @@ -111,13 +111,13 @@ export default class WidgetGroup { if (this.widgets_list.length < max) { this.setup_new_widget(); } - } - }) + }, + }); } } on_delete(name) { - this.widgets_list = this.widgets_list.filter(wid => name != wid.name); + this.widgets_list = this.widgets_list.filter((wid) => name != wid.name); delete this.widgets_dict[name]; this.update_widget_order(); @@ -131,7 +131,7 @@ export default class WidgetGroup { if (name) { this.widget_order.push(name); } - }) + }); } setup_sortable() { @@ -147,17 +147,17 @@ export default class WidgetGroup { this.update_widget_order(); let prepared_dict = {}; - this.widgets_list.forEach(wid => { - let config = wid.get_config() - let name = config.docname ? config.docname : config.name - prepared_dict[name] = config + this.widgets_list.forEach((wid) => { + let config = wid.get_config(); + let name = config.docname ? config.docname : config.name; + prepared_dict[name] = config; }); return { order: this.widget_order, - widgets: prepared_dict - } + widgets: prepared_dict, + }; } } -frappe.widget.WidgetGroup = WidgetGroup; \ No newline at end of file +frappe.widget.WidgetGroup = WidgetGroup; From f6412abc4706ae71070411febf9b9fc773a2d529 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 9 Apr 2020 17:13:13 +0530 Subject: [PATCH 223/408] feat: handle frontmatter via internal API chore: drop frontmatter dependency Co-authored-by: Jonathan Beebe --- frappe/website/router.py | 41 ++++++++++++++++++++++++++++++---------- requirements.txt | 1 - 2 files changed, 31 insertions(+), 11 deletions(-) diff --git a/frappe/website/router.py b/frappe/website/router.py index bce40c7713..14fdc4bd6e 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -2,13 +2,18 @@ # MIT License. See license.txt from __future__ import unicode_literals -import frappe, os -from frappe.website.utils import (can_cache, delete_page_cache, extract_title, - extract_comment_tag) -from frappe.model.document import get_controller -from six import text_type import io +import os +import re + +import yaml +from six import text_type + +import frappe +from frappe.model.document import get_controller +from frappe.website.utils import can_cache, delete_page_cache, extract_comment_tag, extract_title + def resolve_route(path): """Returns the page route object based on searching in pages and generators. @@ -229,10 +234,26 @@ def get_page_info(path, app, start, basepath=None, app_path=None, fname=None): return page_info +def get_frontmatter(string): + """ + Reference: https://github.com/jonbeebe/frontmatter + """ + + fmatter = "" + body = "" + result = re.compile(r'^\s*(?:---|\+\+\+)(.*?)(?:---|\+\+\+)\s*(.+)$', re.S | re.M).search(string) + + if result: + fmatter = result.group(1) + body = result.group(2) + + return { + "attributes": yaml.load(fmatter), + "body": body, + } + def setup_source(page_info): '''Get the HTML source of the template''' - from frontmatter import Frontmatter - jenv = frappe.get_jenv() source = jenv.loader.get_source(jenv, page_info.template)[0] html = '' @@ -241,11 +262,11 @@ def setup_source(page_info): # extract frontmatter block if exists try: # values will be used to update page_info - res = Frontmatter.read(source) + res = get_frontmatter(source) if res['attributes']: page_info.update(res['attributes']) source = res['body'] - except Exception as e: + except Exception: pass if page_info.template.endswith('.md'): @@ -367,4 +388,4 @@ def get_doctypes_with_web_view(): return frappe.cache().get_value('doctypes_with_web_view', _get) def get_start_folders(): - return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') \ No newline at end of file + return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') diff --git a/requirements.txt b/requirements.txt index 55a4910056..b1611ffb02 100644 --- a/requirements.txt +++ b/requirements.txt @@ -12,7 +12,6 @@ cryptography==2.8 dropbox==9.1.0 email-reply-parser==0.5.9 Faker==2.0.4 -frontmatter==3.0.6 future==0.18.2 GitPython==2.1.15 gitdb2==2.0.6;python_version<'3.4' From 3588a2edf39a8ca1c49ef4becc5acc1e9b462a68 Mon Sep 17 00:00:00 2001 From: Vishal Dhayagude Date: Thu, 9 Apr 2020 17:24:21 +0530 Subject: [PATCH 224/408] feat: Add calendar for ToDo (#9808) * feat: Todo Calender Added * fix: indentation * title description added for todo calender * style: Add missing semicolon Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/desk/doctype/todo/todo_calendar.js | 31 +++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 frappe/desk/doctype/todo/todo_calendar.js diff --git a/frappe/desk/doctype/todo/todo_calendar.js b/frappe/desk/doctype/todo/todo_calendar.js new file mode 100644 index 0000000000..4545846cf9 --- /dev/null +++ b/frappe/desk/doctype/todo/todo_calendar.js @@ -0,0 +1,31 @@ +// Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +// License: GNU General Public License v3. See license.txt + +frappe.views.calendar["ToDo"] = { + field_map: { + "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": "Dynamic Link", + "fieldname": "reference_name", + "options": "reference_type", + "label": __("Task") + } + + ], + get_events_method: "frappe.desk.calendar.get_events" +}; + From e7f0b42afb03354092fbc0e75ec43505fdd31528 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 9 Apr 2020 18:36:39 +0530 Subject: [PATCH 225/408] fix: update doc object with current values --- frappe/public/js/frappe/web_form/web_form.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/web_form/web_form.js b/frappe/public/js/frappe/web_form/web_form.js index 71a34d9888..df81f0967d 100644 --- a/frappe/public/js/frappe/web_form/web_form.js +++ b/frappe/public/js/frappe/web_form/web_form.js @@ -105,14 +105,14 @@ export default class WebForm extends frappe.ui.FieldGroup { this.validate && this.validate(); // validation hack: get_values will check for missing data - let isvalid = super.get_values(this.allow_incomplete); + let doc_values = super.get_values(this.allow_incomplete); - if (!isvalid) return; + if (!doc_values) return; if (window.saving) return; let for_payment = Boolean(this.accept_payment && !this.doc.paid); - this.doc = isvalid; + Object.assign(this.doc, doc_values) this.doc.doctype = this.doc_type; this.doc.web_form_name = this.name; From c85a09a739511ed8fb55a9df82b32c350ca6e0fa Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 9 Apr 2020 19:33:04 +0530 Subject: [PATCH 226/408] fix: set method in get_settings --- frappe/public/js/frappe/widgets/chart_widget.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/widgets/chart_widget.js b/frappe/public/js/frappe/widgets/chart_widget.js index 6f83be39c7..9c03f08523 100644 --- a/frappe/public/js/frappe/widgets/chart_widget.js +++ b/frappe/public/js/frappe/widgets/chart_widget.js @@ -429,9 +429,7 @@ export default class ChartWidget extends Widget { } fetch(filters, refresh = false, args) { - let method = this.settings - ? this.settings.method - : "frappe.desk.doctype.dashboard_chart.dashboard_chart.get"; + let method = this.settings.method; if (this.chart_doc.chart_type == "Report") { args = { @@ -559,6 +557,9 @@ export default class ChartWidget extends Widget { }; return Promise.resolve(); } else { + this.settings = { + method: "frappe.desk.doctype.dashboard_chart.dashboard_chart.get" + }; return Promise.resolve(); } }); From 9fb1c7b5b0d1648e4f2cf8cb303ede41f71e3605 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 9 Apr 2020 19:47:52 +0530 Subject: [PATCH 227/408] feat: translate desk --- frappe/desk/desktop.py | 24 +++++++++++++------ .../public/js/frappe/views/desktop/desktop.js | 22 +++++++++-------- frappe/public/js/frappe/widgets/new_widget.js | 2 ++ .../public/js/frappe/widgets/widget_dialog.js | 9 ++++++- 4 files changed, 39 insertions(+), 18 deletions(-) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index b616c2838d..7041704513 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -77,17 +77,17 @@ class Workspace: def build_workspace(self): self.cards = { - 'label': self.doc.cards_label, + 'label': _(self.doc.cards_label), 'items': self.get_cards() } self.charts = { - 'label': self.doc.charts_label, + 'label': _(self.doc.charts_label), 'items': self.get_charts() } self.shortcuts = { - 'label': self.doc.shortcuts_label, + 'label': _(self.doc.charts_label), 'items': self.get_shortcuts() } @@ -121,6 +121,9 @@ class Workspace: item["count"] = count + # Translate label + item["label"] = _(item.label) if item.label else _(item.name) + return item new_data = [] @@ -141,7 +144,7 @@ class Workspace: # Check if user is allowed to view if self.is_item_allowed(item.name, item.type): prepared_item = _prepare_item(item) - new_items.append(item) + new_items.append(prepared_item) if new_items: if isinstance(section, frappe._dict): @@ -149,6 +152,7 @@ class Workspace: else: new_section = section.as_dict().copy() new_section["links"] = new_items + new_section["label"] = _(new_section["label"]) new_data.append(new_section) return new_data @@ -162,7 +166,8 @@ class Workspace: for chart in charts: if frappe.has_permission('Dashboard Chart', doc=chart.chart_name): - chart.label = chart.label if chart.label else chart.chart_name + # Translate label + chart.label = _(chart.label) if chart.label else _(chart.chart_name) all_charts.append(chart) return all_charts @@ -185,12 +190,14 @@ class Workspace: if self.is_item_allowed(item.link_to, item.type) and _in_active_domains(item): if item.type == "Page": page = self.allowed_pages[item.link_to] - new_item['label'] = _(page.get("title", frappe.unscrub(item.link_to))) if item.type == "Report": report = self.allowed_reports.get(item.link_to, {}) if report.get("report_type") in ["Query Report", "Script Report"]: new_item['is_query_report'] = 1 + # Translate label + new_item["label"] = _(item.label) if item.label else _(item.link_to) + items.append(new_item) return items @@ -247,8 +254,11 @@ def get_desk_sidebar_items(): from collections import defaultdict sidebar_items = defaultdict(list) + # The order will be maintained while categorizing for page in pages: - # The order will be maintained while categorizing + # Translate label + page['label'] = _(page.get('name')) + print(page) sidebar_items[page["category"]].append(page) return sidebar_items diff --git a/frappe/public/js/frappe/views/desktop/desktop.js b/frappe/public/js/frappe/views/desktop/desktop.js index e0f400ed9a..742f769b79 100644 --- a/frappe/public/js/frappe/views/desktop/desktop.js +++ b/frappe/public/js/frappe/views/desktop/desktop.js @@ -44,12 +44,12 @@ export default class Desktop { this.desktop_settings = response.message; } else { frappe.throw({ - title: "Couldn't Load Desk", + title: __("Couldn't Load Desk"), message: - "Something went wrong while loading Desk. Please relaod the page. If the problem persists, contact the Administrator", + __("Something went wrong while loading Desk. Please relaod the page. If the problem persists, contact the Administrator"), indicator: "red", primary_action: { - label: "Reload", + label: __("Reload"), action: () => location.reload() } }); @@ -63,7 +63,7 @@ export default class Desktop { item.name}" class="sidebar-item ${ item.selected ? "selected" : "" }"> - ${item.name} + ${item.label || item.name}
`); }; @@ -78,8 +78,10 @@ export default class Desktop { }; const make_category_title = name => { + // DO NOT REMOVE: Comment to load translation + // __("Modules") __("Domains") __("Places") __("Administration") let $title = $( - `` + `` ); $title.appendTo(this.sidebar); }; @@ -157,14 +159,14 @@ class DesktopPage { } make_customization_link() { - this.customize_link = $(`
Customize Workspace
`); + this.customize_link = $(`
${__('Customize Workspace')}
`); this.customize_link.appendTo(this.page); this.customize_link.on('click', () => { this.customize(); }) this.save_or_discard_link = $(`
- Save / Discard + ${__('Save')} / ${__('Discard')}
`).hide(); this.save_or_discard_link.appendTo(this.page); @@ -272,7 +274,7 @@ class DesktopPage { } this.sections["charts"] = new frappe.widget.WidgetGroup({ - title: this.data.charts.label || `${this.page_name} Dashboard`, + title: this.data.charts.label || __('{} Dashboard', [__(this.page_name)]), container: this.page, type: "chart", columns: 1, @@ -291,7 +293,7 @@ class DesktopPage { make_shortcuts() { this.sections["shortcuts"] = new frappe.widget.WidgetGroup({ - title: this.data.shortcuts.label || `Your Shortcuts`, + title: this.data.shortcuts.label || __(`Your Shortcuts`), container: this.page, type: "shortcut", columns: 3, @@ -308,7 +310,7 @@ class DesktopPage { make_cards() { let cards = new frappe.widget.WidgetGroup({ - title: this.data.cards.label || `Reports & Masters`, + title: this.data.cards.label || __(`Reports & Masters`), container: this.page, type: "links", columns: 3, diff --git a/frappe/public/js/frappe/widgets/new_widget.js b/frappe/public/js/frappe/widgets/new_widget.js index ffd96c87da..787cb3a79c 100644 --- a/frappe/public/js/frappe/widgets/new_widget.js +++ b/frappe/public/js/frappe/widgets/new_widget.js @@ -17,6 +17,8 @@ export default class NewWidget { } get_title() { + // DO NOT REMOVE: Comment to load translation + // __("New Chart") __("New Shortcut") return __(`New ${frappe.utils.to_title_case(this.type)}`); } diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 1584c59ed4..c04d0209f9 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -34,7 +34,14 @@ class WidgetDialog { } get_title() { - return __(`New ${frappe.utils.to_title_case(this.type)}`); + // DO NOT REMOVE: Comment to load translation + // __("New Chart") __("New Shortcut") __("Edit Chart") __("Edit Shortcut") + + if (this.values && Object.keys(this.values).length) { + return __(`Edit ${frappe.utils.to_title_case(this.type)}`); + } else { + return __(`Add ${frappe.utils.to_title_case(this.type)}`); + } } get_fields() { From d7dc9bf64e3ea963a64ad6f8ecff0464c910774f Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 9 Apr 2020 20:11:02 +0530 Subject: [PATCH 228/408] refactor: Remove redundant code --- frappe/public/js/frappe/list/list_sidebar_group_by.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/list_sidebar_group_by.js b/frappe/public/js/frappe/list/list_sidebar_group_by.js index f72ff33e4c..7aa62dcb5f 100644 --- a/frappe/public/js/frappe/list/list_sidebar_group_by.js +++ b/frappe/public/js/frappe/list/list_sidebar_group_by.js @@ -64,7 +64,7 @@ frappe.views.ListGroupBy = class ListGroupBy { if (!docfield) { return; } - fieldtype = frappe.meta.get_docfield(this.doctype, fieldname).fieldtype; + fieldtype = docfield.fieldtype; } return `