From 97ae23c2a1f098fc38a408fac8273c3b4e4130c9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 23 Oct 2019 15:42:24 +0530 Subject: [PATCH 001/110] fix: allow overriding columns from settings --- .../list_view_setting/list_view_setting.json | 160 ------------------ .../test_list_view_setting.py | 9 - .../__init__.py | 0 .../list_view_settings.js} | 2 +- .../list_view_settings.json | 59 +++++++ .../list_view_settings.py} | 4 +- frappe/desk/listview.py | 7 +- frappe/public/js/frappe/list/list_view.js | 10 +- frappe/tests/test_listview.py | 14 +- 9 files changed, 77 insertions(+), 188 deletions(-) delete mode 100644 frappe/desk/doctype/list_view_setting/list_view_setting.json delete mode 100644 frappe/desk/doctype/list_view_setting/test_list_view_setting.py rename frappe/desk/doctype/{list_view_setting => list_view_settings}/__init__.py (100%) rename frappe/desk/doctype/{list_view_setting/list_view_setting.js => list_view_settings/list_view_settings.js} (78%) create mode 100644 frappe/desk/doctype/list_view_settings/list_view_settings.json rename frappe/desk/doctype/{list_view_setting/list_view_setting.py => list_view_settings/list_view_settings.py} (85%) diff --git a/frappe/desk/doctype/list_view_setting/list_view_setting.json b/frappe/desk/doctype/list_view_setting/list_view_setting.json deleted file mode 100644 index cd18d3f766..0000000000 --- a/frappe/desk/doctype/list_view_setting/list_view_setting.json +++ /dev/null @@ -1,160 +0,0 @@ -{ - "allow_copy": 0, - "allow_events_in_timeline": 0, - "allow_guest_to_view": 0, - "allow_import": 0, - "allow_rename": 0, - "autoname": "Prompt", - "beta": 0, - "creation": "2019-03-06 13:29:21.101860", - "custom": 0, - "docstatus": 0, - "doctype": "DocType", - "document_type": "", - "editable_grid": 1, - "engine": "InnoDB", - "fields": [ - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disable_count", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable Count", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disable_sidebar_stats", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable Sidebar Stats", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "precision": "", - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "translatable": 0, - "unique": 0 - }, - { - "allow_bulk_edit": 0, - "allow_in_quick_entry": 0, - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "disable_auto_refresh", - "fieldtype": "Check", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_global_search": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Disable Auto Refresh", - "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 - } - ], - "has_web_view": 0, - "hide_heading": 0, - "hide_toolbar": 0, - "idx": 0, - "image_view": 0, - "in_create": 0, - "is_submittable": 0, - "issingle": 0, - "istable": 0, - "max_attachments": 0, - "modified": "2019-03-06 13:40:59.533586", - "modified_by": "Administrator", - "module": "Desk", - "name": "List View Setting", - "name_case": "", - "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, - "write": 1 - } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "show_name_in_global_search": 0, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1, - "track_seen": 0, - "track_views": 0 -} \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_setting/test_list_view_setting.py b/frappe/desk/doctype/list_view_setting/test_list_view_setting.py deleted file mode 100644 index 143fc4cce7..0000000000 --- a/frappe/desk/doctype/list_view_setting/test_list_view_setting.py +++ /dev/null @@ -1,9 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -import unittest - -class TestListViewSetting(unittest.TestCase): - pass diff --git a/frappe/desk/doctype/list_view_setting/__init__.py b/frappe/desk/doctype/list_view_settings/__init__.py similarity index 100% rename from frappe/desk/doctype/list_view_setting/__init__.py rename to frappe/desk/doctype/list_view_settings/__init__.py diff --git a/frappe/desk/doctype/list_view_setting/list_view_setting.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js similarity index 78% rename from frappe/desk/doctype/list_view_setting/list_view_setting.js rename to frappe/desk/doctype/list_view_settings/list_view_settings.js index 2c70ddf82d..20c11c0215 100644 --- a/frappe/desk/doctype/list_view_setting/list_view_setting.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -1,7 +1,7 @@ // Copyright (c) 2019, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('List View Setting', { +frappe.ui.form.on('List View Settings', { // refresh: function(frm) { // } diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json new file mode 100644 index 0000000000..783a65b8aa --- /dev/null +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -0,0 +1,59 @@ +{ + "autoname": "Prompt", + "creation": "2019-10-23 15:00:48.392374", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "disable_count", + "disable_sidebar_stats", + "disable_auto_refresh", + "column_count" + ], + "fields": [ + { + "default": "0", + "fieldname": "disable_count", + "fieldtype": "Check", + "label": "Disable Count" + }, + { + "default": "0", + "fieldname": "disable_sidebar_stats", + "fieldtype": "Check", + "label": "Disable Sidebar Stats" + }, + { + "default": "0", + "fieldname": "disable_auto_refresh", + "fieldtype": "Check", + "label": "Disable Auto Refresh" + }, + { + "fieldname": "column_count", + "fieldtype": "Select", + "label": "Column Count", + "options": "\n4\n5\n6\n7\n8\n9\n10" + } + ], + "modified": "2019-10-23 15:11:41.710014", + "modified_by": "Administrator", + "module": "Desk", + "name": "List View 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/desk/doctype/list_view_setting/list_view_setting.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py similarity index 85% rename from frappe/desk/doctype/list_view_setting/list_view_setting.py rename to frappe/desk/doctype/list_view_settings/list_view_settings.py index b66dc29a43..b7cafed068 100644 --- a/frappe/desk/doctype/list_view_setting/list_view_setting.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class ListViewSetting(Document): - pass +class ListViewSettings(Document): + pass \ No newline at end of file diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index 3dc795191a..7f1c120d31 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -7,17 +7,16 @@ import frappe @frappe.whitelist() def get_list_settings(doctype): try: - return frappe.get_cached_doc("List View Setting", doctype) + return frappe.get_cached_doc("List View Settings", doctype) except frappe.DoesNotExistError: frappe.clear_messages() - @frappe.whitelist() def set_list_settings(doctype, values): try: - doc = frappe.get_doc("List View Setting", doctype) + doc = frappe.get_doc("List View Settings", doctype) except frappe.DoesNotExistError: - doc = frappe.new_doc("List View Setting") + doc = frappe.new_doc("List View Settings") doc.name = doctype frappe.clear_messages() doc.update(frappe.parse_json(values)) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index bb79b3441f..41a48589aa 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -292,13 +292,13 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { // Screen with high density no of columns 8 let column_count = 6; - if (window.innerWidth <= 1200) { + if (window.innerWidth <= 1366) { column_count = 4; - } else if (window.innerWidth > 1440) { + } else if (window.innerWidth >= 1920) { column_count = 8; } - this.columns = this.columns.slice(0, column_count); + this.columns = this.columns.slice(0, this.list_view_settings.column_count || column_count); } get_no_result_message() { @@ -1239,10 +1239,10 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } show_list_settings() { - frappe.model.with_doctype("List View Setting", () => { + frappe.model.with_doctype("List View Settings", () => { let d = new frappe.ui.Dialog({ title: __("Settings"), - fields: frappe.get_meta("List View Setting").fields + fields: frappe.get_meta("List View Settings").fields }); d.set_values(this.list_view_settings); d.show(); diff --git a/frappe/tests/test_listview.py b/frappe/tests/test_listview.py index 5c0657e9ee..9b4623eca6 100644 --- a/frappe/tests/test_listview.py +++ b/frappe/tests/test_listview.py @@ -10,14 +10,14 @@ from frappe.desk.listview import get_list_settings, set_list_settings class TestListView(unittest.TestCase): def setUp(self): - if frappe.db.exists("List View Setting", "DocType"): - frappe.delete_doc("List View Setting", "DocType") + if frappe.db.exists("List View Settings", "DocType"): + frappe.delete_doc("List View Settings", "DocType") def test_get_list_settings_without_settings(self): self.assertIsNone(get_list_settings("DocType"), None) def test_get_list_settings_with_default_settings(self): - frappe.get_doc({"doctype": "List View Setting", "name": "DocType"}).insert() + frappe.get_doc({"doctype": "List View Settings", "name": "DocType"}).insert() settings = get_list_settings("DocType") self.assertIsNotNone(settings) @@ -26,7 +26,7 @@ class TestListView(unittest.TestCase): self.assertEqual(settings.disable_sidebar_stats, 0) def test_get_list_settings_with_non_default_settings(self): - frappe.get_doc({"doctype": "List View Setting", "name": "DocType", "disable_count": 1}).insert() + frappe.get_doc({"doctype": "List View Settings", "name": "DocType", "disable_count": 1}).insert() settings = get_list_settings("DocType") self.assertIsNotNone(settings) @@ -36,16 +36,16 @@ class TestListView(unittest.TestCase): def test_set_list_settings_without_settings(self): set_list_settings("DocType", json.dumps({})) - settings = frappe.get_doc("List View Setting","DocType") + settings = frappe.get_doc("List View Settings","DocType") self.assertEqual(settings.disable_auto_refresh, 0) self.assertEqual(settings.disable_count, 0) self.assertEqual(settings.disable_sidebar_stats, 0) def test_set_list_settings_with_existing_settings(self): - frappe.get_doc({"doctype": "List View Setting", "name": "DocType", "disable_count": 1}).insert() + frappe.get_doc({"doctype": "List View Settings", "name": "DocType", "disable_count": 1}).insert() set_list_settings("DocType", json.dumps({"disable_count": 0, "disable_auto_refresh": 1})) - settings = frappe.get_doc("List View Setting","DocType") + settings = frappe.get_doc("List View Settings","DocType") self.assertEqual(settings.disable_auto_refresh, 1) self.assertEqual(settings.disable_count, 0) From 384cbcb38e5266159bdf3030c1d5aa2b016b834d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 23 Oct 2019 17:02:06 +0530 Subject: [PATCH 002/110] fix: clear cached doc --- .../desk/doctype/list_view_settings/list_view_settings.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index b7cafed068..97803bf6c6 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -3,8 +3,10 @@ # For license information, please see license.txt from __future__ import unicode_literals -# import frappe +import frappe from frappe.model.document import Document class ListViewSettings(Document): - pass \ No newline at end of file + + def validate(self): + frappe.clear_document_cache(self.doctype, self.name) \ No newline at end of file From b8dc3f125803eae84ff83a9f423a762e8cd9c954 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 23 Oct 2019 17:03:50 +0530 Subject: [PATCH 003/110] fix: clear cache on_update --- frappe/desk/doctype/list_view_settings/list_view_settings.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index 97803bf6c6..b1d9fb6da8 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -8,5 +8,5 @@ from frappe.model.document import Document class ListViewSettings(Document): - def validate(self): + def on_update(self): frappe.clear_document_cache(self.doctype, self.name) \ No newline at end of file From 46b25297db9347e353b7fd3b0a1306559296669f Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 23 Oct 2019 19:20:26 +0530 Subject: [PATCH 004/110] fix: clear cache on settings change --- frappe/desk/doctype/list_view_settings/list_view_settings.json | 2 +- frappe/public/js/frappe/list/list_view.js | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 783a65b8aa..aa3bbf464b 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -36,7 +36,7 @@ "options": "\n4\n5\n6\n7\n8\n9\n10" } ], - "modified": "2019-10-23 15:11:41.710014", + "modified": "2019-10-23 19:13:41.427000", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 41a48589aa..2cd04b50c2 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1251,6 +1251,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { frappe.call("frappe.desk.listview.set_list_settings", {doctype: this.doctype, values: values}); Object.assign(this.list_view_settings, values); d.hide(); + frappe.ui.toolbar.clear_cache(); }); }); } From b3322520cfb865cd5faa7aab488f5631f260caff Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 24 Oct 2019 18:12:12 +0530 Subject: [PATCH 005/110] feat: fetch listview columns --- .../list_view_column_order/__init__.py | 0 .../list_view_column_order.json | 35 +++++++++++++++++++ .../list_view_column_order.py | 10 ++++++ .../list_view_settings/list_view_settings.js | 21 +++++++++-- .../list_view_settings.json | 11 ++++-- .../list_view_settings/list_view_settings.py | 29 ++++++++++++++- frappe/public/js/frappe/list/list_view.js | 8 ++++- 7 files changed, 107 insertions(+), 7 deletions(-) create mode 100644 frappe/desk/doctype/list_view_column_order/__init__.py create mode 100644 frappe/desk/doctype/list_view_column_order/list_view_column_order.json create mode 100644 frappe/desk/doctype/list_view_column_order/list_view_column_order.py diff --git a/frappe/desk/doctype/list_view_column_order/__init__.py b/frappe/desk/doctype/list_view_column_order/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/list_view_column_order/list_view_column_order.json b/frappe/desk/doctype/list_view_column_order/list_view_column_order.json new file mode 100644 index 0000000000..2db000e706 --- /dev/null +++ b/frappe/desk/doctype/list_view_column_order/list_view_column_order.json @@ -0,0 +1,35 @@ +{ + "creation": "2019-10-24 07:54:42.901997", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "label", + "fieldname" + ], + "fields": [ + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "read_only": 1 + }, + { + "fieldname": "fieldname", + "fieldtype": "Data", + "label": "Fieldname", + "read_only": 1 + } + ], + "istable": 1, + "modified": "2019-10-24 18:08:40.740953", + "modified_by": "Administrator", + "module": "Desk", + "name": "List View Column Order", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_column_order/list_view_column_order.py b/frappe/desk/doctype/list_view_column_order/list_view_column_order.py new file mode 100644 index 0000000000..598e6398b4 --- /dev/null +++ b/frappe/desk/doctype/list_view_column_order/list_view_column_order.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, 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 ListViewColumnOrder(Document): + pass diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 20c11c0215..4f30dda86b 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -2,7 +2,22 @@ // For license information, please see license.txt frappe.ui.form.on('List View Settings', { - // refresh: function(frm) { - - // } + refresh: function(frm) { + frm.add_custom_button('Get List View Columns', function () { + frappe.call({ + method: "frappe.desk.doctype.list_view_settings.list_view_settings.get_listview_columns", + args: { + doctype: frm.doc.name + }, + callback: function (r) { + if (r && r.message) { + for (let i in r.message) { + frm.add_child("column_order", r.message[i]); + } + frm.refresh(); + } + } + }); + }); + } }); diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index aa3bbf464b..e16893a875 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -8,7 +8,8 @@ "disable_count", "disable_sidebar_stats", "disable_auto_refresh", - "column_count" + "column_count", + "column_order" ], "fields": [ { @@ -34,9 +35,15 @@ "fieldtype": "Select", "label": "Column Count", "options": "\n4\n5\n6\n7\n8\n9\n10" + }, + { + "fieldname": "column_order", + "fieldtype": "Table", + "label": "Column Order", + "options": "List View Column Order" } ], - "modified": "2019-10-23 19:13:41.427000", + "modified": "2019-10-24 14:34:03.358787", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index b1d9fb6da8..f8947c8ee0 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -9,4 +9,31 @@ from frappe.model.document import Document class ListViewSettings(Document): def on_update(self): - frappe.clear_document_cache(self.doctype, self.name) \ No newline at end of file + frappe.clear_document_cache(self.doctype, self.name) + +@frappe.whitelist() +def get_listview_columns(doctype): + meta = frappe.get_meta(doctype) + listview_columns = [] + + subject_field = { + "label": "Name", + "fieldname": "name" + } + + if meta.title_field: + title_field = meta.get_field(meta.title_field) + + subject_field["label"] = title_field.label, + subject_field["fieldname"] = title_field.fieldname + + listview_columns.append(subject_field) + + for field in meta.fields: + if field.in_list_view and field.label and field.fieldname: + listview_columns.append({ + "label": field.label, + "fieldname": field.fieldname + }) + + return listview_columns \ No newline at end of file diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 2cd04b50c2..5f29f3bd9d 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1240,9 +1240,15 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { show_list_settings() { frappe.model.with_doctype("List View Settings", () => { + let list_view_settings = frappe.get_meta("List View Settings"); let d = new frappe.ui.Dialog({ title: __("Settings"), - fields: frappe.get_meta("List View Settings").fields + fields: [ + list_view_settings.fields[0], + list_view_settings.fields[1], + list_view_settings.fields[2], + list_view_settings.fields[3], + ] }); d.set_values(this.list_view_settings); d.show(); From bddef3d20498b22c70846d31fd52cdd8cd76d7f9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 24 Oct 2019 19:15:03 +0530 Subject: [PATCH 006/110] feat: rearrange column order --- .../list_view_settings/list_view_settings.js | 4 +++- .../list_view_settings/list_view_settings.py | 4 ++-- frappe/public/js/frappe/list/list_view.js | 23 +++++++++++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 4f30dda86b..72f6c16684 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -11,8 +11,10 @@ frappe.ui.form.on('List View Settings', { }, callback: function (r) { if (r && r.message) { - for (let i in r.message) { + let i = 0; + while (i < frm.doc.column_count) { frm.add_child("column_order", r.message[i]); + i++; } frm.refresh(); } diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index f8947c8ee0..90ba4d2883 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -24,7 +24,7 @@ def get_listview_columns(doctype): if meta.title_field: title_field = meta.get_field(meta.title_field) - subject_field["label"] = title_field.label, + subject_field["label"] = title_field.label subject_field["fieldname"] = title_field.fieldname listview_columns.append(subject_field) @@ -36,4 +36,4 @@ def get_listview_columns(doctype): "fieldname": field.fieldname }) - return listview_columns \ No newline at end of file + return listview_columns diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 5f29f3bd9d..1dac93c296 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -286,6 +286,29 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { })) ); + if (this.list_view_settings.column_order) { + let custom_column_order = []; + + //title_field or name and status are fixed + custom_column_order.push(this.columns[0]); + custom_column_order.push(this.columns[1]); + + this.columns.splice(0, 2) + + for (let i in this.list_view_settings.column_order) { + let fieldname = this.list_view_settings.column_order[i].fieldname; + for (let j in this.columns) { + let df = this.columns[j].df.fieldname; + if (fieldname === df) { + custom_column_order.push(this.columns[j]); + break; + } + } + } + + this.columns = custom_column_order; + } + // limit max to 8 columns // Screen with low density no of columns 4 // Screen with medium density no of columns 6 From c8ecda58567446e8858ececb701945832bf47658 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 24 Oct 2019 19:46:44 +0530 Subject: [PATCH 007/110] fix: refresh cache on update --- frappe/desk/doctype/list_view_settings/list_view_settings.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index 90ba4d2883..adf92e8ec6 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -10,6 +10,7 @@ class ListViewSettings(Document): def on_update(self): frappe.clear_document_cache(self.doctype, self.name) + frappe.get_cached_doc(self.doctype, self.name) @frappe.whitelist() def get_listview_columns(doctype): From 89801512942bb03385cb43d2c4680591798cef01 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 25 Oct 2019 11:58:00 +0530 Subject: [PATCH 008/110] chore: fix codacy --- frappe/desk/doctype/list_view_settings/list_view_settings.json | 2 +- frappe/public/js/frappe/list/list_view.js | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index e16893a875..3911467980 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -43,7 +43,7 @@ "options": "List View Column Order" } ], - "modified": "2019-10-24 14:34:03.358787", + "modified": "2019-10-25 11:57:00.314570", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 1dac93c296..6a0a8575b8 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -293,7 +293,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { custom_column_order.push(this.columns[0]); custom_column_order.push(this.columns[1]); - this.columns.splice(0, 2) + this.columns.splice(0, 2); for (let i in this.list_view_settings.column_order) { let fieldname = this.list_view_settings.column_order[i].fieldname; @@ -1264,6 +1264,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { show_list_settings() { frappe.model.with_doctype("List View Settings", () => { let list_view_settings = frappe.get_meta("List View Settings"); + let d = new frappe.ui.Dialog({ title: __("Settings"), fields: [ From b7bc2d0fa0abd0ceac587e4f818f53210fcc5763 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 25 Oct 2019 21:43:00 +0530 Subject: [PATCH 009/110] fix: just fix title or name field in listview --- frappe/public/js/frappe/list/list_view.js | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 6a0a8575b8..b88e4a4a7a 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -289,11 +289,9 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { if (this.list_view_settings.column_order) { let custom_column_order = []; - //title_field or name and status are fixed + //title_field is fixed custom_column_order.push(this.columns[0]); - custom_column_order.push(this.columns[1]); - - this.columns.splice(0, 2); + this.columns.splice(0, 1); for (let i in this.list_view_settings.column_order) { let fieldname = this.list_view_settings.column_order[i].fieldname; From b13c9c4408d52dd3616596b26a8ac7d189df59c0 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 25 Oct 2019 22:33:48 +0530 Subject: [PATCH 010/110] fix: column order --- frappe/desk/doctype/list_view_settings/list_view_settings.js | 4 ++++ frappe/public/js/frappe/list/list_view.js | 5 +++-- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 72f6c16684..59ad59444b 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -3,6 +3,10 @@ frappe.ui.form.on('List View Settings', { refresh: function(frm) { + frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => { + frappe.set_route('List', frm.doc.name, 'List'); + }); + frm.add_custom_button('Get List View Columns', function () { frappe.call({ method: "frappe.desk.doctype.list_view_settings.list_view_settings.get_listview_columns", diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index b88e4a4a7a..b1ab2f88c7 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -286,7 +286,8 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { })) ); - if (this.list_view_settings.column_order) { + if (this.list_view_settings.column_order.length > 0 && + this.list_view_settings.column_order.length === this.list_view_settings.column_count) { let custom_column_order = []; //title_field is fixed @@ -307,7 +308,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.columns = custom_column_order; } - // limit max to 8 columns + // limit max to 8 columns if no column_count is set in List View Settings // Screen with low density no of columns 4 // Screen with medium density no of columns 6 // Screen with high density no of columns 8 From 0af5d38b41a37e7ce77afbb7d416087343b51680 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 28 Oct 2019 17:09:05 +0530 Subject: [PATCH 011/110] fix: check if new doc --- .../doctype/list_view_settings/list_view_settings.js | 9 ++++++++- frappe/public/js/frappe/list/list_view.js | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 59ad59444b..8109c6d45b 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -3,6 +3,10 @@ frappe.ui.form.on('List View Settings', { refresh: function(frm) { + if (frm.is_new()) { + return; + } + frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => { frappe.set_route('List', frm.doc.name, 'List'); }); @@ -16,8 +20,11 @@ frappe.ui.form.on('List View Settings', { callback: function (r) { if (r && r.message) { let i = 0; + frm.clear_table("column_order"); while (i < frm.doc.column_count) { - frm.add_child("column_order", r.message[i]); + if (r.message[i]) { + frm.add_child("column_order", r.message[i]); + } i++; } frm.refresh(); diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index b1ab2f88c7..13dd08832a 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -286,7 +286,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { })) ); - if (this.list_view_settings.column_order.length > 0 && + if (this.list_view_settings.column_order && this.list_view_settings.column_order.length > 0 && this.list_view_settings.column_order.length === this.list_view_settings.column_count) { let custom_column_order = []; From a26675098d172174fc57ee6662fa97db7fb78a30 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 18 Nov 2019 10:12:01 +0530 Subject: [PATCH 012/110] fix: remove child table and use html --- .../list_view_column_order/__init__.py | 0 .../list_view_column_order.json | 35 ------------------- .../list_view_column_order.py | 10 ------ .../list_view_settings/list_view_columns.html | 9 +++++ .../list_view_settings/list_view_settings.js | 19 +++++----- .../list_view_settings.json | 13 ++++--- .../test_list_view_settings.py | 10 ++++++ 7 files changed, 38 insertions(+), 58 deletions(-) delete mode 100644 frappe/desk/doctype/list_view_column_order/__init__.py delete mode 100644 frappe/desk/doctype/list_view_column_order/list_view_column_order.json delete mode 100644 frappe/desk/doctype/list_view_column_order/list_view_column_order.py create mode 100644 frappe/desk/doctype/list_view_settings/list_view_columns.html create mode 100644 frappe/desk/doctype/list_view_settings/test_list_view_settings.py diff --git a/frappe/desk/doctype/list_view_column_order/__init__.py b/frappe/desk/doctype/list_view_column_order/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/desk/doctype/list_view_column_order/list_view_column_order.json b/frappe/desk/doctype/list_view_column_order/list_view_column_order.json deleted file mode 100644 index 2db000e706..0000000000 --- a/frappe/desk/doctype/list_view_column_order/list_view_column_order.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "creation": "2019-10-24 07:54:42.901997", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label", - "fieldname" - ], - "fields": [ - { - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "read_only": 1 - }, - { - "fieldname": "fieldname", - "fieldtype": "Data", - "label": "Fieldname", - "read_only": 1 - } - ], - "istable": 1, - "modified": "2019-10-24 18:08:40.740953", - "modified_by": "Administrator", - "module": "Desk", - "name": "List View Column Order", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_column_order/list_view_column_order.py b/frappe/desk/doctype/list_view_column_order/list_view_column_order.py deleted file mode 100644 index 598e6398b4..0000000000 --- a/frappe/desk/doctype/list_view_column_order/list_view_column_order.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, 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 ListViewColumnOrder(Document): - pass diff --git a/frappe/desk/doctype/list_view_settings/list_view_columns.html b/frappe/desk/doctype/list_view_settings/list_view_columns.html new file mode 100644 index 0000000000..50474c5169 --- /dev/null +++ b/frappe/desk/doctype/list_view_settings/list_view_columns.html @@ -0,0 +1,9 @@ +
+ {% for (var i=0, l=fields.length; i < l; i++) { var field = fields[i]; %} +
+ + {%= field.label %} + +
+ {% } %} +
diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 8109c6d45b..3b8654e349 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -19,18 +19,19 @@ frappe.ui.form.on('List View Settings', { }, callback: function (r) { if (r && r.message) { - let i = 0; - frm.clear_table("column_order"); - while (i < frm.doc.column_count) { - if (r.message[i]) { - frm.add_child("column_order", r.message[i]); - } - i++; - } - frm.refresh(); + render_fields(frm, r.message); } } }); }); + + if (frm.doc.column_order) { + render_fields(frm, frm.doc.column_order); + } } }); + +function render_fields(frm, fields) { + let columns_html = frm.get_field("column_order"); + columns_html.html(frappe.render_template("list_view_columns", {fields: fields})); +} \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 3911467980..95ef3b2acb 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -9,6 +9,7 @@ "disable_sidebar_stats", "disable_auto_refresh", "column_count", + "columns", "column_order" ], "fields": [ @@ -38,12 +39,16 @@ }, { "fieldname": "column_order", - "fieldtype": "Table", - "label": "Column Order", - "options": "List View Column Order" + "fieldtype": "Data", + "label": "Column Order" + }, + { + "fieldname": "columns", + "fieldtype": "HTML", + "label": "Columns" } ], - "modified": "2019-10-25 11:57:00.314570", + "modified": "2019-11-18 10:05:11.859157", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", diff --git a/frappe/desk/doctype/list_view_settings/test_list_view_settings.py b/frappe/desk/doctype/list_view_settings/test_list_view_settings.py new file mode 100644 index 0000000000..c1b2f4a0da --- /dev/null +++ b/frappe/desk/doctype/list_view_settings/test_list_view_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 TestListViewSettings(unittest.TestCase): + pass From 4775d1b6ee5761a1ffa4988b463f5cc7854c5f05 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 14 Apr 2020 11:41:31 +0530 Subject: [PATCH 013/110] feat: new UI for list view settings --- .../list_view_settings/list_view_settings.js | 37 +- .../list_view_settings.json | 41 ++- .../list_view_settings/list_view_settings.py | 84 +++-- frappe/public/js/frappe/list/list_settings.js | 320 ++++++++++++++++++ frappe/public/js/frappe/list/list_view.js | 64 ++-- 5 files changed, 442 insertions(+), 104 deletions(-) create mode 100644 frappe/public/js/frappe/list/list_settings.js diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 3b8654e349..db33f71675 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -1,37 +1,8 @@ -// Copyright (c) 2019, Frappe Technologies and contributors +// Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt frappe.ui.form.on('List View Settings', { - refresh: function(frm) { - if (frm.is_new()) { - return; - } + // refresh: function(frm) { - frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => { - frappe.set_route('List', frm.doc.name, 'List'); - }); - - frm.add_custom_button('Get List View Columns', function () { - frappe.call({ - method: "frappe.desk.doctype.list_view_settings.list_view_settings.get_listview_columns", - args: { - doctype: frm.doc.name - }, - callback: function (r) { - if (r && r.message) { - render_fields(frm, r.message); - } - } - }); - }); - - if (frm.doc.column_order) { - render_fields(frm, frm.doc.column_order); - } - } -}); - -function render_fields(frm, fields) { - let columns_html = frm.get_field("column_order"); - columns_html.html(frappe.render_template("list_view_columns", {fields: fields})); -} \ No newline at end of file + // } +}); \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 95ef3b2acb..e59991c52e 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "Prompt", "creation": "2019-10-23 15:00:48.392374", "doctype": "DocType", @@ -8,9 +9,9 @@ "disable_count", "disable_sidebar_stats", "disable_auto_refresh", - "column_count", - "columns", - "column_order" + "total_fields", + "fields_html", + "fields" ], "fields": [ { @@ -32,23 +33,26 @@ "label": "Disable Auto Refresh" }, { - "fieldname": "column_count", + "fieldname": "total_fields", "fieldtype": "Select", - "label": "Column Count", + "label": "Total Fields", "options": "\n4\n5\n6\n7\n8\n9\n10" }, { - "fieldname": "column_order", - "fieldtype": "Data", - "label": "Column Order" + "fieldname": "fields_html", + "fieldtype": "HTML", + "label": "Fields" }, { - "fieldname": "columns", - "fieldtype": "HTML", - "label": "Columns" + "fieldname": "fields", + "fieldtype": "Code", + "hidden": 1, + "label": "Fields", + "read_only": 1 } ], - "modified": "2019-11-18 10:05:11.859157", + "links": [], + "modified": "2020-04-13 23:31:15.411147", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", @@ -63,8 +67,21 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "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 diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index adf92e8ec6..acd8f8a7b6 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -1,40 +1,78 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies and contributors +# 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 six import string_types +from frappe.exceptions import DoesNotExistError +import json class ListViewSettings(Document): def on_update(self): frappe.clear_document_cache(self.doctype, self.name) - frappe.get_cached_doc(self.doctype, self.name) @frappe.whitelist() -def get_listview_columns(doctype): - meta = frappe.get_meta(doctype) - listview_columns = [] +def save_listview_settings(doctype, listview_settings, removed_listview_fields): - subject_field = { - "label": "Name", - "fieldname": "name" - } + if isinstance(listview_settings, string_types): + listview_settings = json.loads(listview_settings) + + if isinstance(removed_listview_fields, string_types): + removed_listview_fields = json.loads(removed_listview_fields) + + try: + doc = frappe.get_doc("List View Settings", doctype) + doc.update(listview_settings) + doc.save() + except DoesNotExistError: + doc = frappe.new_doc("List View Settings") + doc.name = doctype + doc.update(listview_settings) + doc.insert() + + set_listview_fields(doctype, listview_settings.get("fields"), removed_listview_fields) + +def set_listview_fields(doctype, listview_fields, removed_listview_fields): + meta = frappe.get_meta(doctype) + + if isinstance(listview_fields, string_types): + listview_fields = [f.get("fieldname") for f in json.loads(listview_fields)] + + for field in listview_fields: + set_in_list_view_property(doctype, meta.get_field(field), "1") + + for field in removed_listview_fields: + set_in_list_view_property(doctype, meta.get_field(field), "0") + +def set_in_list_view_property(doctype, field, value): + property_setter = frappe.db.get_value("Property Setter", {"doc_type": doctype, "field_name": field.fieldname, "property": "in_list_view"}) + if property_setter: + doc = frappe.get_doc("Property Setter", property_setter) + doc.value = value + doc.save() + else: + frappe.make_property_setter({ + "doctype": doctype, + "doctype_or_field": "DocField", + "fieldname": field.fieldname, + "property": "in_list_view", + "value": value, + "property_type": "Check" + }, ignore_validate=True) + +@frappe.whitelist() +def get_default_listview_fields(doctype): + meta = frappe.get_meta(doctype) + path = frappe.get_module_path(frappe.scrub(meta.module), "doctype", frappe.scrub(meta.name), frappe.scrub(meta.name) + ".json") + json = frappe.get_file_json(path) + + fields = [f.get("fieldname") for f in json.get("fields") if f.get("in_list_view")] if meta.title_field: - title_field = meta.get_field(meta.title_field) + if not meta.title_field.strip() in fields: + fields.append(meta.title_field.strip()) - subject_field["label"] = title_field.label - subject_field["fieldname"] = title_field.fieldname - - listview_columns.append(subject_field) - - for field in meta.fields: - if field.in_list_view and field.label and field.fieldname: - listview_columns.append({ - "label": field.label, - "fieldname": field.fieldname - }) - - return listview_columns + return fields diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js new file mode 100644 index 0000000000..252309dd88 --- /dev/null +++ b/frappe/public/js/frappe/list/list_settings.js @@ -0,0 +1,320 @@ +export default class ListSettings { + constructor({ doctype, meta, settings }) { + if (!doctype) { + frappe.throw(__('Doctype required')); + } + + this.doctype = doctype; + this.meta = meta; + this.settings = settings; + this.dialog = null; + this.fields = []; + this.subject_field = null; + + frappe.model.with_doctype("List View Settings", () => { + this.make(); + this.get_listview_fields(meta); + this.setup_fields(); + this.setup_remove_fields(); + this.add_new_fields(); + this.show_dialog(); + }); + } + + make() { + let me = this; + + let list_view_settings = frappe.get_meta("List View Settings"); + + me.dialog = new frappe.ui.Dialog({ + title: __("{0} List View Settings", [__(me.doctype)]), + fields: list_view_settings.fields + }); + me.dialog.set_values(me.settings); + me.dialog.set_primary_action(__('Save'), () => { + let values = me.dialog.get_values(); + frappe.call("frappe.desk.doctype.list_view_settings.list_view_settings.save_listview_settings", { + doctype: me.doctype, + listview_settings: values, + removed_listview_fields: me.removed_fields || [] + }); + me.dialog.hide(); + frappe.ui.toolbar.clear_cache(); + }); + + me.dialog.fields_dict["total_fields"].df.onchange = () => me.refresh(); + } + + refresh() { + let me = this; + + me.setup_fields(); + me.update_fields(); + me.add_new_fields(); + me.setup_remove_fields(); + } + + show_dialog() { + let me = this; + me.dialog.show(); + } + + setup_fields() { + let me = this; + + let fields_html = me.dialog.get_field("fields_html"); + let $wrapper = fields_html.$wrapper[0]; + let fields = ``; + let total_fields = me.dialog.get_values().total_fields ? me.dialog.get_values().total_fields : me.settings.total_fields; + + for (let idx in me.fields) { + if (idx == parseInt(total_fields)) { + break; + } + let is_sortable = (idx == 0) ? `` : `sortable`; + let can_remove = (idx == 0) ? `hide` : ``; + + fields += ` +
+ +
+
+ +
+
+ ${me.fields[idx].label} +
+
+ + + +
+
+
`; + } + + fields_html.html(` +
+
+ +
+
+ ${fields} +
+ +
+ `); + + new Sortable($wrapper.getElementsByClassName("control-input-wrapper")[0], { + handle: '.sortable-handle', + draggable: '.sortable', + onUpdate: () => { + me.refresh(); + } + }); + } + + add_new_fields() { + let me = this + + let fields_html = me.dialog.get_field("fields_html"); + let add_new_fields = fields_html.$wrapper[0].getElementsByClassName("add-new-fields")[0]; + add_new_fields.onclick = () => me.column_selector(); + } + + setup_remove_fields() { + let me = this; + + let fields_html = me.dialog.get_field("fields_html"); + let remove_fields = fields_html.$wrapper[0].getElementsByClassName("remove-field"); + + for (let idx = 0; idx < remove_fields.length; idx++) { + remove_fields.item(idx).onclick = () => me.remove_fields(remove_fields.item(idx).getAttribute("data-fieldname")); + } + } + + remove_fields(fieldname) { + let me = this; + + for (let idx in me.fields) { + let field = me.fields[idx]; + + if (field.fieldname == fieldname) { + console.log(idx); + me.fields.splice(idx, 1); + break; + } + } + + me.refresh(); + } + + update_fields() { + let me = this; + + let fields_html = me.dialog.get_field("fields_html"); + let $wrapper = fields_html.$wrapper[0]; + + let fields_order = $wrapper.getElementsByClassName("fields_order"); + me.fields = []; + + for (let idx = 0; idx < fields_order.length; idx++) { + me.fields.push({ + fieldname: fields_order.item(idx).getAttribute("data-fieldname"), + label: fields_order.item(idx).getAttribute("data-label") + }); + } + + me.dialog.set_value("fields", JSON.stringify(me.fields)); + } + + column_selector() { + let me = this; + + let d = new frappe.ui.Dialog({ + title: __("{0} Fields", [__(me.doctype)]), + fields: [ + { + label: __("Reset Fields"), + fieldtype: "Button", + fieldname: "reset_fields", + click: () => me.reset_listview_fields(d) + }, + { + label: __("Select Fields"), + fieldtype: "MultiCheck", + fieldname: "fields", + options: me.get_doctype_fields(me.meta, me.fields.map(f => f.fieldname)), + columns: 2 + } + ] + }); + d.set_primary_action(__('Save'), () => { + let values = d.get_values().fields; + me.removed_fields = me.get_removed_listview_fields(values) + me.new_fields = values + + me.fields = []; + me.set_subject_field(me.meta); + + for (let idx in values) { + let value = values[idx]; + + if (me.fields.length === parseInt(me.dialog.get_values().total_fields)) { + break; + } else if (value != me.subject_field.fieldname) { + let field = frappe.meta.get_docfield(me.doctype, value); + me.fields.push({ + label: field.label, + fieldname: field.fieldname + }) + } + } + + me.refresh(); + me.dialog.set_value("fields", JSON.stringify(me.fields)); + d.hide(); + }); + d.show(); + } + + reset_listview_fields(dialog) { + let me = this; + + frappe.xcall("frappe.desk.doctype.list_view_settings.list_view_settings.get_default_listview_fields", { + doctype: me.doctype + }).then((fields) => { + let field = dialog.get_field("fields"); + field.df.options = me.get_doctype_fields(me.meta, fields); + dialog.refresh(); + }); + + } + + get_listview_fields(meta) { + let me = this; + + if (!me.settings.fields) { + me.set_list_view_fields(meta); + } else { + me.fields = JSON.parse(this.settings.fields); + } + + me.fields.uniqBy(f => f.fieldname); + } + + set_list_view_fields(meta) { + let me = this; + + me.set_subject_field(meta); + + meta.fields.forEach(field => { + if (field.in_list_view && !in_list(frappe.model.no_value_type, field.fieldtype) && + me.subject_field.fieldname != field.fieldname) { + + me.fields.push({ + label: field.label, + fieldname: field.fieldname + }) + } + }) + } + + set_subject_field(meta) { + let me = this; + + me.subject_field = { + label: "Name", + fieldname: "name" + } + + if (meta.title_field) { + let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()) + + me.subject_field = { + label: field.label, + fieldname: field.fieldname + } + } + + me.fields.push(me.subject_field); + } + + get_doctype_fields(meta, fields) { + let me = this; + let multiselect_fields = [] + + meta.fields.forEach(field => { + if (!in_list(frappe.model.no_value_type, field.fieldtype)) { + multiselect_fields.push({ + label: field.label, + value: field.fieldname, + checked: in_list(fields, field.fieldname) + }) + } + }) + + return multiselect_fields + } + + get_removed_listview_fields(new_fields) { + let me = this; + + let removed_fields = [] + let existing_fields = me.fields.map(f => f.fieldname); + + existing_fields.forEach(column => { + if (!in_list(new_fields, column)) { + removed_fields.push(column); + } + }); + + return removed_fields; + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index faa6797d43..4913e29d11 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1,4 +1,5 @@ import BulkOperations from "./bulk_operations"; +import ListSettings from "./list_settings"; frappe.provide('frappe.views'); @@ -287,41 +288,47 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { })) ); - if (this.list_view_settings.column_order && this.list_view_settings.column_order.length > 0 && - this.list_view_settings.column_order.length === this.list_view_settings.column_count) { - let custom_column_order = []; + if (this.list_view_settings.fields) { + let fields_order = []; + let fields = JSON.parse(this.list_view_settings.fields); + let is_status_field_set = false; //title_field is fixed - custom_column_order.push(this.columns[0]); + fields_order.push(this.columns[0]); this.columns.splice(0, 1); - for (let i in this.list_view_settings.column_order) { - let fieldname = this.list_view_settings.column_order[i].fieldname; - for (let j in this.columns) { - let df = this.columns[j].df.fieldname; - if (fieldname === df) { - custom_column_order.push(this.columns[j]); + for (let fld in fields) { + for (let col in this.columns) { + let field = fields[fld]; + let column = this.columns[col]; + + if (column.type == "Status" && !is_status_field_set) { + fields_order.push(column); + is_status_field_set = true; + break; + } else if (column.type == "Field" && field.fieldname === column.df.fieldname) { + fields_order.push(column); break; } } } - this.columns = custom_column_order; + this.columns = fields_order; } - // limit max to 8 columns if no column_count is set in List View Settings + // limit max to 8 columns if no total_fields is set in List View Settings // Screen with low density no of columns 4 // Screen with medium density no of columns 6 // Screen with high density no of columns 8 - let column_count = 6; + let total_fields = 6; if (window.innerWidth <= 1366) { - column_count = 4; + total_fields = 4; } else if (window.innerWidth >= 1920) { - column_count = 8; + total_fields = 8; } - this.columns = this.columns.slice(0, this.list_view_settings.column_count || column_count); + this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields); } get_documentation_link() { @@ -1300,26 +1307,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } show_list_settings() { - frappe.model.with_doctype("List View Settings", () => { - let list_view_settings = frappe.get_meta("List View Settings"); - - let d = new frappe.ui.Dialog({ - title: __("Settings"), - fields: [ - list_view_settings.fields[0], - list_view_settings.fields[1], - list_view_settings.fields[2], - list_view_settings.fields[3], - ] - }); - d.set_values(this.list_view_settings); - d.show(); - d.set_primary_action(__('Save'), () => { - let values = d.get_values(); - frappe.call("frappe.desk.listview.set_list_settings", {doctype: this.doctype, values: values}); - Object.assign(this.list_view_settings, values); - d.hide(); - frappe.ui.toolbar.clear_cache(); + frappe.model.with_doctype(this.doctype, () => { + new ListSettings({ + doctype: this.doctype, + settings: this.list_view_settings, + meta: frappe.get_meta(this.doctype) }); }); } From ebf87e430448aad9205105a231bbc4a2bcbdcc76 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 14 Apr 2020 11:44:20 +0530 Subject: [PATCH 014/110] Delete list_view_columns.html --- .../doctype/list_view_settings/list_view_columns.html | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 frappe/desk/doctype/list_view_settings/list_view_columns.html diff --git a/frappe/desk/doctype/list_view_settings/list_view_columns.html b/frappe/desk/doctype/list_view_settings/list_view_columns.html deleted file mode 100644 index 50474c5169..0000000000 --- a/frappe/desk/doctype/list_view_settings/list_view_columns.html +++ /dev/null @@ -1,9 +0,0 @@ -
- {% for (var i=0, l=fields.length; i < l; i++) { var field = fields[i]; %} -
- - {%= field.label %} - -
- {% } %} -
From 7483bf79683430e54a9ce73390fce75dc5b04b7d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 16 Apr 2020 20:20:13 +0530 Subject: [PATCH 015/110] feat: do not refresh when changing columns --- .../list_view_settings/list_view_settings.py | 11 ++-- frappe/public/js/frappe/list/list_settings.js | 51 +++++++++++++------ frappe/public/js/frappe/list/list_view.js | 34 ++++++++++++- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index acd8f8a7b6..6cc7d11950 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -35,18 +35,23 @@ def save_listview_settings(doctype, listview_settings, removed_listview_fields): set_listview_fields(doctype, listview_settings.get("fields"), removed_listview_fields) + return { + "meta": frappe.get_meta(doctype, False), + "listview_settings": doc + } + def set_listview_fields(doctype, listview_fields, removed_listview_fields): meta = frappe.get_meta(doctype) if isinstance(listview_fields, string_types): listview_fields = [f.get("fieldname") for f in json.loads(listview_fields)] - for field in listview_fields: - set_in_list_view_property(doctype, meta.get_field(field), "1") - for field in removed_listview_fields: set_in_list_view_property(doctype, meta.get_field(field), "0") + for field in listview_fields: + set_in_list_view_property(doctype, meta.get_field(field), "1") + def set_in_list_view_property(doctype, field, value): property_setter = frappe.db.get_value("Property Setter", {"doc_type": doctype, "field_name": field.fieldname, "property": "in_list_view"}) if property_setter: diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index 252309dd88..e078a106b1 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -1,9 +1,10 @@ export default class ListSettings { - constructor({ doctype, meta, settings }) { + constructor({ listview, doctype, meta, settings }) { if (!doctype) { frappe.throw(__('Doctype required')); } + this.listview = listview; this.doctype = doctype; this.meta = meta; this.settings = settings; @@ -33,13 +34,24 @@ export default class ListSettings { me.dialog.set_values(me.settings); me.dialog.set_primary_action(__('Save'), () => { let values = me.dialog.get_values(); - frappe.call("frappe.desk.doctype.list_view_settings.list_view_settings.save_listview_settings", { - doctype: me.doctype, - listview_settings: values, - removed_listview_fields: me.removed_fields || [] + + frappe.show_alert({ + message: __("Saving"), + indicator: "green" + }); + + frappe.call({ + method: "frappe.desk.doctype.list_view_settings.list_view_settings.save_listview_settings", + args: { + doctype: me.doctype, + listview_settings: values, + removed_listview_fields: me.removed_fields || [] + }, + callback: function (r) { + me.listview.refresh_columns(r.message.meta, r.message.listview_settings); + me.dialog.hide(); + } }); - me.dialog.hide(); - frappe.ui.toolbar.clear_cache(); }); me.dialog.fields_dict["total_fields"].df.onchange = () => me.refresh(); @@ -49,7 +61,6 @@ export default class ListSettings { let me = this; me.setup_fields(); - me.update_fields(); me.add_new_fields(); me.setup_remove_fields(); } @@ -115,6 +126,7 @@ export default class ListSettings { handle: '.sortable-handle', draggable: '.sortable', onUpdate: () => { + me.update_fields(); me.refresh(); } }); @@ -141,18 +153,19 @@ export default class ListSettings { remove_fields(fieldname) { let me = this; + let existing_fields = me.fields.map(f => f.fieldname); for (let idx in me.fields) { let field = me.fields[idx]; if (field.fieldname == fieldname) { - console.log(idx); me.fields.splice(idx, 1); break; } } - + me.set_removed_fields(me.get_removed_listview_fields(me.fields.map(f => f.fieldname), existing_fields)); me.refresh(); + me.update_fields(); } update_fields() { @@ -197,8 +210,8 @@ export default class ListSettings { }); d.set_primary_action(__('Save'), () => { let values = d.get_values().fields; - me.removed_fields = me.get_removed_listview_fields(values) - me.new_fields = values + + me.set_removed_fields(me.get_removed_listview_fields(values, me.fields.map(f => f.fieldname))); me.fields = []; me.set_subject_field(me.meta); @@ -303,11 +316,9 @@ export default class ListSettings { return multiselect_fields } - get_removed_listview_fields(new_fields) { + get_removed_listview_fields(new_fields, existing_fields) { let me = this; - let removed_fields = [] - let existing_fields = me.fields.map(f => f.fieldname); existing_fields.forEach(column => { if (!in_list(new_fields, column)) { @@ -317,4 +328,14 @@ export default class ListSettings { return removed_fields; } + + set_removed_fields(fields) { + let me = this; + + if (me.removed_fields) { + me.removed_fields.concat(fields); + } else { + me.removed_fields = fields; + } + } } \ No newline at end of file diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 3d629d3fda..b784e7ba92 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -232,6 +232,32 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } + refresh_columns(meta, list_view_settings) { + this.meta = meta; + this.list_view_settings = list_view_settings; + + this.setup_columns(); + this.refresh(true); + } + + refresh(refresh_header=false) { + this.freeze(true); + // fetch data from server + return frappe.call(this.get_call_args()).then(r => { + // render + this.prepare_data(r); + this.toggle_result_area(); + this.before_render(); + this.render_header(refresh_header); + this.render(); + this.after_render(); + this.freeze(false); + if (this.settings.refresh) { + this.settings.refresh(this); + } + }); + } + setup_freeze_area() { this.$freeze = $(`
${__('Loading')}...
`) @@ -329,6 +355,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields); + console.log(this.columns); } get_documentation_link() { @@ -415,7 +442,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } } - render_header() { + render_header(refresh_header=false) { + if (refresh_header) { + this.$result.find('.list-row-head').remove(); + } + if (this.$result.find('.list-row-head').length === 0) { // append header once this.$result.prepend(this.get_header_html()); @@ -1315,6 +1346,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { show_list_settings() { frappe.model.with_doctype(this.doctype, () => { new ListSettings({ + listview: this, doctype: this.doctype, settings: this.list_view_settings, meta: frappe.get_meta(this.doctype) From b9bfc6b2f51e0359f1a4c524d43f13d371d5d8b1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 5 May 2020 15:30:55 +0530 Subject: [PATCH 016/110] fix: review changes --- .../list_view_settings.json | 4 +- .../list_view_settings/list_view_settings.py | 17 +++--- frappe/public/js/frappe/list/list_settings.js | 59 ++++++++++++++++--- frappe/public/js/frappe/list/list_view.js | 53 +++++++++-------- 4 files changed, 87 insertions(+), 46 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index e59991c52e..486d2c1c44 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -35,7 +35,7 @@ { "fieldname": "total_fields", "fieldtype": "Select", - "label": "Total Fields", + "label": "Maximum Number of Fields", "options": "\n4\n5\n6\n7\n8\n9\n10" }, { @@ -52,7 +52,7 @@ } ], "links": [], - "modified": "2020-04-13 23:31:15.411147", + "modified": "2020-05-05 15:15:14.306665", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index 6cc7d11950..db3be47402 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -17,17 +17,14 @@ class ListViewSettings(Document): @frappe.whitelist() def save_listview_settings(doctype, listview_settings, removed_listview_fields): - if isinstance(listview_settings, string_types): - listview_settings = json.loads(listview_settings) + listview_settings = frappe.parse_json(listview_settings) + removed_listview_fields = frappe.parse_json(removed_listview_fields) - if isinstance(removed_listview_fields, string_types): - removed_listview_fields = json.loads(removed_listview_fields) - - try: + if frappe.get_all("List View Settings", filters={"name": doctype}): doc = frappe.get_doc("List View Settings", doctype) doc.update(listview_settings) doc.save() - except DoesNotExistError: + else: doc = frappe.new_doc("List View Settings") doc.name = doctype doc.update(listview_settings) @@ -43,8 +40,7 @@ def save_listview_settings(doctype, listview_settings, removed_listview_fields): def set_listview_fields(doctype, listview_fields, removed_listview_fields): meta = frappe.get_meta(doctype) - if isinstance(listview_fields, string_types): - listview_fields = [f.get("fieldname") for f in json.loads(listview_fields)] + listview_fields = [f.get("fieldname") for f in frappe.parse_json(listview_fields) if f.get("fieldname")] for field in removed_listview_fields: set_in_list_view_property(doctype, meta.get_field(field), "0") @@ -53,6 +49,9 @@ def set_listview_fields(doctype, listview_fields, removed_listview_fields): set_in_list_view_property(doctype, meta.get_field(field), "1") def set_in_list_view_property(doctype, field, value): + if not field or field.fieldname == "status_field": + return + property_setter = frappe.db.get_value("Property Setter", {"doc_type": doctype, "field_name": field.fieldname, "property": "in_list_view"}) if property_setter: doc = frappe.get_doc("Property Setter", property_setter) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index e078a106b1..a2eaaa6689 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -9,7 +9,7 @@ export default class ListSettings { this.meta = meta; this.settings = settings; this.dialog = null; - this.fields = []; + this.fields = this.settings && this.settings.fields ? JSON.parse(this.settings.fields) : []; this.subject_field = null; frappe.model.with_doctype("List View Settings", () => { @@ -28,7 +28,7 @@ export default class ListSettings { let list_view_settings = frappe.get_meta("List View Settings"); me.dialog = new frappe.ui.Dialog({ - title: __("{0} List View Settings", [__(me.doctype)]), + title: __("{0} Settings", [__(me.doctype)]), fields: list_view_settings.fields }); me.dialog.set_values(me.settings); @@ -67,14 +67,35 @@ export default class ListSettings { show_dialog() { let me = this; + + if (!this.settings.fields) { + me.update_fields(); + } + + if (!me.dialog.get_value("total_fields")) { + let field_count = me.fields.length; + + if (field_count < 4) { + field_count = 4; + } else if (field_count > 10) { + field_count = 4; + } + + me.dialog.set_value("total_fields", field_count); + } + me.dialog.show(); } setup_fields() { + function is_status_field(field) { + return field.fieldname === "status_field"; + } + let me = this; let fields_html = me.dialog.get_field("fields_html"); - let $wrapper = fields_html.$wrapper[0]; + let wrapper = fields_html.$wrapper[0]; let fields = ``; let total_fields = me.dialog.get_values().total_fields ? me.dialog.get_values().total_fields : me.settings.total_fields; @@ -83,16 +104,17 @@ export default class ListSettings { break; } let is_sortable = (idx == 0) ? `` : `sortable`; - let can_remove = (idx == 0) ? `hide` : ``; + let show_sortable_handle = (idx == 0) ? `hide` : ``; + let can_remove = (idx == 0 || is_status_field(me.fields[idx])) ? `hide` : ``; fields += `
+ data-label="${me.fields[idx].label}" data-type="${me.fields[idx].type}">
- +
${me.fields[idx].label} @@ -122,7 +144,7 @@ export default class ListSettings {
`); - new Sortable($wrapper.getElementsByClassName("control-input-wrapper")[0], { + new Sortable(wrapper.getElementsByClassName("control-input-wrapper")[0], { handle: '.sortable-handle', draggable: '.sortable', onUpdate: () => { @@ -172,9 +194,9 @@ export default class ListSettings { let me = this; let fields_html = me.dialog.get_field("fields_html"); - let $wrapper = fields_html.$wrapper[0]; + let wrapper = fields_html.$wrapper[0]; - let fields_order = $wrapper.getElementsByClassName("fields_order"); + let fields_order = wrapper.getElementsByClassName("fields_order"); me.fields = []; for (let idx = 0; idx < fields_order.length; idx++) { @@ -185,6 +207,7 @@ export default class ListSettings { } me.dialog.set_value("fields", JSON.stringify(me.fields)); + me.dialog.get_value("fields"); } column_selector() { @@ -215,6 +238,7 @@ export default class ListSettings { me.fields = []; me.set_subject_field(me.meta); + me.set_status_field(); for (let idx in values) { let value = values[idx]; @@ -266,6 +290,7 @@ export default class ListSettings { let me = this; me.set_subject_field(meta); + me.set_status_field(); meta.fields.forEach(field => { if (field.in_list_view && !in_list(frappe.model.no_value_type, field.fieldtype) && @@ -299,6 +324,18 @@ export default class ListSettings { me.fields.push(me.subject_field); } + set_status_field() { + let me = this; + + if (frappe.has_indicator(me.doctype)) { + me.fields.push({ + type: "Status", + label: "Status", + fieldname: "status_field" + }) + } + } + get_doctype_fields(meta, fields) { let me = this; let multiselect_fields = [] @@ -320,6 +357,10 @@ export default class ListSettings { let me = this; let removed_fields = [] + if (frappe.has_indicator(me.doctype)) { + new_fields.push("status_field"); + } + existing_fields.forEach(column => { if (!in_list(new_fields, column)) { removed_fields.push(column); diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index b784e7ba92..98d21f905d 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -315,31 +315,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { ); if (this.list_view_settings.fields) { - let fields_order = []; - let fields = JSON.parse(this.list_view_settings.fields); - let is_status_field_set = false; - - //title_field is fixed - fields_order.push(this.columns[0]); - this.columns.splice(0, 1); - - for (let fld in fields) { - for (let col in this.columns) { - let field = fields[fld]; - let column = this.columns[col]; - - if (column.type == "Status" && !is_status_field_set) { - fields_order.push(column); - is_status_field_set = true; - break; - } else if (column.type == "Field" && field.fieldname === column.df.fieldname) { - fields_order.push(column); - break; - } - } - } - - this.columns = fields_order; + this.columns = this.reorder_listview_fields(); } // limit max to 8 columns if no total_fields is set in List View Settings @@ -355,7 +331,32 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields); - console.log(this.columns); + } + + reorder_listview_fields() { + let fields_order = []; + let fields = JSON.parse(this.list_view_settings.fields); + + //title_field is fixed + fields_order.push(this.columns[0]); + this.columns.splice(0, 1); + + for (let fld in fields) { + for (let col in this.columns) { + let field = fields[fld]; + let column = this.columns[col]; + + if (column.type == "Status" && field.fieldname == "status_field") { + fields_order.push(column); + break; + } else if (column.type == "Field" && field.fieldname === column.df.fieldname) { + fields_order.push(column); + break; + } + } + } + + return fields_order; } get_documentation_link() { From c4320fb22f71e12539d71f0f6374aa3aa9d99e8b Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 5 May 2020 16:05:23 +0530 Subject: [PATCH 017/110] Update frappe/public/js/frappe/list/list_settings.js Co-authored-by: Prssanna Desai --- frappe/public/js/frappe/list/list_settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index a2eaaa6689..e1978cc721 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -97,7 +97,7 @@ export default class ListSettings { let fields_html = me.dialog.get_field("fields_html"); let wrapper = fields_html.$wrapper[0]; let fields = ``; - let total_fields = me.dialog.get_values().total_fields ? me.dialog.get_values().total_fields : me.settings.total_fields; + let total_fields = me.dialog.get_values().total_fields || me.settings.total_fields; for (let idx in me.fields) { if (idx == parseInt(total_fields)) { @@ -379,4 +379,4 @@ export default class ListSettings { me.removed_fields = fields; } } -} \ No newline at end of file +} From 627940c56b7fa435726d79f355ca8d96fd329d57 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 5 May 2020 18:58:38 +0530 Subject: [PATCH 018/110] feat: site wise logs under sites/{sitename}/logs/frappe.log --- frappe/commands/site.py | 2 +- frappe/utils/logger.py | 31 +++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 52994ccec3..362d02c0d2 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -409,7 +409,7 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path= else: click.echo("="*80) click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site)) - click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n")) + click.echo("Reason: {reason}{sep}".format(reason=str(err), sep="\n")) click.echo("Fix the issue and try again.") click.echo( "Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 5a77434cde..e446b39ee0 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -1,23 +1,42 @@ +# imports - compatibility imports from __future__ import unicode_literals -import frappe + +# imports - standard imports import logging +import os from logging.handlers import RotatingFileHandler + +# imports - third party imports from six import text_type +# imports - module imports +import frappe + + default_log_level = logging.DEBUG -LOG_FILENAME = '../logs/{}-frappe.log'.format(frappe.local.site) +site = getattr(frappe.local, 'site', None) +LOG_FILENAME = os.path.join('..', 'logs', 'frappe.log') + def get_logger(module, with_more_info=True): if module in frappe.loggers: return frappe.loggers[module] formatter = logging.Formatter('[%(levelname)s] %(asctime)s | %(pathname)s:\n%(message)s') - # handler = logging.StreamHandler() - handler = RotatingFileHandler( - LOG_FILENAME, maxBytes=100000, backupCount=20) + handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) handler.setFormatter(formatter) + if site: + SITELOG_FOLDER = os.path.join(site, 'logs') + SITELOG_FILENAME = os.path.join(SITELOG_FOLDER, 'frappe.log') + + if not os.path.exists(SITELOG_FOLDER): + os.mkdir(SITELOG_FOLDER) + + handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) + handler.setFormatter(formatter) + if with_more_info: handler.addFilter(SiteContextFilter()) @@ -39,7 +58,7 @@ class SiteContextFilter(logging.Filter): def get_more_info_for_log(): '''Adds Site, Form Dict into log entry''' more_info = [] - site = getattr(frappe.local, 'site', None) + if site: more_info.append('Site: {0}'.format(site)) From ca70bd364cc125be533ec63bcdb2f87dc71096e7 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 5 May 2020 18:59:38 +0530 Subject: [PATCH 019/110] fix: create sites/{site}/logs folder --- frappe/installer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/installer.py b/frappe/installer.py index 54402f0087..565fc9d36b 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -269,6 +269,7 @@ def make_site_dirs(): os.path.join(site_private_path, 'backups'), os.path.join(site_public_path, 'files'), os.path.join(site_private_path, 'files'), + os.path.join(frappe.local.site_path, 'logs'), os.path.join(frappe.local.site_path, 'task-logs')): if not os.path.exists(dir_path): os.makedirs(dir_path) From 763e8cfcaecdfe1c3bb997a6d8e96a15962b5344 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 7 May 2020 17:29:35 +0530 Subject: [PATCH 020/110] fix: remove unnecessary eval from error snapshot Signed-off-by: Chinmay D. Pai --- frappe/utils/error.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/frappe/utils/error.py b/frappe/utils/error.py index c124410a7f..dd6ae05c51 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -123,22 +123,13 @@ def get_snapshot(exception, context=10): # add exception type, value and attributes if isinstance(evalue, BaseException): for name in dir(evalue): - # prevent py26 DeprecationWarning - if (name != 'messages' or sys.version_info < (2.6)) and not name.startswith('__'): + if name != 'messages' and not name.startswith('__'): value = pydoc.text.repr(getattr(evalue, name)) - - # render multilingual string properly - if isinstance(value, six.text_type): - value = eval(value) - s['exception'][name] = encode(value) # add all local values (of last frame) to the snapshot for name, value in locals.items(): - if isinstance(value, six.text_type): - value = eval(value) - - s['locals'][name] = pydoc.text.repr(value) + s['locals'][name] = value if isinstance(value, six.text_type) else pydoc.text.repr(value) return s From b685412b369dac57fa853a8c0e33a0f5d0764328 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 8 May 2020 18:29:45 +0530 Subject: [PATCH 021/110] feat: allow notifications created via Notification in system notifications --- .../notification_log/notification_log.json | 8 ++++--- .../notification_log/notification_log.py | 2 +- .../notification_settings.json | 12 ++++++++++- .../notification_settings.py | 3 +++ .../doctype/notification/notification.json | 12 ++++++++++- .../doctype/notification/notification.py | 21 +++++++++++++++++++ 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index ecb746df64..86213da242 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-08-26 13:37:34.165254", "doctype": "DocType", "editable_grid": 1, @@ -35,7 +36,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Type", - "options": "Mention\nEnergy Point\nAssignment\nShare", + "options": "Mention\nEnergy Point\nAssignment\nShare\nAlert", "read_only": 1, "search_index": 1 }, @@ -82,8 +83,9 @@ } ], "in_create": 1, - "modified": "2019-11-12 15:22:35.283678", - "modified_by": "umair@erpnext.com", + "links": [], + "modified": "2020-05-08 15:16:36.365285", + "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 17eb6371b1..0112e68b1d 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -68,7 +68,7 @@ def make_notification_logs(doc, users): _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') - if _doc.for_user != _doc.from_user or doc.type == 'Energy Point': + if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': _doc.insert(ignore_permissions=True) def send_notification_email(doc): diff --git a/frappe/desk/doctype/notification_settings/notification_settings.json b/frappe/desk/doctype/notification_settings/notification_settings.json index 6af325507b..190e827ab5 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.json +++ b/frappe/desk/doctype/notification_settings/notification_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "Prompt", "creation": "2019-09-11 22:15:44.851526", "doctype": "DocType", @@ -13,6 +14,7 @@ "enable_email_assignment", "enable_email_energy_point", "enable_email_share", + "enable_email_alert", "user", "seen" ], @@ -83,10 +85,18 @@ "fieldtype": "Check", "hidden": 1, "label": "Seen" + }, + { + "default": "0", + "fieldname": "enable_email_alert", + "fieldtype": "Check", + "hidden": 1, + "label": "Alert" } ], "in_create": 1, - "modified": "2019-11-19 12:57:59.356786", + "links": [], + "modified": "2020-05-08 15:15:53.925897", "modified_by": "Administrator", "module": "Desk", "name": "Notification Settings", diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 6b5a13ee27..9b124cd6f4 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -28,6 +28,9 @@ def is_email_notifications_enabled_for_type(user, notification_type): if not is_email_notifications_enabled(user): return False + if notification_type == 'Alert': + 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/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 14eff2251a..d83305ec5b 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_rename": 1, "autoname": "Prompt", "creation": "2014-07-11 17:18:09.923399", @@ -23,6 +24,7 @@ "value_changed", "sender", "sender_email", + "show_in_notifications_dropdown", "section_break_9", "condition", "column_break_6", @@ -260,10 +262,18 @@ "fieldtype": "Link", "label": "Print Format", "options": "Print Format" + }, + { + "default": "0", + "description": "If enabled, the notification will also show up in the notifications dropdown on the top right corner of the navigation bar.", + "fieldname": "show_in_notifications_dropdown", + "fieldtype": "Check", + "label": "Show in Notifications Dropdown" } ], "icon": "fa fa-envelope", - "modified": "2019-07-15 13:17:02.585013", + "links": [], + "modified": "2020-05-06 13:46:21.155535", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 8c011ade65..cac4bbddc2 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -13,6 +13,7 @@ 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.desk.doctype.notification_log.notification_log import enqueue_create_notification class Notification(Document): def onload(self): @@ -125,6 +126,9 @@ def get_context(context): if self.channel == 'Slack': self.send_a_slack_msg(doc, context) + if self.show_in_notifications_dropdown: + self.create_drodown_notification(doc, context) + if self.set_property_after_alert: allow_update = True if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit: @@ -143,6 +147,23 @@ def get_context(context): except Exception: frappe.log_error(title='Document update failed', message=frappe.get_traceback()) + def create_drodown_notification(self, doc, context): + subject = self.subject + if "{" in subject: + subject = frappe.render_template(self.subject, context) + subject = '[Alert] ' + subject + + recipients, cc, bcc = self.get_list_of_recipients(doc, context) + users = recipients + cc + bcc + + notification_doc = { + 'type': 'Alert', + 'document_type': doc.doctype, + 'document_name': doc.name, + 'subject': subject, + } + enqueue_create_notification(recipients, notification_doc) + def send_an_email(self, doc, context): from email.utils import formataddr subject = self.subject From e8f30259c6cdf419129f2a820cbd7b215eeee596 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 15:23:18 +0530 Subject: [PATCH 022/110] fix: link notification log document and show message --- .../notification_log/notification_log.json | 19 +++++++++---------- .../notification_log/notification_log.py | 15 +++++++++++++++ .../doctype/notification/notification.py | 4 +++- .../frappe/ui/notifications/notifications.js | 14 ++++++++++---- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index 86213da242..e8225956b8 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -9,7 +9,6 @@ "for_user", "type", "email_content", - "column_break_4", "document_type", "read", "document_name", @@ -26,6 +25,7 @@ { "fieldname": "for_user", "fieldtype": "Link", + "hidden": 1, "label": "For User", "options": "User", "read_only": 1 @@ -33,6 +33,7 @@ { "fieldname": "type", "fieldtype": "Select", + "hidden": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Type", @@ -42,17 +43,14 @@ }, { "fieldname": "email_content", - "fieldtype": "Text", - "label": "Email Content", + "fieldtype": "Text Editor", + "label": "Message", "read_only": 1 }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, { "fieldname": "document_type", "fieldtype": "Link", + "hidden": 1, "label": "Document Type", "options": "DocType", "read_only": 1, @@ -61,13 +59,14 @@ { "fieldname": "document_name", "fieldtype": "Data", - "label": "Document Name", + "label": "Document Link", "read_only": 1, "search_index": 1 }, { "fieldname": "from_user", "fieldtype": "Link", + "hidden": 1, "label": "From User", "options": "User", "read_only": 1, @@ -84,8 +83,8 @@ ], "in_create": 1, "links": [], - "modified": "2020-05-08 15:16:36.365285", - "modified_by": "Administrator", + "modified": "2020-05-11 15:18:20.893718", + "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 0112e68b1d..2db552e7de 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -65,11 +65,26 @@ def make_notification_logs(doc, users): return _doc = frappe.new_doc('Notification Log') + + attachments = None + if doc.attachments: + attachments = doc.pop('attachments') + _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': _doc.insert(ignore_permissions=True) + if attachments: + attach_file_to_doc(attachments, _doc.name) + + +def attach_file_to_doc(attachments, docname): + from frappe.utils.file_manager import save_file + for attachment in attachments: + attachment.pop("print_format_attachment", None) + print_format_file = frappe._dict(frappe.attach_print(**attachment)) + save_file(print_format_file.fname, print_format_file.fcontent, 'Notification Log', docname) def send_notification_email(doc): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index cac4bbddc2..6d74e499d9 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -151,8 +151,8 @@ def get_context(context): subject = self.subject if "{" in subject: subject = frappe.render_template(self.subject, context) - subject = '[Alert] ' + subject + attachments = self.get_attachment(doc) recipients, cc, bcc = self.get_list_of_recipients(doc, context) users = recipients + cc + bcc @@ -161,6 +161,8 @@ def get_context(context): 'document_type': doc.doctype, 'document_name': doc.name, 'subject': subject, + 'email_content': frappe.render_template(self.message, context), + 'attachments': attachments } enqueue_create_notification(recipients, notification_doc) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index 2420d6772e..14d3b9bbd0 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -304,10 +304,7 @@ frappe.ui.Notifications = class Notifications { } get_dropdown_item_html(field) { - let doc_link = frappe.utils.get_form_link( - field.document_type, - field.document_name - ); + let doc_link = this.get_item_link(field); let read_class = field.read ? '' : 'unread'; let mark_read_action = field.read ? '': 'data-action="mark_as_read"'; let message = field.subject; @@ -336,6 +333,15 @@ frappe.ui.Notifications = class Notifications { return item_html; } + get_item_link(field) { + const link_doctype = field.type == 'Alert'? 'Notification Log': field.document_type; + const link_docname = field.type == 'Alert'? field.name: field.document_name; + return frappe.utils.get_form_link( + link_doctype, + link_docname + ); + } + render_dropdown_headers() { this.categories = [ { From a6a3919ac61cd9217abd32155d5e95e3637c3767 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 19:27:01 +0530 Subject: [PATCH 023/110] fix: add button to route to reference doc form --- frappe/desk/doctype/notification_log/notification_log.js | 6 ++---- .../desk/doctype/notification_log/notification_log.json | 9 ++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.js b/frappe/desk/doctype/notification_log/notification_log.js index 654b2b2b06..2bb48ccdc1 100644 --- a/frappe/desk/doctype/notification_log/notification_log.js +++ b/frappe/desk/doctype/notification_log/notification_log.js @@ -2,11 +2,9 @@ // For license information, please see license.txt frappe.ui.form.on('Notification Log', { - refresh: function(frm) { + open_reference_document: function(frm) { let dt = frm.doc.document_type; let dn = frm.doc.document_name; - frm.fields_dict.document_name.$input_wrapper - .find('.control-value') - .wrapInner(``); + frappe.set_route('Form', dt, dn); } }); diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index e8225956b8..e676bfc1f9 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -12,6 +12,7 @@ "document_type", "read", "document_name", + "open_reference_document", "from_user" ], "fields": [ @@ -59,6 +60,7 @@ { "fieldname": "document_name", "fieldtype": "Data", + "hidden": 1, "label": "Document Link", "read_only": 1, "search_index": 1 @@ -79,11 +81,16 @@ "hidden": 1, "ignore_user_permissions": 1, "label": "Read" + }, + { + "fieldname": "open_reference_document", + "fieldtype": "Button", + "label": "Open Reference Document" } ], "in_create": 1, "links": [], - "modified": "2020-05-11 15:18:20.893718", + "modified": "2020-05-11 19:19:58.578245", "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", From 2a68af05f5bf8e4bfa30a8f714a226fb4b089d69 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 19:36:43 +0530 Subject: [PATCH 024/110] fix: option to disable channel if show in dropdown is enabled --- frappe/email/doctype/notification/notification.json | 13 +++++++++++-- frappe/email/doctype/notification/notification.py | 6 ++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index d83305ec5b..736056056f 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -25,6 +25,7 @@ "sender", "sender_email", "show_in_notifications_dropdown", + "disable_channel", "section_break_9", "condition", "column_break_6", @@ -265,15 +266,23 @@ }, { "default": "0", - "description": "If enabled, the notification will also show up in the notifications dropdown on the top right corner of the navigation bar.", + "description": "If enabled, the notification will show up in the notifications dropdown on the top right corner of the navigation bar.", "fieldname": "show_in_notifications_dropdown", "fieldtype": "Check", "label": "Show in Notifications Dropdown" + }, + { + "default": "0", + "depends_on": "eval: doc.show_in_notifications_dropdown", + "description": "If enabled, the channel set for the Notification will be disabled. The Notification will only be received in the Notifications Dropdown.", + "fieldname": "disable_channel", + "fieldtype": "Check", + "label": "Disable Channel" } ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-05-06 13:46:21.155535", + "modified": "2020-05-11 15:33:28.904019", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 6d74e499d9..8ed0bae067 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -120,10 +120,12 @@ def get_context(context): if self.is_standard: self.load_standard_properties(context) - if self.channel == 'Email': + channel_disabled = self.disable_channel + + if self.channel == 'Email' and not channel_disabled: self.send_an_email(doc, context) - if self.channel == 'Slack': + if self.channel == 'Slack' and not channel_disabled: self.send_a_slack_msg(doc, context) if self.show_in_notifications_dropdown: From 38b51dbf4b4bb2e65ac31a14322c5d2c36d3b892 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 12:44:33 +0530 Subject: [PATCH 025/110] fix: resetting fields --- frappe/public/js/frappe/list/list_settings.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index a2eaaa6689..9487269efa 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -247,10 +247,12 @@ export default class ListSettings { break; } else if (value != me.subject_field.fieldname) { let field = frappe.meta.get_docfield(me.doctype, value); - me.fields.push({ - label: field.label, - fieldname: field.fieldname - }) + if (field) { + me.fields.push({ + label: field.label, + fieldname: field.fieldname + }) + } } } From 77fdb55d106c00b2742c352b9b814d6fa0e5d5ce Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 12:53:59 +0530 Subject: [PATCH 026/110] fix: sider --- .../list_view_settings/list_view_settings.py | 7 ++---- frappe/public/js/frappe/list/list_settings.js | 24 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index db3be47402..74e029f499 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -5,9 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from six import string_types -from frappe.exceptions import DoesNotExistError -import json class ListViewSettings(Document): @@ -71,9 +68,9 @@ def set_in_list_view_property(doctype, field, value): def get_default_listview_fields(doctype): meta = frappe.get_meta(doctype) path = frappe.get_module_path(frappe.scrub(meta.module), "doctype", frappe.scrub(meta.name), frappe.scrub(meta.name) + ".json") - json = frappe.get_file_json(path) + doctype_json = frappe.get_file_json(path) - fields = [f.get("fieldname") for f in json.get("fields") if f.get("in_list_view")] + fields = [f.get("fieldname") for f in doctype_json.get("fields") if f.get("in_list_view")] if meta.title_field: if not meta.title_field.strip() in fields: diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index fa6736a1cd..6cebe421d7 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -155,7 +155,7 @@ export default class ListSettings { } add_new_fields() { - let me = this + let me = this; let fields_html = me.dialog.get_field("fields_html"); let add_new_fields = fields_html.$wrapper[0].getElementsByClassName("add-new-fields")[0]; @@ -301,9 +301,9 @@ export default class ListSettings { me.fields.push({ label: field.label, fieldname: field.fieldname - }) + }); } - }) + }); } set_subject_field(meta) { @@ -312,15 +312,15 @@ export default class ListSettings { me.subject_field = { label: "Name", fieldname: "name" - } + }; if (meta.title_field) { - let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()) + let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()); me.subject_field = { label: field.label, fieldname: field.fieldname - } + }; } me.fields.push(me.subject_field); @@ -334,13 +334,13 @@ export default class ListSettings { type: "Status", label: "Status", fieldname: "status_field" - }) + }); } } get_doctype_fields(meta, fields) { let me = this; - let multiselect_fields = [] + let multiselect_fields = []; meta.fields.forEach(field => { if (!in_list(frappe.model.no_value_type, field.fieldtype)) { @@ -348,16 +348,16 @@ export default class ListSettings { label: field.label, value: field.fieldname, checked: in_list(fields, field.fieldname) - }) + }); } - }) + }); - return multiselect_fields + return multiselect_fields; } get_removed_listview_fields(new_fields, existing_fields) { let me = this; - let removed_fields = [] + let removed_fields = []; if (frappe.has_indicator(me.doctype)) { new_fields.push("status_field"); From 00f7194069369977cce66d1b43a4d115cc9486fa Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 16:28:37 +0530 Subject: [PATCH 027/110] fix: link attachment to doc --- .../notification_log/notification_log.js | 39 ++++++++++++++++++- .../notification_log/notification_log.json | 18 ++++++++- .../notification_log/notification_log.py | 23 ++++------- .../doctype/notification/notification.py | 2 +- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.js b/frappe/desk/doctype/notification_log/notification_log.js index 2bb48ccdc1..1f381d115b 100644 --- a/frappe/desk/doctype/notification_log/notification_log.js +++ b/frappe/desk/doctype/notification_log/notification_log.js @@ -2,9 +2,44 @@ // For license information, please see license.txt frappe.ui.form.on('Notification Log', { + refresh: function(frm) { + if (frm.doc.attached_file) { + frm.trigger('set_attachment'); + } else { + frm.get_field('attachment_link').$wrapper.empty(); + } + }, + open_reference_document: function(frm) { - let dt = frm.doc.document_type; - let dn = frm.doc.document_name; + const dt = frm.doc.document_type; + const dn = frm.doc.document_name; frappe.set_route('Form', dt, dn); + }, + + set_attachment: function(frm) { + const attachment = JSON.parse(frm.doc.attached_file); + + const $wrapper = frm.get_field('attachment_link').$wrapper; + $wrapper.html(` + + `); + + $wrapper.find(".attached-file-link").click(() => { + const w = window.open( + frappe.urllib.get_full_url(`/api/method/frappe.utils.print_format.download_pdf? + doctype=${encodeURIComponent(attachment.doctype)} + &name=${encodeURIComponent(attachment.name)} + &format=${encodeURIComponent(attachment.print_format)} + &lang=${encodeURIComponent(attachment.lang)}`) + ); + if (!w) { + frappe.msgprint(__("Please enable pop-ups")); + } + }); } }); diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index e676bfc1f9..35869f8c2e 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -12,6 +12,8 @@ "document_type", "read", "document_name", + "attached_file", + "attachment_link", "open_reference_document", "from_user" ], @@ -86,12 +88,24 @@ "fieldname": "open_reference_document", "fieldtype": "Button", "label": "Open Reference Document" + }, + { + "fieldname": "attached_file", + "fieldtype": "Code", + "hidden": 1, + "label": "Attached File", + "options": "JSON" + }, + { + "fieldname": "attachment_link", + "fieldtype": "HTML", + "label": "Attachment Link" } ], "in_create": 1, "links": [], - "modified": "2020-05-11 19:19:58.578245", - "modified_by": "umair@erpnext.com", + "modified": "2020-05-12 15:56:46.278677", + "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 2db552e7de..244d0e150c 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +import json from frappe.model.document import Document from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value) @@ -58,6 +59,11 @@ def enqueue_create_notification(users, doc): def make_notification_logs(doc, users): from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled + + attachment = None + if doc.attachment: + attachment = doc.pop('attachment') + for user in users: if frappe.db.exists('User', user): if is_notifications_enabled(user): @@ -65,26 +71,13 @@ def make_notification_logs(doc, users): return _doc = frappe.new_doc('Notification Log') - - attachments = None - if doc.attachments: - attachments = doc.pop('attachments') - _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': + if attachment: + _doc.attached_file = json.dumps(attachment) _doc.insert(ignore_permissions=True) - if attachments: - attach_file_to_doc(attachments, _doc.name) - - -def attach_file_to_doc(attachments, docname): - from frappe.utils.file_manager import save_file - for attachment in attachments: - attachment.pop("print_format_attachment", None) - print_format_file = frappe._dict(frappe.attach_print(**attachment)) - save_file(print_format_file.fname, print_format_file.fcontent, 'Notification Log', docname) def send_notification_email(doc): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 8ed0bae067..3940625e1d 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -164,7 +164,7 @@ def get_context(context): 'document_name': doc.name, 'subject': subject, 'email_content': frappe.render_template(self.message, context), - 'attachments': attachments + 'attachment': attachments[0] } enqueue_create_notification(recipients, notification_doc) From c60a3ebe6a6eb679857a05806d0d138662d1fa29 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 16:32:49 +0530 Subject: [PATCH 028/110] fix: send notification to all users --- frappe/desk/doctype/notification_log/notification_log.py | 1 + frappe/email/doctype/notification/notification.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 244d0e150c..90ae5a437e 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -49,6 +49,7 @@ def enqueue_create_notification(users, doc): if isinstance(users, frappe.string_types): users = [user.strip() for user in users.split(',') if user.strip()] + users = list(set(users)) frappe.enqueue( 'frappe.desk.doctype.notification_log.notification_log.make_notification_logs', diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 3940625e1d..38077154cd 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -166,7 +166,7 @@ def get_context(context): 'email_content': frappe.render_template(self.message, context), 'attachment': attachments[0] } - enqueue_create_notification(recipients, notification_doc) + enqueue_create_notification(users, notification_doc) def send_an_email(self, doc, context): from email.utils import formataddr From 080301925169d60fc9e02fbd1812d6d7d6e575fa Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 18:25:21 +0530 Subject: [PATCH 029/110] style: fix formatting --- frappe/public/js/frappe/list/list_settings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index 6cebe421d7..f2045c9c34 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -251,7 +251,7 @@ export default class ListSettings { me.fields.push({ label: field.label, fieldname: field.fieldname - }) + }); } } } @@ -339,7 +339,6 @@ export default class ListSettings { } get_doctype_fields(meta, fields) { - let me = this; let multiselect_fields = []; meta.fields.forEach(field => { From 68e5a1d3e6575460122834786901dabe640846d2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 18:29:07 +0530 Subject: [PATCH 030/110] fix: remove permission for all in list view settings --- .../list_view_settings/list_view_settings.json | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 486d2c1c44..44761992f1 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -52,7 +52,7 @@ } ], "links": [], - "modified": "2020-05-05 15:15:14.306665", + "modified": "2020-05-12 18:27:15.568199", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", @@ -67,18 +67,6 @@ "role": "System Manager", "share": 1, "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "share": 1, - "write": 1 } ], "read_only": 1, From 77b59a14937ce79b778400e6d26feba8079e10c6 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 19:21:29 +0530 Subject: [PATCH 031/110] fix: hide channel field if disabled --- frappe/email/doctype/notification/notification.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 736056056f..9e52a8b7a8 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -57,6 +57,7 @@ }, { "default": "Email", + "depends_on": "eval: !doc.disable_channel", "fieldname": "channel", "fieldtype": "Select", "label": "Channel", @@ -282,7 +283,7 @@ ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-05-11 15:33:28.904019", + "modified": "2020-05-12 19:20:50.304928", "modified_by": "Administrator", "module": "Email", "name": "Notification", From a86ae948f7b35a4afff6594dbd1499eb6a516b03 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 18 May 2020 13:01:33 +0530 Subject: [PATCH 032/110] fix(commands): raise SiteNotSpecifiedError if site not found in context --- frappe/__init__.py | 3 +-- frappe/commands/__init__.py | 1 - frappe/commands/scheduler.py | 8 +++++++- frappe/commands/site.py | 35 +++++++++++++++++++++++++++++++++-- frappe/commands/translate.py | 2 ++ frappe/commands/utils.py | 26 ++++++++++++++++++++++++++ frappe/exceptions.py | 5 +++++ frappe/utils/bench_helper.py | 16 +++++++++------- 8 files changed, 83 insertions(+), 13 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index f0b6bfe41b..dbc16fa056 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -231,9 +231,8 @@ def get_site_config(sites_path=None, site_path=None): if os.path.exists(site_config): config.update(get_file_json(site_config)) elif local.site and not local.flags.new_site: - print("{0} does not exist".format(local.site)) + print("Site {0} does not exist".format(local.site)) sys.exit(1) - #raise IncorrectSitePath, "{0} does not exist".format(site_config) return _dict(config) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 8110f2ec19..7d36cbe28c 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -44,7 +44,6 @@ def get_site(context): site = context.sites[0] return site except (IndexError, TypeError): - print('Please specify --site sitename') sys.exit(1) def popen(command, *args, **kwargs): diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index 6f51c81211..d073c3d90e 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -4,6 +4,7 @@ import sys import frappe from frappe.utils import cint from frappe.commands import pass_context, get_site +from frappe.exceptions import SiteNotSpecifiedError def _is_scheduler_enabled(): enable_scheduler = False @@ -30,6 +31,8 @@ def trigger_scheduler_event(context, event): frappe.utils.scheduler.trigger(site, event, now=True) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('enable-scheduler') @pass_context @@ -45,6 +48,8 @@ def enable_scheduler(context): print("Enabled for", site) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('disable-scheduler') @pass_context @@ -60,7 +65,8 @@ def disable_scheduler(context): print("Disabled for", site) finally: frappe.destroy() - + else: + raise SiteNotSpecifiedError @click.command('scheduler') diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 82ed72dd5c..f50ec05af4 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -15,6 +15,7 @@ import frappe from frappe import _ from frappe.commands import get_site, pass_context from frappe.commands.scheduler import _is_scheduler_enabled +from frappe.exceptions import SiteNotSpecifiedError from frappe.installer import update_site_config from frappe.utils import get_site_path, touch_file @@ -192,6 +193,8 @@ def install_app(context, apps): _install_app(app, verbose=context.verbose) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('list-apps') @pass_context @@ -221,7 +224,8 @@ def add_system_manager(context, email, first_name, last_name, send_welcome_email frappe.db.commit() finally: frappe.destroy() - + else: + raise SiteNotSpecifiedError @click.command('disable-user') @click.argument('email') @@ -252,6 +256,8 @@ def migrate(context, rebuild_website=False, skip_failing=False): migrate(context.verbose, rebuild_website=rebuild_website, skip_failing=skip_failing) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError print("Compiling Python Files...") compileall.compile_dir('../apps', quiet=1, rx=re.compile('.*node_modules.*')) @@ -264,6 +270,8 @@ def migrate_to(context, frappe_provider): from frappe.integrations.frappe_providers import migrate_to for site in context.sites: migrate_to(site, frappe_provider) + else: + raise SiteNotSpecifiedError @click.command('run-patch') @click.argument('module') @@ -278,6 +286,8 @@ def run_patch(context, module): frappe.modules.patch_handler.run_single(module, force=context.force) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('reload-doc') @click.argument('module') @@ -294,6 +304,8 @@ def reload_doc(context, module, doctype, docname): frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('reload-doctype') @click.argument('doctype') @@ -308,6 +320,8 @@ def reload_doctype(context, doctype): frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('add-to-hosts') @pass_context @@ -315,6 +329,8 @@ def add_to_hosts(context): "Add site to hosts" for site in context.sites: frappe.commands.popen('echo 127.0.0.1\t{0} | sudo tee -a /etc/hosts'.format(site)) + else: + raise SiteNotSpecifiedError @click.command('use') @click.argument('site') @@ -328,7 +344,7 @@ def use(site, sites_path='.'): sitefile.write(site) print("Current Site set to {}".format(site)) else: - print("{} does not exist".format(site)) + print("Site {} does not exist".format(site)) @click.command('backup') @click.option('--with-files', default=False, is_flag=True, help="Take backup with files") @@ -361,6 +377,9 @@ def backup(context, with_files=False, backup_path_db=None, backup_path_files=Non print("Private files: ", odb.backup_path_private_files) frappe.destroy() + else: + raise SiteNotSpecifiedError + sys.exit(exit_code) @click.command('remove-from-installed-apps') @@ -376,6 +395,8 @@ def remove_from_installed_apps(context, app): remove_from_installed_apps(app) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('uninstall-app') @click.argument('app') @@ -392,6 +413,8 @@ def uninstall(context, app, dry_run=False, yes=False): remove_app(app, dry_run, yes) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('drop-site') @@ -483,6 +506,8 @@ def set_admin_password(context, admin_password, logout_all_sessions=False): admin_password = None finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('set-last-active-for-user') @click.option('--user', help="Setup last active date for user") @@ -528,6 +553,8 @@ def publish_realtime(context, event, message, room, user, doctype, docname, afte frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('browse') @click.argument('site', required=False) @@ -555,6 +582,8 @@ def start_recording(context): for site in context.sites: frappe.init(site=site) frappe.recorder.start() + else: + raise SiteNotSpecifiedError @click.command('stop-recording') @@ -563,6 +592,8 @@ def stop_recording(context): for site in context.sites: frappe.init(site=site) frappe.recorder.stop() + else: + raise SiteNotSpecifiedError commands = [ diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index 5a48e2b409..de4d8aab0b 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -15,6 +15,8 @@ def build_message_files(context): frappe.translate.rebuild_all_translation_files() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('new-language') #, help="Create lang-code.csv for given app") @pass_context diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 3610393d9a..835fa2d40e 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -51,6 +51,8 @@ def clear_cache(context): frappe.website.render.clear_cache() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('clear-website-cache') @@ -65,6 +67,8 @@ def clear_website_cache(context): frappe.website.render.clear_cache() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('destroy-all-sessions') @@ -81,6 +85,8 @@ def destroy_all_sessions(context, reason=None): frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('show-config') @@ -117,6 +123,8 @@ def reset_perms(context): reset_perms(d) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('execute') @@ -163,6 +171,8 @@ def execute(context, method, args=None, kwargs=None, profile=False): frappe.destroy() if ret: print(json.dumps(ret, default=json_handler)) + else: + raise SiteNotSpecifiedError @click.command('add-to-email-queue') @@ -197,6 +207,8 @@ def export_doc(context, doctype, docname): frappe.modules.export_doc(doctype, docname) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('export-json') @@ -214,6 +226,8 @@ def export_json(context, doctype, path, name=None): data_import.export_json(doctype, path, name=name) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('export-csv') @@ -230,6 +244,8 @@ def export_csv(context, doctype, path): data_import.export_csv(doctype, path) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('export-fixtures') @@ -245,6 +261,8 @@ def export_fixtures(context, app=None): export_fixtures(app=app) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('import-doc') @@ -267,6 +285,8 @@ def import_doc(context, path, force=False): data_import.import_doc(path, overwrite=context.force) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('import-csv') @@ -577,6 +597,8 @@ def request(context, args=None, path=None): print(frappe.response) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('make-app') @@ -610,6 +632,8 @@ def set_config(context, key, value, global_ = False, as_dict=False): frappe.init(site=site) update_site_config(key, value, validate=False) frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('version') @@ -658,6 +682,8 @@ def rebuild_global_search(context, static_pages=False): finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('auto-deploy') diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 5a1181f31e..1aac339228 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -13,6 +13,11 @@ if sys.version_info.major == 2: else: from builtins import FileNotFoundError +class SiteNotSpecifiedError(Exception): + def __init__(self, *args, **kwargs): + self.message = "Please specify --site sitename" + super(Exception, self).__init__(self.message) + class ValidationError(Exception): http_status_code = 417 diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py index 7c5d209179..c46b42b132 100644 --- a/frappe/utils/bench_helper.py +++ b/frappe/utils/bench_helper.py @@ -50,14 +50,16 @@ def app_group(ctx, site=False, force=False, verbose=False, profile=False): ctx.info_name = '' def get_sites(site_arg): - if site_arg and site_arg == 'all': + if site_arg == 'all': return frappe.utils.get_sites() - else: - if site_arg: - return [site_arg] - if os.path.exists('currentsite.txt'): - with open('currentsite.txt') as f: - return [f.read().strip()] + elif site_arg: + return [site_arg] + elif os.path.exists('currentsite.txt'): + with open('currentsite.txt') as f: + site = f.read().strip() + if site: + return [site] + return [] def get_app_commands(app): if os.path.exists(os.path.join('..', 'apps', app, app, 'commands.py'))\ From 4cbe236cf1420e638369b6b91df839158deaf2a4 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 19 May 2020 13:21:54 +0530 Subject: [PATCH 033/110] fix: make file name and url check stricter * disallow '/' in filenames * check if the file url points to a directory inside /files/ or /private/files - raises error otherwise Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/file/file.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index b35abfa861..a17b3acd02 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -48,6 +48,8 @@ class File(Document): def before_insert(self): frappe.local.rollback_observers.append(self) self.set_folder_name() + if self.file_name: + self.file_name = re.sub(r'/', '', self.file_name) self.content = self.get("content", None) self.decode = self.get("decode", False) if self.content: @@ -192,6 +194,8 @@ class File(Document): def set_file_name(self): if not self.file_name and self.file_url: self.file_name = self.file_url.split('/')[-1] + else: + self.file_name = re.sub(r'/', '', self.file_name) def generate_content_hash(self): if self.content_hash or not self.file_url or self.file_url.startswith('http'): @@ -405,6 +409,12 @@ class File(Document): frappe.throw(_("URL must start with 'http://' or 'https://'")) return + if not self.file_url.startswith(("http://", "https://")): + # local file + root_files_path = get_files_path(is_private=self.is_private) + if not os.path.commonpath([root_files_path]) == os.path.commonpath([root_files_path, self.get_full_path()]): + # basically the file url is skewed to not point to /files/ or /private/files + frappe.throw(_("{0} is not a valid file url").format(self.file_url)) self.file_url = unquote(self.file_url) self.file_size = frappe.form_dict.file_size or self.file_size From 2813a0edc1a92607d4e90fb43b87c59514c6c909 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 22 May 2020 13:31:49 +0530 Subject: [PATCH 034/110] feat: added webhook hmac verification --- .../razorpay_settings/razorpay_settings.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 5e464d4882..98ac022fc4 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -64,6 +64,9 @@ from __future__ import unicode_literals import frappe from frappe import _ import json +import hmac +import sys +import hashlib from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp @@ -317,6 +320,27 @@ class RazorpaySettings(Document): except Exception: frappe.log_error(frappe.get_traceback()) + def verify_signature(self, body, signature, key): + if sys.version_info[0] == 3: + key = bytes(key, 'utf-8') + body = bytes(body, 'utf-8') + + dig = hmac.new(key=key, + msg=body, + digestmod=hashlib.sha256) + + generated_signature = dig.hexdigest() + + if sys.version_info[0:3] < (2, 7, 7): + result = self.compare_string(generated_signature, signature) + else: + result = hmac.compare_digest(generated_signature, signature) + + if not result: + frappe.throw(_('Razorpay Signature Verification Failed'), exc=frappe.PermissionError) + + return result + def capture_payment(is_sandbox=False, sanbox_response=None): """ Verifies the purchase as complete by the merchant. From c329a2c1aff8395aaf7847500bb8521dedfca810 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 25 May 2020 12:22:19 +0530 Subject: [PATCH 035/110] fix: add update_after_submit to actions while setting worflow state --- frappe/model/workflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index 4384e7c8f5..ea563dfc13 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -299,6 +299,7 @@ def set_workflow_state_on_action(doc, workflow_name, action): return action_map = { + 'update_after_submit': '1', 'submit': '1', 'cancel': '2' } From 5d83e94b098cf33b7096153fbde4755d728ee17b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 11:56:09 +0530 Subject: [PATCH 036/110] fix(patch): create a logs folder under respective sites --- frappe/patches.txt | 1 + frappe/patches/v13_0/site_wise_logging.py | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 frappe/patches/v13_0/site_wise_logging.py diff --git a/frappe/patches.txt b/frappe/patches.txt index a086fa6f4a..6b6bd173b8 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -278,3 +278,4 @@ frappe.patches.v13_0.set_path_for_homepage_in_web_page_view frappe.patches.v13_0.migrate_translation_column_data frappe.patches.v13_0.set_read_times frappe.patches.v13_0.remove_web_view +frappe.patches.v13_0.site_wise_logging diff --git a/frappe/patches/v13_0/site_wise_logging.py b/frappe/patches/v13_0/site_wise_logging.py new file mode 100644 index 0000000000..6f04e0c9dd --- /dev/null +++ b/frappe/patches/v13_0/site_wise_logging.py @@ -0,0 +1,10 @@ +import os +import frappe + + +def execute(): + site = frappe.local.site + + log_folder = os.path.join(site, 'logs') + if not os.path.exists(log_folder): + os.mkdir(log_folder) \ No newline at end of file From c39a52d76416d88c9e50f00dabfd6eecb70127ea Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 18:33:03 +0530 Subject: [PATCH 037/110] feat: seperate loggers into frappe, web, scheduler categories and not by module --- frappe/__init__.py | 2 +- .../scheduled_job_type/scheduled_job_type.py | 2 +- frappe/utils/error.py | 2 +- frappe/utils/logger.py | 34 ++++++++++--------- frappe/utils/scheduler.py | 34 +++++++++++++------ 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 7664ac4c61..56017e2bc3 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1562,7 +1562,7 @@ log_level = None def logger(module=None, with_more_info=True): '''Returns a python logger that uses StreamHandler''' from frappe.utils.logger import get_logger - return get_logger(module or 'default', with_more_info=with_more_info) + return get_logger(module=module, with_more_info=with_more_info) def log_error(message=None, title=_("Error")): '''Log error to Error Log''' 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 c179054550..765ae5fe93 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -84,7 +84,7 @@ class ScheduledJobType(Document): def log_status(self, status): # log file - frappe.logger(__name__).info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site)) + frappe.logger("scheduler").info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site)) self.update_scheduler_log(status) def update_scheduler_log(self, status): diff --git a/frappe/utils/error.py b/frappe/utils/error.py index c124410a7f..bb83815d18 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -21,7 +21,7 @@ def make_error_snapshot(exception): if frappe.conf.disable_error_snapshot: return - logger = frappe.logger(__name__, with_more_info=False) + logger = frappe.logger(with_more_info=False) try: error_id = '{timestamp:s}-{ip:s}-{hash:s}'.format( diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index e446b39ee0..5e202f87fb 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -15,35 +15,37 @@ import frappe default_log_level = logging.DEBUG site = getattr(frappe.local, 'site', None) -LOG_FILENAME = os.path.join('..', 'logs', 'frappe.log') +form_dict = getattr(frappe.local, 'form_dict', None) def get_logger(module, with_more_info=True): if module in frappe.loggers: return frappe.loggers[module] - formatter = logging.Formatter('[%(levelname)s] %(asctime)s | %(pathname)s:\n%(message)s') + if not module: + module = "frappe" + logfile = module + '.log' + LOG_FILENAME = os.path.join('..', 'logs', logfile) + + logger = logging.getLogger(module) + logger.setLevel(frappe.log_level or default_log_level) + logger.propagate = False + + formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s') handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) - handler.setFormatter(formatter) - + logger.addHandler(handler) +# if site: - SITELOG_FOLDER = os.path.join(site, 'logs') - SITELOG_FILENAME = os.path.join(SITELOG_FOLDER, 'frappe.log') - - if not os.path.exists(SITELOG_FOLDER): - os.mkdir(SITELOG_FOLDER) - - handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) - handler.setFormatter(formatter) + SITELOG_FILENAME = os.path.join(site, 'logs', logfile) + site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) + site_handler.setFormatter(formatter) + logger.addHandler(site_handler) if with_more_info: handler.addFilter(SiteContextFilter()) - logger = logging.getLogger(module) - logger.setLevel(frappe.log_level or default_log_level) - logger.addHandler(handler) - logger.propagate = False + handler.setFormatter(formatter) frappe.loggers[module] = logger diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index 596595a160..e05419805c 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -7,17 +7,24 @@ Events: monthly weekly """ +# imports - compatibility imports +from __future__ import print_function, unicode_literals -from __future__ import unicode_literals, print_function +# imports - standard imports +import os +import time -import frappe, os, time +# imports - third party imports import schedule -from frappe.utils import now_datetime, get_datetime -from frappe.utils import get_sites -from frappe.installer import update_site_config + +# imports - module imports +import frappe from frappe.core.doctype.user.user import STANDARD_USERS +from frappe.installer import update_site_config +from frappe.utils import get_datetime, get_sites, now_datetime from frappe.utils.background_jobs import get_jobs + DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' def start_scheduler(): @@ -48,9 +55,16 @@ def enqueue_events_for_all_sites(): def enqueue_events_for_site(site): def log_and_raise(): - frappe.logger(__name__).error('Exception in Enqueue Events for Site {0}'.format(site) + - '\n' + frappe.get_traceback()) - raise # pylint: disable=misplaced-bare-raise + error_message = 'Exception in Enqueue Events for Site {0}\n{1}'.format(site, frappe.get_traceback()) + error_message = json.dumps({ + "type": "Exception", + "event": "Enqueue Events", + "site": site, + "traceback": frappe.get_traceback() + }) + + frappe.logger("scheduler").error(error_message) + SITELOG_FILENAME = os.path.join(site or "..", 'logs', 'schedule.log') try: frappe.init(site=site) @@ -60,10 +74,10 @@ def enqueue_events_for_site(site): enqueue_events(site=site) - frappe.logger(__name__).debug('Queued events for site {0}'.format(site)) + frappe.logger("scheduler").debug('Queued events for site {0}'.format(site)) except frappe.db.OperationalError as e: if frappe.db.is_access_denied(e): - frappe.logger(__name__).debug('Access denied for site {0}'.format(site)) + frappe.logger("scheduler").debug('Access denied for site {0}'.format(site)) else: log_and_raise() except: From 746b540af8845f495b8b19be5cf77f23a06fa6f2 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 19:09:18 +0530 Subject: [PATCH 038/110] fix: Add Form Dict information only for frappe.log --- frappe/__init__.py | 2 +- frappe/app.py | 11 ++++++++++- frappe/utils/error.py | 2 +- frappe/utils/logger.py | 28 +++++++--------------------- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 56017e2bc3..d958f1ad86 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1559,7 +1559,7 @@ def get_doctype_app(doctype): loggers = {} log_level = None -def logger(module=None, with_more_info=True): +def logger(module=None, with_more_info=False): '''Returns a python logger that uses StreamHandler''' from frappe.utils.logger import get_logger return get_logger(module=module, with_more_info=with_more_info) diff --git a/frappe/app.py b/frappe/app.py index 41798b0bc4..5f9a22891f 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -96,6 +96,16 @@ def application(request): frappe.monitor.stop(response) frappe.recorder.dump() + frappe.logger("web").info({ + "site": get_site_name(request.host), + "remote_addr": request.remote_addr, + "base_url": request.base_url, + "full_path": request.full_path, + "method": request.method, + "scheme": request.scheme, + "http_status_code": response.status_code + }) + frappe.destroy() return response @@ -186,7 +196,6 @@ def handle_exception(e): frappe.local.login_manager.clear_cookies() if http_status_code >= 500: - frappe.logger().error('Request Error', exc_info=True) make_error_snapshot(e) if return_as_message: diff --git a/frappe/utils/error.py b/frappe/utils/error.py index bb83815d18..af8c0952e8 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -21,7 +21,7 @@ def make_error_snapshot(exception): if frappe.conf.disable_error_snapshot: return - logger = frappe.logger(with_more_info=False) + logger = frappe.logger(with_more_info=True) try: error_id = '{timestamp:s}-{ip:s}-{hash:s}'.format( diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 5e202f87fb..89e3711b0f 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -15,17 +15,19 @@ import frappe default_log_level = logging.DEBUG site = getattr(frappe.local, 'site', None) -form_dict = getattr(frappe.local, 'form_dict', None) -def get_logger(module, with_more_info=True): +def get_logger(module, with_more_info=False): + global site if module in frappe.loggers: return frappe.loggers[module] if not module: module = "frappe" + with_more_info = True logfile = module + '.log' + site = getattr(frappe.local, 'site', None) LOG_FILENAME = os.path.join('..', 'logs', logfile) logger = logging.getLogger(module) @@ -54,25 +56,9 @@ def get_logger(module, with_more_info=True): class SiteContextFilter(logging.Filter): """This is a filter which injects request information (if available) into the log.""" def filter(self, record): - record.msg = get_more_info_for_log() + text_type(record.msg) - return True - -def get_more_info_for_log(): - '''Adds Site, Form Dict into log entry''' - more_info = [] - - if site: - more_info.append('Site: {0}'.format(site)) - - form_dict = getattr(frappe.local, 'form_dict', None) - if form_dict: - more_info.append('Form Dict: {0}'.format(frappe.as_json(form_dict))) - - if more_info: - # to append a \n - more_info = more_info + [''] - - return '\n'.join(more_info) + if "Form Dict" not in text_type(record.msg): + record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(site, getattr(frappe.local, 'form_dict', None)) + return True def set_log_level(level): '''Use this method to set log level to something other than the default DEBUG''' From 9a6151ec32c8682b564a04bc52c6458748b8bd5c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 19:10:04 +0530 Subject: [PATCH 039/110] perf: memoize get_site_name function --- frappe/utils/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 956846d50c..1574bbb30c 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals, print_function from werkzeug.test import Client import os, re, sys, json, hashlib, requests, traceback +import functools from .html_utils import sanitize_html import frappe from frappe.utils.identicon import Identicon @@ -360,6 +361,7 @@ def decode_dict(d, encoding="utf-8"): return d +@functools.lru_cache() def get_site_name(hostname): return hostname.split(':')[0] From 6b88c5979e0ed56ccdcca7f5191b90ca00a2ae3e Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 19:53:51 +0530 Subject: [PATCH 040/110] chore: drop unused code --- frappe/utils/scheduler.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index e05419805c..749a41682f 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -21,7 +21,7 @@ import schedule import frappe from frappe.core.doctype.user.user import STANDARD_USERS from frappe.installer import update_site_config -from frappe.utils import get_datetime, get_sites, now_datetime +from frappe.utils import get_sites, now_datetime from frappe.utils.background_jobs import get_jobs @@ -56,15 +56,7 @@ def enqueue_events_for_all_sites(): def enqueue_events_for_site(site): def log_and_raise(): error_message = 'Exception in Enqueue Events for Site {0}\n{1}'.format(site, frappe.get_traceback()) - error_message = json.dumps({ - "type": "Exception", - "event": "Enqueue Events", - "site": site, - "traceback": frappe.get_traceback() - }) - frappe.logger("scheduler").error(error_message) - SITELOG_FILENAME = os.path.join(site or "..", 'logs', 'schedule.log') try: frappe.init(site=site) From aabbfb8df823e59ff7ea565254c08b1526c1a499 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 18:17:41 +0530 Subject: [PATCH 041/110] feat: switch teams if you are a part of multiple --- .../frappe_providers/frappecloud.py | 107 ++++++------------ frappe/utils/commands.py | 9 ++ 2 files changed, 45 insertions(+), 71 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 4f33c990f9..3503f9d684 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -13,7 +13,28 @@ import requests import frappe import frappe.utils.backups from frappe.utils import get_installed_apps_info -from frappe.utils.commands import render_table, add_line_after +from frappe.utils.commands import render_table, add_line_after, add_line_before + + +@add_line_before +def select_team(session): + # get team options + account_details_sc = session.post(account_details_url) + if account_details_sc.ok: + account_details = account_details_sc.json()["message"] + available_teams = account_details["teams"] + + # ask if they want to select, go ahead with if only one exists + if len(available_teams) == 1: + team = available_teams[0] + else: + render_teams_table(available_teams) + idx = click.prompt("Select Team", type=click.IntRange(1, len(available_teams))) - 1 + team = available_teams[idx] + + print("Team '{}' set for current session".format(team)) + + return team def get_new_site_options(): @@ -148,24 +169,6 @@ def filter_apps(app_groups): return selected_group["name"], filtered_apps -@add_line_after -def create_session(): - # take user input from STDIN - username = click.prompt("Username").strip() - password = getpass.unix_getpass() - - auth_credentials = {"usr": username, "pwd": password} - - session = requests.Session() - login_sc = session.post(login_url, auth_credentials) - - if login_sc.ok: - print("Authorization Successful! ✅") - session.headers.update({"X-Press-Team": username}) - return session - else: - print("Authorization Failed with Error Code {}".format(login_sc.status_code)) - @add_line_after def get_subdomain(domain): @@ -208,61 +211,23 @@ def upload_backup(local_site): return files_uploaded -def frappecloud_migrator(local_site, remote_site): - global login_url, upload_url, files_url, options_url, site_exists_url, session - - login_url = "https://{}/api/method/login".format(remote_site) - upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) - files_url = "https://{}/api/method/upload_file".format(remote_site) - options_url = "https://{}/api/method/press.api.site.options_for_new".format(remote_site) - site_exists_url = "https://{}/api/method/press.api.site.exists".format(remote_site) - +@add_line_after +def create_session(): print("Frappe Cloud credentials @ {}".format(remote_site)) - # get credentials + auth user + start session - session = create_session() + # take user input from STDIN + username = click.prompt("Username").strip() + password = getpass.unix_getpass() - if session: - # connect to site db - frappe.init(site=local_site) - frappe.connect() + auth_credentials = {"usr": username, "pwd": password} - # get new site options - site_options = get_new_site_options() - - # set preferences from site options - subdomain = get_subdomain(site_options["domain"]) - plan = choose_plan(site_options["plans"]) - - app_groups = site_options["groups"] - selected_group, filtered_apps = filter_apps(app_groups) - files_uploaded = upload_backup(local_site) - - # push to frappe_cloud - payload = json.dumps({ - "site": { - "apps": filtered_apps, - "files": files_uploaded, - "group": selected_group, - "name": subdomain, - "plan": plan - } - }) - - session.headers.update({"Content-Type": "application/json; charset=utf-8"}) - site_creation_request = session.post(upload_url, payload) - frappe.destroy() - - if site_creation_request.ok: - site_url = site_creation_request.json()["message"] - print("Your site {} is being migrated ✨".format(local_site)) - print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, site_url)) - print("Your site URL: {}".format(site_url)) - else: - print("Request failed with error code {}".format(site_creation_request.status_code)) - reason = html2text(site_creation_request.text) - print(reason) - sys.exit(1) + session = requests.Session() + login_sc = session.post(login_url, auth_credentials) + if login_sc.ok: + print("Authorization Successful! ✅") + team = select_team(session) + session.headers.update({"X-Press-Team": team }) + return session else: - sys.exit(1) + handle_request_failure(message="Authorization Failed with Error Code {}".format(login_sc.status_code), traceback=False) diff --git a/frappe/utils/commands.py b/frappe/utils/commands.py index 99322b50ba..113014c135 100644 --- a/frappe/utils/commands.py +++ b/frappe/utils/commands.py @@ -27,6 +27,15 @@ def add_line_after(function): return empty_line +def add_line_before(function): + """Adds an extra line to STDOUT before the execution of a function this decorates""" + def empty_line(*args, **kwargs): + print() + result = function(*args, **kwargs) + return result + return empty_line + + def log(message, colour=''): """Coloured log outputs to STDOUT""" colours = { From b8138004d9fcc500f9f4590ce5b10f3e1062d3c3 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 18:18:38 +0530 Subject: [PATCH 042/110] feat: allow restoring to existing FC sites --- .../frappe_providers/frappecloud.py | 225 +++++++++++++++--- 1 file changed, 197 insertions(+), 28 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 3503f9d684..39712ecc42 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -16,6 +16,107 @@ from frappe.utils import get_installed_apps_info from frappe.utils.commands import render_table, add_line_after, add_line_before +# TODO: check upgrade compatibility + + +def render_actions_table(): + actions_table = [["#", "Action"]] + actions = [] + + for n, action in enumerate(migrator_actions): + actions_table.append([n+1, action["title"]]) + actions.append(action["fn"]) + + render_table(actions_table) + return actions + + +def render_site_table(sites_info): + sites_table = [["#", "Site Name", "Status"]] + available_sites = [] + + for n, site_data in enumerate(sites_info): + name, status = site_data["name"], site_data["status"] + if status not in ("Inactive", "Suspended"): + sites_table.append([n + 1, name, status]) + available_sites.append(name) + + render_table(sites_table) + return available_sites + + +def render_teams_table(teams): + teams_table = [["#", "Team"]] + + for n, team in enumerate(teams): + teams_table.append([n+1, team]) + + render_table(teams_table) + + +def render_plan_table(plans_list): + plans_table = [["Plan", "CPU Time"]] + visible_headers = ["name", "cpu_time_per_day"] + + for plan in plans_list: + plan, cpu_time = [plan[header] for header in visible_headers] + plans_table.append([plan, "{} hour{}/day".format(cpu_time, "" if cpu_time < 2 else "s")]) + + render_table(plans_table) + + +def render_group_table(app_groups): + # title row + app_groups_table = [["#", "App Group", "Apps"]] + + # all rows + for idx, app_group in enumerate(app_groups): + apps_list = ", ".join(["{}:{}".format(app["scrubbed"], app["branch"]) for app in app_group["apps"]]) + row = [idx + 1, app_group["name"], apps_list] + app_groups_table.append(row) + + render_table(app_groups_table) + + +def handle_request_failure(request=None, message=None, traceback=True, exit_code=1): + message = message or "Request failed with error code {}".format(request.status_code) + response = html2text(request.text) if traceback else "" + + print("{0}{1}".format(message, "\n" + response)) + sys.exit(exit_code) + + +@add_line_after +def select_primary_action(): + actions = render_actions_table() + idx = click.prompt("What do you want to do?", type=click.IntRange(1, len(actions))) - 1 + + return actions[idx] + + +@add_line_after +def select_site(): + get_all_sites_request = session.post(all_site_url, headers={ + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "content-type": "application/json; charset=utf-8" + }) + + if get_all_sites_request.ok: + all_sites = get_all_sites_request.json()["message"] + available_sites = render_site_table(all_sites) + + while True: + selected_site = click.prompt("Name of the site you want to restore to", type=str).strip() + if selected_site in available_sites: + return selected_site + else: + print("Site {} does not exist. Try again ❌".format(site)) + else: + print("Couldn't retrive sites list...Try again later") + sys.exit(1) + + @add_line_before def select_team(session): # get team options @@ -67,21 +168,6 @@ def is_subdomain_available(subdomain): return available -def render_plan_table(plans_list): - plans_table = [] - - # title row - visible_headers = ["name", "cpu_time_per_day"] - plans_table.append(["Plan", "CPU Time"]) - - # all rows - for plan in plans_list: - plan, cpu_time = [plan[header] for header in visible_headers] - plans_table.append([plan, "{} hour{}/day".format(cpu_time, "" if cpu_time < 2 else "s")]) - - render_table(plans_table) - - @add_line_after def choose_plan(plans_list): print("{} plans available".format(len(plans_list))) @@ -134,19 +220,6 @@ def check_app_compat(available_group): return is_compat, filtered_apps -def render_group_table(app_groups): - # title row - app_groups_table = [["#", "App Group", "Apps"]] - - # all rows - for idx, app_group in enumerate(app_groups): - apps_list = ", ".join(["{}:{}".format(app["scrubbed"], app["branch"]) for app in app_group["apps"]]) - row = [idx + 1, app_group["name"], apps_list] - app_groups_table.append(row) - - render_table(app_groups_table) - - @add_line_after def filter_apps(app_groups): render_group_table(app_groups) @@ -211,6 +284,68 @@ def upload_backup(local_site): return files_uploaded +def new_site(local_site): + # get new site options + site_options = get_new_site_options() + + # set preferences from site options + subdomain = get_subdomain(site_options["domain"]) + plan = choose_plan(site_options["plans"]) + + app_groups = site_options["groups"] + selected_group, filtered_apps = filter_apps(app_groups) + files_uploaded = upload_backup(local_site) + + # push to frappe_cloud + payload = json.dumps({ + "site": { + "apps": filtered_apps, + "files": files_uploaded, + "group": selected_group, + "name": subdomain, + "plan": plan + } + }) + + session.headers.update({"Content-Type": "application/json; charset=utf-8"}) + site_creation_request = session.post(upload_url, payload) + + if site_creation_request.ok: + site_url = site_creation_request.json()["message"] + print("Your site {} is being migrated ✨".format(local_site)) + print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, site_url)) + print("Your site URL: {}".format(site_url)) + else: + handle_request_failure(site_creation_request) + + +def restore_site(local_site): + # get list of existing sites they can restore + selected_site = select_site() + + # TODO: check if they can restore it + + click.confirm("This is an irreversible action. Are you sure you want to continue?", abort=True) + + # backup site + files_uploaded = upload_backup(local_site) + + # push to frappe_cloud + payload = json.dumps({ + "name": selected_site, + "files": files_uploaded + }) + headers = {"Content-Type": "application/json; charset=utf-8"} + site_restore_request = session.post(restore_site_url, payload, headers=headers) + + if site_restore_request.ok: + print("Your site {0} is being restored on {1} ✨".format(local_site, selected_site)) + print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, selected_site)) + print("Your site URL: {}".format(selected_site)) + else: + handle_request_failure(site_restore_request) + + @add_line_after def create_session(): print("Frappe Cloud credentials @ {}".format(remote_site)) @@ -231,3 +366,37 @@ def create_session(): return session else: handle_request_failure(message="Authorization Failed with Error Code {}".format(login_sc.status_code), traceback=False) + + +def frappecloud_migrator(local_site, frappecloud_site): + global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url + global session, migrator_actions, remote_site + + remote_site = frappecloud_site + + login_url = "https://{}/api/method/login".format(remote_site) + upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) + files_url = "https://{}/api/method/upload_file".format(remote_site) + options_url = "https://{}/api/method/press.api.site.options_for_new".format(remote_site) + site_exists_url = "https://{}/api/method/press.api.site.exists".format(remote_site) + account_details_url = "https://{}/api/method/press.api.account.get".format(remote_site) + all_site_url = "https://{}/api/method/press.api.site.all".format(remote_site) + restore_site_url = "https://{}/api/method/press.api.site.restore".format(remote_site) + + migrator_actions = [ + { "title": "Create a new site", "fn": new_site }, + { "title": "Restore to an existing site", "fn": restore_site } + ] + + # get credentials + auth user + start session + session = create_session() + + # available actions defined in migrator_actions + primary_action = select_primary_action() + + frappe.init(site=local_site) + frappe.connect() + + primary_action(local_site) + + frappe.destroy() From a1ee529cb744617b24c49cf9aa544a6d88811f8a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 18:42:06 +0530 Subject: [PATCH 043/110] fix: show only Active and Broken sites in Sites List --- frappe/integrations/frappe_providers/frappecloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 39712ecc42..7013dbf1ba 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -37,7 +37,7 @@ def render_site_table(sites_info): for n, site_data in enumerate(sites_info): name, status = site_data["name"], site_data["status"] - if status not in ("Inactive", "Suspended"): + if status in ("Active", "Broken"): sites_table.append([n + 1, name, status]) available_sites.append(name) @@ -372,7 +372,7 @@ def frappecloud_migrator(local_site, frappecloud_site): global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url global session, migrator_actions, remote_site - remote_site = frappecloud_site + remote_site = "staging.frappe.cloud" #frappecloud_site login_url = "https://{}/api/method/login".format(remote_site) upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) From a65be3e22a71fdd5d88a87655011df4cced89638 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 11:07:14 +0530 Subject: [PATCH 044/110] fix: undefined variables --- frappe/integrations/frappe_providers/frappecloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 7013dbf1ba..d8741e0834 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -111,7 +111,7 @@ def select_site(): if selected_site in available_sites: return selected_site else: - print("Site {} does not exist. Try again ❌".format(site)) + print("Site {} does not exist. Try again ❌".format(selected_site)) else: print("Couldn't retrive sites list...Try again later") sys.exit(1) @@ -372,7 +372,7 @@ def frappecloud_migrator(local_site, frappecloud_site): global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url global session, migrator_actions, remote_site - remote_site = "staging.frappe.cloud" #frappecloud_site + remote_site = frappecloud_site login_url = "https://{}/api/method/login".format(remote_site) upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) From 4f99b5879ec9e5aff46294b58474e76a54f65427 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 20:28:55 +0530 Subject: [PATCH 045/110] fix: set frappecloud_url in conf to override specified url --- frappe/integrations/frappe_providers/__init__.py | 1 - frappe/integrations/frappe_providers/frappecloud.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/integrations/frappe_providers/__init__.py b/frappe/integrations/frappe_providers/__init__.py index 0b689478d2..887e191e16 100644 --- a/frappe/integrations/frappe_providers/__init__.py +++ b/frappe/integrations/frappe_providers/__init__.py @@ -7,7 +7,6 @@ from frappe.integrations.frappe_providers.frappecloud import frappecloud_migrato def migrate_to(local_site, frappe_provider): if frappe_provider in ("frappe.cloud", "frappecloud.com"): - frappe_provider = "frappecloud.com" return frappecloud_migrator(local_site, frappe_provider) else: print("{} is not supported yet".format(frappe_provider)) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index d8741e0834..291b8af647 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -372,7 +372,7 @@ def frappecloud_migrator(local_site, frappecloud_site): global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url global session, migrator_actions, remote_site - remote_site = frappecloud_site + remote_site = frappe.conf.frappecloud_url or "frappecloud.com" login_url = "https://{}/api/method/login".format(remote_site) upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) From 5e7fcb46f22760026b847c9adb98984953cdb63b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 13:22:52 +0530 Subject: [PATCH 046/110] refactor: use six instead of sys.version_info --- .../doctype/razorpay_settings/razorpay_settings.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 98ac022fc4..680225df0e 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -65,8 +65,8 @@ import frappe from frappe import _ import json import hmac -import sys import hashlib +import six from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp @@ -321,7 +321,7 @@ class RazorpaySettings(Document): frappe.log_error(frappe.get_traceback()) def verify_signature(self, body, signature, key): - if sys.version_info[0] == 3: + if six.PY3: key = bytes(key, 'utf-8') body = bytes(body, 'utf-8') @@ -331,7 +331,7 @@ class RazorpaySettings(Document): generated_signature = dig.hexdigest() - if sys.version_info[0:3] < (2, 7, 7): + if six.PY2: result = self.compare_string(generated_signature, signature) else: result = hmac.compare_digest(generated_signature, signature) From 2ef76f25720e41b2b2df53c8196730e17e0508b9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 13:24:04 +0530 Subject: [PATCH 047/110] style: linting fixes --- .../doctype/razorpay_settings/razorpay_settings.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 680225df0e..b4f4f9d090 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -325,9 +325,7 @@ class RazorpaySettings(Document): key = bytes(key, 'utf-8') body = bytes(body, 'utf-8') - dig = hmac.new(key=key, - msg=body, - digestmod=hashlib.sha256) + dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256) generated_signature = dig.hexdigest() From 55aca943c3b1992c88db8c7855dff13e94fb25e0 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 14:09:05 +0530 Subject: [PATCH 048/110] feat: make verification function python 3 only --- .../doctype/razorpay_settings/razorpay_settings.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index b4f4f9d090..1d2f7f9495 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -66,7 +66,6 @@ from frappe import _ import json import hmac import hashlib -import six from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp @@ -321,18 +320,13 @@ class RazorpaySettings(Document): frappe.log_error(frappe.get_traceback()) def verify_signature(self, body, signature, key): - if six.PY3: - key = bytes(key, 'utf-8') - body = bytes(body, 'utf-8') + key = bytes(key, 'utf-8') + body = bytes(body, 'utf-8') dig = hmac.new(key=key, msg=body, digestmod=hashlib.sha256) generated_signature = dig.hexdigest() - - if six.PY2: - result = self.compare_string(generated_signature, signature) - else: - result = hmac.compare_digest(generated_signature, signature) + result = hmac.compare_digest(generated_signature, signature) if not result: frappe.throw(_('Razorpay Signature Verification Failed'), exc=frappe.PermissionError) From 71442033c4d8f7aac24361cff66a4fd8b074bf03 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 15:14:18 +0530 Subject: [PATCH 049/110] fix: breadcrumb routing bug for desk pages --- frappe/boot.py | 2 ++ frappe/desk/doctype/desk_page/desk_page.py | 11 +++++++++++ frappe/public/js/frappe/views/breadcrumbs.js | 13 +++++++++---- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index e615cc49fa..695a4d754b 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -19,6 +19,7 @@ from frappe.email.inbox import get_email_accounts from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled from frappe.website.doctype.web_page_view.web_page_view import is_tracking_enabled from frappe.social.doctype.energy_point_log.energy_point_log import get_energy_points +from frappe.model.base_document import get_controller from frappe.social.doctype.post.post import frequently_visited_links def get_bootinfo(): @@ -106,6 +107,7 @@ def load_desktop_data(bootinfo): 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) + bootinfo.module_page_map = get_controller("Desk Page").get_module_page_map() bootinfo.dashboards = frappe.get_all("Dashboard") def get_allowed_pages(cache=False): diff --git a/frappe/desk/doctype/desk_page/desk_page.py b/frappe/desk/doctype/desk_page/desk_page.py index dd9cc0706a..f14535cb5f 100644 --- a/frappe/desk/doctype/desk_page/desk_page.py +++ b/frappe/desk/doctype/desk_page/desk_page.py @@ -20,6 +20,17 @@ class DeskPage(Document): if frappe.conf.developer_mode and self.is_standard: export_to_files(record_list=[['Desk Page', self.name]], record_module=self.module) + @staticmethod + def get_module_page_map(): + filters = { + 'extends_another_page': 0, + 'for_user': '', + } + + pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1) + + return { page[1]: page[0] for page in pages } + def disable_saving_as_standard(): return frappe.flags.in_install or \ frappe.flags.in_patch or \ diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 1c1049391f..0058310e3f 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -89,16 +89,21 @@ frappe.breadcrumbs = { breadcrumbs.module = frappe.breadcrumbs.module_map[breadcrumbs.module]; } - if(frappe.get_module(breadcrumbs.module)) { + let current_module = breadcrumbs.module + // Check if a desk page exists + if (frappe.boot.module_page_map[breadcrumbs.module]) { + breadcrumbs.module = frappe.boot.module_page_map[breadcrumbs.module]; + } + + if(frappe.get_module(current_module)) { // if module access exists - var module_info = frappe.get_module(breadcrumbs.module), + var module_info = frappe.get_module(current_module), icon = module_info && module_info.icon, label = module_info ? module_info.label : breadcrumbs.module; - if(module_info && !module_info.blocked && frappe.visible_modules.includes(module_info.module_name)) { $(repl('
  • %(label)s
  • ', - { module: breadcrumbs.module, label: __(label) })) + { module: breadcrumbs.module, label: __(breadcrumbs.module) })) .appendTo($breadcrumbs); } } From 95bfedfff5af97353ce235296df20baf89d40456 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 29 May 2020 15:16:46 +0530 Subject: [PATCH 050/110] fix: Remove web page script from Website Theme --- .../public/js/frappe/utils/web_page_block.js | 38 ---------------- frappe/website/doctype/web_page/web_page.js | 44 +++++++++++++++++-- .../doctype/website_theme/website_theme.js | 3 -- 3 files changed, 40 insertions(+), 45 deletions(-) delete mode 100644 frappe/public/js/frappe/utils/web_page_block.js diff --git a/frappe/public/js/frappe/utils/web_page_block.js b/frappe/public/js/frappe/utils/web_page_block.js deleted file mode 100644 index db655df98b..0000000000 --- a/frappe/public/js/frappe/utils/web_page_block.js +++ /dev/null @@ -1,38 +0,0 @@ -frappe.ui.form.on("Web Page Block", { - edit_values(frm, cdt, cdn) { - let row = frm.selected_doc; - frappe.model.with_doc("Web Template", row.web_template).then((doc) => { - let d = new frappe.ui.Dialog({ - title: __("Edit Values"), - fields: doc.fields.map((df) => { - if (df.fieldtype == "Section Break") { - df.collapsible = 1; - } - return df; - }), - primary_action(values) { - frappe.model.set_value( - cdt, - cdn, - "web_template_values", - JSON.stringify(values) - ); - d.hide(); - }, - }); - let values = JSON.parse(row.web_template_values || "{}"); - d.set_values(values); - d.show(); - - d.sections.forEach((sect) => { - let fields_with_value = sect.fields_list.filter( - (field) => values[field.df.fieldname] - ); - - if (fields_with_value.length) { - sect.collapse(false); - } - }); - }); - }, -}); \ No newline at end of file diff --git a/frappe/website/doctype/web_page/web_page.js b/frappe/website/doctype/web_page/web_page.js index 437a86b5d0..b2e06efc79 100644 --- a/frappe/website/doctype/web_page/web_page.js +++ b/frappe/website/doctype/web_page/web_page.js @@ -2,9 +2,6 @@ // MIT License. See license.txt frappe.ui.form.on('Web Page', { - onload: function() { - frappe.require('/assets/frappe/js/frappe/utils/web_page_block.js'); - }, title: function(frm) { if (frm.doc.title && !frm.doc.route) { frm.set_value('route', frappe.scrub(frm.doc.title, '-')); @@ -49,6 +46,45 @@ frappe.ui.form.on('Web Page', { } }); +frappe.ui.form.on("Web Page Block", { + edit_values(frm, cdt, cdn) { + let row = frm.selected_doc; + frappe.model.with_doc("Web Template", row.web_template).then((doc) => { + let d = new frappe.ui.Dialog({ + title: __("Edit Values"), + fields: doc.fields.map((df) => { + if (df.fieldtype == "Section Break") { + df.collapsible = 1; + } + return df; + }), + primary_action(values) { + frappe.model.set_value( + cdt, + cdn, + "web_template_values", + JSON.stringify(values) + ); + d.hide(); + }, + }); + let values = JSON.parse(row.web_template_values || "{}"); + d.set_values(values); + d.show(); + + d.sections.forEach((sect) => { + let fields_with_value = sect.fields_list.filter( + (field) => values[field.df.fieldname] + ); + + if (fields_with_value.length) { + sect.collapse(false); + } + }); + }); + }, +}); + frappe.tour['Web Page'] = [ { fieldname: "title", @@ -102,4 +138,4 @@ frappe.tour['Web Page'] = [ title: __("Meta Image"), description: __("The meta image is unique image representing the content of the page. Images for this Card should be at least 280px in width, and at least 150px in height.") }, -]; \ No newline at end of file +]; diff --git a/frappe/website/doctype/website_theme/website_theme.js b/frappe/website/doctype/website_theme/website_theme.js index 28b18a1bcd..75ecbe15e3 100644 --- a/frappe/website/doctype/website_theme/website_theme.js +++ b/frappe/website/doctype/website_theme/website_theme.js @@ -2,9 +2,6 @@ // MIT License. See license.txt frappe.ui.form.on('Website Theme', { - onload: function() { - frappe.require('/assets/frappe/js/frappe/utils/web_page_block.js'); - }, refresh(frm) { frm.clear_custom_buttons(); frm.toggle_display(["module", "custom"], !frappe.boot.developer_mode); From 12f9e9f4c139bb46f039dad5017b4101fcc6e368 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 29 May 2020 16:35:50 +0530 Subject: [PATCH 051/110] fix: separate channel for system notifications --- .../doctype/notification/notification.js | 1 - .../doctype/notification/notification.json | 161 ++++++++++++------ .../doctype/notification/notification.py | 10 +- 3 files changed, 116 insertions(+), 56 deletions(-) diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 44056955f7..02fc8512ca 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -80,7 +80,6 @@ frappe.ui.form.on("Notification", { }); }, refresh: function(frm) { - frm.toggle_reqd("recipients", frm.doc.channel=="Email"); frappe.notification.setup_fieldname_select(frm); frm.get_field("is_standard").toggle(frappe.boot.developer_mode); frm.trigger('event'); diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 9e52a8b7a8..d1526f5fe4 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -23,9 +23,8 @@ "days_in_advance", "value_changed", "sender", + "send_system_notification", "sender_email", - "show_in_notifications_dropdown", - "disable_channel", "section_break_9", "condition", "column_break_6", @@ -49,11 +48,15 @@ "default": "1", "fieldname": "enabled", "fieldtype": "Check", - "label": "Enabled" + "label": "Enabled", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_2", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "default": "Email", @@ -61,21 +64,27 @@ "fieldname": "channel", "fieldtype": "Select", "label": "Channel", - "options": "Email\nSlack", + "options": "Email\nSlack\nSystem Notification", "reqd": 1, - "set_only_once": 1 + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.channel=='Slack'", "fieldname": "slack_webhook_url", "fieldtype": "Link", "label": "Slack Channel", - "options": "Slack Webhook URL" + "mandatory_depends_on": "eval:doc.channel=='Slack'", + "options": "Slack Webhook URL", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "filters", "fieldtype": "Section Break", - "label": "Filters" + "label": "Filters", + "show_days": 1, + "show_seconds": 1 }, { "description": "To add dynamic subject, use jinja tags like\n\n
    {{ doc.name }} Delivered
    ", @@ -84,7 +93,9 @@ "ignore_xss_filter": 1, "in_list_view": 1, "label": "Subject", - "reqd": 1 + "reqd": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "document_type", @@ -94,13 +105,17 @@ "label": "Document Type", "options": "DocType", "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "is_standard", "fieldtype": "Check", - "label": "Is Standard" + "label": "Is Standard", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "is_standard", @@ -108,11 +123,15 @@ "fieldtype": "Link", "in_standard_filter": 1, "label": "Module", - "options": "Module Def" + "options": "Module Def", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "col_break_1", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "event", @@ -121,21 +140,27 @@ "label": "Send Alert On", "options": "\nNew\nSave\nSubmit\nCancel\nDays After\nDays Before\nValue Change\nMethod\nCustom", "reqd": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.event=='Method'", "description": "Trigger on valid methods like \"before_insert\", \"after_update\", etc (will depend on the DocType selected)", "fieldname": "method", "fieldtype": "Data", - "label": "Trigger Method" + "label": "Trigger Method", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.event==\"Days After\" || doc.event==\"Days Before\"", "description": "Send alert if date matches this field's value", "fieldname": "date_changed", "fieldtype": "Select", - "label": "Reference Date" + "label": "Reference Date", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -143,31 +168,41 @@ "description": "Send days before or after the reference date", "fieldname": "days_in_advance", "fieldtype": "Int", - "label": "Days Before or After" + "label": "Days Before or After", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "eval:doc.event==\"Value Change\"", "description": "Send alert if this field's value changes", "fieldname": "value_changed", "fieldtype": "Select", - "label": "Value Changed" + "label": "Value Changed", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sender", "fieldtype": "Link", "label": "Sender", - "options": "Email Account" + "options": "Email Account", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "sender_email", "fieldtype": "Data", "label": "Sender Email", "options": "Email", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "section_break_9", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "show_days": 1, + "show_seconds": 1 }, { "description": "Optional: The alert will be sent if this expression is true", @@ -175,115 +210,143 @@ "fieldtype": "Code", "ignore_xss_filter": 1, "in_list_view": 1, - "label": "Condition" + "label": "Condition", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_6", - "fieldtype": "Column Break" + "fieldtype": "Column Break", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "html_7", "fieldtype": "HTML", - "options": "

    Condition Examples:

    \n
    doc.status==\"Open\"
    doc.due_date==nowdate()
    doc.total > 40000\n
    \n" + "options": "

    Condition Examples:

    \n
    doc.status==\"Open\"
    doc.due_date==nowdate()
    doc.total > 40000\n
    \n", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "fieldname": "property_section", "fieldtype": "Section Break", - "label": "Set Property After Alert" + "label": "Set Property After Alert", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "set_property_after_alert", "fieldtype": "Select", - "label": "Set Property After Alert" + "label": "Set Property After Alert", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "property_value", "fieldtype": "Data", - "label": "Value To Be Set" + "label": "Value To Be Set", + "show_days": 1, + "show_seconds": 1 }, { - "depends_on": "eval:doc.channel=='Email'", + "depends_on": "eval:doc.channel!=='Slack'", "fieldname": "column_break_5", "fieldtype": "Section Break", - "label": "Recipients" + "label": "Recipients", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "recipients", "fieldtype": "Table", "label": "Recipients", - "options": "Notification Recipient" + "mandatory_depends_on": "eval:doc.channel!=='Slack'", + "options": "Notification Recipient", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "message_sb", "fieldtype": "Section Break", - "label": "Message" + "label": "Message", + "show_days": 1, + "show_seconds": 1 }, { "default": "Add your message here", "fieldname": "message", "fieldtype": "Code", "ignore_xss_filter": 1, - "label": "Message" + "label": "Message", + "show_days": 1, + "show_seconds": 1 }, { "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
    " + "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
    ", + "show_days": 1, + "show_seconds": 1 }, { "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
    " + "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
    ", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "view_properties", "fieldtype": "Button", - "label": "View Properties (via Customize Form)" + "label": "View Properties (via Customize Form)", + "show_days": 1, + "show_seconds": 1 }, { "collapsible": 1, "collapsible_depends_on": "attach_print", "fieldname": "column_break_25", "fieldtype": "Section Break", - "label": "Print Settings" + "label": "Print Settings", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "attach_print", "fieldtype": "Check", - "label": "Attach Print" + "label": "Attach Print", + "show_days": 1, + "show_seconds": 1 }, { "depends_on": "attach_print", "fieldname": "print_format", "fieldtype": "Link", "label": "Print Format", - "options": "Print Format" + "options": "Print Format", + "show_days": 1, + "show_seconds": 1 }, { "default": "0", + "depends_on": "eval: doc.channel !== 'System Notification'", "description": "If enabled, the notification will show up in the notifications dropdown on the top right corner of the navigation bar.", - "fieldname": "show_in_notifications_dropdown", + "fieldname": "send_system_notification", "fieldtype": "Check", - "label": "Show in Notifications Dropdown" - }, - { - "default": "0", - "depends_on": "eval: doc.show_in_notifications_dropdown", - "description": "If enabled, the channel set for the Notification will be disabled. The Notification will only be received in the Notifications Dropdown.", - "fieldname": "disable_channel", - "fieldtype": "Check", - "label": "Disable Channel" + "label": "Send System Notification", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-05-12 19:20:50.304928", + "modified": "2020-05-29 16:03:10.914526", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 38077154cd..eb5102c3fe 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -120,15 +120,13 @@ def get_context(context): if self.is_standard: self.load_standard_properties(context) - channel_disabled = self.disable_channel - - if self.channel == 'Email' and not channel_disabled: + if self.channel == 'Email': self.send_an_email(doc, context) - if self.channel == 'Slack' and not channel_disabled: + if self.channel == 'Slack': self.send_a_slack_msg(doc, context) - if self.show_in_notifications_dropdown: + if self.channel == 'System Notification' or self.send_system_notification: self.create_drodown_notification(doc, context) if self.set_property_after_alert: @@ -164,7 +162,7 @@ def get_context(context): 'document_name': doc.name, 'subject': subject, 'email_content': frappe.render_template(self.message, context), - 'attachment': attachments[0] + 'attachment': attachments and attachments[0] } enqueue_create_notification(users, notification_doc) From d13a217db8b11e3e4f4a8d06c268ebdc2bfc1775 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 16:46:13 +0530 Subject: [PATCH 052/110] fix: check if request status code is ok instead of specific codes --- frappe/core/doctype/access_log/test_access_log.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/frappe/core/doctype/access_log/test_access_log.py b/frappe/core/doctype/access_log/test_access_log.py index 312f77c026..9830507423 100644 --- a/frappe/core/doctype/access_log/test_access_log.py +++ b/frappe/core/doctype/access_log/test_access_log.py @@ -158,11 +158,7 @@ class TestAccessLog(unittest.TestCase): request = requests.post(private_file_link, headers=self.header) last_doc = frappe.get_last_doc('Access Log') - if request.status_code == 403: - # if file is not accessible, access log wont be generated - pass - - else: + if request.ok: # check for the access log of downloaded file self.assertEqual(new_private_file.doctype, last_doc.export_from) self.assertEqual(new_private_file.name, last_doc.reference_document) From d6206ed411c812aead75cb2b412703f7b6689ebd Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 29 May 2020 16:50:31 +0530 Subject: [PATCH 053/110] fix: update assignemnts on merging docs --- frappe/model/rename_doc.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 4491a352bc..24df3ab91b 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals, print_function import frappe +import json from frappe import _, bold from frappe.utils import cint from frappe.model.naming import validate_name @@ -56,6 +57,8 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F if not merge: rename_parent_and_child(doctype, old, new, meta) + else: + update_assignments(old, new, doctype) # update link fields' values link_fields = get_link_fields(doctype) @@ -104,6 +107,27 @@ def rename_doc(doctype, old, new, force=False, merge=False, ignore_permissions=F return new +def update_assignments(old, new, doctype): + old_assignments = frappe.parse_json(frappe.db.get_value(doctype, old, '_assign')) or [] + new_assignments = frappe.parse_json(frappe.db.get_value(doctype, new, '_assign')) or [] + common_assignments = list(set(old_assignments).intersection(new_assignments)) + + for user in common_assignments: + # delete todos linked to old doc + todos = frappe.db.get_all('ToDo', + { + 'owner': user, + 'reference_type': doctype, + 'reference_name': old, + }, + ['name', 'description'] + ) + + for todo in todos: + frappe.delete_doc('ToDo', todo.name) + + unique_assignments = list(set(old_assignments + new_assignments)) + frappe.db.set_value(doctype, new, '_assign', json.dumps(unique_assignments)) def update_user_settings(old, new, link_fields): ''' From a3d64fb4c5c680202070248d579ae7094dd851b5 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 16:50:07 +0530 Subject: [PATCH 054/110] fix: use fallback attr for web logging --- frappe/app.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index dc985c12f6..50d09177d6 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -101,12 +101,12 @@ def application(request): frappe.logger("web").info({ "site": get_site_name(request.host), - "remote_addr": request.remote_addr, - "base_url": request.base_url, - "full_path": request.full_path, - "method": request.method, - "scheme": request.scheme, - "http_status_code": response.status_code + "remote_addr": getattr(request, "remote_addr", "NOTFOUND"), + "base_url": getattr(request, "base_url", "NOTFOUND"), + "full_path": getattr(request, "full_path", "NOTFOUND"), + "method": getattr(request, "method", "NOTFOUND"), + "scheme": getattr(request, "scheme", "NOTFOUND"), + "http_status_code": getattr(response, "status_code", "NOTFOUND") }) if response and hasattr(frappe.local, 'rate_limiter'): From 56eba9cb5d0d803468031f8a69945db8862651bb Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 17:37:35 +0530 Subject: [PATCH 055/110] fix: handle SiteNotSpecifiedError in passcontext --- frappe/commands/__init__.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 7d36cbe28c..832a7eb415 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -22,7 +22,11 @@ def pass_context(f): pr = cProfile.Profile() pr.enable() - ret = f(frappe._dict(ctx.obj), *args, **kwargs) + try: + ret = f(frappe._dict(ctx.obj), *args, **kwargs) + except frappe.exceptions.SiteNotSpecifiedError as e: + click.secho(str(e), fg='yellow') + sys.exit(1) if profile: pr.disable() From e3ae498e4d69c9bceab5c2b0841ee03e77c1b9fd Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 17:45:43 +0530 Subject: [PATCH 056/110] fix: fixed imports --- frappe/commands/translate.py | 1 + frappe/commands/utils.py | 1 + 2 files changed, 2 insertions(+) diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index de4d8aab0b..f11954cb28 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals, absolute_import, print_function import click from frappe.commands import pass_context, get_site +from frappe.exceptions import SiteNotSpecifiedError # translation @click.command('build-message-files') diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 835fa2d40e..869178ff3c 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -5,6 +5,7 @@ import click import json, os, sys, subprocess from distutils.spawn import find_executable import frappe +from frappe.exceptions import SiteNotSpecifiedError from frappe.commands import pass_context, get_site from frappe.utils import update_progress_bar, get_bench_path from frappe.utils.response import json_handler From 2e5740682b93e570b182d84c78f513ccbe588793 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 18:28:51 +0530 Subject: [PATCH 057/110] fix: maintain original file of .gz to uncompress --- frappe/installer.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/installer.py b/frappe/installer.py index 54402f0087..17e9bceed7 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -298,7 +298,8 @@ def remove_missing_apps(): def extract_sql_gzip(sql_gz_path): try: - subprocess.check_call(['gzip', '-d', '-v', '-f', sql_gz_path]) + # kdvf - keep, decompress, verbose, force + subprocess.check_call(['gzip', '-kdvf', sql_gz_path]) except: raise From 1f329a5c46d478c032457fe748a02d1ed71d6178 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sat, 30 May 2020 11:07:14 +0530 Subject: [PATCH 058/110] fix: maintain path for files in args and remove temporary files only --- frappe/commands/site.py | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index f0c4adb157..b0abe9e2e6 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -130,30 +130,47 @@ def restore(context, sql_file_path, mariadb_root_username=None, mariadb_root_pas # Extract the gzip file if user has passed *.sql.gz file instead of *.sql file if not os.path.exists(sql_file_path): - sql_file_path = '../' + sql_file_path + base_path = '..' + sql_file_path = os.path.join(base_path, sql_file_path) if not os.path.exists(sql_file_path): print('Invalid path {0}'.format(sql_file_path[3:])) sys.exit(1) + elif sql_file_path.startswith(os.sep): + base_path = os.sep + else: + base_path = '.' + if sql_file_path.endswith('sql.gz'): - sql_file_path = extract_sql_gzip(os.path.abspath(sql_file_path)) + decompressed_file_name = extract_sql_gzip(os.path.abspath(sql_file_path)) + else: + decompressed_file_name = sql_file_path site = get_site(context) frappe.init(site=site) _new_site(frappe.conf.db_name, site, mariadb_root_username=mariadb_root_username, mariadb_root_password=mariadb_root_password, admin_password=admin_password, - verbose=context.verbose, install_apps=install_app, source_sql=sql_file_path, - force=context.force) + verbose=context.verbose, install_apps=install_app, source_sql=decompressed_file_name, + force=True) # Extract public and/or private files to the restored site, if user has given the path if with_public_files: + with_public_files = os.path.join(base_path, with_public_files) public = extract_tar_files(site, with_public_files, 'public') os.remove(public) if with_private_files: + with_private_files = os.path.join(base_path, with_private_files) private = extract_tar_files(site, with_private_files, 'private') os.remove(private) + # Removing temporarily created file + if decompressed_file_name != sql_file_path: + os.remove(decompressed_file_name) + + success_message = "Site {0} has been restored{1}".format(site, " with files" if (with_public_files or with_private_files) else "") + click.secho(success_message, fg="green") + @click.command('reinstall') @click.option('--admin-password', help='Administrator Password for reinstalled site') @click.option('--mariadb-root-username', help='Root username for MariaDB') From 6b2efc5f5d54168887a23bac9586cd44ca455fdd Mon Sep 17 00:00:00 2001 From: prssanna Date: Sat, 30 May 2020 14:51:43 +0530 Subject: [PATCH 059/110] fix: fix translation syntax --- frappe/email/doctype/notification/notification.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index eb5102c3fe..06a76a2005 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -252,7 +252,7 @@ def get_context(context): # ignoring attachment as draft and cancelled documents are not allowed to print status = "Draft" if doc.docstatus == 0 else "Cancelled" frappe.throw(_("""Not allowed to attach {0} document, - please enable Allow Print For {0} in Print Settings""".format(status)), + please enable Allow Print For {0} in Print Settings""").format(status), title=_("Error in Notification")) else: return [{ From 09dd31a79cf6de9f70814b198427fbf040325e13 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 9 Apr 2020 16:48:04 +0530 Subject: [PATCH 060/110] feat: show warning if documents have workflow states that do not exist in the workflow --- frappe/public/js/frappe/form/form.js | 8 +- frappe/public/js/frappe/ui/dialog.js | 6 ++ frappe/workflow/doctype/workflow/workflow.js | 99 +++++++++++++++++++- frappe/workflow/doctype/workflow/workflow.py | 13 ++- 4 files changed, 121 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 369e4a56d4..a6c11a3dac 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -616,8 +616,12 @@ frappe.ui.form.Form = class FrappeForm { // validate frappe.validated = true; frappe.run_serially([ - () => this.script_manager.trigger("validate"), - () => this.script_manager.trigger("before_save"), + () => { + return this.script_manager.trigger("validate") + }, + () => { + return this.script_manager.trigger("before_save") + }, () => { if(!frappe.validated) { fail(); diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 0f4332a91a..2bb01311d9 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -42,6 +42,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { this.body = this.$body.get(0); this.$message = $('').appendTo(this.modal_body); this.header = this.$wrapper.find(".modal-header"); + this.set_indicator(); // make fields (if any) super.make(); @@ -164,6 +165,11 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { set_title(t) { this.$wrapper.find(".modal-title").html(t); } + set_indicator() { + if (this.indicator) { + this.header.find('.indicator').removeClass().addClass('indicator ' + this.indicator); + } + } show() { // show it if ( this.animate ) { diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index 6e12f5fa46..cb6ed98b1a 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -6,6 +6,25 @@ frappe.ui.form.on("Workflow", { }, refresh: function(frm) { frm.events.update_field_options(frm); + frm.ignore_warning = false; + }, + validate: (frm) => { + if (frm.ignore_warning) return; + let states_list = []; + frm.doc.states.map(state=> states_list.push(state.state)); + return frappe.xcall( + 'frappe.workflow.doctype.workflow.workflow.get_workflow_state_count', + { + doctype: frm.doc.document_type, + workflow_state_field: frm.doc.workflow_state_field, + states: states_list + }).then(result => { + if (result && result.length) { + frappe.validated = false; + frm.states = result; + frm.trigger('create_warning_dialog'); + } + }); }, document_type: function(frm) { frm.events.update_field_options(frm); @@ -19,6 +38,82 @@ frappe.ui.form.on("Workflow", { frappe.meta.get_docfield("Workflow Document State", "update_field", frm.doc.name).options = [""].concat(resp); }) } - } -}) + }, + create_warning_dialog: function(frm) { + frm.warning_dialog = new frappe.ui.Dialog({ + title: __(`Worflow States Don't Exist`), + indicator: 'red', + fields: [ + { + fieldname: 'warning_text', + label: __('Warning'), + fieldtype: 'HTML', + }, + { + fieldname: 'state_table', + label: __('Count of existing Document States'), + fieldtype: 'HTML', + }, + ], + primary_action_label: __(`Don't Save`), + primary_action: () => { + frm.warning_dialog.hide(); + }, + }); + frm.warning_dialog.get_close_btn().hide(); + + frm.warning_dialog.get_field('warning_text').$wrapper.html( + `

    + ${__(`There are documents which have workflow states that do not exist in this Workflow. + It is recommended that you add these states to the Workflow and change their states + before removing these states.`)} +

    ` + ) + frm.trigger('render_state_table'); + frm.trigger('render_dismiss_button'); + frm.warning_dialog.show(); + }, + render_state_table: function(frm) { + let wrapper = frm.warning_dialog.get_field('state_table').$wrapper; + let rows = frm.states.map(r => { + let indicator_color_map = { + '0': 'red', + '1': 'green', + '2': 'darkgrey' + } + return ` + +
    + ${r[frm.doc.workflow_state_field]} +
    + + ${r.count} + `; + }).join(''); + + $(` + + + + + + + + ${rows} + +
    ${__('State')}${__('Count')}
    `).appendTo(wrapper); + }, + render_dismiss_button: function(frm) { + frm.warning_dialog.header.find('.buttons').prepend( + `` + ); + + frm.warning_dialog.$wrapper.find('.dismiss').on('click', () => { + frm.warning_dialog.hide(); + frm.ignore_warning = true; + frm.save(); + }); + } + +}); diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index 62e0b39b08..a740dc136f 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -59,7 +59,7 @@ class Workflow(Document): def update_doc_status(self): ''' - Checks if the docstatus of a state was updated. + Checks if the docstatus of a state was updated. If yes then the docstatus of the document with same state will be updated ''' doc_before_save = self.get_doc_before_save() @@ -112,3 +112,14 @@ class Workflow(Document): def get_fieldnames_for(doctype): return [f.fieldname for f in frappe.get_meta(doctype).fields \ if f.fieldname not in no_value_fields] + +@frappe.whitelist() +def get_workflow_state_count(doctype, workflow_state_field, states): + states = frappe.parse_json(states) + return frappe.get_all( + doctype, + fields=[workflow_state_field, 'count(*) as count', 'docstatus'], + filters = {'workflow_state': ['not in', states]}, + group_by = workflow_state_field + ) + From 5eb22194f507db89b11f2738fce2a0a94c27b3a0 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 9 Apr 2020 23:01:28 +0530 Subject: [PATCH 061/110] feat: create frappe.warn dialog --- frappe/public/js/frappe/ui/dialog.js | 1 + frappe/public/js/frappe/ui/messages.js | 27 ++++++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/frappe/public/js/frappe/ui/dialog.js b/frappe/public/js/frappe/ui/dialog.js index 2bb01311d9..d77481f8b9 100644 --- a/frappe/public/js/frappe/ui/dialog.js +++ b/frappe/public/js/frappe/ui/dialog.js @@ -42,6 +42,7 @@ frappe.ui.Dialog = class Dialog extends frappe.ui.FieldGroup { this.body = this.$body.get(0); this.$message = $('').appendTo(this.modal_body); this.header = this.$wrapper.find(".modal-header"); + this.buttons = this.header.find('.buttons'); this.set_indicator(); // make fields (if any) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index c6bc994a9d..4decc26a7f 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -53,6 +53,33 @@ frappe.confirm = function(message, ifyes, ifno) { return d; } +frappe.warn = function(title, message_html, proceed_action, primary_label) { + const d = new frappe.ui.Dialog({ + title: title, + indicator: 'red', + fields: [ + { + fieldtype:"HTML", + fieldname: 'warning_message', + options: `
    ${message_html}
    ` + } + ], + primary_action_label: primary_label, + primary_action: () => { + if (proceed_action) proceed_action(); + d.hide(); + }, + secondary_action_label: __("Cancel"), + }); + + d.buttons.find('.btn-primary').removeClass('btn-primary').addClass('btn-danger'); + const modal_footer = $(``).insertAfter($(d.modal_body)); + modal_footer.html(d.buttons); + + d.show(); + return d; +} + frappe.prompt = function(fields, callback, title, primary_label) { if (typeof fields === "string") { fields = [{ From d1c4af1255d57a7c5b139d944ed30afaaf32b56c Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 9 Apr 2020 23:01:54 +0530 Subject: [PATCH 062/110] refactor: use frappe.warn and render table in form also --- frappe/workflow/doctype/workflow/workflow.js | 104 ++++++++----------- 1 file changed, 45 insertions(+), 59 deletions(-) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index cb6ed98b1a..a8fe5472db 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -8,23 +8,17 @@ frappe.ui.form.on("Workflow", { frm.events.update_field_options(frm); frm.ignore_warning = false; }, + onload_post_render: function(frm) { + frm.trigger('get_orphaned_states_and_count').then(()=> { + frm.trigger('render_state_table'); + }); + }, validate: (frm) => { if (frm.ignore_warning) return; - let states_list = []; - frm.doc.states.map(state=> states_list.push(state.state)); - return frappe.xcall( - 'frappe.workflow.doctype.workflow.workflow.get_workflow_state_count', - { - doctype: frm.doc.document_type, - workflow_state_field: frm.doc.workflow_state_field, - states: states_list - }).then(result => { - if (result && result.length) { - frappe.validated = false; - frm.states = result; - frm.trigger('create_warning_dialog'); - } - }); + return frm.trigger('get_orphaned_states_and_count').then(()=> { + frappe.validated = false; + frm.trigger('create_warning_dialog'); + }); }, document_type: function(frm) { frm.events.update_field_options(frm); @@ -40,41 +34,23 @@ frappe.ui.form.on("Workflow", { } }, create_warning_dialog: function(frm) { - frm.warning_dialog = new frappe.ui.Dialog({ - title: __(`Worflow States Don't Exist`), - indicator: 'red', - fields: [ - { - fieldname: 'warning_text', - label: __('Warning'), - fieldtype: 'HTML', - }, - { - fieldname: 'state_table', - label: __('Count of existing Document States'), - fieldtype: 'HTML', - }, - ], - primary_action_label: __(`Don't Save`), - primary_action: () => { - frm.warning_dialog.hide(); - }, - }); - frm.warning_dialog.get_close_btn().hide(); - - frm.warning_dialog.get_field('warning_text').$wrapper.html( - `

    - ${__(`There are documents which have workflow states that do not exist in this Workflow. + const warning_html = + `

    + ${__('Are you sure you want to save this document?')} +

    +

    ${__(`There are documents which have workflow states that do not exist in this Workflow. It is recommended that you add these states to the Workflow and change their states before removing these states.`)} -

    ` - ) - frm.trigger('render_state_table'); - frm.trigger('render_dismiss_button'); - frm.warning_dialog.show(); +

    `; + const message_html = warning_html + frm.state_table_html; + let proceed_action = () => { + frm.ignore_warning = true; + frm.save(); + } + + frappe.warn(__(`Worflow States Don't Exist`), message_html, proceed_action, __(`Save Anyway`)); }, - render_state_table: function(frm) { - let wrapper = frm.warning_dialog.get_field('state_table').$wrapper; + set_table_html: function(frm) { let rows = frm.states.map(r => { let indicator_color_map = { '0': 'red', @@ -91,7 +67,7 @@ frappe.ui.form.on("Workflow", { `; }).join(''); - $(` + frm.state_table_html = (`
    @@ -101,19 +77,29 @@ frappe.ui.form.on("Workflow", { ${rows} -
    ${__('State')}
    `).appendTo(wrapper); + `); }, - render_dismiss_button: function(frm) { - frm.warning_dialog.header.find('.buttons').prepend( - `` - ); - - frm.warning_dialog.$wrapper.find('.dismiss').on('click', () => { - frm.warning_dialog.hide(); - frm.ignore_warning = true; - frm.save(); + get_orphaned_states_and_count: function(frm) { + let states_list = []; + frm.doc.states.map(state=> states_list.push(state.state)); + return frappe.xcall('frappe.workflow.doctype.workflow.workflow.get_workflow_state_count',{ + doctype: frm.doc.document_type, + workflow_state_field: frm.doc.workflow_state_field, + states: states_list + }).then(result => { + if (result && result.length) { + frm.states = result; + frm.trigger('set_table_html'); + } }); + }, + render_state_table: function(frm) { + const $wrapper = frm.get_field('states').$wrapper; + const label_html = + `

    + ${'Document States that do not exist in your Workflow'} +

    `; + $(label_html + frm.state_table_html).insertAfter($wrapper); } - }); From aaf1618898629232137864061cdd8bee464f4e25 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 10 Apr 2020 00:23:45 +0530 Subject: [PATCH 063/110] refactor: refactor rendering of table --- frappe/public/js/frappe/ui/messages.js | 2 +- frappe/workflow/doctype/workflow/workflow.js | 59 +++++++++++++++----- frappe/workflow/doctype/workflow/workflow.py | 3 +- 3 files changed, 49 insertions(+), 15 deletions(-) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index 4decc26a7f..f4eacb142b 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -59,7 +59,7 @@ frappe.warn = function(title, message_html, proceed_action, primary_label) { indicator: 'red', fields: [ { - fieldtype:"HTML", + fieldtype: 'HTML', fieldname: 'warning_message', options: `
    ${message_html}
    ` } diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index a8fe5472db..db941a003b 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -7,17 +7,21 @@ frappe.ui.form.on("Workflow", { refresh: function(frm) { frm.events.update_field_options(frm); frm.ignore_warning = false; - }, - onload_post_render: function(frm) { + frm.states = null; + frm.trigger('make_state_table'); frm.trigger('get_orphaned_states_and_count').then(()=> { frm.trigger('render_state_table'); }); }, validate: (frm) => { - if (frm.ignore_warning) return; + if (frm.ignore_warning) { + return; + } return frm.trigger('get_orphaned_states_and_count').then(()=> { + if (frm.states && frm.states.length) { frappe.validated = false; frm.trigger('create_warning_dialog'); + } }); }, document_type: function(frm) { @@ -51,23 +55,25 @@ frappe.ui.form.on("Workflow", { frappe.warn(__(`Worflow States Don't Exist`), message_html, proceed_action, __(`Save Anyway`)); }, set_table_html: function(frm) { + let rows = frm.states.map(r => { let indicator_color_map = { '0': 'red', '1': 'green', '2': 'darkgrey' - } + }; + return `
    - ${r[frm.doc.workflow_state_field]} + ${r[frm.doc.workflow_state_field]}
    ${r.count} `; }).join(''); - frm.state_table_html = (` + frm.state_table_html = (`
    @@ -78,11 +84,12 @@ frappe.ui.form.on("Workflow", { ${rows}
    ${__('State')}
    `); + }, get_orphaned_states_and_count: function(frm) { let states_list = []; frm.doc.states.map(state=> states_list.push(state.state)); - return frappe.xcall('frappe.workflow.doctype.workflow.workflow.get_workflow_state_count',{ + return frappe.xcall('frappe.workflow.doctype.workflow.workflow.get_workflow_state_count', { doctype: frm.doc.document_type, workflow_state_field: frm.doc.workflow_state_field, states: states_list @@ -93,13 +100,39 @@ frappe.ui.form.on("Workflow", { } }); }, + make_state_table: function(frm) { + const wrapper = frm.get_field('states').$wrapper; + if (frm.state_table) { + frm.state_table.empty(); + } + frm.state_table = $(`
    `).insertAfter(wrapper); + }, render_state_table: function(frm) { - const $wrapper = frm.get_field('states').$wrapper; - const label_html = - `

    - ${'Document States that do not exist in your Workflow'} -

    `; - $(label_html + frm.state_table_html).insertAfter($wrapper); + if (frm.states && frm.states.length) { + const form_state_table_html = + `

    + ${'Document States that do not exist in your Workflow'} +

    + ${frm.state_table_html} +
    `; + frm.state_table.html(form_state_table_html); + + $(frm.state_table).find('a.orphaned-state').on('click', (e) => { + const state = $(e.currentTarget).text(); + let filters = {}; + filters[frm.workflow_state_field] = state; + frappe.set_route('List', frm.doc.document_type, filters); + }) + } + } + +}); + +frappe.ui.form.on("Workflow Document State", { + states_remove: function(frm) { + frm.trigger('get_orphaned_states_and_count').then(()=> { + frm.trigger('render_state_table'); + }); } }); diff --git a/frappe/workflow/doctype/workflow/workflow.py b/frappe/workflow/doctype/workflow/workflow.py index a740dc136f..553f21dbab 100644 --- a/frappe/workflow/doctype/workflow/workflow.py +++ b/frappe/workflow/doctype/workflow/workflow.py @@ -116,10 +116,11 @@ def get_fieldnames_for(doctype): @frappe.whitelist() def get_workflow_state_count(doctype, workflow_state_field, states): states = frappe.parse_json(states) - return frappe.get_all( + result = frappe.get_all( doctype, fields=[workflow_state_field, 'count(*) as count', 'docstatus'], filters = {'workflow_state': ['not in', states]}, group_by = workflow_state_field ) + return [r for r in result if r[workflow_state_field]] From f61f5ec8344c2deab14b53a698cda8d242181e06 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 26 Apr 2020 17:02:40 +0530 Subject: [PATCH 064/110] fix: get indicator color from state --- frappe/public/js/frappe/utils/utils.js | 19 +++++++++ frappe/workflow/doctype/workflow/workflow.js | 45 ++++++++++---------- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 24fa946fc4..ff49e86e49 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -292,6 +292,25 @@ Object.assign(frappe.utils, { return frappe.utils.guess_style(text, null, true); }, + get_indicator_color: function(state) { + return frappe.db.get_list('Workflow State', {filters: {name: state}, fields: ['name', 'style']}).then(res => { + const state = res[0]; + if (!state.style) { + return frappe.utils.guess_colour(state.name); + } + const style = state.style; + const colour_map = { + "Success": "green", + "Warning": "orange", + "Danger": "red", + "Primary": "blue", + } + + return colour_map[style]; + }); + + }, + sort: function(list, key, compare_type, reverse) { if(!list || list.length < 2) return list || []; diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index db941a003b..448e8db796 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -56,35 +56,34 @@ frappe.ui.form.on("Workflow", { }, set_table_html: function(frm) { - let rows = frm.states.map(r => { - let indicator_color_map = { - '0': 'red', - '1': 'green', - '2': 'darkgrey' - }; - - return ` + const promises = frm.states.map(r => { + const state = r[frm.doc.workflow_state_field]; + return frappe.utils.get_indicator_color(state).then(color => { + return ` -
    + ${r.count} `; - }).join(''); - - frm.state_table_html = (` - - - - - - - - ${rows} - -
    ${__('State')}${__('Count')}
    `); + }) + }); + Promise.all(promises).then(rows => { + const rows_html = rows.join(''); + frm.state_table_html = (` + + + + + + + + ${rows_html} + +
    ${__('State')}${__('Count')}
    `); + }); }, get_orphaned_states_and_count: function(frm) { let states_list = []; @@ -96,7 +95,7 @@ frappe.ui.form.on("Workflow", { }).then(result => { if (result && result.length) { frm.states = result; - frm.trigger('set_table_html'); + return frm.trigger('set_table_html'); } }); }, From 2df481e990489607b8bd45f22064ab16f78b6d5d Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 26 Apr 2020 17:04:04 +0530 Subject: [PATCH 065/110] fix: increase table width --- frappe/workflow/doctype/workflow/workflow.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index 448e8db796..ef9f32ec4f 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -72,8 +72,8 @@ frappe.ui.form.on("Workflow", { Promise.all(promises).then(rows => { const rows_html = rows.join(''); - frm.state_table_html = (` - + frm.state_table_html = (`
    + From 37dcef5f530cc356dfff819f0f10a2f79124002a Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 26 Apr 2020 17:06:37 +0530 Subject: [PATCH 066/110] fix: code formatting --- frappe/public/js/frappe/utils/utils.js | 2 +- frappe/workflow/doctype/workflow/workflow.js | 9 ++++----- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index ff49e86e49..f4dde5804f 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -304,7 +304,7 @@ Object.assign(frappe.utils, { "Warning": "orange", "Danger": "red", "Primary": "blue", - } + }; return colour_map[style]; }); diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index ef9f32ec4f..11d63bccb4 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -50,7 +50,7 @@ frappe.ui.form.on("Workflow", { let proceed_action = () => { frm.ignore_warning = true; frm.save(); - } + }; frappe.warn(__(`Worflow States Don't Exist`), message_html, proceed_action, __(`Save Anyway`)); }, @@ -65,9 +65,8 @@ frappe.ui.form.on("Workflow", { ${r[frm.doc.workflow_state_field]} - - `; - }) + `; + }); }); Promise.all(promises).then(rows => { @@ -121,7 +120,7 @@ frappe.ui.form.on("Workflow", { let filters = {}; filters[frm.workflow_state_field] = state; frappe.set_route('List', frm.doc.document_type, filters); - }) + }); } } From 3ee944d659c70e5cf77a15ff5119e9db91cb1eab Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 5 May 2020 12:52:09 +0530 Subject: [PATCH 067/110] style: missing semicolon --- frappe/public/js/frappe/ui/messages.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/messages.js b/frappe/public/js/frappe/ui/messages.js index f4eacb142b..ab20feeedd 100644 --- a/frappe/public/js/frappe/ui/messages.js +++ b/frappe/public/js/frappe/ui/messages.js @@ -78,7 +78,7 @@ frappe.warn = function(title, message_html, proceed_action, primary_label) { d.show(); return d; -} +}; frappe.prompt = function(fields, callback, title, primary_label) { if (typeof fields === "string") { From 49f4eea9636f36878511c2f92aa60a0a22c2f14b Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 12:14:08 +0530 Subject: [PATCH 068/110] fix: don't get states count if form is new --- frappe/workflow/doctype/workflow/workflow.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index 11d63bccb4..500bece21d 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -6,7 +6,12 @@ frappe.ui.form.on("Workflow", { }, refresh: function(frm) { frm.events.update_field_options(frm); - frm.ignore_warning = false; + frm.ignore_warning = frm.is_new() ? true : false; + + if (frm.is_new()) { + return; + } + frm.states = null; frm.trigger('make_state_table'); frm.trigger('get_orphaned_states_and_count').then(()=> { From d621a994a135da65dcb5236d73eac72b2c864c3e Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 12:14:49 +0530 Subject: [PATCH 069/110] fix: fix state filter --- frappe/workflow/doctype/workflow/workflow.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index 500bece21d..189d80a893 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -123,7 +123,7 @@ frappe.ui.form.on("Workflow", { $(frm.state_table).find('a.orphaned-state').on('click', (e) => { const state = $(e.currentTarget).text(); let filters = {}; - filters[frm.workflow_state_field] = state; + filters[frm.doc.workflow_state_field] = state; frappe.set_route('List', frm.doc.document_type, filters); }); } From 62ac2494e7d52bee16f0fdaaa67585a9bb630937 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 12:16:17 +0530 Subject: [PATCH 070/110] style: fix formatting --- frappe/workflow/doctype/workflow/workflow.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index 189d80a893..a423b2610c 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -14,7 +14,7 @@ frappe.ui.form.on("Workflow", { frm.states = null; frm.trigger('make_state_table'); - frm.trigger('get_orphaned_states_and_count').then(()=> { + frm.trigger('get_orphaned_states_and_count').then(() => { frm.trigger('render_state_table'); }); }, @@ -22,7 +22,7 @@ frappe.ui.form.on("Workflow", { if (frm.ignore_warning) { return; } - return frm.trigger('get_orphaned_states_and_count').then(()=> { + return frm.trigger('get_orphaned_states_and_count').then(() => { if (frm.states && frm.states.length) { frappe.validated = false; frm.trigger('create_warning_dialog'); @@ -91,7 +91,7 @@ frappe.ui.form.on("Workflow", { }, get_orphaned_states_and_count: function(frm) { let states_list = []; - frm.doc.states.map(state=> states_list.push(state.state)); + frm.doc.states.map(state => states_list.push(state.state)); return frappe.xcall('frappe.workflow.doctype.workflow.workflow.get_workflow_state_count', { doctype: frm.doc.document_type, workflow_state_field: frm.doc.workflow_state_field, @@ -133,7 +133,7 @@ frappe.ui.form.on("Workflow", { frappe.ui.form.on("Workflow Document State", { states_remove: function(frm) { - frm.trigger('get_orphaned_states_and_count').then(()=> { + frm.trigger('get_orphaned_states_and_count').then(() => { frm.trigger('render_state_table'); }); } From c7f0aafa7e45ba8adbf2fd9f43319bac0952c2f7 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 28 May 2020 14:24:33 +0530 Subject: [PATCH 071/110] feat: add button to go to ref doctype list --- frappe/workflow/doctype/workflow/workflow.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/workflow/doctype/workflow/workflow.js b/frappe/workflow/doctype/workflow/workflow.js index a423b2610c..aba6f6fe48 100644 --- a/frappe/workflow/doctype/workflow/workflow.js +++ b/frappe/workflow/doctype/workflow/workflow.js @@ -5,6 +5,12 @@ frappe.ui.form.on("Workflow", { frm.set_query("document_type", {"issingle": 0, "istable": 0}); }, refresh: function(frm) { + if (frm.doc.document_type) { + frm.add_custom_button(__('Go to {0} List', [frm.doc.document_type]), () => { + frappe.set_route('List', frm.doc.document_type); + }); + } + frm.events.update_field_options(frm); frm.ignore_warning = frm.is_new() ? true : false; From 1c7c647015d7d89ff213b1ba408a6a3bd115e38c Mon Sep 17 00:00:00 2001 From: prssanna Date: Sat, 30 May 2020 15:33:38 +0530 Subject: [PATCH 072/110] fix: revert form.js --- frappe/public/js/frappe/form/form.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index a6c11a3dac..369e4a56d4 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -616,12 +616,8 @@ frappe.ui.form.Form = class FrappeForm { // validate frappe.validated = true; frappe.run_serially([ - () => { - return this.script_manager.trigger("validate") - }, - () => { - return this.script_manager.trigger("before_save") - }, + () => this.script_manager.trigger("validate"), + () => this.script_manager.trigger("before_save"), () => { if(!frappe.validated) { fail(); From 6c500c463e64ed7d9365cc32dfbc45bb3e1d40cc Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 22:18:30 +0530 Subject: [PATCH 073/110] fix: delete unused field --- .../notification_settings.json | 50 ++++++++++++------- 1 file changed, 31 insertions(+), 19 deletions(-) diff --git a/frappe/desk/doctype/notification_settings/notification_settings.json b/frappe/desk/doctype/notification_settings/notification_settings.json index 190e827ab5..85f93e156e 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.json +++ b/frappe/desk/doctype/notification_settings/notification_settings.json @@ -14,7 +14,6 @@ "enable_email_assignment", "enable_email_energy_point", "enable_email_share", - "enable_email_alert", "user", "seen" ], @@ -23,52 +22,68 @@ "default": "1", "fieldname": "enabled", "fieldtype": "Check", - "label": "Enabled" + "label": "Enabled", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "subscribed_documents", "fieldtype": "Table MultiSelect", "label": "Subscribed Documents", - "options": "Notification Subscribed Document" + "options": "Notification Subscribed Document", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "column_break_3", "fieldtype": "Section Break", - "label": "Email Settings" + "label": "Email Settings", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "fieldname": "enable_email_notifications", "fieldtype": "Check", - "label": "Enable Email Notifications" + "label": "Enable Email Notifications", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "depends_on": "enable_email_notifications", "fieldname": "enable_email_mention", "fieldtype": "Check", - "label": "Mentions" + "label": "Mentions", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "depends_on": "enable_email_notifications", "fieldname": "enable_email_assignment", "fieldtype": "Check", - "label": "Assignments" + "label": "Assignments", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "depends_on": "enable_email_notifications", "fieldname": "enable_email_energy_point", "fieldtype": "Check", - "label": "Energy Points" + "label": "Energy Points", + "show_days": 1, + "show_seconds": 1 }, { "default": "1", "depends_on": "enable_email_notifications", "fieldname": "enable_email_share", "fieldtype": "Check", - "label": "Document Share" + "label": "Document Share", + "show_days": 1, + "show_seconds": 1 }, { "default": "__user", @@ -77,26 +92,23 @@ "hidden": 1, "label": "User", "options": "User", - "read_only": 1 + "read_only": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", "fieldname": "seen", "fieldtype": "Check", "hidden": 1, - "label": "Seen" - }, - { - "default": "0", - "fieldname": "enable_email_alert", - "fieldtype": "Check", - "hidden": 1, - "label": "Alert" + "label": "Seen", + "show_days": 1, + "show_seconds": 1 } ], "in_create": 1, "links": [], - "modified": "2020-05-08 15:15:53.925897", + "modified": "2020-05-31 22:16:40.798019", "modified_by": "Administrator", "module": "Desk", "name": "Notification Settings", From 809937dfaef4869852dbc576b45d66bc1aba5b71 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 22:19:08 +0530 Subject: [PATCH 074/110] fix: rename argument --- frappe/public/js/frappe/ui/notifications/notifications.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index 14d3b9bbd0..64d19ce63f 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -333,9 +333,11 @@ frappe.ui.Notifications = class Notifications { return item_html; } - get_item_link(field) { - const link_doctype = field.type == 'Alert'? 'Notification Log': field.document_type; - const link_docname = field.type == 'Alert'? field.name: field.document_name; + get_item_link(notification_doc) { + const link_doctype = + notification_doc.type == 'Alert' ? 'Notification Log': notification_doc.document_type; + const link_docname = + notification_doc.type == 'Alert' ? notification_doc.name: notification_doc.document_name; return frappe.utils.get_form_link( link_doctype, link_docname From 5dcc36d14503d67380e817743243c5da8c85b271 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 22:28:40 +0530 Subject: [PATCH 075/110] fix: remove create and write permission for notification log --- .../notification_log/notification_log.json | 53 ++++++++++++------- 1 file changed, 33 insertions(+), 20 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index 35869f8c2e..9e7e7a941f 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -23,7 +23,8 @@ "fieldtype": "Text", "in_list_view": 1, "label": "Subject", - "read_only": 1 + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "for_user", @@ -31,7 +32,8 @@ "hidden": 1, "label": "For User", "options": "User", - "read_only": 1 + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "type", @@ -41,14 +43,16 @@ "in_standard_filter": 1, "label": "Type", "options": "Mention\nEnergy Point\nAssignment\nShare\nAlert", - "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "email_content", "fieldtype": "Text Editor", "label": "Message", - "read_only": 1 + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "document_type", @@ -56,16 +60,18 @@ "hidden": 1, "label": "Document Type", "options": "DocType", - "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "document_name", "fieldtype": "Data", "hidden": 1, "label": "Document Link", - "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "from_user", @@ -73,8 +79,9 @@ "hidden": 1, "label": "From User", "options": "User", - "read_only": 1, - "search_index": 1 + "search_index": 1, + "show_days": 1, + "show_seconds": 1 }, { "default": "0", @@ -82,44 +89,50 @@ "fieldtype": "Check", "hidden": 1, "ignore_user_permissions": 1, - "label": "Read" + "label": "Read", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "open_reference_document", "fieldtype": "Button", - "label": "Open Reference Document" + "label": "Open Reference Document", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "attached_file", "fieldtype": "Code", "hidden": 1, "label": "Attached File", - "options": "JSON" + "options": "JSON", + "show_days": 1, + "show_seconds": 1 }, { "fieldname": "attachment_link", "fieldtype": "HTML", - "label": "Attachment Link" + "label": "Attachment Link", + "show_days": 1, + "show_seconds": 1 } ], "in_create": 1, "links": [], - "modified": "2020-05-12 15:56:46.278677", - "modified_by": "Administrator", + "modified": "2020-05-31 22:30:22.330366", + "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", "owner": "Administrator", "permissions": [ { - "create": 1, "email": 1, "export": 1, "print": 1, "read": 1, "report": 1, "role": "All", - "share": 1, - "write": 1 + "share": 1 } ], "sort_field": "modified", From bc7ffcab21a2a8f66a57b845bf996f72c2e47c10 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 22:37:37 +0530 Subject: [PATCH 076/110] hide sidebar in notification log --- frappe/desk/doctype/notification_log/notification_log.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index 9e7e7a941f..050bf85ead 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -117,9 +117,10 @@ "show_seconds": 1 } ], + "hide_toolbar": 1, "in_create": 1, "links": [], - "modified": "2020-05-31 22:30:22.330366", + "modified": "2020-05-31 22:31:12.886950", "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", From 73d3d1f409cdc035ea71d13c56d38151addacd65 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 22:43:14 +0530 Subject: [PATCH 077/110] fix: pass attached_doc to doc --- frappe/desk/doctype/notification_log/notification_log.py | 6 ------ frappe/email/doctype/notification/notification.py | 6 +++--- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 90ae5a437e..f9f7e5967c 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -61,10 +61,6 @@ def enqueue_create_notification(users, doc): def make_notification_logs(doc, users): from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled - attachment = None - if doc.attachment: - attachment = doc.pop('attachment') - for user in users: if frappe.db.exists('User', user): if is_notifications_enabled(user): @@ -76,8 +72,6 @@ def make_notification_logs(doc, users): _doc.for_user = user _doc.subject = _doc.subject.replace('
    ', '').replace('
    ', '') if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': - if attachment: - _doc.attached_file = json.dumps(attachment) _doc.insert(ignore_permissions=True) def send_notification_email(doc): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 06a76a2005..da8c247d9c 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -127,7 +127,7 @@ def get_context(context): self.send_a_slack_msg(doc, context) if self.channel == 'System Notification' or self.send_system_notification: - self.create_drodown_notification(doc, context) + self.create_system_notification(doc, context) if self.set_property_after_alert: allow_update = True @@ -147,7 +147,7 @@ def get_context(context): except Exception: frappe.log_error(title='Document update failed', message=frappe.get_traceback()) - def create_drodown_notification(self, doc, context): + def create_system_notification(self, doc, context): subject = self.subject if "{" in subject: subject = frappe.render_template(self.subject, context) @@ -162,7 +162,7 @@ def get_context(context): 'document_name': doc.name, 'subject': subject, 'email_content': frappe.render_template(self.message, context), - 'attachment': attachments and attachments[0] + 'attached_file': attachments and json.dumps(attachments[0]) } enqueue_create_notification(users, notification_doc) From e6f9e7fe39cd15ccb01e458510b2a69dd898cace Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 22:45:56 +0530 Subject: [PATCH 078/110] fix: unused import --- frappe/desk/doctype/notification_log/notification_log.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index f9f7e5967c..211b3ae5e6 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe from frappe import _ -import json from frappe.model.document import Document from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value) From b8a3f53c3c24797ff7584c962f17268b5c2b37a4 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 22:48:27 +0530 Subject: [PATCH 079/110] fix: fix translation syntax --- frappe/email/doctype/notification/notification.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index da8c247d9c..8e53b50fa2 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -251,8 +251,7 @@ def get_context(context): # ignoring attachment as draft and cancelled documents are not allowed to print status = "Draft" if doc.docstatus == 0 else "Cancelled" - frappe.throw(_("""Not allowed to attach {0} document, - please enable Allow Print For {0} in Print Settings""").format(status), + frappe.throw(_("""Not allowed to attach {0} document, please enable Allow Print For {0} in Print Settings""").format(status), title=_("Error in Notification")) else: return [{ From c8928aa774850575c8e5115c8f6fd08216b2130f Mon Sep 17 00:00:00 2001 From: Mathieu Brunot Date: Mon, 1 Jun 2020 06:44:04 +0200 Subject: [PATCH 080/110] fix: File upload fallback (#10163) Signed-off-by: mathieu.brunot --- frappe/public/js/frappe/socketio_client.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/socketio_client.js b/frappe/public/js/frappe/socketio_client.js index 9983a35779..1411b6289d 100644 --- a/frappe/public/js/frappe/socketio_client.js +++ b/frappe/public/js/frappe/socketio_client.js @@ -287,7 +287,8 @@ frappe.socketio.SocketIOUploader = class SocketIOUploader { } function fallback_required() { - return !frappe.boot.sysdefaults.use_socketio_to_upload_file || !frappe.socketio.socket.connected; + return !frappe.socketio.socket.connected + || !( !frappe.boot.sysdefaults || frappe.boot.sysdefaults.use_socketio_to_upload_file ); } if (fallback_required()) { From 53e2e60cabdf22828bd483160f276b1c93e5d8d1 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 20 May 2020 19:28:52 +0530 Subject: [PATCH 081/110] feat: filter for current timespan --- frappe/model/db_query.py | 14 ++++++++++-- frappe/public/js/frappe/ui/filters/filter.js | 13 ++++++----- frappe/utils/data.py | 23 +++++++++++++++----- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 596aa18b09..5317e15362 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -16,7 +16,8 @@ import frappe, json, copy, re from frappe.model import optional_fields from frappe.client import check_parent_permission from frappe.model.utils.user_settings import get_user_settings, update_user_settings -from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, nowdate +from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, nowdate,\ + get_first_day, get_first_day_of_week, get_quarter_start, get_year_start from frappe.model.meta import get_table_columns class DatabaseQuery(object): @@ -426,7 +427,7 @@ class DatabaseQuery(object): if df and df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"): can_be_null = False - if f.operator.lower() in ('previous', 'next'): + if f.operator.lower() in ('previous', 'next', 'current'): if f.operator.lower() == "previous": if f.value == "1 week": date_range = [add_to_date(nowdate(), days=-7), nowdate()] @@ -438,6 +439,15 @@ class DatabaseQuery(object): date_range = [add_to_date(nowdate(), months=-6), nowdate()] elif f.value == "1 year": date_range = [add_to_date(nowdate(), years=-1), nowdate()] + elif f.operator.lower() == "current": + if f.value == "1 week": + date_range = [get_first_day_of_week(nowdate(), as_str=True), nowdate()] + elif f.value == "1 month": + date_range = [get_first_day(nowdate(), as_str=True), nowdate()] + elif f.value == "3 months": + date_range = [get_quarter_start(nowdate(), as_str=True), nowdate()] + elif f.value == "1 year": + date_range = [get_year_start(nowdate(), as_str=True), nowdate()] elif f.operator.lower() == "next": if f.value == "1 week": date_range = [nowdate(), add_to_date(nowdate(), days=7)] diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 818612d442..25ef982c6e 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -20,6 +20,7 @@ frappe.ui.Filter = class { ["<=", "<="], ["Between", __("Between")], ["Previous", __("Previous")], + ["Current", __("Current")], ["Next", __("Next")] ]; @@ -35,11 +36,11 @@ frappe.ui.Filter = class { this.invalid_condition_map = { Date: ['like', 'not like'], Datetime: ['like', 'not like'], - Data: ['Between', 'Previous', 'Next'], - Select: ['like', 'not like', 'Between', 'Previous', 'Next'], - Link: ["Between", 'Previous', 'Next', '>', '<', '>=', '<='], - Currency: ["Between", 'Previous', 'Next'], - Color: ["Between", 'Previous', 'Next'], + Data: ['Between', 'Previous', 'Current', 'Next'], + Select: ['like', 'not like', 'Between', 'Previous', 'Current', 'Next'], + Link: ["Between", 'Previous', 'Current', 'Next', '>', '<', '>=', '<='], + Currency: ["Between", 'Previous', 'Current', 'Next'], + Color: ["Between", 'Previous', 'Current', 'Next'], Check: this.conditions.map(c => c[0]).filter(c => c !== '=') }; this.make(); @@ -203,7 +204,7 @@ frappe.ui.Filter = class { this.fieldselect.selected_doctype = doctype; this.fieldselect.selected_fieldname = fieldname; - if(["Previous", "Next"].includes(condition) && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { + if(["Previous", "Current", "Next"].includes(condition) && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { df.fieldtype = 'Select'; df.options = [ { diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 7e991f472e..4e85e599e0 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -174,7 +174,7 @@ def nowtime(): """return current time in hh:mm""" return now_datetime().strftime(TIME_FORMAT) -def get_first_day(dt, d_years=0, d_months=0): +def get_first_day(dt, d_years=0, d_months=0, as_str=False): """ Returns the first day of the month for the date specified by date object Also adds `d_years` and `d_months` if specified @@ -185,10 +185,23 @@ def get_first_day(dt, d_years=0, d_months=0): overflow_years, month = divmod(dt.month + d_months - 1, 12) year = dt.year + d_years + overflow_years - return datetime.date(year, month + 1, 1) + return datetime.date(year, month + 1, 1).strftime(DATE_FORMAT) if as_str else datetime.date(year, month + 1, 1) -def get_first_day_of_week(dt): - return dt - datetime.timedelta(days=dt.weekday()) +def get_quarter_start(dt, as_str=False): + date = getdate(dt) + quarter = (date.month - 1) // 3 + 1 + first_date_of_quarter = datetime.date(date.year, ((quarter - 1) * 3) + 1, 1) + return first_date_of_quarter.strftime(DATE_FORMAT) if as_str else first_date_of_quarter + +def get_first_day_of_week(dt, as_str=False): + dt = getdate(dt) + date = dt - datetime.timedelta(days=dt.weekday()) + return date.strftime(DATE_FORMAT) if as_str else date + +def get_year_start(dt, as_str=False): + dt = getdate(dt) + date = datetime.date(dt.year, 1, 1) + return date.strftime(DATE_FORMAT) if as_str else date def get_last_day_of_week(dt): dt = get_first_day_of_week(dt) @@ -1033,7 +1046,7 @@ def get_filter(doctype, f): f.operator = "=" valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "is", - "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "previous", "next") + "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "previous", "current", "next") if f.operator.lower() not in valid_operators: frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators))) From 4c16b2a6087f6049afc75e21e247f908d533017a Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 22 May 2020 15:10:51 +0530 Subject: [PATCH 082/110] feat: api to get additional filters --- frappe/boot.py | 8 ++++ frappe/model/db_query.py | 20 ++++++-- frappe/public/js/frappe/list/base_list.js | 5 ++ frappe/public/js/frappe/ui/filters/filter.js | 47 +++++++++++++++++-- .../js/frappe/ui/filters/filter_list.js | 5 +- frappe/utils/data.py | 9 +++- 6 files changed, 83 insertions(+), 11 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 695a4d754b..92c81d808e 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -85,6 +85,7 @@ def get_bootinfo(): bootinfo.points = get_energy_points(frappe.session.user) bootinfo.frequently_visited_links = frequently_visited_links() bootinfo.link_preview_doctypes = get_link_preview_doctypes() + bootinfo.filters_config = get_filters_config() return bootinfo @@ -297,3 +298,10 @@ def get_link_preview_doctypes(): link_preview_doctypes.append(custom.doc_type) return link_preview_doctypes + +def get_filters_config(): + filter_config = frappe._dict() + filter_hooks = frappe.get_hooks('filters_config') + for hook in filter_hooks: + filter_config.update(frappe.get_attr(hook)()) + return filter_config diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 5317e15362..5ee4e21d34 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -355,7 +355,9 @@ class DatabaseQuery(object): ifnull(`tabDocType`.`fieldname`, fallback) operator "value" """ - f = get_filter(self.doctype, f) + from frappe.boot import get_filters_config + additional_filters_config = get_filters_config() + f = get_filter(self.doctype, f, additional_filters_config) tname = ('`tab' + f.doctype + '`') if not tname in self.tables: @@ -369,7 +371,9 @@ class DatabaseQuery(object): can_be_null = True - # prepare in condition + if f.operator.lower() in additional_filters_config: + f.update(get_additional_filter_field(additional_filters_config, f, f.value)) + if f.operator.lower() in ('ancestors of', 'descendants of', 'not ancestors of', 'not descendants of'): values = f.value or '' @@ -853,4 +857,14 @@ def get_between_date_filter(value, df=None): frappe.db.format_date(from_date), frappe.db.format_date(to_date)) - return data \ No newline at end of file + return data + +def get_additional_filter_field(additional_filters_config, f, value): + additional_filter = additional_filters_config[f.operator.lower()] + f = frappe._dict(frappe.get_attr(additional_filter['get_field'])()) + if f.query_value: + for option in f.options: + option = frappe._dict(option) + if option.value == value: + f.value = option.query_value + return f \ No newline at end of file diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 15f77fada5..cb1d705018 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -338,6 +338,11 @@ frappe.views.BaseList = class BaseList { : []; } + get_filter_value(fieldname) { + return this.get_filters_for_args().filter(f=> f[1] == fieldname)[0] && + this.get_filters_for_args().filter(f=> f[1] == fieldname)[0][3]; + } + get_args() { return { doctype: this.doctype, diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 25ef982c6e..2b4252cdca 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -6,6 +6,12 @@ frappe.ui.Filter = class { } this.utils = frappe.ui.filter_utils; + this.set_conditions(); + this.set_conditions_from_config(); + this.make(); + } + + set_conditions() { this.conditions = [ ["=", __("Equals")], ["!=", __("Not Equals")], @@ -43,10 +49,21 @@ frappe.ui.Filter = class { Color: ["Between", 'Previous', 'Current', 'Next'], Check: this.conditions.map(c => c[0]).filter(c => c !== '=') }; - this.make(); - this.make_select(); - this.set_events(); - this.setup(); + } + + set_conditions_from_config() { + if (frappe.boot.filters_config) { + this.filters_config = frappe.boot.filters_config; + for (let key of Object.keys(this.filters_config)) { + const filter = this.filters_config[key]; + this.conditions.push([key, __(`{0}`, [filter.label])]); + for (let fieldtype of Object.keys(this.invalid_condition_map)) { + if (!filter.fieldtypes.includes(fieldtype)) { + this.invalid_condition_map[fieldtype].push(filter.label); + } + } + } + } } make() { @@ -54,6 +71,10 @@ frappe.ui.Filter = class { conditions: this.conditions })) .appendTo(this.parent.find('.filter-edit-area')); + + this.make_select(); + this.set_events(); + this.setup(); } make_select() { @@ -121,6 +142,7 @@ frappe.ui.Filter = class { } freeze() { + console.log('freeze here') this.update_filter_tag(); } @@ -230,7 +252,22 @@ frappe.ui.Filter = class { ]; } - this.make_field(df, cur.fieldtype); + if (this.filters_config[condition] && this.filters_config[condition].fieldtypes.includes(this.field.df.fieldtype)) { + let args = {}; + if (this.filters_config[condition].depends_on) { + const field_name = this.filters_config[condition].depends_on; + const filter_value = this.base_list.get_filter_value(field_name); + args[field_name] = filter_value; + } + frappe.xcall(this.filters_config[condition].get_field, args).then(field => { + df.fieldtype = field.fieldtype; + df.options = field.options; + df.fieldname = fieldname; + this.make_field(df, cur.fieldtype); + }); + } else { + this.make_field(df, cur.fieldtype); + } } make_field(df, old_fieldtype) { diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index db6398ca78..759ae6fafa 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -103,7 +103,8 @@ frappe.ui.FilterGroup = class { }, filter_items: (doctype, fieldname) => { return !this.filter_exists([doctype, fieldname]); - } + }, + base_list: this.base_list }; let filter = new frappe.ui.Filter(args); this.filters.push(filter); @@ -132,7 +133,7 @@ frappe.ui.FilterGroup = class { get_filters() { return this.filters.filter(f => f.field).map(f => { - f.freeze(); + // f.freeze(); return f.get_value(); }); // {}: this.list.update_standard_filters(values); diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 4e85e599e0..b9b6c962e5 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1011,7 +1011,7 @@ def compare(val1, condition, val2): return ret -def get_filter(doctype, f): +def get_filter(doctype, f, filters_config=None): """Returns a _dict like { @@ -1047,6 +1047,13 @@ def get_filter(doctype, f): valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "is", "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "previous", "current", "next") + + if filters_config: + additional_operators = [] + for key in filters_config: + additional_operators.append(key.lower()) + valid_operators = tuple(set(valid_operators + tuple(additional_operators))) + if f.operator.lower() not in valid_operators: frappe.throw(frappe._("Operator must be one of {0}").format(", ".join(valid_operators))) From f0d62d4007eabd80054e30d95672ff1aa12830f8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 22 May 2020 15:46:01 +0530 Subject: [PATCH 083/110] fix: all periods in timespan filter --- frappe/model/db_query.py | 68 ++++++++++---------- frappe/public/js/frappe/ui/filters/filter.js | 57 ++++++++-------- frappe/utils/data.py | 2 +- 3 files changed, 62 insertions(+), 65 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 5ee4e21d34..9cf1fb8a4b 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -431,38 +431,8 @@ class DatabaseQuery(object): if df and df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"): can_be_null = False - if f.operator.lower() in ('previous', 'next', 'current'): - if f.operator.lower() == "previous": - if f.value == "1 week": - date_range = [add_to_date(nowdate(), days=-7), nowdate()] - elif f.value == "1 month": - date_range = [add_to_date(nowdate(), months=-1), nowdate()] - elif f.value == "3 months": - date_range = [add_to_date(nowdate(), months=-3), nowdate()] - elif f.value == "6 months": - date_range = [add_to_date(nowdate(), months=-6), nowdate()] - elif f.value == "1 year": - date_range = [add_to_date(nowdate(), years=-1), nowdate()] - elif f.operator.lower() == "current": - if f.value == "1 week": - date_range = [get_first_day_of_week(nowdate(), as_str=True), nowdate()] - elif f.value == "1 month": - date_range = [get_first_day(nowdate(), as_str=True), nowdate()] - elif f.value == "3 months": - date_range = [get_quarter_start(nowdate(), as_str=True), nowdate()] - elif f.value == "1 year": - date_range = [get_year_start(nowdate(), as_str=True), nowdate()] - elif f.operator.lower() == "next": - if f.value == "1 week": - date_range = [nowdate(), add_to_date(nowdate(), days=7)] - elif f.value == "1 month": - date_range = [nowdate(), add_to_date(nowdate(), months=1)] - elif f.value == "3 months": - date_range = [nowdate(), add_to_date(nowdate(), months=3)] - elif f.value == "6 months": - date_range = [nowdate(), add_to_date(nowdate(), months=6)] - elif f.value == "1 year": - date_range = [nowdate(), add_to_date(nowdate(), years=1)] + if f.operator.lower() == 'timespan': + date_range = get_timespan_date_range(f.value) f.operator = "Between" f.value = date_range fallback = "'0001-01-01 00:00:00'" @@ -867,4 +837,36 @@ def get_additional_filter_field(additional_filters_config, f, value): option = frappe._dict(option) if option.value == value: f.value = option.query_value - return f \ No newline at end of file + return f + +def get_timespan_date_range(period): + if period == "last week": + date_range = [add_to_date(nowdate(), days=-7), nowdate()] + elif period == "last month": + date_range = [add_to_date(nowdate(), months=-1), nowdate()] + elif period == "last quarter": + date_range = [add_to_date(nowdate(), months=-3), nowdate()] + elif period == "last 6 months": + date_range = [add_to_date(nowdate(), months=-6), nowdate()] + elif period == "last year": + date_range = [add_to_date(nowdate(), years=-1), nowdate()] + elif period == "this week": + date_range = [get_first_day_of_week(nowdate(), as_str=True), nowdate()] + elif period == "this month": + date_range = [get_first_day(nowdate(), as_str=True), nowdate()] + elif period == "this quarter": + date_range = [get_quarter_start(nowdate(), as_str=True), nowdate()] + elif period == "this year": + date_range = [get_year_start(nowdate(), as_str=True), nowdate()] + elif period == "next week": + date_range = [nowdate(), add_to_date(nowdate(), days=7)] + elif period == "next month": + date_range = [nowdate(), add_to_date(nowdate(), months=1)] + elif period == "next quarter": + date_range = [nowdate(), add_to_date(nowdate(), months=3)] + elif period == "next 6 months": + date_range = [nowdate(), add_to_date(nowdate(), months=6)] + elif period == "next year": + date_range = [nowdate(), add_to_date(nowdate(), years=1)] + + return date_range \ No newline at end of file diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 2b4252cdca..1fcac04b88 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -25,9 +25,7 @@ frappe.ui.Filter = class { [">=", ">="], ["<=", "<="], ["Between", __("Between")], - ["Previous", __("Previous")], - ["Current", __("Current")], - ["Next", __("Next")] + ["Timespan", __("Timespan")], ]; this.nested_set_conditions = [ @@ -42,11 +40,11 @@ frappe.ui.Filter = class { this.invalid_condition_map = { Date: ['like', 'not like'], Datetime: ['like', 'not like'], - Data: ['Between', 'Previous', 'Current', 'Next'], - Select: ['like', 'not like', 'Between', 'Previous', 'Current', 'Next'], - Link: ["Between", 'Previous', 'Current', 'Next', '>', '<', '>=', '<='], - Currency: ["Between", 'Previous', 'Current', 'Next'], - Color: ["Between", 'Previous', 'Current', 'Next'], + Data: ['Between', 'Timespan'], + Select: ['like', 'not like', 'Between', 'Timespan'], + Link: ["Between", 'Timespan', '>', '<', '>=', '<='], + Currency: ["Between", 'Timespan'], + Color: ["Between", 'Timespan'], Check: this.conditions.map(c => c[0]).filter(c => c !== '=') }; } @@ -226,30 +224,9 @@ frappe.ui.Filter = class { this.fieldselect.selected_doctype = doctype; this.fieldselect.selected_fieldname = fieldname; - if(["Previous", "Current", "Next"].includes(condition) && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { + if(condition == 'Timespan' && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { df.fieldtype = 'Select'; - df.options = [ - { - label: __('1 week'), - value: '1 week' - }, - { - label: __('1 month'), - value: '1 month' - }, - { - label: __('3 months'), - value: '3 months' - }, - { - label: __('6 months'), - value: '6 months' - }, - { - label: __('1 year'), - value: '1 year' - } - ]; + df.options = this.utils.get_timespan_options(['Last', 'This', 'Next']);c } if (this.filters_config[condition] && this.filters_config[condition].fieldtypes.includes(this.field.df.fieldtype)) { @@ -485,5 +462,23 @@ frappe.ui.filter_utils = { { label: __('Not Set'), value: 'not set' }, ]; } + }, + + get_timespan_options(periods) { + const period_map = { + 'Last': ['Week', 'Month', 'Quarter', '6 months', 'Year'], + 'This': ['Week', 'Month', 'Quarter', 'Year'], + 'Next': ['Week', 'Month', 'Quarter', '6 months', 'Year'] + } + let options = []; + periods.forEach(period => { + period_map[period].forEach(p => { + options.push({ + label: __(`{0} {1}`, [period, p]), + value: `${period.toLowerCase()} ${p.toLowerCase()}`, + }); + }); + }); + return options; } }; diff --git a/frappe/utils/data.py b/frappe/utils/data.py index b9b6c962e5..ec83dbe7a0 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1046,7 +1046,7 @@ def get_filter(doctype, f, filters_config=None): f.operator = "=" valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "is", - "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "previous", "current", "next") + "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "timespan") if filters_config: additional_operators = [] From 2fcd8c29054880143d8b9a0cb61932ef8f7ec5ce Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 22 May 2020 15:49:08 +0530 Subject: [PATCH 084/110] fix: move timespan function to data.py --- frappe/model/db_query.py | 36 ++---------------------------------- frappe/utils/data.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 34 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 9cf1fb8a4b..9939b39abb 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -16,8 +16,7 @@ import frappe, json, copy, re from frappe.model import optional_fields from frappe.client import check_parent_permission from frappe.model.utils.user_settings import get_user_settings, update_user_settings -from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, nowdate,\ - get_first_day, get_first_day_of_week, get_quarter_start, get_year_start +from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, nowdate, get_timespan_date_range from frappe.model.meta import get_table_columns class DatabaseQuery(object): @@ -374,6 +373,7 @@ class DatabaseQuery(object): if f.operator.lower() in additional_filters_config: f.update(get_additional_filter_field(additional_filters_config, f, f.value)) + # prepare in condition if f.operator.lower() in ('ancestors of', 'descendants of', 'not ancestors of', 'not descendants of'): values = f.value or '' @@ -838,35 +838,3 @@ def get_additional_filter_field(additional_filters_config, f, value): if option.value == value: f.value = option.query_value return f - -def get_timespan_date_range(period): - if period == "last week": - date_range = [add_to_date(nowdate(), days=-7), nowdate()] - elif period == "last month": - date_range = [add_to_date(nowdate(), months=-1), nowdate()] - elif period == "last quarter": - date_range = [add_to_date(nowdate(), months=-3), nowdate()] - elif period == "last 6 months": - date_range = [add_to_date(nowdate(), months=-6), nowdate()] - elif period == "last year": - date_range = [add_to_date(nowdate(), years=-1), nowdate()] - elif period == "this week": - date_range = [get_first_day_of_week(nowdate(), as_str=True), nowdate()] - elif period == "this month": - date_range = [get_first_day(nowdate(), as_str=True), nowdate()] - elif period == "this quarter": - date_range = [get_quarter_start(nowdate(), as_str=True), nowdate()] - elif period == "this year": - date_range = [get_year_start(nowdate(), as_str=True), nowdate()] - elif period == "next week": - date_range = [nowdate(), add_to_date(nowdate(), days=7)] - elif period == "next month": - date_range = [nowdate(), add_to_date(nowdate(), months=1)] - elif period == "next quarter": - date_range = [nowdate(), add_to_date(nowdate(), months=3)] - elif period == "next 6 months": - date_range = [nowdate(), add_to_date(nowdate(), months=6)] - elif period == "next year": - date_range = [nowdate(), add_to_date(nowdate(), years=1)] - - return date_range \ No newline at end of file diff --git a/frappe/utils/data.py b/frappe/utils/data.py index ec83dbe7a0..4741e304f5 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -373,6 +373,38 @@ def get_weekday(datetime=None): weekdays = get_weekdays() return weekdays[datetime.weekday()] +def get_timespan_date_range(period): + if period == "last week": + date_range = [add_to_date(nowdate(), days=-7), nowdate()] + elif period == "last month": + date_range = [add_to_date(nowdate(), months=-1), nowdate()] + elif period == "last quarter": + date_range = [add_to_date(nowdate(), months=-3), nowdate()] + elif period == "last 6 months": + date_range = [add_to_date(nowdate(), months=-6), nowdate()] + elif period == "last year": + date_range = [add_to_date(nowdate(), years=-1), nowdate()] + elif period == "this week": + date_range = [get_first_day_of_week(nowdate(), as_str=True), nowdate()] + elif period == "this month": + date_range = [get_first_day(nowdate(), as_str=True), nowdate()] + elif period == "this quarter": + date_range = [get_quarter_start(nowdate(), as_str=True), nowdate()] + elif period == "this year": + date_range = [get_year_start(nowdate(), as_str=True), nowdate()] + elif period == "next week": + date_range = [nowdate(), add_to_date(nowdate(), days=7)] + elif period == "next month": + date_range = [nowdate(), add_to_date(nowdate(), months=1)] + elif period == "next quarter": + date_range = [nowdate(), add_to_date(nowdate(), months=3)] + elif period == "next 6 months": + date_range = [nowdate(), add_to_date(nowdate(), months=6)] + elif period == "next year": + date_range = [nowdate(), add_to_date(nowdate(), years=1)] + + return date_range + def global_date_format(date, format="long"): """returns localized date in the form of January 1, 2012""" date = getdate(date) From 6495ee24266b93809c2e697231aeee29f3809e1e Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 22 May 2020 22:24:00 +0530 Subject: [PATCH 085/110] style: fix formatting --- frappe/model/db_query.py | 2 +- frappe/public/js/frappe/ui/filters/filter.js | 7 +++---- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 9939b39abb..507c503fec 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -16,7 +16,7 @@ import frappe, json, copy, re from frappe.model import optional_fields from frappe.client import check_parent_permission from frappe.model.utils.user_settings import get_user_settings, update_user_settings -from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, nowdate, get_timespan_date_range +from frappe.utils import flt, cint, get_time, make_filter_tuple, get_filter, add_to_date, cstr, get_timespan_date_range from frappe.model.meta import get_table_columns class DatabaseQuery(object): diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 1fcac04b88..eee9694ed8 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -140,7 +140,6 @@ frappe.ui.Filter = class { } freeze() { - console.log('freeze here') this.update_filter_tag(); } @@ -224,9 +223,9 @@ frappe.ui.Filter = class { this.fieldselect.selected_doctype = doctype; this.fieldselect.selected_fieldname = fieldname; - if(condition == 'Timespan' && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { + if (condition == 'Timespan' && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { df.fieldtype = 'Select'; - df.options = this.utils.get_timespan_options(['Last', 'This', 'Next']);c + df.options = this.utils.get_timespan_options(['Last', 'This', 'Next']); } if (this.filters_config[condition] && this.filters_config[condition].fieldtypes.includes(this.field.df.fieldtype)) { @@ -469,7 +468,7 @@ frappe.ui.filter_utils = { 'Last': ['Week', 'Month', 'Quarter', '6 months', 'Year'], 'This': ['Week', 'Month', 'Quarter', 'Year'], 'Next': ['Week', 'Month', 'Quarter', '6 months', 'Year'] - } + }; let options = []; periods.forEach(period => { period_map[period].forEach(p => { From 4b2d730a8cbc6c989661417131a6357b9002ea49 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 25 May 2020 16:20:01 +0530 Subject: [PATCH 086/110] fix: variable naming --- frappe/boot.py | 5 +++-- frappe/model/db_query.py | 4 ++-- frappe/public/js/frappe/ui/filters/filter.js | 9 +++++---- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 92c81d808e..739ec77ee7 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -85,7 +85,7 @@ def get_bootinfo(): bootinfo.points = get_energy_points(frappe.session.user) bootinfo.frequently_visited_links = frequently_visited_links() bootinfo.link_preview_doctypes = get_link_preview_doctypes() - bootinfo.filters_config = get_filters_config() + bootinfo.additional_filters_config = get_additional_filters_config() return bootinfo @@ -299,9 +299,10 @@ def get_link_preview_doctypes(): return link_preview_doctypes -def get_filters_config(): +def get_additional_filters_config(): filter_config = frappe._dict() filter_hooks = frappe.get_hooks('filters_config') for hook in filter_hooks: filter_config.update(frappe.get_attr(hook)()) + return filter_config diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 507c503fec..72af05c09e 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -354,8 +354,8 @@ class DatabaseQuery(object): ifnull(`tabDocType`.`fieldname`, fallback) operator "value" """ - from frappe.boot import get_filters_config - additional_filters_config = get_filters_config() + from frappe.boot import get_additional_filters_config + additional_filters_config = get_additional_filters_config() f = get_filter(self.doctype, f, additional_filters_config) tname = ('`tab' + f.doctype + '`') diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index eee9694ed8..2eb2d1156e 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -50,13 +50,13 @@ frappe.ui.Filter = class { } set_conditions_from_config() { - if (frappe.boot.filters_config) { - this.filters_config = frappe.boot.filters_config; + if (frappe.boot.additional_filters_config) { + this.filters_config = frappe.boot.additional_filters_config; for (let key of Object.keys(this.filters_config)) { const filter = this.filters_config[key]; this.conditions.push([key, __(`{0}`, [filter.label])]); for (let fieldtype of Object.keys(this.invalid_condition_map)) { - if (!filter.fieldtypes.includes(fieldtype)) { + if (!filter.valid_for_fieldtypes.includes(fieldtype)) { this.invalid_condition_map[fieldtype].push(filter.label); } } @@ -228,7 +228,8 @@ frappe.ui.Filter = class { df.options = this.utils.get_timespan_options(['Last', 'This', 'Next']); } - if (this.filters_config[condition] && this.filters_config[condition].fieldtypes.includes(this.field.df.fieldtype)) { + if (this.filters_config[condition] + && this.filters_config[condition].valid_for_fieldtypes.includes(this.field.df.fieldtype)) { let args = {}; if (this.filters_config[condition].depends_on) { const field_name = this.filters_config[condition].depends_on; From 548813af69ff01ca3a00220ec642b7cb71af56ab Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 25 May 2020 17:05:20 +0530 Subject: [PATCH 087/110] fix: fieldtype doesn't change on condition change --- frappe/public/js/frappe/ui/filters/filter.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index 2eb2d1156e..ead4c4110c 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -223,13 +223,8 @@ frappe.ui.Filter = class { this.fieldselect.selected_doctype = doctype; this.fieldselect.selected_fieldname = fieldname; - if (condition == 'Timespan' && ['Date', 'Datetime', 'DateRange', 'Select'].includes(this.field.df.fieldtype)) { - df.fieldtype = 'Select'; - df.options = this.utils.get_timespan_options(['Last', 'This', 'Next']); - } - if (this.filters_config[condition] - && this.filters_config[condition].valid_for_fieldtypes.includes(this.field.df.fieldtype)) { + && this.filters_config[condition].valid_for_fieldtypes.includes(df.fieldtype)) { let args = {}; if (this.filters_config[condition].depends_on) { const field_name = this.filters_config[condition].depends_on; @@ -455,6 +450,10 @@ frappe.ui.filter_utils = { if(condition == "Between" && (df.fieldtype == 'Date' || df.fieldtype == 'Datetime')){ df.fieldtype = 'DateRange'; } + if (condition == 'Timespan' && ['Date', 'Datetime', 'DateRange', 'Select'].includes(df.fieldtype)) { + df.fieldtype = 'Select'; + df.options = this.get_timespan_options(['Last', 'This', 'Next']); + } if (condition === 'is') { df.fieldtype = 'Select'; df.options = [ @@ -462,6 +461,7 @@ frappe.ui.filter_utils = { { label: __('Not Set'), value: 'not set' }, ]; } + return; }, get_timespan_options(periods) { From 2ebf67af39ef219b9b488db220e948cdafa3d96d Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 26 May 2020 19:26:25 +0530 Subject: [PATCH 088/110] fix: remove freeze --- frappe/public/js/frappe/ui/filters/filter_list.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 759ae6fafa..ed9ddefe64 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -133,7 +133,6 @@ frappe.ui.FilterGroup = class { get_filters() { return this.filters.filter(f => f.field).map(f => { - // f.freeze(); return f.get_value(); }); // {}: this.list.update_standard_filters(values); From 17bb72d2344bb8ee52f34144f4b538c88a7e3c88 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 26 May 2020 19:30:16 +0530 Subject: [PATCH 089/110] fix: check if filter config exists --- frappe/public/js/frappe/ui/filters/filter.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index ead4c4110c..b9d8188b17 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -223,7 +223,7 @@ frappe.ui.Filter = class { this.fieldselect.selected_doctype = doctype; this.fieldselect.selected_fieldname = fieldname; - if (this.filters_config[condition] + if (this.filters_config && this.filters_config[condition] && this.filters_config[condition].valid_for_fieldtypes.includes(df.fieldtype)) { let args = {}; if (this.filters_config[condition].depends_on) { From f7f2be59260a13db527dace1920ee7cc4d71ffff Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 26 May 2020 19:31:11 +0530 Subject: [PATCH 090/110] fix: rename function --- frappe/boot.py | 4 ++-- frappe/model/db_query.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/boot.py b/frappe/boot.py index 739ec77ee7..8862ce3c61 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -85,7 +85,7 @@ def get_bootinfo(): bootinfo.points = get_energy_points(frappe.session.user) bootinfo.frequently_visited_links = frequently_visited_links() bootinfo.link_preview_doctypes = get_link_preview_doctypes() - bootinfo.additional_filters_config = get_additional_filters_config() + bootinfo.additional_filters_config = get_additional_filters_from_hooks() return bootinfo @@ -299,7 +299,7 @@ def get_link_preview_doctypes(): return link_preview_doctypes -def get_additional_filters_config(): +def get_additional_filters_from_hooks(): filter_config = frappe._dict() filter_hooks = frappe.get_hooks('filters_config') for hook in filter_hooks: diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index 72af05c09e..caf993ed99 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -354,8 +354,8 @@ class DatabaseQuery(object): ifnull(`tabDocType`.`fieldname`, fallback) operator "value" """ - from frappe.boot import get_additional_filters_config - additional_filters_config = get_additional_filters_config() + from frappe.boot import get_additional_filters_from_hooks + additional_filters_config = get_additional_filters_from_hooks() f = get_filter(self.doctype, f, additional_filters_config) tname = ('`tab' + f.doctype + '`') From d9e8b7ef4f95b7a45866021bd8d893c54534b9f8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 10:56:04 +0530 Subject: [PATCH 091/110] fix: handle old date filters in query --- frappe/model/db_query.py | 21 +++++++++++++++++++++ frappe/utils/data.py | 3 ++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index caf993ed99..e3922e9555 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -431,6 +431,8 @@ class DatabaseQuery(object): if df and df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"): can_be_null = False + if f.operator.lower() == 'previous' or f.operator.lower() == 'next': + f = replace_old_date_filters(f) if f.operator.lower() == 'timespan': date_range = get_timespan_date_range(f.value) f.operator = "Between" @@ -838,3 +840,22 @@ def get_additional_filter_field(additional_filters_config, f, value): if option.value == value: f.value = option.query_value return f + +def replace_old_date_filters(f): + timespan_map = { + '1 week': 'week', + '1 month': 'month', + '3 months': 'quarter', + '6 months': '6 months', + '1 year': 'year', + } + + period_map = { + 'Previous': 'last', + 'Next': 'next' + } + + f.value = period_map[f.operator] + ' ' + timespan_map[f.value] + f.operator = 'Timespan' + + return f \ No newline at end of file diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 4741e304f5..c177236f1a 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1078,7 +1078,8 @@ def get_filter(doctype, f, filters_config=None): f.operator = "=" valid_operators = ("=", "!=", ">", "<", ">=", "<=", "like", "not like", "in", "not in", "is", - "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", "timespan") + "between", "descendants of", "ancestors of", "not descendants of", "not ancestors of", + "timespan", "previous", "next") if filters_config: additional_operators = [] From b6acba644670afe046eadd3cf255397a0a4c3ebf Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 10:57:05 +0530 Subject: [PATCH 092/110] fix(patch): patch to replace filters in user settings --- frappe/patches.txt | 1 + .../update_date_filters_in_user_settings.py | 54 +++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 frappe/patches/v13_0/update_date_filters_in_user_settings.py diff --git a/frappe/patches.txt b/frappe/patches.txt index d8923ca320..fb5bf447b7 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -287,3 +287,4 @@ execute:frappe.delete_doc("Web Template", "Section with Left Image", force=1) execute:frappe.delete_doc("DocType", "Onboarding Slide") execute:frappe.delete_doc("DocType", "Onboarding Slide Field") execute:frappe.delete_doc("DocType", "Onboarding Slide Help Link") +frappe.patches.v13_0.update_date_filters_in_user_settings diff --git a/frappe/patches/v13_0/update_date_filters_in_user_settings.py b/frappe/patches/v13_0/update_date_filters_in_user_settings.py new file mode 100644 index 0000000000..fa7e2d7a4f --- /dev/null +++ b/frappe/patches/v13_0/update_date_filters_in_user_settings.py @@ -0,0 +1,54 @@ +from __future__ import unicode_literals +import frappe, json +from frappe.model.utils.user_settings import update_user_settings, sync_user_settings + +def execute(): + from frappe.model.utils.user_settings import update_user_settings, sync_user_settings + users = frappe.db.sql("select distinct(user) from `__UserSettings`", as_dict=True) + + for user in users: + user_settings = frappe.db.sql(''' + select + * from `__UserSettings` + where + user="{user}" + '''.format(user = user.user), as_dict=True) + + for setting in user_settings: + data = frappe.parse_json(setting.get('data')) + if data: + for key in data: + update_user_setting_filters(data, key, setting) + + sync_user_settings() + + +def update_user_setting_filters(data, key, user_setting): + timespan_map = { + '1 week': 'week', + '1 month': 'month', + '3 months': 'quarter', + '6 months': '6 months', + '1 year': 'year', + } + + period_map = { + 'Previous': 'last', + 'Next': 'next' + } + + if data.get(key): + update = False + if isinstance(data.get(key), dict): + filters = data.get(key).get('filters') + for f in filters: + if f[2] == 'Next' or f[2] == 'Previous': + update = True + f[3] = period_map[f[2]] + ' ' + timespan_map[f[3]] + f[2] = 'Timespan' + + if update: + data[key]['filters'] = filters + update_user_settings(user_setting['doctype'], json.dumps(data), for_update=True) + + From 55cdd20fea69079a59945a5eb2e302ab6ada1824 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Wed, 27 May 2020 10:59:11 +0530 Subject: [PATCH 093/110] fix: use map to calculate period value Co-authored-by: Shivam Mishra --- frappe/utils/data.py | 47 ++++++++++++++++---------------------------- 1 file changed, 17 insertions(+), 30 deletions(-) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index c177236f1a..ad1c057133 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -374,37 +374,24 @@ def get_weekday(datetime=None): return weekdays[datetime.weekday()] def get_timespan_date_range(period): - if period == "last week": - date_range = [add_to_date(nowdate(), days=-7), nowdate()] - elif period == "last month": - date_range = [add_to_date(nowdate(), months=-1), nowdate()] - elif period == "last quarter": - date_range = [add_to_date(nowdate(), months=-3), nowdate()] - elif period == "last 6 months": - date_range = [add_to_date(nowdate(), months=-6), nowdate()] - elif period == "last year": - date_range = [add_to_date(nowdate(), years=-1), nowdate()] - elif period == "this week": - date_range = [get_first_day_of_week(nowdate(), as_str=True), nowdate()] - elif period == "this month": - date_range = [get_first_day(nowdate(), as_str=True), nowdate()] - elif period == "this quarter": - date_range = [get_quarter_start(nowdate(), as_str=True), nowdate()] - elif period == "this year": - date_range = [get_year_start(nowdate(), as_str=True), nowdate()] - elif period == "next week": - date_range = [nowdate(), add_to_date(nowdate(), days=7)] - elif period == "next month": - date_range = [nowdate(), add_to_date(nowdate(), months=1)] - elif period == "next quarter": - date_range = [nowdate(), add_to_date(nowdate(), months=3)] - elif period == "next 6 months": - date_range = [nowdate(), add_to_date(nowdate(), months=6)] - elif period == "next year": - date_range = [nowdate(), add_to_date(nowdate(), years=1)] - - return date_range + date_range_map = { + "last week": [add_to_date(nowdate(), days=-7), nowdate()], + "last month": [add_to_date(nowdate(), months=-1), nowdate()], + "last quarter": [add_to_date(nowdate(), months=-3), nowdate()], + "last 6 months": [add_to_date(nowdate(), months=-6), nowdate()], + "last year": [add_to_date(nowdate(), years=-1), nowdate()], + "this week": [get_first_day_of_week(nowdate(), as_str=True), nowdate()], + "this month": [get_first_day(nowdate(), as_str=True), nowdate()], + "this quarter": [get_quarter_start(nowdate(), as_str=True), nowdate()], + "this year": [get_year_start(nowdate(), as_str=True), nowdate()], + "next week": [nowdate(), add_to_date(nowdate(), days=7)], + "next month": [nowdate(), add_to_date(nowdate(), months=1)], + "next quarter": [nowdate(), add_to_date(nowdate(), months=3)], + "next 6 months": [nowdate(), add_to_date(nowdate(), months=6)], + "next year": [nowdate(), add_to_date(nowdate(), years=1)], + } + return date_range_map.get("period"); def global_date_format(date, format="long"): """returns localized date in the form of January 1, 2012""" date = getdate(date) From 3a55ba0d583c2ea1cb58a0aca4a177a9295e54b7 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 11:03:07 +0530 Subject: [PATCH 094/110] fix: missing newline --- frappe/public/js/frappe/list/base_list.js | 4 ++-- frappe/utils/data.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index cb1d705018..b94257106e 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -339,8 +339,8 @@ frappe.views.BaseList = class BaseList { } get_filter_value(fieldname) { - return this.get_filters_for_args().filter(f=> f[1] == fieldname)[0] && - this.get_filters_for_args().filter(f=> f[1] == fieldname)[0][3]; + return this.get_filters_for_args().filter(f => f[1] == fieldname)[0] && + this.get_filters_for_args().filter(f => f[1] == fieldname)[0][3]; } get_args() { diff --git a/frappe/utils/data.py b/frappe/utils/data.py index ad1c057133..185d63d78c 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -392,6 +392,7 @@ def get_timespan_date_range(period): } return date_range_map.get("period"); + def global_date_format(date, format="long"): """returns localized date in the form of January 1, 2012""" date = getdate(date) From 5eceaf59112804c9a33abb5712f45d10c9b846fc Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 27 May 2020 12:49:40 +0530 Subject: [PATCH 095/110] fix: remove unused import --- frappe/patches/v13_0/update_date_filters_in_user_settings.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/patches/v13_0/update_date_filters_in_user_settings.py b/frappe/patches/v13_0/update_date_filters_in_user_settings.py index fa7e2d7a4f..2d6c123064 100644 --- a/frappe/patches/v13_0/update_date_filters_in_user_settings.py +++ b/frappe/patches/v13_0/update_date_filters_in_user_settings.py @@ -3,7 +3,6 @@ import frappe, json from frappe.model.utils.user_settings import update_user_settings, sync_user_settings def execute(): - from frappe.model.utils.user_settings import update_user_settings, sync_user_settings users = frappe.db.sql("select distinct(user) from `__UserSettings`", as_dict=True) for user in users: From 1b691c799d172baad9b165f3574887470b7c4739 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 29 May 2020 15:10:56 +0530 Subject: [PATCH 096/110] refactor: refactor get_date_range --- frappe/model/db_query.py | 18 +++++++----------- frappe/utils/data.py | 4 ++-- 2 files changed, 9 insertions(+), 13 deletions(-) diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index e3922e9555..19517aa4a1 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -431,10 +431,8 @@ class DatabaseQuery(object): if df and df.fieldtype in ("Check", "Float", "Int", "Currency", "Percent"): can_be_null = False - if f.operator.lower() == 'previous' or f.operator.lower() == 'next': - f = replace_old_date_filters(f) - if f.operator.lower() == 'timespan': - date_range = get_timespan_date_range(f.value) + if f.operator.lower() in ('previous', 'next', 'timespan'): + date_range = get_date_range(f.operator.lower(), f.value) f.operator = "Between" f.value = date_range fallback = "'0001-01-01 00:00:00'" @@ -841,7 +839,7 @@ def get_additional_filter_field(additional_filters_config, f, value): f.value = option.query_value return f -def replace_old_date_filters(f): +def get_date_range(operator, value): timespan_map = { '1 week': 'week', '1 month': 'month', @@ -849,13 +847,11 @@ def replace_old_date_filters(f): '6 months': '6 months', '1 year': 'year', } - period_map = { - 'Previous': 'last', - 'Next': 'next' + 'previous': 'last', + 'next': 'next', } - f.value = period_map[f.operator] + ' ' + timespan_map[f.value] - f.operator = 'Timespan' + timespan = period_map[operator] + ' ' + timespan_map[value] if operator != 'timespan' else value - return f \ No newline at end of file + return get_timespan_date_range(timespan) \ No newline at end of file diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 185d63d78c..6f5a530c08 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -373,7 +373,7 @@ def get_weekday(datetime=None): weekdays = get_weekdays() return weekdays[datetime.weekday()] -def get_timespan_date_range(period): +def get_timespan_date_range(timespan): date_range_map = { "last week": [add_to_date(nowdate(), days=-7), nowdate()], "last month": [add_to_date(nowdate(), months=-1), nowdate()], @@ -391,7 +391,7 @@ def get_timespan_date_range(period): "next year": [nowdate(), add_to_date(nowdate(), years=1)], } - return date_range_map.get("period"); + return date_range_map.get(timespan) def global_date_format(date, format="long"): """returns localized date in the form of January 1, 2012""" From b291e0aaa993eb950d46eb84740127db55b30811 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 29 May 2020 15:25:05 +0530 Subject: [PATCH 097/110] feat: add timespan option for today --- frappe/public/js/frappe/ui/filters/filter.js | 20 ++++++++++++++------ frappe/utils/data.py | 1 + 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/ui/filters/filter.js b/frappe/public/js/frappe/ui/filters/filter.js index b9d8188b17..37eab50957 100644 --- a/frappe/public/js/frappe/ui/filters/filter.js +++ b/frappe/public/js/frappe/ui/filters/filter.js @@ -452,7 +452,7 @@ frappe.ui.filter_utils = { } if (condition == 'Timespan' && ['Date', 'Datetime', 'DateRange', 'Select'].includes(df.fieldtype)) { df.fieldtype = 'Select'; - df.options = this.get_timespan_options(['Last', 'This', 'Next']); + df.options = this.get_timespan_options(['Last', 'Today', 'This', 'Next']); } if (condition === 'is') { df.fieldtype = 'Select'; @@ -467,17 +467,25 @@ frappe.ui.filter_utils = { get_timespan_options(periods) { const period_map = { 'Last': ['Week', 'Month', 'Quarter', '6 months', 'Year'], + 'Today': null, 'This': ['Week', 'Month', 'Quarter', 'Year'], 'Next': ['Week', 'Month', 'Quarter', '6 months', 'Year'] }; let options = []; periods.forEach(period => { - period_map[period].forEach(p => { - options.push({ - label: __(`{0} {1}`, [period, p]), - value: `${period.toLowerCase()} ${p.toLowerCase()}`, + if (period_map[period]) { + period_map[period].forEach(p => { + options.push({ + label: __(`{0} {1}`, [period, p]), + value: `${period.toLowerCase()} ${p.toLowerCase()}`, + }); }); - }); + } else { + options.push({ + label: __(`{0}`, [period]), + value: `${period.toLowerCase()}`, + }); + } }); return options; } diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 6f5a530c08..1a4604ffff 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -380,6 +380,7 @@ def get_timespan_date_range(timespan): "last quarter": [add_to_date(nowdate(), months=-3), nowdate()], "last 6 months": [add_to_date(nowdate(), months=-6), nowdate()], "last year": [add_to_date(nowdate(), years=-1), nowdate()], + "today": [nowdate(), nowdate()], "this week": [get_first_day_of_week(nowdate(), as_str=True), nowdate()], "this month": [get_first_day(nowdate(), as_str=True), nowdate()], "this quarter": [get_quarter_start(nowdate(), as_str=True), nowdate()], From d50070d51351c8c249c7f4403e8d5d5f0595ef93 Mon Sep 17 00:00:00 2001 From: prssanna Date: Sun, 31 May 2020 13:06:28 +0530 Subject: [PATCH 098/110] fix: fix relative filters test --- ...{relative_filters.js => relative_time_filters.js} | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) rename cypress/integration/{relative_filters.js => relative_time_filters.js} (85%) diff --git a/cypress/integration/relative_filters.js b/cypress/integration/relative_time_filters.js similarity index 85% rename from cypress/integration/relative_filters.js rename to cypress/integration/relative_time_filters.js index 411ede62fa..ac70c44345 100644 --- a/cypress/integration/relative_filters.js +++ b/cypress/integration/relative_time_filters.js @@ -9,14 +9,14 @@ context('Relative Timeframe', () => { frappe.call("frappe.tests.ui_test_helpers.create_todo_records"); }); }); - it('set relative filter for Previous and check list', () => { + it('sets relative timespan filter for last week and filters list', () => { cy.visit('/desk#List/ToDo/List'); cy.get('.list-row:contains("this is fourth todo")').should('exist'); cy.get('.tag-filters-area .btn:contains("Add Filter")').click(); cy.get('.fieldname-select-area').should('exist'); cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 }); - cy.get('select.condition.form-control').select("Previous"); - cy.get('.filter-field select.input-with-feedback.form-control').select("1 week"); + cy.get('select.condition.form-control').select("Timespan"); + cy.get('.filter-field select.input-with-feedback.form-control').select("last week"); cy.server(); cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh'); cy.get('.filter-box .btn:contains("Apply")').click(); @@ -28,13 +28,13 @@ context('Relative Timeframe', () => { cy.get('.remove-filter.btn').click(); cy.wait('@save_user_settings'); }); - it('set relative filter for Next and check list', () => { + it('sets relative timespan filter for next week and filters list', () => { cy.visit('/desk#List/ToDo/List'); cy.get('.list-row:contains("this is fourth todo")').should('exist'); cy.get('.tag-filters-area .btn:contains("Add Filter")').click(); cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 }); - cy.get('select.condition.form-control').select("Next"); - cy.get('.filter-field select.input-with-feedback.form-control').select("1 week"); + cy.get('select.condition.form-control').select("Timespan"); + cy.get('.filter-field select.input-with-feedback.form-control').select("next week"); cy.server(); cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh'); cy.get('.filter-box .btn:contains("Apply")').click(); From 454a574d43782b5dd79e48633a4461241f351778 Mon Sep 17 00:00:00 2001 From: Prssanna Desai Date: Mon, 1 Jun 2020 12:19:04 +0530 Subject: [PATCH 099/110] fix: use frappe.as_json Co-authored-by: gavin --- frappe/model/rename_doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 24df3ab91b..f016b16d2b 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -127,7 +127,7 @@ def update_assignments(old, new, doctype): frappe.delete_doc('ToDo', todo.name) unique_assignments = list(set(old_assignments + new_assignments)) - frappe.db.set_value(doctype, new, '_assign', json.dumps(unique_assignments)) + frappe.db.set_value(doctype, new, '_assign', frappe.as_json(unique_assignments), indent=0) def update_user_settings(old, new, link_fields): ''' From f9b863706098c2a7658c617da4af004599e0254b Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 1 Jun 2020 12:20:34 +0530 Subject: [PATCH 100/110] fix: remove unused import --- frappe/model/rename_doc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 24df3ab91b..ce44b9f71f 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -3,7 +3,6 @@ from __future__ import unicode_literals, print_function import frappe -import json from frappe import _, bold from frappe.utils import cint from frappe.model.naming import validate_name From 2149283fd1ec45e26cbb0f7b0c97fca987487e36 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 1 Jun 2020 12:43:41 +0530 Subject: [PATCH 101/110] fix: raise SitNotSpecifiedError via get_site --- frappe/commands/__init__.py | 2 +- frappe/commands/utils.py | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 832a7eb415..42f4440547 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -48,7 +48,7 @@ def get_site(context): site = context.sites[0] return site except (IndexError, TypeError): - sys.exit(1) + raise frappe.SiteNotSpecifiedError def popen(command, *args, **kwargs): output = kwargs.get('output', True) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 869178ff3c..9bed7eb472 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -385,6 +385,8 @@ def mariadb(context): import os site = get_site(context) + if not site: + raise SiteNotSpecifiedError frappe.init(site=site) # This is assuming you're within the bench instance. From 5245119772c5cc44e6185c73848416967bebff22 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Mon, 1 Jun 2020 12:51:14 +0530 Subject: [PATCH 102/110] fix: translate label for open document counts --- frappe/public/js/frappe/ui/notifications/notifications.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index 64d19ce63f..3570420c81 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -153,12 +153,12 @@ frappe.ui.Notifications = class Notifications { let title = target ? `title="${__('Your Target')}"` : ''; let $list_item = !target ? $(`
  • - ${label} + ${__(label)} ${value}
  • `) : $(`
  • - ${label} + ${__(label)}
    From 07c15d0fe141a4067b610592ea471ad156800a0e Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 1 Jun 2020 18:42:40 +0530 Subject: [PATCH 103/110] fix: replace terrible code with bad one --- frappe/commands/scheduler.py | 7 ++++++- frappe/commands/site.py | 32 +++++++++++++++++++++++++++++++- frappe/commands/translate.py | 2 ++ frappe/commands/utils.py | 36 +++++++++++++++++++++++++----------- 4 files changed, 64 insertions(+), 13 deletions(-) diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index 6f51c81211..f730b2d776 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -30,6 +30,8 @@ def trigger_scheduler_event(context, event): frappe.utils.scheduler.trigger(site, event, now=True) finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('enable-scheduler') @pass_context @@ -45,6 +47,8 @@ def enable_scheduler(context): print("Enabled for", site) finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('disable-scheduler') @pass_context @@ -60,7 +64,8 @@ def disable_scheduler(context): print("Disabled for", site) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('scheduler') diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 163d25f183..8fcb7bf84a 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -217,6 +217,8 @@ def install_app(context, apps): _install_app(app, verbose=context.verbose) finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('list-apps') @pass_context @@ -246,7 +248,8 @@ def add_system_manager(context, email, first_name, last_name, send_welcome_email frappe.db.commit() finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('disable-user') @click.argument('email') @@ -277,6 +280,8 @@ def migrate(context, rebuild_website=False, skip_failing=False): migrate(context.verbose, rebuild_website=rebuild_website, skip_failing=skip_failing) finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError print("Compiling Python Files...") compileall.compile_dir('../apps', quiet=1, rx=re.compile('.*node_modules.*')) @@ -289,6 +294,8 @@ def migrate_to(context, frappe_provider): from frappe.integrations.frappe_providers import migrate_to for site in context.sites: migrate_to(site, frappe_provider) + if not context.sites: + raise SiteNotSpecifiedError @click.command('run-patch') @click.argument('module') @@ -303,6 +310,8 @@ def run_patch(context, module): frappe.modules.patch_handler.run_single(module, force=context.force) finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('reload-doc') @click.argument('module') @@ -319,6 +328,8 @@ def reload_doc(context, module, doctype, docname): frappe.db.commit() finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('reload-doctype') @click.argument('doctype') @@ -333,6 +344,8 @@ def reload_doctype(context, doctype): frappe.db.commit() finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('add-to-hosts') @pass_context @@ -340,6 +353,8 @@ def add_to_hosts(context): "Add site to hosts" for site in context.sites: frappe.commands.popen('echo 127.0.0.1\t{0} | sudo tee -a /etc/hosts'.format(site)) + if not context.sites: + raise SiteNotSpecifiedError @click.command('use') @click.argument('site') @@ -386,6 +401,9 @@ def backup(context, with_files=False, backup_path_db=None, backup_path_files=Non print("Private files: ", odb.backup_path_private_files) frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError + sys.exit(exit_code) @click.command('remove-from-installed-apps') @@ -401,6 +419,8 @@ def remove_from_installed_apps(context, app): remove_from_installed_apps(app) finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('uninstall-app') @click.argument('app') @@ -417,6 +437,8 @@ def uninstall(context, app, dry_run=False, yes=False): remove_app(app, dry_run, yes) finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('drop-site') @@ -508,6 +530,8 @@ def set_admin_password(context, admin_password, logout_all_sessions=False): admin_password = None finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('set-last-active-for-user') @click.option('--user', help="Setup last active date for user") @@ -553,6 +577,8 @@ def publish_realtime(context, event, message, room, user, doctype, docname, afte frappe.db.commit() finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('browse') @click.argument('site', required=False) @@ -580,6 +606,8 @@ def start_recording(context): for site in context.sites: frappe.init(site=site) frappe.recorder.start() + if not context.sites: + raise SiteNotSpecifiedError @click.command('stop-recording') @@ -588,6 +616,8 @@ def stop_recording(context): for site in context.sites: frappe.init(site=site) frappe.recorder.stop() + if not context.sites: + raise SiteNotSpecifiedError commands = [ diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index 5a48e2b409..334a4c1057 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -15,6 +15,8 @@ def build_message_files(context): frappe.translate.rebuild_all_translation_files() finally: frappe.destroy() + if not context.sites: + raise SiteNotSpecifiedError @click.command('new-language') #, help="Create lang-code.csv for given app") @pass_context diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 3610393d9a..702bddc58a 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -51,7 +51,8 @@ def clear_cache(context): frappe.website.render.clear_cache() finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('clear-website-cache') @pass_context @@ -65,7 +66,8 @@ def clear_website_cache(context): frappe.website.render.clear_cache() finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('destroy-all-sessions') @click.option('--reason') @@ -81,7 +83,8 @@ def destroy_all_sessions(context, reason=None): frappe.db.commit() finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('show-config') @pass_context @@ -117,7 +120,8 @@ def reset_perms(context): reset_perms(d) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('execute') @click.argument('method') @@ -164,6 +168,9 @@ def execute(context, method, args=None, kwargs=None, profile=False): if ret: print(json.dumps(ret, default=json_handler)) + if not context.sites: + raise SiteNotSpecifiedError + @click.command('add-to-email-queue') @click.argument('email-path') @@ -197,7 +204,8 @@ def export_doc(context, doctype, docname): frappe.modules.export_doc(doctype, docname) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('export-json') @click.argument('doctype') @@ -214,7 +222,8 @@ def export_json(context, doctype, path, name=None): data_import.export_json(doctype, path, name=name) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('export-csv') @click.argument('doctype') @@ -230,7 +239,8 @@ def export_csv(context, doctype, path): data_import.export_csv(doctype, path) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('export-fixtures') @click.option('--app', default=None, help='Export fixtures of a specific app') @@ -245,7 +255,8 @@ def export_fixtures(context, app=None): export_fixtures(app=app) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('import-doc') @click.argument('path') @@ -267,7 +278,8 @@ def import_doc(context, path, force=False): data_import.import_doc(path, overwrite=context.force) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('import-csv') @click.argument('path') @@ -577,7 +589,8 @@ def request(context, args=None, path=None): print(frappe.response) finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('make-app') @click.argument('destination') @@ -658,7 +671,8 @@ def rebuild_global_search(context, static_pages=False): finally: frappe.destroy() - + if not context.sites: + raise SiteNotSpecifiedError @click.command('auto-deploy') @click.argument('app') From 9d11f2c44fa379c539b6892662645dced9f07b5c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 1 Jun 2020 19:28:00 +0530 Subject: [PATCH 104/110] fix: redo improper merge commit --- frappe/__init__.py | 3 +-- frappe/commands/__init__.py | 9 ++++++--- frappe/commands/scheduler.py | 1 + frappe/commands/site.py | 3 ++- frappe/commands/translate.py | 1 + frappe/commands/utils.py | 3 +++ frappe/exceptions.py | 5 +++++ frappe/utils/bench_helper.py | 16 +++++++++------- 8 files changed, 28 insertions(+), 13 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index d917458510..8f36c0c4d3 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -231,9 +231,8 @@ def get_site_config(sites_path=None, site_path=None): if os.path.exists(site_config): config.update(get_file_json(site_config)) elif local.site and not local.flags.new_site: - print("{0} does not exist".format(local.site)) + print("Site {0} does not exist".format(local.site)) sys.exit(1) - #raise IncorrectSitePath, "{0} does not exist".format(site_config) return _dict(config) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 8110f2ec19..42f4440547 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -22,7 +22,11 @@ def pass_context(f): pr = cProfile.Profile() pr.enable() - ret = f(frappe._dict(ctx.obj), *args, **kwargs) + try: + ret = f(frappe._dict(ctx.obj), *args, **kwargs) + except frappe.exceptions.SiteNotSpecifiedError as e: + click.secho(str(e), fg='yellow') + sys.exit(1) if profile: pr.disable() @@ -44,8 +48,7 @@ def get_site(context): site = context.sites[0] return site except (IndexError, TypeError): - print('Please specify --site sitename') - sys.exit(1) + raise frappe.SiteNotSpecifiedError def popen(command, *args, **kwargs): output = kwargs.get('output', True) diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index f730b2d776..511fac6e0d 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -4,6 +4,7 @@ import sys import frappe from frappe.utils import cint from frappe.commands import pass_context, get_site +from frappe.exceptions import SiteNotSpecifiedError def _is_scheduler_enabled(): enable_scheduler = False diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 8fcb7bf84a..51b613fd56 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -15,6 +15,7 @@ import frappe from frappe import _ from frappe.commands import get_site, pass_context from frappe.commands.scheduler import _is_scheduler_enabled +from frappe.exceptions import SiteNotSpecifiedError from frappe.installer import update_site_config from frappe.utils import get_site_path, touch_file @@ -368,7 +369,7 @@ def use(site, sites_path='.'): sitefile.write(site) print("Current Site set to {}".format(site)) else: - print("{} does not exist".format(site)) + print("Site {} does not exist".format(site)) @click.command('backup') @click.option('--with-files', default=False, is_flag=True, help="Take backup with files") diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index 334a4c1057..48a7fd1db7 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals, absolute_import, print_function import click from frappe.commands import pass_context, get_site +from frappe.exceptions import SiteNotSpecifiedError # translation @click.command('build-message-files') diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 702bddc58a..86db7cdc8f 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -6,6 +6,7 @@ import json, os, sys, subprocess from distutils.spawn import find_executable import frappe from frappe.commands import pass_context, get_site +from frappe.exceptions import SiteNotSpecifiedError from frappe.utils import update_progress_bar, get_bench_path from frappe.utils.response import json_handler from coverage import Coverage @@ -376,6 +377,8 @@ def mariadb(context): import os site = get_site(context) + if not site: + raise SiteNotSpecifiedError frappe.init(site=site) # This is assuming you're within the bench instance. diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 5a1181f31e..1aac339228 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -13,6 +13,11 @@ if sys.version_info.major == 2: else: from builtins import FileNotFoundError +class SiteNotSpecifiedError(Exception): + def __init__(self, *args, **kwargs): + self.message = "Please specify --site sitename" + super(Exception, self).__init__(self.message) + class ValidationError(Exception): http_status_code = 417 diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py index 7c5d209179..c46b42b132 100644 --- a/frappe/utils/bench_helper.py +++ b/frappe/utils/bench_helper.py @@ -50,14 +50,16 @@ def app_group(ctx, site=False, force=False, verbose=False, profile=False): ctx.info_name = '' def get_sites(site_arg): - if site_arg and site_arg == 'all': + if site_arg == 'all': return frappe.utils.get_sites() - else: - if site_arg: - return [site_arg] - if os.path.exists('currentsite.txt'): - with open('currentsite.txt') as f: - return [f.read().strip()] + elif site_arg: + return [site_arg] + elif os.path.exists('currentsite.txt'): + with open('currentsite.txt') as f: + site = f.read().strip() + if site: + return [site] + return [] def get_app_commands(app): if os.path.exists(os.path.join('..', 'apps', app, app, 'commands.py'))\ From a7aefdfb88d7a702dc8e77c9d03a203847a60496 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 1 Jun 2020 21:13:13 +0530 Subject: [PATCH 105/110] fix: Syntax error --- frappe/model/rename_doc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/rename_doc.py b/frappe/model/rename_doc.py index 5b795f279b..1e3f127b99 100644 --- a/frappe/model/rename_doc.py +++ b/frappe/model/rename_doc.py @@ -126,7 +126,7 @@ def update_assignments(old, new, doctype): frappe.delete_doc('ToDo', todo.name) unique_assignments = list(set(old_assignments + new_assignments)) - frappe.db.set_value(doctype, new, '_assign', frappe.as_json(unique_assignments), indent=0) + frappe.db.set_value(doctype, new, '_assign', frappe.as_json(unique_assignments, indent=0)) def update_user_settings(old, new, link_fields): ''' From 075d009e70a1336845fbeac467426b8e4ef21970 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 1 Jun 2020 22:05:46 +0530 Subject: [PATCH 106/110] fix: _assign should also have closed assignments (#10532) --- frappe/desk/doctype/event/test_event.py | 4 ++-- frappe/desk/doctype/todo/todo.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/event/test_event.py b/frappe/desk/doctype/event/test_event.py index dcfb38bd08..2926a74a55 100644 --- a/frappe/desk/doctype/event/test_event.py +++ b/frappe/desk/doctype/event/test_event.py @@ -93,10 +93,10 @@ class TestEvent(unittest.TestCase): self.assertEqual(set(json.loads(ev._assign)), set(["test@example.com", self.test_user])) - # close an assignment + # Remove an assignment todo = frappe.get_doc("ToDo", {"reference_type": ev.doctype, "reference_name": ev.name, "owner": self.test_user}) - todo.status = "Closed" + todo.status = "Cancelled" todo.save() ev = frappe.get_doc("Event", ev.name) diff --git a/frappe/desk/doctype/todo/todo.py b/frappe/desk/doctype/todo/todo.py index 8e8102d093..804174b56b 100644 --- a/frappe/desk/doctype/todo/todo.py +++ b/frappe/desk/doctype/todo/todo.py @@ -64,7 +64,7 @@ class ToDo(Document): filters={ "reference_type": self.reference_type, "reference_name": self.reference_name, - "status": "Open" + "status": ("!=", "Cancelled") }, fields=["owner"], as_list=True)] From 63d252c69a6c09ac65dd1579ab53162c870ba811 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Mon, 1 Jun 2020 22:22:45 +0530 Subject: [PATCH 107/110] fix: filter table multiselect fields for filters (bp #10476) (#10557) * fix: filter table multiselect fields for filters * Update frappe/public/js/frappe/ui/filters/field_select.js Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> * Update field_select.js Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> (cherry picked from commit 750ea36162b40d2c6cc705678937fd0fce3987cf) Co-authored-by: Himanshu --- .../js/frappe/ui/filters/field_select.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/ui/filters/field_select.js b/frappe/public/js/frappe/ui/filters/field_select.js index 672991a554..65d32184e3 100644 --- a/frappe/public/js/frappe/ui/filters/field_select.js +++ b/frappe/public/js/frappe/ui/filters/field_select.js @@ -119,7 +119,14 @@ frappe.ui.FieldSelect = Class.extend({ // child tables $.each(me.table_fields, function(i, table_df) { if(table_df.options) { - var child_table_fields = [].concat(frappe.meta.docfield_list[table_df.options]); + let child_table_fields = [].concat(frappe.meta.docfield_list[table_df.options]); + + if (table_df.fieldtype === "Table MultiSelect") { + const link_field = frappe.meta.get_docfields(table_df.options) + .find(df => df.fieldtype === 'Link'); + child_table_fields = link_field ? [link_field] : []; + } + $.each(frappe.utils.sort(child_table_fields, "label", "string"), function(i, df) { // show fields where user has read access and if report hide flag is not set if(frappe.perm.has_perm(me.doctype, df.permlevel, "read")) @@ -130,15 +137,22 @@ frappe.ui.FieldSelect = Class.extend({ }, add_field_option(df) { - if (df.fieldname == 'docstatus' && !frappe.model.is_submittable(this.doctype)) + let me = this; + + if (df.fieldname == 'docstatus' && !frappe.model.is_submittable(me.doctype)) return; - var me = this; - var label, table; + if (frappe.model.table_fields.includes(df.fieldtype)) { + me.table_fields.push(df); + return; + } + + let label = null; + let table = null; + if(me.doctype && df.parent==me.doctype) { label = __(df.label); table = me.doctype; - if(frappe.model.table_fields.includes(df.fieldtype)) me.table_fields.push(df); } else { label = __(df.label) + ' (' + __(df.parent) + ')'; table = df.parent; From a3bb293deffb0b77afb5ce4be4e4d0a395a4e1b2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 2 Jun 2020 11:05:15 +0530 Subject: [PATCH 108/110] fix: check if filters exist and is list --- .../v13_0/update_date_filters_in_user_settings.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frappe/patches/v13_0/update_date_filters_in_user_settings.py b/frappe/patches/v13_0/update_date_filters_in_user_settings.py index 2d6c123064..d4c6aa1d03 100644 --- a/frappe/patches/v13_0/update_date_filters_in_user_settings.py +++ b/frappe/patches/v13_0/update_date_filters_in_user_settings.py @@ -40,11 +40,12 @@ def update_user_setting_filters(data, key, user_setting): update = False if isinstance(data.get(key), dict): filters = data.get(key).get('filters') - for f in filters: - if f[2] == 'Next' or f[2] == 'Previous': - update = True - f[3] = period_map[f[2]] + ' ' + timespan_map[f[3]] - f[2] = 'Timespan' + if filters and isinstance(filters, list): + for f in filters: + if f[2] == 'Next' or f[2] == 'Previous': + update = True + f[3] = period_map[f[2]] + ' ' + timespan_map[f[3]] + f[2] = 'Timespan' if update: data[key]['filters'] = filters From 1f1c7b13900782a9007f2820a548302c962257da Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 2 Jun 2020 12:58:50 +0530 Subject: [PATCH 109/110] fix: Init site before calling migrate_to Locals are not initialized otherwise which is required to access site_config --- frappe/commands/site.py | 3 +++ frappe/integrations/frappe_providers/frappecloud.py | 5 ----- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 51b613fd56..399d0efd68 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -294,7 +294,10 @@ def migrate_to(context, frappe_provider): "Migrates site to the specified provider" from frappe.integrations.frappe_providers import migrate_to for site in context.sites: + frappe.init(site=site) + frappe.connect() migrate_to(site, frappe_provider) + frappe.destroy() if not context.sites: raise SiteNotSpecifiedError diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 291b8af647..3e4b584246 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -394,9 +394,4 @@ def frappecloud_migrator(local_site, frappecloud_site): # available actions defined in migrator_actions primary_action = select_primary_action() - frappe.init(site=local_site) - frappe.connect() - primary_action(local_site) - - frappe.destroy() From dee71e5167b27350e8cb78a098f00e2e8f7115c8 Mon Sep 17 00:00:00 2001 From: marination Date: Tue, 2 Jun 2020 19:46:13 +0530 Subject: [PATCH 110/110] fix: Number Card doesn't fetch data if country not India. --- frappe/public/js/frappe/widgets/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/utils.js b/frappe/public/js/frappe/widgets/utils.js index c92bdc1b5f..dff4db807e 100644 --- a/frappe/public/js/frappe/widgets/utils.js +++ b/frappe/public/js/frappe/widgets/utils.js @@ -128,7 +128,7 @@ function go_to_list_with_filters(doctype, filters) { } function shorten_number(number, country) { - country = country || ''; + country = (country == 'India') ? country : ''; const number_system = get_number_system(country); let x = Math.abs(Math.round(number)); for (const map of number_system) {
  • ${__('State')} ${__('Count')}${r.count}
    ${r.count}