From b0b53e03f6a602c5cddcf2c3de5db580dba197af Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:01:18 +0530 Subject: [PATCH 01/85] feat: Add Bulk restore API --- .../deleted_document/deleted_document.py | 34 +++++++++++++++++-- frappe/exceptions.py | 1 + 2 files changed, 33 insertions(+), 2 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index bc2962ab3f..a1786e0a3e 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -4,6 +4,7 @@ from __future__ import unicode_literals import frappe, json +from frappe.desk.doctype.bulk_update.bulk_update import show_progress from frappe.model.document import Document from frappe import _ @@ -11,9 +12,14 @@ class DeletedDocument(Document): pass @frappe.whitelist() -def restore(name): +def restore(name, alert=True): deleted = frappe.get_doc('Deleted Document', name) + + if deleted.restored: + frappe.throw(_("Document {0} Already Restored".format(name)), exc=frappe.DocumentAlreadyRestored) + doc = frappe.get_doc(json.loads(deleted.data)) + try: doc.insert() except frappe.DocstatusTransitionError: @@ -27,4 +33,28 @@ def restore(name): deleted.restored = 1 deleted.db_update() - frappe.msgprint(_('Document Restored')) + if alert: + frappe.msgprint(_('Document Restored')) + + +@frappe.whitelist() +def bulk_restore(docnames): + docnames = frappe.parse_json(docnames) + doctype = "Deleted Document" + restored = [] + failed = [] + + for i, d in enumerate(docnames): + try: + restore(d, alert=False) + message = _('Restoring {0}').format(doctype) + frappe.db.commit() + show_progress(docnames, message, i+1, d) + restored.append(d) + except frappe.DocumentAlreadyRestored: + failed.append(d) + except Exception as e: + failed.append(d) + frappe.db.rollback() + + return restored \ No newline at end of file diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 8ebda9c7b8..88428b875c 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -104,6 +104,7 @@ class IncompatibleApp(ValidationError): pass class InvalidDates(ValidationError): pass class DataTooLongException(ValidationError): pass class FileAlreadyAttachedException(Exception): pass +class DocumentAlreadyRestored(Exception): pass # OAuth exceptions class InvalidAuthorizationHeader(CSRFTokenError): pass class InvalidAuthorizationPrefix(CSRFTokenError): pass From 70e966d5b691b8b9d3889fb21e65d828d35549af Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:01:51 +0530 Subject: [PATCH 02/85] style: formatting + dropped unused variables --- .../deleted_document/deleted_document.py | 20 ++++++++++--------- 1 file changed, 11 insertions(+), 9 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index a1786e0a3e..636e3c4f37 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -3,14 +3,17 @@ # For license information, please see license.txt from __future__ import unicode_literals -import frappe, json +import frappe +import json from frappe.desk.doctype.bulk_update.bulk_update import show_progress from frappe.model.document import Document from frappe import _ + class DeletedDocument(Document): pass + @frappe.whitelist() def restore(name, alert=True): deleted = frappe.get_doc('Deleted Document', name) @@ -40,21 +43,20 @@ def restore(name, alert=True): @frappe.whitelist() def bulk_restore(docnames): docnames = frappe.parse_json(docnames) - doctype = "Deleted Document" + message = _('Restoring Deleted Document') restored = [] - failed = [] for i, d in enumerate(docnames): try: + show_progress(docnames, message, i + 1, d) restore(d, alert=False) - message = _('Restoring {0}').format(doctype) frappe.db.commit() - show_progress(docnames, message, i+1, d) restored.append(d) + except frappe.DocumentAlreadyRestored: - failed.append(d) - except Exception as e: - failed.append(d) + pass + + except Exception: frappe.db.rollback() - return restored \ No newline at end of file + return restored From 9dd0304773c10fb363351cf1e0f07634082ac624 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:02:25 +0530 Subject: [PATCH 03/85] feat: Bulk Restore action under Deleted Document --- .../deleted_document/deleted_document_list.js | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 frappe/core/doctype/deleted_document/deleted_document_list.js diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js new file mode 100644 index 0000000000..eb5a9e087d --- /dev/null +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -0,0 +1,33 @@ +frappe.listview_settings["Deleted Document"] = { + onload: function (doclist) { + const action = () => { + const selected_docs = doclist.get_checked_items(); + if (selected_docs.length > 0) { + let docnames = selected_docs.map((doc) => { + return doc.name; + }); + frappe.call({ + method: + "frappe.core.doctype.deleted_document.deleted_document.bulk_restore", + args: { docnames: docnames }, + callback: function (r) { + if (r.message) { + let num = r.message.length; + frappe.msgprint( + __("{0} Document{1} {2} Restored", [ + num, + num == 1 ? "" : "s", + num == 1 ? "was" : "were", + ]) + ); + if (num > 0) { + doclist.refresh(); + } + } + }, + }); + } + }; + doclist.page.add_actions_menu_item(__("Restore"), action, false); + }, +}; From 0fdb7953bb8b86b7fabe3f34cbf9987bf9d107c9 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 23 Jul 2020 19:12:08 +0530 Subject: [PATCH 04/85] fix: translation syntax and style fixes --- frappe/core/doctype/deleted_document/deleted_document.py | 2 +- .../core/doctype/deleted_document/deleted_document_list.js | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index 636e3c4f37..598bd00cf6 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -19,7 +19,7 @@ def restore(name, alert=True): deleted = frappe.get_doc('Deleted Document', name) if deleted.restored: - frappe.throw(_("Document {0} Already Restored".format(name)), exc=frappe.DocumentAlreadyRestored) + frappe.throw(_("Document {0} Already Restored").format(name), exc=frappe.DocumentAlreadyRestored) doc = frappe.get_doc(json.loads(deleted.data)) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index eb5a9e087d..12ed62f6de 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -7,9 +7,8 @@ frappe.listview_settings["Deleted Document"] = { return doc.name; }); frappe.call({ - method: - "frappe.core.doctype.deleted_document.deleted_document.bulk_restore", - args: { docnames: docnames }, + method: "frappe.core.doctype.deleted_document.deleted_document.bulk_restore", + args: { "docnames": docnames }, callback: function (r) { if (r.message) { let num = r.message.length; From ac1f82af9aeb457a202bb8a2907f3aed1df3e7aa Mon Sep 17 00:00:00 2001 From: gavin Date: Fri, 24 Jul 2020 11:41:10 +0530 Subject: [PATCH 05/85] fix: translation syntax Co-authored-by: Shivam Mishra --- .../deleted_document/deleted_document_list.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index 12ed62f6de..4443dc883d 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -12,13 +12,13 @@ frappe.listview_settings["Deleted Document"] = { callback: function (r) { if (r.message) { let num = r.message.length; - frappe.msgprint( - __("{0} Document{1} {2} Restored", [ - num, - num == 1 ? "" : "s", - num == 1 ? "was" : "were", - ]) - ); + let message; + if (num === 1) { + message = "{0} Document was Restored" + } else { + message = "{0} Documents were Restored" + } + frappe.msgprint(__(message, [num]) if (num > 0) { doclist.refresh(); } From cfa286c5b03a01d9a6898f4bc90058c1470a08d1 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Jul 2020 11:58:50 +0530 Subject: [PATCH 06/85] style: formatting, syntax and spacing --- .../core/doctype/deleted_document/deleted_document_list.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index 4443dc883d..a49d8125e3 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -14,11 +14,11 @@ frappe.listview_settings["Deleted Document"] = { let num = r.message.length; let message; if (num === 1) { - message = "{0} Document was Restored" + message = "{0} Document was Restored"; } else { - message = "{0} Documents were Restored" + message = "{0} Documents were Restored"; } - frappe.msgprint(__(message, [num]) + frappe.msgprint(__(message, [num])); if (num > 0) { doclist.refresh(); } From 85f8c712dcfba4f9c643e9d2f1dc4efd85aff010 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Jul 2020 11:59:29 +0530 Subject: [PATCH 07/85] fix: Show files restore summary --- .../deleted_document/deleted_document.py | 31 ++++++++++++++++++- 1 file changed, 30 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index 598bd00cf6..e637f4f000 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -45,6 +45,8 @@ def bulk_restore(docnames): docnames = frappe.parse_json(docnames) message = _('Restoring Deleted Document') restored = [] + invalid = [] + failed = [] for i, d in enumerate(docnames): try: @@ -54,9 +56,36 @@ def bulk_restore(docnames): restored.append(d) except frappe.DocumentAlreadyRestored: - pass + frappe.message_log.pop() + invalid.append(d) except Exception: + failed.append(d) frappe.db.rollback() + frappe.message_log.pop() + + if failed or invalid: + tail = "" + + restored_data = "" + restored_head = _("Documents restored successfully") + "
    " + for docname in restored: + restored_data += "
  • {0}
  • ".format(docname) + restored_body = restored_head + restored_data + tail + + invalid_data = "" + invalid_head = _("Documents that were already Restored") + "
      " + for docname in invalid: + invalid_data += "
    • {0}
    • ".format(docname) + invalid_body = invalid_head + invalid_data + tail + + failed_data = "" + failed_head = _("Documents that Failed to Restore") + "
        " + for docname in failed: + failed_data += "
      • {0}
      • ".format(docname) + failed_body = failed_head + failed_data + tail + + summary = (restored_body if restored else "") + (invalid_body if invalid else "") + (failed_body if failed else "") + frappe.msgprint(summary, title="Document Restoration Summary", indicator="orange", is_minimizable=True) return restored From 9d24389c096f1dd2c4edfdfd588f9192bf94e437 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 24 Jul 2020 12:21:02 +0530 Subject: [PATCH 08/85] style: commonify summary generation --- .../deleted_document/deleted_document.py | 35 +++++++------------ 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document.py b/frappe/core/doctype/deleted_document/deleted_document.py index e637f4f000..d8f39ca139 100644 --- a/frappe/core/doctype/deleted_document/deleted_document.py +++ b/frappe/core/doctype/deleted_document/deleted_document.py @@ -44,9 +44,7 @@ def restore(name, alert=True): def bulk_restore(docnames): docnames = frappe.parse_json(docnames) message = _('Restoring Deleted Document') - restored = [] - invalid = [] - failed = [] + restored, invalid, failed = [], [], [] for i, d in enumerate(docnames): try: @@ -60,32 +58,25 @@ def bulk_restore(docnames): invalid.append(d) except Exception: + frappe.message_log.pop() failed.append(d) frappe.db.rollback() - frappe.message_log.pop() if failed or invalid: - tail = "
      " + def body(docnames): + href = "
    • {0}
    • " + return "
        " + "".join([href.format(docname) for docname in docnames]) - restored_data = "" - restored_head = _("Documents restored successfully") + "
          " - for docname in restored: - restored_data += "
        • {0}
        • ".format(docname) - restored_body = restored_head + restored_data + tail + def message(title, docnames): + if docnames: + return _(title) + body(docnames) + "
        " + return "" - invalid_data = "" - invalid_head = _("Documents that were already Restored") + "
          " - for docname in invalid: - invalid_data += "
        • {0}
        • ".format(docname) - invalid_body = invalid_head + invalid_data + tail + restored_summary = message("Documents restored successfully", restored) + invalid_summary = message("Documents that were already Restored", invalid) + failed_summary = message("Documents that Failed to Restore", failed) - failed_data = "" - failed_head = _("Documents that Failed to Restore") + "
            " - for docname in failed: - failed_data += "
          • {0}
          • ".format(docname) - failed_body = failed_head + failed_data + tail - - summary = (restored_body if restored else "") + (invalid_body if invalid else "") + (failed_body if failed else "") + summary = restored_summary + invalid_summary + failed_summary frappe.msgprint(summary, title="Document Restoration Summary", indicator="orange", is_minimizable=True) return restored From 1903e169dfa464704f2360bd7e1ccb8c03962424 Mon Sep 17 00:00:00 2001 From: gavin Date: Wed, 29 Jul 2020 11:16:24 +0530 Subject: [PATCH 09/85] fix: Update translations ref: https://github.com/frappe/frappe/pull/11094#discussion_r462050750 --- .../doctype/deleted_document/deleted_document_list.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index a49d8125e3..ac21d0ff42 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -13,12 +13,14 @@ frappe.listview_settings["Deleted Document"] = { if (r.message) { let num = r.message.length; let message; - if (num === 1) { - message = "{0} Document was Restored"; + if (num === 0) { + message = __("No Documents were Restored"); + } else if (num === 1) { + message = __("Document was Restored"); } else { - message = "{0} Documents were Restored"; + message = __("{0} Documents were Restored", [num]); } - frappe.msgprint(__(message, [num])); + frappe.msgprint(message); if (num > 0) { doclist.refresh(); } From 91d0e47f1dacf544533dc840b848cb5a59646a6a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 17 Jun 2020 20:03:38 +0530 Subject: [PATCH 10/85] feat: optimize while storing index --- frappe/modules/full_text_search.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/modules/full_text_search.py b/frappe/modules/full_text_search.py index fce9983907..a9b93a1265 100644 --- a/frappe/modules/full_text_search.py +++ b/frappe/modules/full_text_search.py @@ -67,7 +67,7 @@ def build_index(index_name, documents): title=document.title, path=document.path, content=document.content ) - writer.commit() + writer.commit(optimize=True) def search(index_name, text, scope=None, limit=20): From aa0cf2bad4ba4a0ed749e0187bab7f39fcb5cd92 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 17 Jun 2020 20:05:08 +0530 Subject: [PATCH 11/85] feat: added re-index method --- frappe/modules/full_text_search.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/frappe/modules/full_text_search.py b/frappe/modules/full_text_search.py index a9b93a1265..d06d925d08 100644 --- a/frappe/modules/full_text_search.py +++ b/frappe/modules/full_text_search.py @@ -101,6 +101,26 @@ def search(index_name, text, scope=None, limit=20): return out +def reindex_path(index_name, path): + document = get_document_to_index(path): + reindex(index_name, document) + +def reindex(index_name, document): + index_dir = get_index_path(index_name) + ix = open_dir(index_dir) + + with ix.searcher() as searcher: + writer = ix.writer() + + # Remove the index of the particular file + writer.delete_by_term('path', document.path) + + # that wasn't indexed before. So index it! + writer.add_document( + title=document.title, path=document.path, content=document.content + ) + + writer.commit(optimize=True) def get_index_path(index_name): return frappe.get_site_path("indexes", index_name) From d5d32770136fc71e6d0de8bbc3a291b825d916a0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 17 Jun 2020 20:05:55 +0530 Subject: [PATCH 12/85] feat: move search portable --- frappe/templates/doc.html | 66 +-------------------------- frappe/website/js/website.js | 86 +++++++++++++++++++++++++++++++++++- 2 files changed, 87 insertions(+), 65 deletions(-) diff --git a/frappe/templates/doc.html b/frappe/templates/doc.html index 3e1cc5509a..1c70aace98 100644 --- a/frappe/templates/doc.html +++ b/frappe/templates/doc.html @@ -22,7 +22,7 @@
            -
          ": ""; } - let { restored, invalid, failed } = r.message; - let restored_summary = message("Documents restored successfully", restored); - let invalid_summary = message("Documents that were already Restored", invalid); - let failed_summary = message("Documents that Failed to Restore", failed); - let summary = restored_summary + invalid_summary + failed_summary; + const { restored, invalid, failed } = r.message; + const restored_summary = message(__("Documents restored successfully"), restored); + const invalid_summary = message(__("Documents that were already Restored"), invalid); + const failed_summary = message(__("Documents that Failed to Restore"), failed); + const summary = restored_summary + invalid_summary + failed_summary; frappe.msgprint(summary, "Document Restoration Summary", true); From b7e6f85a85a823900002821f04a6b024d28ebb44 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 13 Aug 2020 16:44:56 +0530 Subject: [PATCH 78/85] fix: convert to sentence case --- .../core/doctype/deleted_document/deleted_document_list.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index d639ecb631..f5e1147dfb 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -21,11 +21,11 @@ frappe.listview_settings["Deleted Document"] = { const { restored, invalid, failed } = r.message; const restored_summary = message(__("Documents restored successfully"), restored); - const invalid_summary = message(__("Documents that were already Restored"), invalid); - const failed_summary = message(__("Documents that Failed to Restore"), failed); + const invalid_summary = message(__("Documents that were already restored"), invalid); + const failed_summary = message(__("Documents that failed to restore"), failed); const summary = restored_summary + invalid_summary + failed_summary; - frappe.msgprint(summary, "Document Restoration Summary", true); + frappe.msgprint(summary, __("Document Restoration Summary"), true); if (restored.length > 0) { doclist.refresh(); From b49571f9b354451815f17605c16709aa004785a5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 13 Aug 2020 17:53:09 +0530 Subject: [PATCH 79/85] feat(notification): Add WhatsApp/SMS channels (#9623) Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/core/doctype/role/role.py | 22 +- .../core/doctype/sms_settings/sms_settings.py | 3 + .../doctype/notification/notification.js | 163 +++++++++--- .../doctype/notification/notification.json | 32 ++- .../doctype/notification/notification.py | 67 ++++- .../doctype/notification/test_notification.py | 4 +- .../doctype/notification/test_records.json | 12 +- .../notification_recipient.json | 246 ++++-------------- .../doctype/twilio_number_group/__init__.py | 0 .../twilio_number_group.json | 32 +++ .../twilio_number_group.py | 10 + .../doctype/twilio_settings/__init__.py | 0 .../twilio_settings/test_twilio_settings.py | 10 + .../twilio_settings/twilio_settings.js | 8 + .../twilio_settings/twilio_settings.json | 57 ++++ .../twilio_settings/twilio_settings.py | 51 ++++ frappe/patches.txt | 1 + .../v13_0/rename_notification_fields.py | 16 ++ requirements.txt | 3 +- 19 files changed, 458 insertions(+), 279 deletions(-) create mode 100644 frappe/integrations/doctype/twilio_number_group/__init__.py create mode 100644 frappe/integrations/doctype/twilio_number_group/twilio_number_group.json create mode 100644 frappe/integrations/doctype/twilio_number_group/twilio_number_group.py create mode 100644 frappe/integrations/doctype/twilio_settings/__init__.py create mode 100644 frappe/integrations/doctype/twilio_settings/test_twilio_settings.py create mode 100644 frappe/integrations/doctype/twilio_settings/twilio_settings.js create mode 100644 frappe/integrations/doctype/twilio_settings/twilio_settings.json create mode 100644 frappe/integrations/doctype/twilio_settings/twilio_settings.py create mode 100644 frappe/patches/v13_0/rename_notification_fields.py diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py index 657340ec24..e458b401e4 100644 --- a/frappe/core/doctype/role/role.py +++ b/frappe/core/doctype/role/role.py @@ -33,16 +33,22 @@ class Role(Document): if user_type != user.user_type: user.save() -# Get email addresses of all users that have been assigned this role -def get_emails_from_role(role): - emails = [] - for user in get_users(role): - user_email, enabled = frappe.db.get_value("User", user, ["email", "enabled"]) - if enabled and user_email not in ["admin@example.com", "guest@example.com"]: - emails.append(user_email) +def get_info_based_on_role(role, field='email'): + ''' Get information of all users that have been assigned this role ''' + users = frappe.get_list("Has Role", filters={"role": role, "parenttype": "User"}, + fields=["parent"]) - return emails + return get_user_info(users, field) + +def get_user_info(users, field='email'): + ''' Fetch details about users for the specified field ''' + info_list = [] + for user in users: + user_info, enabled = frappe.db.get_value("User", user.parent, [field, "enabled"]) + if enabled and user_info not in ["admin@example.com", "guest@example.com"]: + info_list.append(user_info) + return info_list def get_users(role): return [d.parent for d in frappe.get_all("Has Role", filters={"role": role, "parenttype": "User"}, diff --git a/frappe/core/doctype/sms_settings/sms_settings.py b/frappe/core/doctype/sms_settings/sms_settings.py index f6134e045a..ac835108c1 100644 --- a/frappe/core/doctype/sms_settings/sms_settings.py +++ b/frappe/core/doctype/sms_settings/sms_settings.py @@ -18,6 +18,9 @@ class SMSSettings(Document): def validate_receiver_nos(receiver_list): validated_receiver_list = [] for d in receiver_list: + if not d: + break + # remove invalid character for x in [' ','-', '(', ')']: d = d.replace(x, '') diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 02fc8512ca..454514f922 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -3,104 +3,175 @@ this.frm.add_fetch('sender', 'email_id', 'sender_email'); -this.frm.fields_dict.sender.get_query = function(){ +this.frm.fields_dict.sender.get_query = function() { return { filters: { - 'enable_outgoing': 1 + enable_outgoing: 1 } - } + }; }; frappe.notification = { setup_fieldname_select: function(frm) { // get the doctype to update fields - if(!frm.doc.document_type) { + if (!frm.doc.document_type) { return; } frappe.model.with_doctype(frm.doc.document_type, function() { let get_select_options = function(df) { - return {value: df.fieldname, label: df.fieldname + " (" + __(df.label) + ")"}; - } + return { + value: df.fieldname, + label: df.fieldname + ' (' + __(df.label) + ')' + }; + }; let get_date_change_options = function() { let date_options = $.map(fields, function(d) { - return (d.fieldtype=="Date" || d.fieldtype=="Datetime")? - get_select_options(d) : null; + return d.fieldtype == 'Date' || d.fieldtype == 'Datetime' + ? get_select_options(d) + : null; }); // append creation and modified date to Date Change field return date_options.concat([ - { value: "creation", label: `creation (${__('Created On')})` }, - { value: "modified", label: `modified (${__('Last Modified Date')})` } + { value: 'creation', label: `creation (${__('Created On')})` }, + { value: 'modified', label: `modified (${__('Last Modified Date')})` } ]); - } + }; - let fields = frappe.get_doc("DocType", frm.doc.document_type).fields; - let options = $.map(fields, - function(d) { return in_list(frappe.model.no_value_type, d.fieldtype) ? - null : get_select_options(d); }); + let fields = frappe.get_doc('DocType', frm.doc.document_type).fields; + let options = $.map(fields, function(d) { + return in_list(frappe.model.no_value_type, d.fieldtype) + ? null : get_select_options(d); + }); // set value changed options - frm.set_df_property("value_changed", "options", [""].concat(options)); - frm.set_df_property("set_property_after_alert", "options", [""].concat(options)); + frm.set_df_property('value_changed', 'options', [''].concat(options)); + frm.set_df_property( + 'set_property_after_alert', + 'options', + [''].concat(options) + ); // set date changed options - frm.set_df_property("date_changed", "options", get_date_change_options()); + frm.set_df_property('date_changed', 'options', get_date_change_options()); - let email_fields = $.map(fields, - function(d) { return (d.options == "Email" || - (d.options=='User' && d.fieldtype=='Link')) ? - get_select_options(d) : null; }); + let receiver_fields = []; + if (frm.doc.channel === 'Email') { + receiver_fields = $.map(fields, function(d) { + return d.options == 'Email' || + (d.options == 'User' && d.fieldtype == 'Link') + ? get_select_options(d) : null; + }); + } else if (in_list(['WhatsApp', 'SMS'], frm.doc.channel)) { + receiver_fields = $.map(fields, function(d) { + return d.options == 'Phone' ? get_select_options(d) : null; + }); + } // set email recipient options - frappe.meta.get_docfield("Notification Recipient", "email_by_document_field", + frappe.meta.get_docfield( + 'Notification Recipient', + 'receiver_by_document_field', // set first option as blank to allow notification not to be defaulted to the owner - frm.doc.name).options = [""].concat(["owner"].concat(email_fields)); + frm.doc.name + ).options = [''].concat(["owner"]).concat(receiver_fields); frm.fields_dict.recipients.grid.refresh(); }); - } -} + }, + setup_example_message: function(frm) { + let template = ''; + if (frm.doc.channel === 'WhatsApp') { + template = `
          Warning:
          Only Use Pre-Approved WhatsApp for Business Template +
          Message Example
          -frappe.ui.form.on("Notification", { +
          +Your {{ doc.name }} order of {{ doc.total }} has shipped and should be delivered on {{ doc.date }}. Details : {{doc.customer}}
          +
          `; + } else if (frm.doc.channel === 'Email') { + template = `
          Message Example
          + +
          <h3>Order Overdue</h3>
          +
          +<p>Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.</p>
          +
          +<!-- show last comment -->
          +{% if comments %}
          +Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }}
          +{% endif %}
          +
          +<h4>Details</h4>
          +
          +<ul>
          +<li>Customer: {{ doc.customer }}
          +<li>Amount: {{ doc.grand_total }}
          +</ul>
          +
          + `; + } else { + template = `
          Message Example
          + +
          *Order Overdue*
          +
          +Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.
          +
          +
          +{% if comments %}
          +Last comment: {{ comments[-1].comment }} by {{ comments[-1].by }}
          +{% endif %}
          +
          +*Details*
          +
          +• Customer: {{ doc.customer }}
          +• Amount: {{ doc.grand_total }}
          +
          `; + } + frm.set_df_property('message_examples', 'options', template); + + } +}; + +frappe.ui.form.on('Notification', { onload: function(frm) { - frm.set_query("document_type", function() { + frm.set_query('document_type', function() { return { - "filters": { - "istable": 0 + filters: { + istable: 0 } - } + }; }); - frm.set_query("print_format", function() { + frm.set_query('print_format', function() { return { - "filters": { - "doc_type": frm.doc.document_type + filters: { + doc_type: frm.doc.document_type } - } + }; }); }, refresh: function(frm) { frappe.notification.setup_fieldname_select(frm); - frm.get_field("is_standard").toggle(frappe.boot.developer_mode); + frm.get_field('is_standard').toggle(frappe.boot.developer_mode); frm.trigger('event'); }, document_type: function(frm) { frappe.notification.setup_fieldname_select(frm); }, view_properties: function(frm) { - frappe.route_options = {doc_type:frm.doc.document_type}; - frappe.set_route("Form", "Customize Form"); + frappe.route_options = { doc_type: frm.doc.document_type }; + frappe.set_route('Form', 'Customize Form'); }, event: function(frm) { - if(in_list(['Days Before', 'Days After'], frm.doc.event)) { + if (in_list(['Days Before', 'Days After'], frm.doc.event)) { frm.add_custom_button(__('Get Alerts for Today'), function() { frappe.call({ - method: 'frappe.email.doctype.notification.notification.get_documents_for_today', + method: + 'frappe.email.doctype.notification.notification.get_documents_for_today', args: { notification: frm.doc.name }, callback: function(r) { - if(r.message) { + if (r.message) { frappe.msgprint(r.message); } else { frappe.msgprint(__('No alerts for today')); @@ -111,6 +182,14 @@ frappe.ui.form.on("Notification", { } }, channel: function(frm) { - frm.toggle_reqd("recipients", frm.doc.channel=="Email"); + frm.toggle_reqd('recipients', frm.doc.channel == 'Email'); + frappe.notification.setup_fieldname_select(frm); + frappe.notification.setup_example_message(frm); + if (frm.doc.channel === 'SMS' && frm.doc.__islocal) { + frm.set_df_property('channel', + 'description', `To use SMS Channel, initialize SMS Settings.`); + } else { + frm.set_df_property('channel', 'description', ` `); + } } }); diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 932f0491a9..95f218ad73 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -10,6 +10,7 @@ "enabled", "column_break_2", "channel", + "twilio_number", "slack_webhook_url", "filters", "subject", @@ -37,7 +38,6 @@ "message_sb", "message", "message_examples", - "slack_message_examples", "view_properties", "column_break_25", "attach_print", @@ -60,11 +60,13 @@ "fieldname": "channel", "fieldtype": "Select", "label": "Channel", - "options": "Email\nSlack\nSystem Notification", - "reqd": 1 + "options": "Email\nSlack\nSystem Notification\nWhatsApp\nSMS", + "reqd": 1, + "set_only_once": 1 }, { "depends_on": "eval:doc.channel=='Slack'", + "description": "To use Slack Channel, add a Slack Webhook URL.", "fieldname": "slack_webhook_url", "fieldtype": "Link", "label": "Slack Channel", @@ -77,13 +79,14 @@ "label": "Filters" }, { + "depends_on": "eval: !in_list(['SMS', 'WhatsApp'], doc.channel)", "description": "To add dynamic subject, use jinja tags like\n\n
          {{ doc.name }} Delivered
          ", "fieldname": "subject", "fieldtype": "Data", "ignore_xss_filter": 1, "in_list_view": 1, "label": "Subject", - "reqd": 1 + "mandatory_depends_on": "eval:!in_list(['SMS', 'WhatsApp'], doc.channel)" }, { "fieldname": "document_type", @@ -153,6 +156,7 @@ "label": "Value Changed" }, { + "depends_on": "eval: doc.channel == 'Email'", "fieldname": "sender", "fieldtype": "Link", "label": "Sender", @@ -203,7 +207,7 @@ "label": "Value To Be Set" }, { - "depends_on": "eval:doc.channel!=='Slack'", + "depends_on": "eval:in_list(['Email', 'SMS', 'WhatsApp'], doc.channel)", "fieldname": "column_break_5", "fieldtype": "Section Break", "label": "Recipients" @@ -228,19 +232,11 @@ "label": "Message" }, { - "depends_on": "eval:doc.channel=='Email'", "fieldname": "message_examples", "fieldtype": "HTML", "label": "Message Examples", "options": "
          Message Example
          \n\n
          <h3>Order Overdue</h3>\n\n<p>Transaction {{ doc.name }} has exceeded Due Date. Please take necessary action.</p>\n\n<!-- show last comment -->\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n<h4>Details</h4>\n\n<ul>\n<li>Customer: {{ doc.customer }}\n<li>Amount: {{ doc.grand_total }}\n</ul>\n
          " }, - { - "depends_on": "eval:doc.channel=='Slack'", - "fieldname": "slack_message_examples", - "fieldtype": "HTML", - "label": "Message Examples", - "options": "
          Message Example
          \n\n
          *Order Overdue*\n\nTransaction {{ doc.name }} has exceeded Due Date. Please take necessary action.\n\n\n{% if comments %}\nLast comment: {{ comments[-1].comment }} by {{ comments[-1].by }}\n{% endif %}\n\n*Details*\n\n\u2022 Customer: {{ doc.customer }}\n\u2022 Amount: {{ doc.grand_total }}\n
          " - }, { "fieldname": "view_properties", "fieldtype": "Button", @@ -266,6 +262,14 @@ "label": "Print Format", "options": "Print Format" }, + { + "depends_on": "eval: doc.channel==='WhatsApp'", + "description": "To use WhatsApp for Business, initialize Twilio Settings.", + "fieldname": "twilio_number", + "fieldtype": "Link", + "label": "Twilio Number", + "options": "Twilio Number Group" + }, { "default": "0", "depends_on": "eval: doc.channel !== 'System Notification'", @@ -277,7 +281,7 @@ ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-06-23 14:01:25.462544", + "modified": "2020-08-11 19:24:35.479373", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 81670756f6..2ec208c89d 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -7,12 +7,14 @@ import frappe import json, os from frappe import _ from frappe.model.document import Document -from frappe.core.doctype.role.role import get_emails_from_role +from frappe.core.doctype.role.role import get_info_based_on_role, get_user_info from frappe.utils import validate_email_address, nowdate, parse_val, is_html, add_to_date from frappe.utils.jinja import validate_template from frappe.modules.utils import export_module_json, get_doc_module from six import string_types from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message +from frappe.integrations.doctype.twilio_settings.twilio_settings import send_whatsapp_message +from frappe.core.doctype.sms_settings.sms_settings import send_sms from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification class Notification(Document): @@ -26,7 +28,9 @@ class Notification(Document): self.name = self.subject def validate(self): - validate_template(self.subject) + if self.channel not in ('WhatsApp', 'SMS'): + validate_template(self.subject) + validate_template(self.message) if self.event in ("Days Before", "Days After") and not self.date_changed: @@ -126,8 +130,15 @@ def get_context(context): if self.channel == 'Slack': self.send_a_slack_msg(doc, context) + if self.channel == 'WhatsApp': + self.send_whatsapp_msg(doc, context) + + if self.channel == 'SMS': + self.send_sms(doc, context) + if self.channel == 'System Notification' or self.send_system_notification: self.create_system_notification(doc, context) + except: frappe.log_error(title='Failed to send notification', message=frappe.get_traceback()) @@ -195,11 +206,24 @@ def get_context(context): and attachments[0].get('print_letterhead')) or False)) def send_a_slack_msg(self, doc, context): - send_slack_message( - webhook_url=self.slack_webhook_url, - message=frappe.render_template(self.message, context), - reference_doctype = doc.doctype, - reference_name = doc.name) + send_slack_message( + webhook_url=self.slack_webhook_url, + message=frappe.render_template(self.message, context), + reference_doctype=doc.doctype, + reference_name=doc.name) + + def send_whatsapp_msg(self, doc, context): + send_whatsapp_message( + sender=self.twilio_number, + receiver_list=self.get_receiver_list(doc, context), + message=frappe.render_template(self.message, context), + ) + + def send_sms(self, doc, context): + send_sms( + receiver_list=self.get_receiver_list(doc, context), + msg=frappe.render_template(self.message, context) + ) def get_list_of_recipients(self, doc, context): recipients = [] @@ -209,8 +233,8 @@ def get_context(context): if recipient.condition: if not frappe.safe_eval(recipient.condition, None, context): continue - if recipient.email_by_document_field: - email_ids_value = doc.get(recipient.email_by_document_field) + if recipient.receiver_by_document_field: + email_ids_value = doc.get(recipient.receiver_by_document_field) if validate_email_address(email_ids_value): email_ids = email_ids_value.replace(",", "\n") recipients = recipients + email_ids.split("\n") @@ -232,8 +256,8 @@ def get_context(context): bcc = bcc + recipient.bcc.split("\n") #For sending emails to specified role - if recipient.email_by_role: - emails = get_emails_from_role(recipient.email_by_role) + if recipient.receiver_by_role: + emails = get_info_based_on_role(recipient.receiver_by_role, 'email') for email in emails: recipients = recipients + email.split("\n") @@ -242,6 +266,27 @@ def get_context(context): return None, None, None return list(set(recipients)), list(set(cc)), list(set(bcc)) + def get_receiver_list(self, doc, context): + ''' return receiver list based on the doc field and role specified ''' + receiver_list = [] + for recipient in self.recipients: + if recipient.condition: + if not frappe.safe_eval(recipient.condition, None, context): + continue + + # For sending messages to the owner's mobile phone number + if recipient.receiver_by_document_field == 'owner': + receiver_list.append(get_user_info(doc.get('owner'), 'mobile_no')) + # For sending messages to the number specified in the receiver field + elif recipient.receiver_by_document_field: + receiver_list.append(doc.get(recipient.receiver_by_document_field)) + + #For sending messages to specified role + if recipient.receiver_by_role: + receiver_list += get_info_based_on_role(recipient.receiver_by_role, 'mobile_no') + + return receiver_list + def get_attachment(self, doc): """ check print settings are attach the pdf """ if not self.attach_print: diff --git a/frappe/email/doctype/notification/test_notification.py b/frappe/email/doctype/notification/test_notification.py index b9bbde172d..9bdf09375d 100644 --- a/frappe/email/doctype/notification/test_notification.py +++ b/frappe/email/doctype/notification/test_notification.py @@ -63,7 +63,7 @@ class TestNotification(unittest.TestCase): notification.message = "test" recipent = frappe.new_doc("Notification Recipient") - recipent.email_by_document_field = "owner" + recipent.receiver_by_document_field = "owner" notification.recipents = recipent notification.condition = "test" @@ -105,7 +105,7 @@ class TestNotification(unittest.TestCase): "value_changed": "description1", "message": "Description changed", "recipients": [ - { "email_by_document_field": "owner" } + { "receiver_by_document_field": "owner" } ] }).insert() frappe.db.commit() diff --git a/frappe/email/doctype/notification/test_records.json b/frappe/email/doctype/notification/test_records.json index 865b2ac021..665f800c0f 100644 --- a/frappe/email/doctype/notification/test_records.json +++ b/frappe/email/doctype/notification/test_records.json @@ -8,7 +8,7 @@ "message": "New comment {{ doc.content }} created", "condition": "doc.communication_type=='Comment'", "recipients": [ - { "email_by_document_field": "owner" } + { "receiver_by_document_field": "owner" } ] }, { @@ -20,7 +20,7 @@ "message": "New comment {{ doc.content }} saved", "condition": "doc.communication_type=='Comment'", "recipients": [ - { "email_by_document_field": "owner" } + { "receiver_by_document_field": "owner" } ], "set_property_after_alert": "subject", "property_value": "__testing__" @@ -34,7 +34,7 @@ "condition": "doc.event_type=='Public'", "message": "A new public event {{ doc.subject }} on {{ doc.starts_on }} is created", "recipients": [ - { "email_by_document_field": "owner" } + { "receiver_by_document_field": "owner" } ] }, { @@ -46,7 +46,7 @@ "value_changed": "description", "message": "Description changed", "recipients": [ - { "email_by_document_field": "owner" } + { "receiver_by_document_field": "owner" } ] }, { @@ -59,7 +59,7 @@ "days_in_advance": 2, "message": "Description changed", "recipients": [ - { "email_by_document_field": "owner" } + { "receiver_by_document_field": "owner" } ] }, { @@ -70,7 +70,7 @@ "attach_print": 0, "message": "New user {{ doc.name }} created", "recipients": [ - { "email_by_document_field": "owner", "cc": "{{ doc.email }}" } + { "receiver_by_document_field": "owner", "cc": "{{ doc.email }}" } ] } ] diff --git a/frappe/email/doctype/notification_recipient/notification_recipient.json b/frappe/email/doctype/notification_recipient/notification_recipient.json index ec35dccc63..201899cd57 100644 --- a/frappe/email/doctype/notification_recipient/notification_recipient.json +++ b/frappe/email/doctype/notification_recipient/notification_recipient.json @@ -1,204 +1,60 @@ { - "allow_copy": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2014-07-11 17:19:37.037109", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", + "actions": [], + "creation": "2014-07-11 17:19:37.037109", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "receiver_by_document_field", + "receiver_by_role", + "cc", + "bcc", + "condition" + ], "fields": [ { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "", - "fieldname": "email_by_document_field", - "fieldtype": "Select", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Email By Document Field", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:parent.channel=='Email'", + "description": "Optional: Always send to these ids. Each Email Address on a new row", + "fieldname": "cc", + "fieldtype": "Code", + "label": "CC" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "email_by_role", - "fieldtype": "Link", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 1, - "in_standard_filter": 0, - "label": "Email By Role", - "length": 0, - "no_copy": 0, - "options": "Role", - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "depends_on": "eval:parent.channel=='Email'", + "fieldname": "bcc", + "fieldtype": "Code", + "label": "BCC" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Optional: Always send to these ids. Each Email Address on a new row", - "fieldname": "cc", - "fieldtype": "Code", - "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": "CC", - "length": 0, - "no_copy": 0, - "options": "", - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, + "description": "Expression, Optional", + "fieldname": "condition", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Condition" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "bcc", - "fieldtype": "Code", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "BCC", - "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": "receiver_by_document_field", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Receiver By Document Field" + }, { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "description": "Expression, Optional", - "fieldname": "condition", - "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": "Condition", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 + "fieldname": "receiver_by_role", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Receiver By Role", + "options": "Role" } - ], - "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-09-03 18:37:57.043251", - "modified_by": "Administrator", - "module": "Email", - "name": "Notification Recipient", - "name_case": "", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 0, - "track_seen": 0, - "track_views": 0 + ], + "istable": 1, + "links": [], + "modified": "2020-02-21 11:18:40.125233", + "modified_by": "Administrator", + "module": "Email", + "name": "Notification Recipient", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC" } \ No newline at end of file diff --git a/frappe/integrations/doctype/twilio_number_group/__init__.py b/frappe/integrations/doctype/twilio_number_group/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/twilio_number_group/twilio_number_group.json b/frappe/integrations/doctype/twilio_number_group/twilio_number_group.json new file mode 100644 index 0000000000..1790581ca7 --- /dev/null +++ b/frappe/integrations/doctype/twilio_number_group/twilio_number_group.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "autoname": "field:phone_number", + "creation": "2020-02-24 13:58:58.036914", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "phone_number" + ], + "fields": [ + { + "fieldname": "phone_number", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Phone Number", + "unique": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-02 14:54:34.396254", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Twilio Number Group", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/twilio_number_group/twilio_number_group.py b/frappe/integrations/doctype/twilio_number_group/twilio_number_group.py new file mode 100644 index 0000000000..04cb9ae146 --- /dev/null +++ b/frappe/integrations/doctype/twilio_number_group/twilio_number_group.py @@ -0,0 +1,10 @@ +# -*- 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 + +class TwilioNumberGroup(Document): + pass diff --git a/frappe/integrations/doctype/twilio_settings/__init__.py b/frappe/integrations/doctype/twilio_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/integrations/doctype/twilio_settings/test_twilio_settings.py b/frappe/integrations/doctype/twilio_settings/test_twilio_settings.py new file mode 100644 index 0000000000..bcb1368d68 --- /dev/null +++ b/frappe/integrations/doctype/twilio_settings/test_twilio_settings.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 TestTwilioSettings(unittest.TestCase): + pass diff --git a/frappe/integrations/doctype/twilio_settings/twilio_settings.js b/frappe/integrations/doctype/twilio_settings/twilio_settings.js new file mode 100644 index 0000000000..59ebcf2e7d --- /dev/null +++ b/frappe/integrations/doctype/twilio_settings/twilio_settings.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Twilio Settings', { + refresh: function(frm) { + frm.dashboard.set_headline(__("For more information, {0}.", [`${__('Click here')}`])); + } +}); diff --git a/frappe/integrations/doctype/twilio_settings/twilio_settings.json b/frappe/integrations/doctype/twilio_settings/twilio_settings.json new file mode 100644 index 0000000000..e54500fd5d --- /dev/null +++ b/frappe/integrations/doctype/twilio_settings/twilio_settings.json @@ -0,0 +1,57 @@ +{ + "actions": [], + "creation": "2020-01-28 15:21:44.457163", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "account_sid", + "auth_token", + "column_break_2", + "twilio_number" + ], + "fields": [ + { + "fieldname": "account_sid", + "fieldtype": "Data", + "label": "Account SID" + }, + { + "fieldname": "auth_token", + "fieldtype": "Password", + "label": "Auth Token" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "fieldname": "twilio_number", + "fieldtype": "Table", + "label": "Twilio Number", + "options": "Twilio Number Group" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-08-11 15:28:57.860554", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Twilio Settings", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/integrations/doctype/twilio_settings/twilio_settings.py b/frappe/integrations/doctype/twilio_settings/twilio_settings.py new file mode 100644 index 0000000000..ba0565b3af --- /dev/null +++ b/frappe/integrations/doctype/twilio_settings/twilio_settings.py @@ -0,0 +1,51 @@ +# -*- 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 +from twilio.rest import Client +from frappe import _ +from frappe.utils.password import get_decrypted_password +from six import string_types + +class TwilioSettings(Document): + pass + +def send_whatsapp_message(sender, receiver_list, message): + import json + if isinstance(receiver_list, string_types): + receiver_list = json.loads(receiver_list) + if not isinstance(receiver_list, list): + receiver_list = [receiver_list] + + + twilio_settings = frappe.get_doc("Twilio Settings") + auth_token = get_decrypted_password("Twilio Settings", "Twilio Settings", 'auth_token') + client = Client(twilio_settings.account_sid, auth_token) + args = { + "from_": 'whatsapp:+{}'.format(sender), + "body": message + } + + failed_delivery = [] + + for rec in receiver_list: + args.update({"to": 'whatsapp:{}'.format(rec)}) + resp = _send_whatsapp(args, client) + if not resp or resp.error_message: + failed_delivery.append(rec) + + if failed_delivery: + frappe.log_error(_("The message wasn't correctly delivered to: {}".format(", ".join(failed_delivery))), _('Delivery Failed')) + + +def _send_whatsapp(message_dict, client): + response = frappe._dict() + try: + response = client.messages.create(**message_dict) + except Exception as e: + frappe.log_error(e, title = _('Twilio WhatsApp Message Error')) + + return response \ No newline at end of file diff --git a/frappe/patches.txt b/frappe/patches.txt index 8f100c79f1..8ea6d8ae29 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -299,3 +299,4 @@ frappe.patches.v13_0.rename_is_custom_field_in_dashboard_chart frappe.patches.v13_0.add_standard_navbar_items frappe.patches.v13_0.generate_theme_files_in_public_folder frappe.patches.v13_0.increase_password_length +frappe.patches.v13_0.rename_notification_fields diff --git a/frappe/patches/v13_0/rename_notification_fields.py b/frappe/patches/v13_0/rename_notification_fields.py new file mode 100644 index 0000000000..2984e6503c --- /dev/null +++ b/frappe/patches/v13_0/rename_notification_fields.py @@ -0,0 +1,16 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.utils.rename_field import rename_field + +def execute(): + """ + Change notification recipient fields from email to receiver fields + """ + frappe.reload_doc("Email", "doctype", "Notification Recipient") + frappe.reload_doc("Email", "doctype", "Notification") + + rename_field("Notification Recipient", "email_by_document_field", "receiver_by_document_field") + rename_field("Notification Recipient", "email_by_role", "receiver_by_role") \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 46e853794e..a88d0e3c1d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -70,4 +70,5 @@ xlrd==1.2.0 zxcvbn-python==4.4.24 pycryptodome==3.9.8 paytmchecksum==1.7.0 -wrapt==1.10.11 \ No newline at end of file +wrapt==1.10.11 +twilio==6.44.2 \ No newline at end of file From 6bfd07be51f4c0117a8f9100cced951018628ade Mon Sep 17 00:00:00 2001 From: Anurag Mishra <32095923+Anurag810@users.noreply.github.com> Date: Fri, 14 Aug 2020 16:12:02 +0530 Subject: [PATCH 80/85] feat: Ignore doctypes for cancel_all (#11034) --- frappe/core/doctype/doctype/test_doctype.py | 93 +++++++++++++++++++++ frappe/desk/form/linked_with.py | 15 ++-- frappe/public/js/frappe/form/form.js | 42 ++++++---- 3 files changed, 130 insertions(+), 20 deletions(-) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index fe9f88b9b3..00e80ce4e7 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -376,3 +376,96 @@ class TestDocType(unittest.TestCase): link_doc.delete() doc.delete() frappe.db.commit() + + def test_ignore_cancelation_of_linked_doctype_during_cancell(self): + import json + from frappe.desk.form.linked_with import get_submitted_linked_docs, cancel_all_linked_docs + + #create linked doctype + link_doc = self.new_doctype('Test Linked Doctype 1') + link_doc.is_submittable = 1 + for data in link_doc.get('permissions'): + data.submit = 1 + data.cancel = 1 + link_doc.insert() + + #create first parent doctype + test_doc_1 = self.new_doctype('Test Doctype 1') + test_doc_1.is_submittable = 1 + + field_2 = test_doc_1.append('fields', {}) + field_2.label = 'Test Linked Doctype 1' + field_2.fieldname = 'test_linked_doctype_a' + field_2.fieldtype = 'Link' + field_2.options = 'Test Linked Doctype 1' + + for data in test_doc_1.get('permissions'): + data.submit = 1 + data.cancel = 1 + test_doc_1.insert() + + #crete second parent doctype + doc = self.new_doctype('Test Doctype 2') + doc.is_submittable = 1 + + field_2 = doc.append('fields', {}) + field_2.label = 'Test Linked Doctype 1' + field_2.fieldname = 'test_linked_doctype_a' + field_2.fieldtype = 'Link' + field_2.options = 'Test Linked Doctype 1' + + for data in link_doc.get('permissions'): + data.submit = 1 + data.cancel = 1 + doc.insert() + + # create doctype data + data_link_doc_1 = frappe.new_doc('Test Linked Doctype 1') + data_link_doc_1.some_fieldname = 'Data1' + data_link_doc_1.insert() + data_link_doc_1.save() + data_link_doc_1.submit() + + data_doc_2 = frappe.new_doc('Test Doctype 1') + data_doc_2.some_fieldname = 'Data1' + data_doc_2.test_linked_doctype_a = data_link_doc_1.name + data_doc_2.insert() + data_doc_2.save() + data_doc_2.submit() + + data_doc = frappe.new_doc('Test Doctype 2') + data_doc.some_fieldname = 'Data1' + data_doc.test_linked_doctype_a = data_link_doc_1.name + data_doc.insert() + data_doc.save() + data_doc.submit() + + docs = get_submitted_linked_docs(link_doc.name, data_link_doc_1.name) + dump_docs = json.dumps(docs.get('docs')) + + cancel_all_linked_docs(dump_docs, ignore_doctypes_on_cancel_all=["Test Doctype 2"]) + + # checking that doc for Test Doctype 2 is not canceled + self.assertRaises(frappe.LinkExistsError, data_link_doc_1.cancel) + + data_doc.load_from_db() + data_doc_2.load_from_db() + self.assertEqual(data_link_doc_1.docstatus, 2) + + #linked doc is canceled + self.assertEqual(data_doc_2.docstatus, 2) + + #ignored doctype 2 during cancel + self.assertEqual(data_doc.docstatus, 1) + + # delete doctype record + data_doc.cancel() + data_doc.delete() + data_doc_2.delete() + data_link_doc_1.delete() + + # delete doctype + link_doc.delete() + doc.delete() + test_doc_1.delete() + frappe.db.commit() diff --git a/frappe/desk/form/linked_with.py b/frappe/desk/form/linked_with.py index 5bae49ea95..733ee1774c 100644 --- a/frappe/desk/form/linked_with.py +++ b/frappe/desk/form/linked_with.py @@ -11,7 +11,6 @@ from frappe import _ from frappe.model.meta import is_single from frappe.modules import load_doctype_module - @frappe.whitelist() def get_submitted_linked_docs(doctype, name, docs=None, visited=None): """ @@ -78,7 +77,7 @@ def get_submitted_linked_docs(doctype, name, docs=None, visited=None): @frappe.whitelist() -def cancel_all_linked_docs(docs): +def cancel_all_linked_docs(docs, ignore_doctypes_on_cancel_all=[]): """ Cancel all linked doctype @@ -87,14 +86,16 @@ def cancel_all_linked_docs(docs): """ docs = json.loads(docs) + if isinstance(ignore_doctypes_on_cancel_all, string_types): + ignore_doctypes_on_cancel_all = json.loads(ignore_doctypes_on_cancel_all) for i, doc in enumerate(docs, 1): - if validate_linked_doc(doc) is True: - frappe.publish_progress(percent=i * 100 / len(docs), title=_("Cancelling documents")) + if validate_linked_doc(doc, ignore_doctypes_on_cancel_all) is True: + frappe.publish_progress(percent=i * 100 / ((len(docs) - len(ignore_doctypes_on_cancel_all))), title=_("Cancelling documents")) linked_doc = frappe.get_doc(doc.get("doctype"), doc.get("name")) linked_doc.cancel() -def validate_linked_doc(docinfo): +def validate_linked_doc(docinfo, ignore_doctypes_on_cancel_all=[]): """ Validate a document to be submitted and non-exempted from auto-cancel. @@ -105,6 +106,10 @@ def validate_linked_doc(docinfo): bool: True if linked document passes all validations, else False """ + #ignore doctype to cancel + if docinfo.get("doctype") in ignore_doctypes_on_cancel_all: + return False + # skip non-submittable doctypes since they don't need to be cancelled if not frappe.get_meta(docinfo.get('doctype')).is_submittable: return False diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index c0b76ee94d..b4bc758399 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -667,22 +667,29 @@ frappe.ui.form.Form = class FrappeForm { savecancel(btn, callback, on_error) { const me = this; this.validate_form_action('Cancel'); - + me.ignore_doctypes_on_cancel_all = me.ignore_doctypes_on_cancel_all || []; frappe.call({ method: "frappe.desk.form.linked_with.get_submitted_linked_docs", args: { doctype: me.doc.doctype, name: me.doc.name }, - freeze: true, - callback: (r) => { - if (!r.exc && r.message.count > 0) { - me._cancel_all(r, btn, callback, on_error); - } else { - me._cancel(btn, callback, on_error, false); + freeze: true + }).then(r => { + if (!r.exc) { + let doctypes_to_cancel = (r.message.docs || []).map(value => { + return value.doctype; + }).filter(value => { + return !me.ignore_doctypes_on_cancel_all.includes(value); + }); + + if (doctypes_to_cancel.length) { + return me._cancel_all(r, btn, callback, on_error); } } - }); + return me._cancel(btn, callback, on_error, false); + } + ); } _cancel_all(r, btn, callback, on_error) { @@ -693,12 +700,16 @@ frappe.ui.form.Form = class FrappeForm { let links = r.message.docs; const doctypes = Array.from(new Set(links.map(link => link.doctype))); + me.ignore_doctypes_on_cancel_all = me.ignore_doctypes_on_cancel_all || []; + for (let doctype of doctypes) { - let docnames = links - .filter((link) => link.doctype == doctype) - .map((link) => frappe.utils.get_form_link(link.doctype, link.name, true)) - .join(", "); - links_text += `
        • ${doctype}: ${docnames}
        • `; + if (!me.ignore_doctypes_on_cancel_all.includes(doctype)) { + let docnames = links + .filter((link) => link.doctype == doctype) + .map((link) => frappe.utils.get_form_link(link.doctype, link.name, true)) + .join(", "); + links_text += `
        • ${doctype}: ${docnames}
        • `; + } } links_text = `
            ${links_text}
          `; @@ -728,7 +739,8 @@ frappe.ui.form.Form = class FrappeForm { frappe.call({ method: "frappe.desk.form.linked_with.cancel_all_linked_docs", args: { - docs: links + docs: links, + ignore_doctypes_on_cancel_all: me.ignore_doctypes_on_cancel_all || [] }, freeze: true, callback: (resp) => { @@ -742,7 +754,7 @@ frappe.ui.form.Form = class FrappeForm { } d.show(); - }; + } _cancel(btn, callback, on_error, skip_confirm) { const me = this; From 14400fe1d34ce23499028987da63fe63167b2327 Mon Sep 17 00:00:00 2001 From: Mohammad Hasnain Mohsin Rajan Date: Fri, 14 Aug 2020 17:04:09 +0530 Subject: [PATCH 81/85] style: fix typo tood to todo (#11272) --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index a68c32fe03..6b50f8ab28 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -763,7 +763,7 @@ def get_doc(*args, **kwargs): # insert a new document todo = frappe.get_doc({"doctype":"ToDo", "description": "test"}) - tood.insert() + todo.insert() # open an existing document todo = frappe.get_doc("ToDo", "TD0001") From c2ea55b0c0c47f9e17359dbfd075db9a20a418df Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 7 Aug 2020 21:58:23 +0530 Subject: [PATCH 82/85] style: Black --- frappe/utils/backups.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/frappe/utils/backups.py b/frappe/utils/backups.py index 71906f540f..a64ba8c3a7 100644 --- a/frappe/utils/backups.py +++ b/frappe/utils/backups.py @@ -102,7 +102,7 @@ class BackupGenerator: "database": "*-{}-database.sql.gz", "public": "*-{}-files.tar", "private": "*-{}-private-files.tar", - "config": "*-{}-site_config_backup.json" + "config": "*-{}-site_config_backup.json", } def backup_time(file_path): @@ -111,7 +111,8 @@ class BackupGenerator: return timegm(datetime.strptime(file_timestamp, "%Y%m%d_%H%M%S").utctimetuple()) def get_latest(file_pattern): - file_list = glob(os.path.join(backup_path, file_pattern)) + file_pattern = os.path.join(backup_path, file_pattern.format(self.site_slug)) + file_list = glob(file_pattern) if file_list: return max(file_list, key=backup_time) @@ -122,14 +123,20 @@ class BackupGenerator: return file_path latest_backups = { - file_type: get_latest(pattern.format(self.site_slug)) for file_type, pattern in file_type_slugs.items() + file_type: get_latest(pattern) + for file_type, pattern in file_type_slugs.items() } recent_backups = { file_type: old_enough(file_name) for file_type, file_name in latest_backups.items() } - return recent_backups.get("database"), recent_backups.get("public"), recent_backups.get("private"), recent_backups.get("config") + return ( + recent_backups.get("database"), + recent_backups.get("public"), + recent_backups.get("private"), + recent_backups.get("config"), + ) def zip_files(self): for folder in ("public", "private"): @@ -232,7 +239,14 @@ def fetch_latest_backups(): dict: relative Backup Paths """ frappe.only_for("System Manager") - odb = BackupGenerator(frappe.conf.db_name, frappe.conf.db_name, frappe.conf.db_password, db_host=frappe.db.host, db_type=frappe.conf.db_type, db_port=frappe.conf.db_port) + odb = BackupGenerator( + frappe.conf.db_name, + frappe.conf.db_name, + frappe.conf.db_password, + db_host=frappe.db.host, + db_type=frappe.conf.db_type, + db_port=frappe.conf.db_port, + ) database, public, private, config = odb.get_recent_backup(older_than=24 * 30) return { From 9e000393512b1125bb1e2ee9a069e5c177150795 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 14 Aug 2020 17:25:18 +0530 Subject: [PATCH 83/85] style: Black --- frappe/integrations/offsite_backup_utils.py | 30 +++++++++++++++------ 1 file changed, 22 insertions(+), 8 deletions(-) diff --git a/frappe/integrations/offsite_backup_utils.py b/frappe/integrations/offsite_backup_utils.py index 6e64bc5428..a551d8edf1 100644 --- a/frappe/integrations/offsite_backup_utils.py +++ b/frappe/integrations/offsite_backup_utils.py @@ -12,8 +12,10 @@ from frappe.utils import split_emails, get_backups_path def send_email(success, service_name, doctype, email_field, error_status=None): recipients = get_recipients(doctype, 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)) + 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: @@ -23,7 +25,9 @@ def send_email(success, service_name, doctype, email_field, error_status=None): 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) +

          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" @@ -31,7 +35,9 @@ def send_email(success, service_name, doctype, email_field, error_status=None):

          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) +

          Please contact your system manager for more information.

          """.format( + service_name, error_status + ) frappe.sendmail(recipients=recipients, subject=subject, message=message) @@ -45,7 +51,15 @@ def get_recipients(doctype, email_field): def get_latest_backup_file(with_files=False): from frappe.utils.backups import BackupGenerator - odb = BackupGenerator(frappe.conf.db_name, frappe.conf.db_name, frappe.conf.db_password, db_host=frappe.db.host, db_type=frappe.conf.db_type, db_port=frappe.conf.db_port) + + odb = BackupGenerator( + frappe.conf.db_name, + frappe.conf.db_name, + frappe.conf.db_password, + db_host=frappe.db.host, + db_type=frappe.conf.db_type, + db_port=frappe.conf.db_port, + ) database, public, private, config = odb.get_recent_backup(older_than=24 * 30) if with_files: @@ -56,11 +70,11 @@ def get_latest_backup_file(with_files=False): def get_file_size(file_path, unit): if not unit: - unit = 'MB' + unit = "MB" file_size = os.path.getsize(file_path) - memory_size_unit_mapper = {'KB': 1, 'MB': 2, 'GB': 3, 'TB': 4} + 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 @@ -72,7 +86,7 @@ def get_file_size(file_path, unit): def validate_file_size(): frappe.flags.create_new_backup = True latest_file, site_config = get_latest_backup_file() - file_size = get_file_size(latest_file, unit='GB') + file_size = get_file_size(latest_file, unit="GB") if file_size > 1: frappe.flags.create_new_backup = False From beaaf16758000bec232ec295ffa0b7548b2a66ca Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 14 Aug 2020 18:30:27 +0530 Subject: [PATCH 84/85] test: show traceback on smtp error --- frappe/email/smtp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index aa025465e5..2fb9534f61 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -244,7 +244,7 @@ class SMTPServer: except _socket.error as e: # Invalid mail server -- due to refusing connection - frappe.msgprint(_('Invalid Outgoing Mail Server or Port')) + frappe.msgprint(_('Invalid Outgoing Mail Server or Port
          {}').format(frappe.get_traceback())) traceback = sys.exc_info()[2] raise_(frappe.ValidationError, e, traceback) From 88d28d7757049ae8ba7b78734f4eb57e78f538d4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 14 Aug 2020 18:47:10 +0530 Subject: [PATCH 85/85] chore(revert): show traceback on smtp error This reverts commit beaaf16758000bec232ec295ffa0b7548b2a66ca. --- frappe/email/smtp.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/smtp.py b/frappe/email/smtp.py index 2fb9534f61..aa025465e5 100644 --- a/frappe/email/smtp.py +++ b/frappe/email/smtp.py @@ -244,7 +244,7 @@ class SMTPServer: except _socket.error as e: # Invalid mail server -- due to refusing connection - frappe.msgprint(_('Invalid Outgoing Mail Server or Port
          {}').format(frappe.get_traceback())) + frappe.msgprint(_('Invalid Outgoing Mail Server or Port')) traceback = sys.exc_info()[2] raise_(frappe.ValidationError, e, traceback)