From 97ae23c2a1f098fc38a408fac8273c3b4e4130c9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 23 Oct 2019 15:42:24 +0530 Subject: [PATCH 001/498] 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/498] 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/498] 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/498] 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/498] 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/498] 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/498] 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/498] 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/498] 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/498] 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/498] 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/498] 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 9e139171f41405d677d62361fb2ef2190d475d5e Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 8 Apr 2020 18:35:49 +0530 Subject: [PATCH 013/498] feat: custom form dashboards --- frappe/custom/doctype/custom_link/__init__.py | 0 .../custom/doctype/custom_link/custom_link.js | 16 ++++++ .../doctype/custom_link/custom_link.json | 52 +++++++++++++++++++ .../custom/doctype/custom_link/custom_link.py | 43 +++++++++++++++ .../doctype/custom_link/test_custom_link.py | 10 ++++ frappe/desk/form/meta.py | 2 - frappe/desk/notifications.py | 7 +-- frappe/model/meta.py | 4 +- 8 files changed, 128 insertions(+), 6 deletions(-) create mode 100644 frappe/custom/doctype/custom_link/__init__.py create mode 100644 frappe/custom/doctype/custom_link/custom_link.js create mode 100644 frappe/custom/doctype/custom_link/custom_link.json create mode 100644 frappe/custom/doctype/custom_link/custom_link.py create mode 100644 frappe/custom/doctype/custom_link/test_custom_link.py diff --git a/frappe/custom/doctype/custom_link/__init__.py b/frappe/custom/doctype/custom_link/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/custom_link/custom_link.js b/frappe/custom/doctype/custom_link/custom_link.js new file mode 100644 index 0000000000..f1c06daeeb --- /dev/null +++ b/frappe/custom/doctype/custom_link/custom_link.js @@ -0,0 +1,16 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Custom Link', { + refresh: function(frm) { + frm.set_query("document_type", function () { + return { + filters: { + custom: 0, + istable: 0, + module: ['not in', ["Email", "Core", "Custom", "Event Streaming", "Social", "Data Migration", "Geo", "Desk"]] + } + } + }); + } +}); diff --git a/frappe/custom/doctype/custom_link/custom_link.json b/frappe/custom/doctype/custom_link/custom_link.json new file mode 100644 index 0000000000..350e6b1c2d --- /dev/null +++ b/frappe/custom/doctype/custom_link/custom_link.json @@ -0,0 +1,52 @@ +{ + "actions": [], + "autoname": "field:document_type", + "creation": "2020-04-08 15:16:44.342509", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "links" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1, + "unique": 1 + }, + { + "fieldname": "links", + "fieldtype": "Table", + "label": "Links", + "options": "DocType Link" + } + ], + "links": [], + "modified": "2020-04-08 16:42:59.402671", + "modified_by": "Administrator", + "module": "Custom", + "name": "Custom Link", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/custom_link/custom_link.py b/frappe/custom/doctype/custom_link/custom_link.py new file mode 100644 index 0000000000..b9a88b58f2 --- /dev/null +++ b/frappe/custom/doctype/custom_link/custom_link.py @@ -0,0 +1,43 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document + +class CustomLink(Document): + pass + +def get_custom_doctype_links(doctype, data): + if frappe.get_all("Custom Link", {"document_type": doctype}): + doc = frappe.get_doc("Custom Link", doctype) + + if not data.transactions: + # init groups + data.transactions = [] + data.non_standard_fieldnames = {} + + for link in doc.links: + link.added = False + for group in data.transactions: + # group found + if group.get("label") == link.group: + if not link.link_doctype in group.get("items"): + group.get("items").append(link.link_doctype) + link.added = True + + if not link.added: + # group not found, make a new group + data.transactions.append({ + "label": link.group, + "items": [link.link_doctype] + }) + + if link.link_fieldname != data.fieldname: + if data.fieldname: + data.non_standard_fieldnames[link.link_doctype] = link.link_fieldname + else: + data.fieldname = link.link_fieldname + + return data \ No newline at end of file diff --git a/frappe/custom/doctype/custom_link/test_custom_link.py b/frappe/custom/doctype/custom_link/test_custom_link.py new file mode 100644 index 0000000000..a292f73ad0 --- /dev/null +++ b/frappe/custom/doctype/custom_link/test_custom_link.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestCustomLink(unittest.TestCase): + pass diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index 26fc6037fd..ba0e5c2216 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -196,8 +196,6 @@ class FormMeta(Meta): self.get("__messages").update(messages, as_value=True) def load_dashboard(self): - if self.custom: - return self.set('__dashboard', self.get_dashboard_data()) def load_kanban_meta(self): diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index 3a8815ca71..109dd25f4f 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -268,8 +268,9 @@ def get_open_count(doctype, name, items=[]): "count": out, } - module = frappe.get_meta_module(doctype) - if hasattr(module, "get_timeline_data"): - out["timeline_data"] = module.get_timeline_data(doctype, name) + if not meta.custom: + module = frappe.get_meta_module(doctype) + if hasattr(module, "get_timeline_data"): + out["timeline_data"] = module.get_timeline_data(doctype, name) return out diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 5065684311..e6a89be108 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -26,6 +26,7 @@ from frappe.model.base_document import BaseDocument from frappe.modules import load_doctype_module from frappe.model.workflow import get_workflow_name from frappe import _ +from frappe.custom.doctype.custom_link.custom_link import get_custom_doctype_links def get_meta(doctype, cached=True): if cached: @@ -429,6 +430,7 @@ class Meta(Document): pass self.add_doctype_links(data) + get_custom_doctype_links(self.name, data) for hook in frappe.get_hooks("override_doctype_dashboards", {}).get(self.name, []): data = frappe.get_attr(hook)(data=data) @@ -447,7 +449,7 @@ class Meta(Document): link.added = False for group in data.transactions: # group found - if group.label == link.label: + if group.label == link.group: if not link.link_doctype in group.items: group.items.append(link.link_doctype) link.added = True From a25088a606dd50a293d1f6b542af9b8c75e8cd5e Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 13 Apr 2020 19:10:19 +0530 Subject: [PATCH 014/498] chore: show message on migrate when services are not running shows message before migrate if the database or redis service is not running instead of trying to migrate and then failing. Signed-off-by: Chinmay D. Pai --- frappe/migrate.py | 16 +++++++++++++ frappe/utils/connections.py | 48 +++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 frappe/utils/connections.py diff --git a/frappe/migrate.py b/frappe/migrate.py index 043b6817d7..711f51c57a 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -5,11 +5,13 @@ from __future__ import unicode_literals import json import os +import sys import frappe import frappe.translate import frappe.modules.patch_handler import frappe.model.sync from frappe.utils.fixtures import sync_fixtures +from frappe.utils.connections import check_connection from frappe.cache_manager import clear_global_cache from frappe.desk.notifications import clear_notifications from frappe.website import render @@ -18,6 +20,7 @@ from frappe.modules.utils import sync_customizations from frappe.core.doctype.scheduled_job_type.scheduled_job_type import sync_jobs from frappe.utils import global_search + def migrate(verbose=True, rebuild_website=False, skip_failing=False): '''Migrate all apps to the latest version, will: - run before migrate hooks @@ -30,6 +33,19 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False): - run after migrate hooks ''' + service_status = check_connection(redis_services=["redis_cache"]) + if False in service_status.values(): + for service in service_status: + if not service_status.get(service, True): + print("{} service is not running.".format(service)) + print("""Cannot run bench migrate without the services running. +If you are running bench in development mode, make sure that bench is running: + +$ bench start + +Otherwise, check the server logs and ensure that all the required services are running.""") + sys.exit(1) + touched_tables_file = frappe.get_site_path('touched_tables.json') if os.path.exists(touched_tables_file): os.remove(touched_tables_file) diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py new file mode 100644 index 0000000000..57b9399986 --- /dev/null +++ b/frappe/utils/connections.py @@ -0,0 +1,48 @@ +import socket + +from six.moves.urllib.parse import urlparse +from frappe import get_conf + +config = get_conf() +REDIS_KEYS = ('redis_cache', 'redis_queue', 'redis_socketio') + + +def is_open(ip, port, timeout=10): + s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) + s.settimeout(timeout) + try: + s.connect((ip, int(port))) + s.shutdown(socket.SHUT_RDWR) + return True + except socket.error: + return False + finally: + s.close() + + +def check_database(): + db_type = config.get("db_type", "mariadb") + if db_type == "mariadb": + db_host = config.get("db_host", "localhost") + db_port = config.get("db_port", 3306) + else: + db_host = "localhost" + db_port = 5342 + return {"db_type": is_open(db_host, db_port)} + + +def check_redis(redis_services=None): + services = redis_services or REDIS_KEYS + status = {} + for conn in services: + redis_url = urlparse(config.get(conn)).netloc + redis_host, redis_port = redis_url.split(":") + status[conn] = is_open(redis_host, redis_port) + return status + + +def check_connection(redis_services=None): + service_status = {} + service_status.update(check_database()) + service_status.update(check_redis(redis_services)) + return service_status From 318e397ab35cb0508ea8697b0088f23c37e67df2 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 13 Apr 2020 19:13:43 +0530 Subject: [PATCH 015/498] chore: unquote db_type Signed-off-by: Chinmay D. Pai --- frappe/utils/connections.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py index 57b9399986..bffbd98134 100644 --- a/frappe/utils/connections.py +++ b/frappe/utils/connections.py @@ -28,7 +28,7 @@ def check_database(): else: db_host = "localhost" db_port = 5342 - return {"db_type": is_open(db_host, db_port)} + return {db_type: is_open(db_host, db_port)} def check_redis(redis_services=None): From c0d0fcfd69227562069933449881d27470bb904b Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 13 Apr 2020 19:24:51 +0530 Subject: [PATCH 016/498] chore: do not hardcode anything for postgres Signed-off-by: Chinmay D. Pai --- frappe/utils/connections.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/frappe/utils/connections.py b/frappe/utils/connections.py index bffbd98134..6bd24d57ec 100644 --- a/frappe/utils/connections.py +++ b/frappe/utils/connections.py @@ -22,12 +22,8 @@ def is_open(ip, port, timeout=10): def check_database(): db_type = config.get("db_type", "mariadb") - if db_type == "mariadb": - db_host = config.get("db_host", "localhost") - db_port = config.get("db_port", 3306) - else: - db_host = "localhost" - db_port = 5342 + db_host = config.get("db_host", "localhost") + db_port = config.get("db_port", 3306 if db_type == "mariadb" else 5342) return {db_type: is_open(db_host, db_port)} From 4775d1b6ee5761a1ffa4988b463f5cc7854c5f05 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 14 Apr 2020 11:41:31 +0530 Subject: [PATCH 017/498] 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 018/498] 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 fca6181d6771235d883bd1c4cc0ce57c3c32065c Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 15 Apr 2020 21:32:22 +0530 Subject: [PATCH 019/498] feat: multiple assignments --- frappe/desk/form/assign_to.py | 77 +++---- .../js/frappe/form/sidebar/assign_to.js | 202 +++++++++++------- 2 files changed, 164 insertions(+), 115 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 76c7caa63d..a0f2f99910 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -11,6 +11,7 @@ from frappe.desk.doctype.notification_log.notification_log import enqueue_create get_title, get_title_html import frappe.utils import frappe.share +import json class DuplicateToDoError(frappe.ValidationError): pass @@ -19,7 +20,7 @@ def get(args=None): if not args: args = frappe.local.form_dict - return frappe.get_all('ToDo', fields = ['owner', 'description'], filters = dict( + return frappe.get_all('ToDo', fields = ['owner', 'name'], filters = dict( reference_type = args.get('doctype'), reference_name = args.get('name'), status = ('!=', 'Cancelled') @@ -40,49 +41,53 @@ def add(args=None): if not args: args = frappe.local.form_dict - if frappe.db.sql("""SELECT `owner` - FROM `tabToDo` - WHERE `reference_type`=%(doctype)s - AND `reference_name`=%(name)s - AND `status`='Open' - AND `owner`=%(assign_to)s""", args): - frappe.throw(_("Already in user's To Do list"), DuplicateToDoError) - else: - from frappe.utils import nowdate - - if not args.get('description'): - args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) - - d = frappe.get_doc({ - "doctype":"ToDo", - "owner": args['assign_to'], + for assign_to in json.loads(args.get("assign_to")): + filters = { "reference_type": args['doctype'], "reference_name": args['name'], - "description": args.get('description'), - "priority": args.get("priority", "Medium"), "status": "Open", - "date": args.get('date', nowdate()), - "assigned_by": args.get('assigned_by', frappe.session.user), - 'assignment_rule': args.get('assignment_rule') - }).insert(ignore_permissions=True) + "owner": assign_to + } - # set assigned_to if field exists - if frappe.get_meta(args['doctype']).get_field("assigned_to"): - frappe.db.set_value(args['doctype'], args['name'], "assigned_to", args['assign_to']) + if frappe.get_all("ToDo", filters=filters): + if not args.get("bulk_assign"): + frappe.throw(_("Already in user's ToDo list"), DuplicateToDoError) + else: + from frappe.utils import nowdate - doc = frappe.get_doc(args['doctype'], args['name']) + if not args.get('description'): + args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) - # if assignee does not have permissions, share - if not frappe.has_permission(doc=doc, user=args['assign_to']): - frappe.share.add(doc.doctype, doc.name, args['assign_to']) - frappe.msgprint(_('Shared with user {0} with read access').format(args['assign_to']), alert=True) + d = frappe.get_doc({ + "doctype":"ToDo", + "owner": assign_to, + "reference_type": args['doctype'], + "reference_name": args['name'], + "description": args.get('description'), + "priority": args.get("priority", "Medium"), + "status": "Open", + "date": args.get('date', nowdate()), + "assigned_by": args.get('assigned_by', frappe.session.user), + 'assignment_rule': args.get('assignment_rule') + }).insert(ignore_permissions=True) - # make this document followed by assigned user - follow_document(args['doctype'], args['name'], args['assign_to']) + # set assigned_to if field exists + if frappe.get_meta(args['doctype']).get_field("assigned_to"): + frappe.db.set_value(args['doctype'], args['name'], "assigned_to", assign_to) - # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ - description=args.get("description")) + doc = frappe.get_doc(args['doctype'], args['name']) + + # if assignee does not have permissions, share + if not frappe.has_permission(doc=doc, user=assign_to): + frappe.share.add(doc.doctype, doc.name, assign_to) + frappe.msgprint(_('Shared with user {0} with read access').format(assign_to, alert=True)) + + # make this document followed by assigned user + follow_document(args['doctype'], args['name'], assign_to) + + # notify + notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ + description=args.get("description")) return get(args) diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 61d1789518..b78540f6a7 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -87,23 +87,17 @@ frappe.ui.form.AssignTo = Class.extend({ if(!me.assign_to) { me.assign_to = new frappe.ui.form.AssignToDialog({ - obj: me, - method: 'frappe.desk.form.assign_to.add', + method: "frappe.desk.form.assign_to.add", doctype: me.frm.doctype, docname: me.frm.docname, - callback: function(r) { + frm: me.frm, + callback: function (r) { me.render(r.message); } }); } me.assign_to.dialog.clear(); - - if(me.frm.meta.title_field) { - me.assign_to.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]) - } - me.assign_to.dialog.show(); - me.assign_to = null; }, remove: function(owner) { var me = this; @@ -130,81 +124,131 @@ frappe.ui.form.AssignTo = Class.extend({ frappe.ui.form.AssignToDialog = Class.extend({ init: function(opts){ - var me = this - var dialog = new frappe.ui.Dialog({ - title: __('Add to To Do'), - fields: [ - { fieldtype: 'Link', fieldname: 'assign_to', options: 'User', label: __("Assign To"), reqd: true, filters: { 'user_type': 'System User' }}, - { fieldtype: 'Check', fieldname: 'myself', label: __("Assign to me"), "default": 0 }, - { fieldtype: 'Small Text', fieldname: 'description', label: __("Comment") }, - { fieldtype: 'Section Break' }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Date', fieldname: 'date', label: __("Complete By") }, - { fieldtype: 'Column Break' }, - { fieldtype: 'Select', fieldname: 'priority', label: __("Priority"), - options: [ - { value: 'Low', label: __('Low') }, - { value: 'Medium', label: __('Medium') }, - { value: 'High', label: __('High') } - ], - // Pick up priority from the source document, if it exists and is available in ToDo - 'default': ["Low", "Medium", "High"].includes(opts.obj.frm && opts.obj.frm.doc.priority - ? opts.obj.frm.doc.priority : 'Medium') - }, - ], - primary_action: function() { frappe.ui.add_assignment(opts, this) }, - primary_action_label: __("Add") - }) - $.extend(me, dialog); + $.extend(this, opts) - me.dialog = dialog; - - me.dialog.fields_dict.assign_to.get_query = "frappe.core.doctype.user.user.user_query"; - - var myself = me.dialog.get_input("myself").on("click", function() { - me.toggle_myself(this); - }); - me.toggle_myself(myself); - }, - toggle_myself: function(myself) { - var me = this; - if($(myself).prop("checked")) { - me.dialog.set_value("assign_to", frappe.session.user); - me.dialog.get_field("notify").$wrapper.toggle(false); - me.dialog.get_field("assign_to").$wrapper.toggle(false); - } else { - me.dialog.set_value("assign_to", ""); - me.dialog.get_field("assign_to").$wrapper.toggle(true); - } + this.make(); + this.set_description_from_doc(); }, + make: function() { + let me = this -}); + me.dialog = new frappe.ui.Dialog({ + title: __('Add to ToDo'), + fields: me.get_fields(), + primary_action_label: __("Add"), + primary_action: function() { + let args = me.dialog.get_values(); -frappe.ui.add_assignment = function(opts, dialog) { - var assign_to = dialog.fields_dict.assign_to.get_value(); - var args = dialog.get_values(); - if(args && assign_to) { - dialog.set_message('Assigning...'); - return frappe.call({ - method: opts.method, - args: $.extend(args, { - doctype: opts.doctype, - name: opts.docname, - assign_to: assign_to, - bulk_assign: opts.bulk_assign || false, - re_assign: opts.re_assign || false - }), - btn: dialog.get_primary_btn(), - callback: function(r) { - if(!r.exc) { - if(opts.callback){ - opts.callback(r); - } - dialog && dialog.hide(); - } else { - dialog.clear_message(); + if (args && args.assign_to) { + me.dialog.set_message("Assigning..."); + + frappe.call({ + method: me.method, + args: $.extend(args, { + doctype: me.doctype, + name: me.docname, + assign_to: args.assign_to, + bulk_assign: me.bulk_assign || false, + re_assign: me.re_assign || false + }), + btn: me.dialog.get_primary_btn(), + callback: function(r) { + if (!r.exc) { + if (me.callback) { + me.callback(r); + } + me.dialog && me.dialog.hide(); + } else { + me.dialog.clear_message(); + } + }, + }); } }, }); + }, + assign_to_me: function() { + let me = this; + let assign_to = []; + + if(me.dialog.get_value("assign_to_me")) { + assign_to.push(frappe.session.user); + } + + me.dialog.set_value("assign_to", assign_to); + }, + set_description_from_doc: function() { + let me = this; + + if (me.frm && me.frm.meta.title_field) { + me.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]); + } + }, + set_assign_to_field_description: function() { + let me = this; + + let assignees = me.dialog.get_value("assign_to"); + let field = me.dialog.get_field("assign_to"); + let description = ""; + + if (assignees.length > 0) { + description = "Assign To: " + assignees.join(", "); + } + + field.set_description(description); + }, + get_fields: function() { + let me = this; + + return [ + { + fieldtype: 'MultiSelectList', + fieldname: 'assign_to', + label: __("Assign To"), + reqd: true, + get_data: function(txt) { + return frappe.db.get_link_options("User", txt, {user_type: "System User", enabled: 1}); + }, + onchange: () => me.set_assign_to_field_description() + }, + { + label: __("Assign to me"), + fieldtype: 'Check', + fieldname: 'assign_to_me', + default: 0, + onchange: () => me.assign_to_me() + }, + { + label: __("Comment"), + fieldtype: 'Small Text', + fieldname: 'description' + }, + { + fieldtype: 'Section Break' + }, + { + fieldtype: 'Column Break' + }, + { + label: __("Complete By"), + fieldtype: 'Date', + fieldname: 'date' + }, + { + fieldtype: 'Column Break' + }, + { + label: __("Priority"), + fieldtype: 'Select', + fieldname: 'priority', + options: [ + { value: 'Low', label: __('Low') }, + { value: 'Medium', label: __('Medium') }, + { value: 'High', label: __('High') } + ], + // Pick up priority from the source document, if it exists and is available in ToDo + default: ["Low", "Medium", "High"].includes(me.frm && me.frm.doc.priority ? me.frm.doc.priority : 'Medium') + } + ] } -} +}); From 6b9de21d539d4a64fe82473be994af9bc814eb61 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 15 Apr 2020 22:20:23 +0530 Subject: [PATCH 020/498] fix: minor bug fixes for notifying assignees --- frappe/desk/form/assign_to.py | 4 ++-- frappe/public/js/frappe/form/sidebar/assign_to.js | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index a0f2f99910..8a8219eb22 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -85,8 +85,8 @@ def add(args=None): # make this document followed by assigned user follow_document(args['doctype'], args['name'], assign_to) - # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ + # notify + notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ description=args.get("description")) return get(args) diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index b78540f6a7..03fba17ec7 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -124,7 +124,7 @@ frappe.ui.form.AssignTo = Class.extend({ frappe.ui.form.AssignToDialog = Class.extend({ init: function(opts){ - $.extend(this, opts) + $.extend(this, opts); this.make(); this.set_description_from_doc(); @@ -148,7 +148,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ doctype: me.doctype, name: me.docname, assign_to: args.assign_to, - bulk_assign: me.bulk_assign || false, + bulk_assign: me.bulk_assign || false, re_assign: me.re_assign || false }), btn: me.dialog.get_primary_btn(), @@ -171,7 +171,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ let me = this; let assign_to = []; - if(me.dialog.get_value("assign_to_me")) { + if (me.dialog.get_value("assign_to_me")) { assign_to.push(frappe.session.user); } @@ -249,6 +249,6 @@ frappe.ui.form.AssignToDialog = Class.extend({ // Pick up priority from the source document, if it exists and is available in ToDo default: ["Low", "Medium", "High"].includes(me.frm && me.frm.doc.priority ? me.frm.doc.priority : 'Medium') } - ] + ]; } }); From 7886365c090ad1b645b63c0987af83e13366b1e9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 15 Apr 2020 22:48:58 +0530 Subject: [PATCH 021/498] chore: code cleanup --- frappe/desk/form/assign_to.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 8a8219eb22..be83dc957e 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -20,11 +20,11 @@ def get(args=None): if not args: args = frappe.local.form_dict - return frappe.get_all('ToDo', fields = ['owner', 'name'], filters = dict( + return frappe.get_all('ToDo', fields=['owner', 'name'], filters=dict( reference_type = args.get('doctype'), reference_name = args.get('name'), status = ('!=', 'Cancelled') - ), limit = 5) + ), limit=5) @frappe.whitelist() def add(args=None): @@ -59,7 +59,7 @@ def add(args=None): args['description'] = _('Assignment for {0} {1}').format(args['doctype'], args['name']) d = frappe.get_doc({ - "doctype":"ToDo", + "doctype": "ToDo", "owner": assign_to, "reference_type": args['doctype'], "reference_name": args['name'], @@ -86,15 +86,13 @@ def add(args=None): follow_document(args['doctype'], args['name'], assign_to) # notify - notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN',\ + notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN', description=args.get("description")) return get(args) @frappe.whitelist() def add_multiple(args=None): - import json - if not args: args = frappe.local.form_dict From 7483bf79683430e54a9ce73390fce75dc5b04b7d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 16 Apr 2020 20:20:13 +0530 Subject: [PATCH 022/498] 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 7dac9e2a0342809453bdb44d0588606715b5154b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 16 Apr 2020 21:55:52 +0530 Subject: [PATCH 023/498] fix: change to multiselect pills --- .../js/frappe/form/sidebar/assign_to.js | 33 ++++++++----------- 1 file changed, 14 insertions(+), 19 deletions(-) diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 03fba17ec7..84fde564c0 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -184,32 +184,18 @@ frappe.ui.form.AssignToDialog = Class.extend({ me.dialog.set_value("description", me.frm.doc[me.frm.meta.title_field]); } }, - set_assign_to_field_description: function() { - let me = this; - - let assignees = me.dialog.get_value("assign_to"); - let field = me.dialog.get_field("assign_to"); - let description = ""; - - if (assignees.length > 0) { - description = "Assign To: " + assignees.join(", "); - } - - field.set_description(description); - }, get_fields: function() { let me = this; return [ { - fieldtype: 'MultiSelectList', + fieldtype: 'MultiSelectPills', fieldname: 'assign_to', label: __("Assign To"), reqd: true, get_data: function(txt) { return frappe.db.get_link_options("User", txt, {user_type: "System User", enabled: 1}); - }, - onchange: () => me.set_assign_to_field_description() + } }, { label: __("Assign to me"), @@ -242,9 +228,18 @@ frappe.ui.form.AssignToDialog = Class.extend({ fieldtype: 'Select', fieldname: 'priority', options: [ - { value: 'Low', label: __('Low') }, - { value: 'Medium', label: __('Medium') }, - { value: 'High', label: __('High') } + { + value: 'Low', + label: __('Low') + }, + { + value: 'Medium', + label: __('Medium') + }, + { + value: 'High', + label: __('High') + } ], // Pick up priority from the source document, if it exists and is available in ToDo default: ["Low", "Medium", "High"].includes(me.frm && me.frm.doc.priority ? me.frm.doc.priority : 'Medium') From acc7de7335e28b13c5590b95a42ed972198de83b Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 17 Apr 2020 18:49:04 +0530 Subject: [PATCH 024/498] feat: added pandas date_range to utils --- frappe/utils/dateutils.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/utils/dateutils.py b/frappe/utils/dateutils.py index d5b7a3136b..907cab3d02 100644 --- a/frappe/utils/dateutils.py +++ b/frappe/utils/dateutils.py @@ -73,3 +73,9 @@ def datetime_in_user_format(date_time): date_time = get_datetime(date_time) from frappe.utils import formatdate return formatdate(date_time.date()) + " " + date_time.strftime("%H:%M") + +def get_date_range(from_date, to_date, frequency="M", normalize=True): + from pandas import date_range + date_range = date_range(start=from_date, end=to_date, freq=frequency, normalize=normalize).to_pydatetime().tolist() + + return date_range \ No newline at end of file From da6024242769e8c0934f8bc283ae8c9d93fbc573 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 17 Apr 2020 18:49:12 +0530 Subject: [PATCH 025/498] feat: added website analytics report --- frappe/website/report/__init__.py | 0 .../report/website_analytics/__init__.py | 0 .../website_analytics/website_analytics.js | 34 ++++ .../website_analytics/website_analytics.json | 27 ++++ .../website_analytics/website_analytics.py | 145 ++++++++++++++++++ 5 files changed, 206 insertions(+) create mode 100644 frappe/website/report/__init__.py create mode 100644 frappe/website/report/website_analytics/__init__.py create mode 100644 frappe/website/report/website_analytics/website_analytics.js create mode 100644 frappe/website/report/website_analytics/website_analytics.json create mode 100644 frappe/website/report/website_analytics/website_analytics.py diff --git a/frappe/website/report/__init__.py b/frappe/website/report/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/report/website_analytics/__init__.py b/frappe/website/report/website_analytics/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/report/website_analytics/website_analytics.js b/frappe/website/report/website_analytics/website_analytics.js new file mode 100644 index 0000000000..b607a16eb4 --- /dev/null +++ b/frappe/website/report/website_analytics/website_analytics.js @@ -0,0 +1,34 @@ +// Copyright (c) 2016, Frappe Technologies and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Website Analytics"] = { + "filters": [ + { + fieldname: "from_date", + label: __("From Date"), + fieldtype: "Date", + default: frappe.datetime.add_days(frappe.datetime.now_date(true), -7), + reqd: 1 + }, + { + fieldname:"to_date", + label: __("To Date"), + fieldtype: "Date", + default: frappe.datetime.now_date(true), + reqd: 1 + }, + { + fieldname: "range", + label: __("Range"), + fieldtype: "Select", + options: [ + { "value": "D", "label": __("Daily") }, + { "value": "W", "label": __("Weekly") }, + { "value": "M", "label": __("Monthly") }, + ], + default: "D", + reqd: 1 + } + ] +}; diff --git a/frappe/website/report/website_analytics/website_analytics.json b/frappe/website/report/website_analytics/website_analytics.json new file mode 100644 index 0000000000..62c5751a5c --- /dev/null +++ b/frappe/website/report/website_analytics/website_analytics.json @@ -0,0 +1,27 @@ +{ + "add_total_row": 0, + "creation": "2020-04-17 13:04:45.770148", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "idx": 0, + "is_standard": "Yes", + "modified": "2020-04-17 16:10:30.168312", + "modified_by": "Administrator", + "module": "Website", + "name": "Website Analytics", + "owner": "Administrator", + "prepared_report": 0, + "ref_doctype": "Web Page View", + "report_name": "Website Analytics", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + }, + { + "role": "Website Manager" + } + ] +} \ No newline at end of file diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py new file mode 100644 index 0000000000..8b2d5b3806 --- /dev/null +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -0,0 +1,145 @@ +# Copyright (c) 2013, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.utils.dateutils import get_date_range + +def execute(filters=None): + return WebsiteAnalytics(filters).run() + +class WebsiteAnalytics(object): + def __init__(self, filters=None): + self.filters = frappe._dict(filters or {}) + self.filters.to_date = frappe.utils.add_days(self.filters.to_date, 1) + self.query_filters = {'creation': ['between', [self.filters.from_date, self.filters.to_date]]} + + def run(self): + columns = self.get_columns() + data = self.get_data() + summary = self.get_report_summary() + chart = self.get_chart_data() + return columns, data, None, chart, summary + + def get_columns(self): + return [ + { + "fieldname": "path", + "label": "Page", + "fieldtype": "Data", + "width": 300 + }, + { + "fieldname": "count", + "label": "Page Views", + "fieldtype": "Int", + "width": 150 + } + ] + + def get_data(self): + data = frappe.get_all("Web Page View", fields=['path', 'count(*) as count'], filters=self.query_filters, group_by="path", order_by='count desc') + return data + + def get_chart_data(self): + def _get_field_for_chart(filters_range): + field = 'creation' + date_format = '%Y-%m-%d' + + if filters_range == "W": + field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' + + elif filters_range == "M": + date_format = '%Y-%m-01' + + return field, date_format + + field, date_format = _get_field_for_chart(self.filters.range) + + data = frappe.db.sql(""" + SELECT + DATE_FORMAT({0}, %s) as date, + COUNT(*) as count, + count(CASE WHEN is_unique = 1 THEN 1 END) as unique_count + FROM `tabWeb Page View` + WHERE creation BETWEEN %s AND %s + GROUP BY DATE_FORMAT({0}, %s) + ORDER BY creation + """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1, debug=1) + + return self.prepare_chart_data(data) + + def prepare_chart_data(self, data): + date_range = get_date_range(self.filters.from_date, self.filters.to_date, self.filters.range) + if self.filters.range == "M": + date_range = [frappe.utils.add_days(dd, 1) for dd in date_range] + + labels = [] + total_dataset = [] + unique_dataset = [] + + def get_data_for_date(date): + for item in data: + item_date = frappe.utils.get_datetime(item.get("date")).date() + if item_date == date.date(): + return item + return {'count': 0, 'unique_count': 0} + + + for date in date_range: + labels.append(date.strftime("%b %d %Y")) + match = get_data_for_date(date) + total_dataset.append(match.get('count', 0)) + unique_dataset.append(match.get('unique_count', 0)) + + chart = { + "data": { + 'labels': labels, + 'datasets': [ + { + 'name': "Total Views", + 'type': 'line', + 'values': total_dataset + }, + { + 'name': "Unique Visits", + 'type': 'line', + 'values': unique_dataset + } + ] + }, + "type": "axis-mixed", + 'lineOptions': { + 'regionFill': 1, + }, + 'axisOptions': { + 'xIsSeries': 1 + } + } + + return chart + + + def get_report_summary(self): + summary_data = frappe.get_all("Web Page View", fields=['is_unique', 'count(*) as count'], filters=self.query_filters, group_by="is_unique") + + total_count = 0 + unique_count = 0 + for data in summary_data: + if data.get('is_unique'): + unique_count = data.get('count') + total_count += data.get('count') + report_summary = [ + { + "value": total_count, + "label": "Total Page Views", + "datatype": "Int", + }, + { + "value": unique_count, + "label": "Unique Page Views", + "datatype": "Int", + }, + + ] + return report_summary \ No newline at end of file From bf8d930d8e19f39433aa27c3827a20b1b6893ffb Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Mon, 27 Apr 2020 18:21:53 +0530 Subject: [PATCH 026/498] fix: sanitize input before displaying search results Signed-off-by: Chinmay D. Pai --- frappe/templates/includes/search_template.html | 2 +- frappe/utils/safe_exec.py | 1 + frappe/www/search.py | 2 +- 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/templates/includes/search_template.html b/frappe/templates/includes/search_template.html index 8ad73f836a..91a9a1dc47 100644 --- a/frappe/templates/includes/search_template.html +++ b/frappe/templates/includes/search_template.html @@ -28,7 +28,7 @@ {% if frappe.form_dict.scope %} - + {% endif %} diff --git a/frappe/utils/safe_exec.py b/frappe/utils/safe_exec.py index f80d819907..daae75228e 100644 --- a/frappe/utils/safe_exec.py +++ b/frappe/utils/safe_exec.py @@ -83,6 +83,7 @@ def get_safe_globals(): make_post_request = frappe.integrations.utils.make_post_request, socketio_port=frappe.conf.socketio_port, get_hooks=frappe.get_hooks, + sanitize_html=frappe.utils.sanitize_html ), style=frappe._dict( border_color='#d1d8dd' diff --git a/frappe/www/search.py b/frappe/www/search.py index 19df1e48e0..a8bb1a5294 100644 --- a/frappe/www/search.py +++ b/frappe/www/search.py @@ -13,7 +13,7 @@ def get_context(context): context.title = _('Search Results for ') context.query = query context.route = '/search' - context.update(get_search_results(query, frappe.form_dict.scope)) + context.update(get_search_results(query, frappe.utils.sanitize_html(frappe.form_dict.scope))) else: context.title = _('Search') From 9b185802aaab09aa84dff1fce76292ea1edcdc54 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 27 Apr 2020 18:56:12 +0530 Subject: [PATCH 027/498] fix: fix calculation of dates and values for dashboard charts --- .../dashboard_chart/dashboard_chart.py | 108 ++++-------------- .../dashboard_chart/test_dashboard_chart.py | 58 +++++++--- 2 files changed, 64 insertions(+), 102 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 7bed8f4504..4502748504 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -128,7 +128,6 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): to_date = datetime.datetime.now() doctype = chart.document_type - unit_function = get_unit_function(doctype, chart.based_on, timegrain) datefield = chart.based_on aggregate_function = get_aggregate_function(chart.chart_type) value_field = chart.value_based_on or '1' @@ -141,23 +140,18 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): data = frappe.db.get_all( doctype, fields = [ - 'extract(year from `tab{doctype}`.{datefield}) as _year'.format(doctype=doctype, datefield=datefield), - '{} as _unit'.format(unit_function), + '{} as _unit'.format(datefield), '{aggregate_function}({value_field})'.format(aggregate_function=aggregate_function, value_field=value_field), ], filters = filters, - group_by = '_year, _unit', - order_by = '_year asc, _unit asc', + group_by = '_unit', + order_by = '_unit asc', as_list = True, ignore_ifnull = True ) + result = get_result(data, timegrain, from_date, to_date) - # result given as year, unit -> convert it to end of period of that unit - result = convert_to_dates(data, timegrain) - - # add missing data points for periods where there was no result - result = add_missing_values(result, timegrain, timespan, from_date, to_date) chart_config = { "labels": [formatdate(r[0].strftime('%Y-%m-%d')) for r in result], "datasets": [{ @@ -217,75 +211,22 @@ def get_aggregate_function(chart_type): }[chart_type] -def convert_to_dates(data, timegrain): - """ Converts individual dates within data to the end of period """ - result = [] - for d in data: - if d[2] != 0: - if timegrain == 'Daily': - result.append([add_to_date('{:d}-01-01'.format(int(d[0])), days = d[1] - 1), d[2]]) - elif timegrain == 'Weekly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), weeks = d[1] + 1), days = -1), d[2]]) - elif timegrain == 'Monthly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=d[1]), days = -1), d[2]]) - elif timegrain == 'Quarterly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=d[1] * 3), days = -1), d[2]]) - elif timegrain == 'Yearly': - result.append([add_to_date(add_to_date('{:d}-01-01'.format(int(d[0])), months=12), days = -1), d[2]]) - result[-1][0] = getdate(result[-1][0]) - - return result - -def get_unit_function(doctype, datefield, timegrain): - unit_function = '' - if timegrain=='Daily': - if frappe.db.db_type == 'mariadb': - unit_function = 'dayofyear(`tab{doctype}`.{datefield})'.format( - doctype=doctype, datefield=datefield) - else: - unit_function = 'extract(doy from `tab{doctype}`.{datefield})'.format( - doctype=doctype, datefield=datefield) - - else: - unit_function = 'extract({unit} from `tab{doctype}`.{datefield})'.format( - unit = timegrain[:-2].lower(), doctype=doctype, datefield=datefield) - - return unit_function - -def add_missing_values(data, timegrain, timespan, from_date, to_date): - # add missing intervals +def get_result(data, timegrain, from_date, to_date): + start_date = getdate(from_date) + end_date = getdate(to_date) result = [] - if timespan != 'All Time': - first_expected_date = get_period_ending(from_date, timegrain) - # fill out data before the first data point - first_data_point_date = data[0][0] if data else getdate(add_to_date(to_date, days=1)) - while first_data_point_date > first_expected_date: - result.append([first_expected_date, 0.0]) - first_expected_date = get_next_expected_date(first_expected_date, timegrain) + while start_date <= end_date: + next_date = get_next_expected_date(start_date, timegrain) + result.append([next_date, 0.0]) + start_date = next_date - # fill data points and missing points - for i, d in enumerate(data): - result.append(d) - - next_expected_date = get_next_expected_date(d[0], timegrain) - - if i < len(data)-1: - next_date = data[i+1][0] - else: - # already reached at end of data, see if we need any more dates - next_date = getdate(nowdate()) - - # if next data point is earler than the expected date - # need to fill out missing data points - while next_date > next_expected_date: - # fill missing value - result.append([next_expected_date, 0.0]) - next_expected_date = get_next_expected_date(next_expected_date, timegrain) - - # add date for the last period (if missing) - if result and get_period_ending(to_date, timegrain) > result[-1][0]: - result.append([get_period_ending(to_date, timegrain), 0.0]) + data_index = 0 + if data: + for i, d in enumerate(result): + while data_index < len(data) and getdate(data[data_index][0]) <= d[0]: + d[1] += data[data_index][1] + data_index += 1 return result @@ -314,17 +255,12 @@ def get_period_ending(date, timegrain): return getdate(date) def get_week_ending(date): - # fun fact: week ends on the day before 1st Jan of the year. - # for 2019 it is Monday + # week starts on monday + from datetime import timedelta + start = date - timedelta(days = date.weekday()) + end = start + timedelta(days=6) - week_of_the_year = int(date.strftime('%U')) - - if week_of_the_year == 52: - date = add_to_date(date, years=1) - # first day of next week - date = add_to_date('{}-01-01'.format(date.year), weeks = (week_of_the_year%52) + 1) - # last day of this week - return add_to_date(date, days=-1) + return end def get_month_ending(date): month_of_the_year = int(date.strftime('%m')) diff --git a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py index 4425c4fd45..dfc6edbf58 100644 --- a/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/test_dashboard_chart.py @@ -17,10 +17,9 @@ class TestDashboardChart(unittest.TestCase): self.assertEqual(get_period_ending('2019-04-10', 'Daily'), getdate('2019-04-10')) - # fun fact: week ends on the day before 1st Jan of the year. - # for 2019 it is Monday + # week starts on monday self.assertEqual(get_period_ending('2019-04-10', 'Weekly'), - getdate('2019-04-15')) + getdate('2019-04-14')) self.assertEqual(get_period_ending('2019-04-10', 'Monthly'), getdate('2019-04-30')) @@ -133,6 +132,34 @@ class TestDashboardChart(unittest.TestCase): frappe.db.rollback() + def test_weekly_dashboard_chart(self): + insert_test_records() + + if frappe.db.exists('Dashboard Chart', 'Test Weekly Dashboard Chart'): + frappe.delete_doc('Dashboard Chart', 'Test Weekly Dashboard Chart') + + frappe.get_doc(dict( + doctype = 'Dashboard Chart', + chart_name = 'Test Weekly Dashboard Chart', + chart_type = 'Sum', + document_type = 'Communication', + based_on = 'communication_date', + value_based_on = 'rating', + timespan = 'Select Date Range', + time_interval = 'Weekly', + from_date = datetime(2018, 12, 30), + to_date = datetime(2019, 1, 15), + filters_json = '[]', + timeseries = 1 + )).insert() + + result = get(chart_name ='Test Weekly Dashboard Chart', refresh = 1) + + self.assertEqual(result.get('datasets')[0].get('values'), [200.0, 400.0, 0.0]) + self.assertEqual(result.get('labels'), [formatdate('2019-01-06'), formatdate('2019-01-13'), formatdate('2019-01-20')]) + + frappe.db.rollback() + def test_group_by_chart_type(self): if frappe.db.exists('Dashboard Chart', 'Test Group By Dashboard Chart'): frappe.delete_doc('Dashboard Chart', 'Test Group By Dashboard Chart') @@ -155,17 +182,16 @@ class TestDashboardChart(unittest.TestCase): frappe.db.rollback() - def test_dashboard_with_single_doctype(self): - if frappe.db.exists('Dashboard Chart', 'Test Single DocType In Dashboard Chart'): - frappe.delete_doc('Dashboard Chart', 'Test Single DocType In Dashboard Chart') +def insert_test_records(): + create_new_communication(datetime(2019, 1, 10), 100) + create_new_communication(datetime(2019, 1, 6), 200) + create_new_communication(datetime(2019, 1, 8), 300) - chart_doc = frappe.get_doc(dict( - doctype = 'Dashboard Chart', - chart_name = 'Test Single DocType In Dashboard Chart', - chart_type = 'Count', - document_type = 'System Settings', - group_by_based_on = 'Created On', - filters_json = '{}', - )) - - self.assertRaises(frappe.ValidationError, chart_doc.insert) +def create_new_communication(date, rating): + communication = { + 'doctype': 'Communication', + 'subject': 'Test Communication', + 'rating': rating, + 'communication_date': date + } + frappe.get_doc(communication).insert() From 90de23129e94593020fa45b4ff7913489b94e000 Mon Sep 17 00:00:00 2001 From: "mathieu.brunot" Date: Mon, 27 Apr 2020 15:33:08 +0200 Subject: [PATCH 028/498] fix: socketio default port to fix #10119 Signed-off-by: mathieu.brunot --- frappe/templates/base.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/templates/base.html b/frappe/templates/base.html index 907a7b6113..4ec9550820 100644 --- a/frappe/templates/base.html +++ b/frappe/templates/base.html @@ -65,7 +65,7 @@ frappe.ready_events.push(fn); } window.dev_server = {{ dev_server }}; - window.socketio_port = {{ frappe.socketio_port }}; + window.socketio_port = {{ (frappe.socketio_port or (3000 if dev_server else 80)) }}; From 19dc0a65b06e75def4d5b755eefb00eb805c64ab Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 17:16:52 +0530 Subject: [PATCH 029/498] feat: initial bingup for cutomization export --- .../doctype/customization_export/__init__.py | 0 .../customization_export.json | 56 +++++++++++++ .../customization_export.py | 10 +++ .../doctype/manage_customization/__init__.py | 0 .../manage_customization.js | 46 +++++++++++ .../manage_customization.json | 73 +++++++++++++++++ .../manage_customization.py | 78 +++++++++++++++++++ .../test_manage_customization.py | 10 +++ 8 files changed, 273 insertions(+) create mode 100644 frappe/custom/doctype/customization_export/__init__.py create mode 100644 frappe/custom/doctype/customization_export/customization_export.json create mode 100644 frappe/custom/doctype/customization_export/customization_export.py create mode 100644 frappe/custom/doctype/manage_customization/__init__.py create mode 100644 frappe/custom/doctype/manage_customization/manage_customization.js create mode 100644 frappe/custom/doctype/manage_customization/manage_customization.json create mode 100644 frappe/custom/doctype/manage_customization/manage_customization.py create mode 100644 frappe/custom/doctype/manage_customization/test_manage_customization.py diff --git a/frappe/custom/doctype/customization_export/__init__.py b/frappe/custom/doctype/customization_export/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/customization_export/customization_export.json b/frappe/custom/doctype/customization_export/customization_export.json new file mode 100644 index 0000000000..bc59a89f4f --- /dev/null +++ b/frappe/custom/doctype/customization_export/customization_export.json @@ -0,0 +1,56 @@ +{ + "actions": [], + "creation": "2020-03-12 14:51:09.260025", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "sb_00", + "filters", + "cb_00", + "or_filters" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "filters", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Filters" + }, + { + "fieldname": "or_filters", + "fieldtype": "Code", + "in_list_view": 1, + "label": "Or Filters" + }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break" + }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-13 16:53:15.214275", + "modified_by": "Administrator", + "module": "Custom", + "name": "Customization Export", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/customization_export/customization_export.py b/frappe/custom/doctype/customization_export/customization_export.py new file mode 100644 index 0000000000..70a7da3b08 --- /dev/null +++ b/frappe/custom/doctype/customization_export/customization_export.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class CustomizationExport(Document): + pass diff --git a/frappe/custom/doctype/manage_customization/__init__.py b/frappe/custom/doctype/manage_customization/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/manage_customization/manage_customization.js new file mode 100644 index 0000000000..41e2fd316b --- /dev/null +++ b/frappe/custom/doctype/manage_customization/manage_customization.js @@ -0,0 +1,46 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Manage Customization', { + refresh: function(frm) { + frappe.realtime.on("exporting_progress", (data) => { + if (data.progress !== data.total) { + frm.dashboard.show_progress("Exporting", data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + + frappe.realtime.on("importing_progress", (data) => { + if (data.progress !== data.total) { + frm.dashboard.show_progress("Importing", data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + }, + export: function(frm) { + frappe.call({ + method: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + callback: function(r) { + if (r.message) { + const args = { + cmd: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + data: r.message.data + }; + open_url_post(frappe.request.url, args); + } + } + }); + }, + import: function(frm) { + frappe.call({ + method: 'frappe.custom.doctype.manage_customization.manage_customization.import_customizations', + callback: function(r) { + if (r.message) { + console.log("done") + } + } + }); + } +}); diff --git a/frappe/custom/doctype/manage_customization/manage_customization.json b/frappe/custom/doctype/manage_customization/manage_customization.json new file mode 100644 index 0000000000..43c4596590 --- /dev/null +++ b/frappe/custom/doctype/manage_customization/manage_customization.json @@ -0,0 +1,73 @@ +{ + "actions": [], + "creation": "2020-03-01 23:21:17.394500", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "sb_00", + "export_customization", + "export", + "sb_01", + "import_customization", + "import" + ], + "fields": [ + { + "fieldname": "export_customization", + "fieldtype": "Table", + "label": "Customizations to Export", + "options": "Customization Export" + }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break", + "label": "Export Customizations" + }, + { + "fieldname": "sb_01", + "fieldtype": "Section Break", + "label": "Import Customizations" + }, + { + "fieldname": "import_customization", + "fieldtype": "Attach", + "label": "Attach" + }, + { + "depends_on": "export_customization", + "fieldname": "export", + "fieldtype": "Button", + "label": "Export" + }, + { + "depends_on": "import_customization", + "fieldname": "import", + "fieldtype": "Button", + "label": "Import" + } + ], + "issingle": 1, + "links": [], + "modified": "2020-03-13 17:14:00.382402", + "modified_by": "Administrator", + "module": "Custom", + "name": "Manage Customization", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/manage_customization/manage_customization.py new file mode 100644 index 0000000000..3e21b587c9 --- /dev/null +++ b/frappe/custom/doctype/manage_customization/manage_customization.py @@ -0,0 +1,78 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.model.document import Document +import json + +class ManageCustomization(Document): + pass + +@frappe.whitelist() +def export_customizations(): + """Export fixtures as JSON""" + + export_customizations_doc = frappe.get_single("Manage Customization") + customizations = [] + + for doctype in export_customizations_doc.export_customization: + filters, or_filters = {}, {} + + if doctype.get("filters"): + filters = json.loads(doctype.get("filters")) + if doctype.get("or_filters"): + or_filters = json.loads(doctype.get("or_filters")) + + docs = frappe.get_all(doctype.get("document_type"), filters=filters, or_filters=or_filters) + length = len(docs) + for idx, doc in enumerate(docs): + frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) + customizations.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) + + return frappe._dict({ + "data": post_process(customizations) + }) + +@frappe.whitelist() +def import_customizations(): + """Import fixtures as JSON""" + + import_customizations_doc = frappe.get_single("Manage Customization") + + with open(frappe.utils.get_url() + import_customizations_doc.import_customization, 'r') as f: + f = json.loads(f) + + length = f.get("data") + + for idx, doc in enumerate(f.get("data")): + frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) + frappe.get_doc(doc).insert(ignore_permissions=True) + +def post_process(customizations): + del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') + + for doc in customizations: + for key in del_keys: + if key in doc: + del doc[key] + + for key, value in doc.items(): + if not isinstance(value, list): + continue + + for child in value: + for key in del_keys: + if key in child: + del child[key] + + return customizations + +@frappe.whitelist() +def download_customization_json(): + data = frappe._dict(frappe.local.form_dict) + frappe.response['filename'] = 'Customizations.json' + frappe.response['filecontent'] = data.get("data") + frappe.response['content_type'] = 'application/json' + frappe.response['type'] = 'download' \ No newline at end of file diff --git a/frappe/custom/doctype/manage_customization/test_manage_customization.py b/frappe/custom/doctype/manage_customization/test_manage_customization.py new file mode 100644 index 0000000000..af1a332bb4 --- /dev/null +++ b/frappe/custom/doctype/manage_customization/test_manage_customization.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestManageCustomization(unittest.TestCase): + pass From eb57ec7903cbd5dc58e5deb8281f850d37d3f327 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 18:21:14 +0530 Subject: [PATCH 030/498] fix: importer for customization fix --- .../manage_customization.js | 6 ++---- .../manage_customization.py | 18 +++++++++++------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/manage_customization/manage_customization.js index 41e2fd316b..418a7c2c17 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.js +++ b/frappe/custom/doctype/manage_customization/manage_customization.js @@ -36,10 +36,8 @@ frappe.ui.form.on('Manage Customization', { import: function(frm) { frappe.call({ method: 'frappe.custom.doctype.manage_customization.manage_customization.import_customizations', - callback: function(r) { - if (r.message) { - console.log("done") - } + callback: function() { + frappe.msgprint(__("Customizations Imported.")); } }); } diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/manage_customization/manage_customization.py index 3e21b587c9..feb140251a 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.py +++ b/frappe/custom/doctype/manage_customization/manage_customization.py @@ -39,16 +39,20 @@ def export_customizations(): def import_customizations(): """Import fixtures as JSON""" - import_customizations_doc = frappe.get_single("Manage Customization") + import_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Manage Customization", + "attached_to_name": "Manage Customization" + }, limit=1, order_by="creation desc") - with open(frappe.utils.get_url() + import_customizations_doc.import_customization, 'r') as f: - f = json.loads(f) + if not import_file: + return - length = f.get("data") + content = json.loads(frappe.get_doc("File", import_file[0].name).get_content()) + length = len(content) - for idx, doc in enumerate(f.get("data")): - frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) - frappe.get_doc(doc).insert(ignore_permissions=True) + for idx, doc in enumerate(content.get("message").get("data")): + frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) + frappe.get_doc(doc).insert(ignore_permissions=True) def post_process(customizations): del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') From 846594d6c8516a9ed89ec73404b47cc46d5936a8 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 13 Mar 2020 18:41:36 +0530 Subject: [PATCH 031/498] fix: miscellaneous fixes --- .../custom/doctype/manage_customization/manage_customization.js | 2 +- .../custom/doctype/manage_customization/manage_customization.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/manage_customization/manage_customization.js index 418a7c2c17..4c86519dd8 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.js +++ b/frappe/custom/doctype/manage_customization/manage_customization.js @@ -28,7 +28,7 @@ frappe.ui.form.on('Manage Customization', { cmd: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', data: r.message.data }; - open_url_post(frappe.request.url, args); + open_url_post(frappe.request.url, args, true); } } }); diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/manage_customization/manage_customization.py index feb140251a..f3c18d5017 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.py +++ b/frappe/custom/doctype/manage_customization/manage_customization.py @@ -52,7 +52,7 @@ def import_customizations(): for idx, doc in enumerate(content.get("message").get("data")): frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) - frappe.get_doc(doc).insert(ignore_permissions=True) + frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) def post_process(customizations): del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') From b3f45bd81c81747713359bab032b962c4d166fac Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Sat, 14 Mar 2020 11:59:13 +0530 Subject: [PATCH 032/498] feat: rename to package --- frappe/config/customization.py | 8 +-- .../__init__.py | 0 .../package.js} | 18 +++--- .../package.json} | 58 +++++++++++-------- .../package.py} | 44 +++++++------- .../test_package.py} | 2 +- .../__init__.py | 0 .../package_doctype.json} | 22 +++---- .../package_doctype.py} | 2 +- 9 files changed, 82 insertions(+), 72 deletions(-) rename frappe/custom/doctype/{customization_export => package}/__init__.py (100%) rename frappe/custom/doctype/{manage_customization/manage_customization.js => package/package.js} (56%) rename frappe/custom/doctype/{manage_customization/manage_customization.json => package/package.json} (63%) rename frappe/custom/doctype/{manage_customization/manage_customization.py => package/package.py} (55%) rename frappe/custom/doctype/{manage_customization/test_manage_customization.py => package/test_package.py} (78%) rename frappe/custom/doctype/{manage_customization => package_doctype}/__init__.py (100%) rename frappe/custom/doctype/{customization_export/customization_export.json => package_doctype/package_doctype.json} (88%) rename frappe/custom/doctype/{customization_export/customization_export.py => package_doctype/package_doctype.py} (86%) diff --git a/frappe/config/customization.py b/frappe/config/customization.py index 06eaa2ea00..3d587e6839 100644 --- a/frappe/config/customization.py +++ b/frappe/config/customization.py @@ -3,7 +3,7 @@ from frappe import _ def get_data(): return [ - { + { "label": _("Form Customization"), "icon": "fa fa-glass", "items": [ @@ -57,9 +57,9 @@ def get_data(): }, { "type": "doctype", - "label": _("Custom Tags"), - "name": "Tag Category", - "description": _("Add your own Tag Categories") + "label": _("Package"), + "name": "Package", + "description": _("Import and Export Packages.") } ] } diff --git a/frappe/custom/doctype/customization_export/__init__.py b/frappe/custom/doctype/package/__init__.py similarity index 100% rename from frappe/custom/doctype/customization_export/__init__.py rename to frappe/custom/doctype/package/__init__.py diff --git a/frappe/custom/doctype/manage_customization/manage_customization.js b/frappe/custom/doctype/package/package.js similarity index 56% rename from frappe/custom/doctype/manage_customization/manage_customization.js rename to frappe/custom/doctype/package/package.js index 4c86519dd8..c362c51b24 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.js +++ b/frappe/custom/doctype/package/package.js @@ -1,18 +1,18 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Manage Customization', { +frappe.ui.form.on('Package', { refresh: function(frm) { - frappe.realtime.on("exporting_progress", (data) => { - if (data.progress !== data.total) { + frappe.realtime.on("exporting_package", (data) => { + if (data.progress !== (data.total-1)) { frm.dashboard.show_progress("Exporting", data.progress / data.total * 100, __("{0}", [data.message])); } else { frm.dashboard.hide_progress(); } }); - frappe.realtime.on("importing_progress", (data) => { - if (data.progress !== data.total) { + frappe.realtime.on("importing_package", (data) => { + if (data.progress !== (data.total-1)) { frm.dashboard.show_progress("Importing", data.progress / data.total * 100, __("{0}", [data.message])); } else { frm.dashboard.hide_progress(); @@ -21,11 +21,11 @@ frappe.ui.form.on('Manage Customization', { }, export: function(frm) { frappe.call({ - method: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + method: 'frappe.custom.doctype.package.package.export_package', callback: function(r) { if (r.message) { const args = { - cmd: 'frappe.custom.doctype.manage_customization.manage_customization.export_customizations', + cmd: 'frappe.custom.doctype.package.package.download_package', data: r.message.data }; open_url_post(frappe.request.url, args, true); @@ -35,9 +35,9 @@ frappe.ui.form.on('Manage Customization', { }, import: function(frm) { frappe.call({ - method: 'frappe.custom.doctype.manage_customization.manage_customization.import_customizations', + method: 'frappe.custom.doctype.package.package.import_package', callback: function() { - frappe.msgprint(__("Customizations Imported.")); + frappe.msgprint(__("Package Imported.")); } }); } diff --git a/frappe/custom/doctype/manage_customization/manage_customization.json b/frappe/custom/doctype/package/package.json similarity index 63% rename from frappe/custom/doctype/manage_customization/manage_customization.json rename to frappe/custom/doctype/package/package.json index 43c4596590..cad2ca6788 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.json +++ b/frappe/custom/doctype/package/package.json @@ -1,58 +1,58 @@ { "actions": [], - "creation": "2020-03-01 23:21:17.394500", + "creation": "2020-03-14 11:20:14.850552", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ "sb_00", - "export_customization", + "export_package", "export", "sb_01", - "import_customization", + "import_package", "import" ], "fields": [ - { - "fieldname": "export_customization", - "fieldtype": "Table", - "label": "Customizations to Export", - "options": "Customization Export" - }, { "fieldname": "sb_00", "fieldtype": "Section Break", - "label": "Export Customizations" + "label": "Export Package" }, { - "fieldname": "sb_01", - "fieldtype": "Section Break", - "label": "Import Customizations" - }, - { - "fieldname": "import_customization", - "fieldtype": "Attach", - "label": "Attach" - }, - { - "depends_on": "export_customization", + "depends_on": "export_package", "fieldname": "export", "fieldtype": "Button", "label": "Export" }, { - "depends_on": "import_customization", + "fieldname": "sb_01", + "fieldtype": "Section Break", + "label": "Import Package" + }, + { + "depends_on": "import_package", "fieldname": "import", "fieldtype": "Button", "label": "Import" + }, + { + "fieldname": "export_package", + "fieldtype": "Table", + "label": "Package", + "options": "Package DocType" + }, + { + "fieldname": "import_package", + "fieldtype": "Attach", + "label": "Attach" } ], "issingle": 1, "links": [], - "modified": "2020-03-13 17:14:00.382402", + "modified": "2020-03-14 11:52:58.221081", "modified_by": "Administrator", "module": "Custom", - "name": "Manage Customization", + "name": "Package", "owner": "Administrator", "permissions": [ { @@ -64,6 +64,16 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "All", + "share": 1, + "write": 1 } ], "quick_entry": 1, diff --git a/frappe/custom/doctype/manage_customization/manage_customization.py b/frappe/custom/doctype/package/package.py similarity index 55% rename from frappe/custom/doctype/manage_customization/manage_customization.py rename to frappe/custom/doctype/package/package.py index f3c18d5017..e3a29144d9 100644 --- a/frappe/custom/doctype/manage_customization/manage_customization.py +++ b/frappe/custom/doctype/package/package.py @@ -7,17 +7,17 @@ import frappe from frappe.model.document import Document import json -class ManageCustomization(Document): +class Package(Document): pass @frappe.whitelist() -def export_customizations(): - """Export fixtures as JSON""" +def export_package(): + """Export package as JSON""" - export_customizations_doc = frappe.get_single("Manage Customization") - customizations = [] + package_doc = frappe.get_single("Package") + package = [] - for doctype in export_customizations_doc.export_customization: + for doctype in package_doc.export_package: filters, or_filters = {}, {} if doctype.get("filters"): @@ -28,36 +28,36 @@ def export_customizations(): docs = frappe.get_all(doctype.get("document_type"), filters=filters, or_filters=or_filters) length = len(docs) for idx, doc in enumerate(docs): - frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) - customizations.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) + frappe.publish_realtime("exporting_package", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) + package.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) return frappe._dict({ - "data": post_process(customizations) + "data": post_process(package) }) @frappe.whitelist() -def import_customizations(): - """Import fixtures as JSON""" +def import_package(): + """Import package from JSON""" - import_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Manage Customization", - "attached_to_name": "Manage Customization" + package_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Package", + "attached_to_name": "Package" }, limit=1, order_by="creation desc") - if not import_file: + if not package_file: return - content = json.loads(frappe.get_doc("File", import_file[0].name).get_content()) + content = json.loads(frappe.get_doc("File", package_file[0].name).get_content()) length = len(content) for idx, doc in enumerate(content.get("message").get("data")): - frappe.publish_realtime("exporting_progress", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) + frappe.publish_realtime("importing_package", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) -def post_process(customizations): +def post_process(package): del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') - for doc in customizations: + for doc in package: for key in del_keys: if key in doc: del doc[key] @@ -71,12 +71,12 @@ def post_process(customizations): if key in child: del child[key] - return customizations + return package @frappe.whitelist() -def download_customization_json(): +def download_package(): data = frappe._dict(frappe.local.form_dict) - frappe.response['filename'] = 'Customizations.json' + frappe.response['filename'] = 'Package.json' frappe.response['filecontent'] = data.get("data") frappe.response['content_type'] = 'application/json' frappe.response['type'] = 'download' \ No newline at end of file diff --git a/frappe/custom/doctype/manage_customization/test_manage_customization.py b/frappe/custom/doctype/package/test_package.py similarity index 78% rename from frappe/custom/doctype/manage_customization/test_manage_customization.py rename to frappe/custom/doctype/package/test_package.py index af1a332bb4..3a17d51260 100644 --- a/frappe/custom/doctype/manage_customization/test_manage_customization.py +++ b/frappe/custom/doctype/package/test_package.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestManageCustomization(unittest.TestCase): +class TestPackage(unittest.TestCase): pass diff --git a/frappe/custom/doctype/manage_customization/__init__.py b/frappe/custom/doctype/package_doctype/__init__.py similarity index 100% rename from frappe/custom/doctype/manage_customization/__init__.py rename to frappe/custom/doctype/package_doctype/__init__.py diff --git a/frappe/custom/doctype/customization_export/customization_export.json b/frappe/custom/doctype/package_doctype/package_doctype.json similarity index 88% rename from frappe/custom/doctype/customization_export/customization_export.json rename to frappe/custom/doctype/package_doctype/package_doctype.json index bc59a89f4f..cfc4cbbe8f 100644 --- a/frappe/custom/doctype/customization_export/customization_export.json +++ b/frappe/custom/doctype/package_doctype/package_doctype.json @@ -1,6 +1,6 @@ { "actions": [], - "creation": "2020-03-12 14:51:09.260025", + "creation": "2020-03-14 11:20:42.875446", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", @@ -20,33 +20,33 @@ "options": "DocType", "reqd": 1 }, + { + "fieldname": "sb_00", + "fieldtype": "Section Break" + }, { "fieldname": "filters", "fieldtype": "Code", "in_list_view": 1, "label": "Filters" }, + { + "fieldname": "cb_00", + "fieldtype": "Column Break" + }, { "fieldname": "or_filters", "fieldtype": "Code", "in_list_view": 1, "label": "Or Filters" - }, - { - "fieldname": "sb_00", - "fieldtype": "Section Break" - }, - { - "fieldname": "cb_00", - "fieldtype": "Column Break" } ], "istable": 1, "links": [], - "modified": "2020-03-13 16:53:15.214275", + "modified": "2020-03-14 11:20:42.875446", "modified_by": "Administrator", "module": "Custom", - "name": "Customization Export", + "name": "Package DocType", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/frappe/custom/doctype/customization_export/customization_export.py b/frappe/custom/doctype/package_doctype/package_doctype.py similarity index 86% rename from frappe/custom/doctype/customization_export/customization_export.py rename to frappe/custom/doctype/package_doctype/package_doctype.py index 70a7da3b08..c185395965 100644 --- a/frappe/custom/doctype/customization_export/customization_export.py +++ b/frappe/custom/doctype/package_doctype/package_doctype.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class CustomizationExport(Document): +class PackageDocType(Document): pass From 6aa5580eaee2bc6112d81a5d6be36dcc45a6ab35 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Sat, 14 Mar 2020 12:16:30 +0530 Subject: [PATCH 033/498] Update package.py --- frappe/custom/doctype/package/package.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index e3a29144d9..e3b6468791 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -50,7 +50,7 @@ def import_package(): content = json.loads(frappe.get_doc("File", package_file[0].name).get_content()) length = len(content) - for idx, doc in enumerate(content.get("message").get("data")): + for idx, doc in enumerate(content): frappe.publish_realtime("importing_package", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) @@ -79,4 +79,4 @@ def download_package(): frappe.response['filename'] = 'Package.json' frappe.response['filecontent'] = data.get("data") frappe.response['content_type'] = 'application/json' - frappe.response['type'] = 'download' \ No newline at end of file + frappe.response['type'] = 'download' From ea93f12a9a8b5557b1bbcc90c1d656f577f680aa Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Mon, 16 Mar 2020 22:29:55 +0530 Subject: [PATCH 034/498] feat: use UI for adding filters --- frappe/custom/doctype/package/package.js | 80 +++++++++++++++++++ frappe/custom/doctype/package/package.py | 29 +++---- .../package_doctype/package_doctype.json | 32 +++----- 3 files changed, 105 insertions(+), 36 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index c362c51b24..08ec8ac96b 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -42,3 +42,83 @@ frappe.ui.form.on('Package', { }); } }); + +frappe.ui.form.on('Package DocType', { + form_render: function (frm, cdt, cdn) { + function _show_filters(filters, table) { + table.find('tbody').empty(); + + if (filters.length > 0) { + filters.forEach(filter => { + const filter_row = + $(` + ${filter[1]} + ${filter[2] || ""} + ${filter[3]} + `); + + table.find('tbody').append(filter_row); + }); + } else { + const filter_row = $(` + ${__("Click to Set Filters")}`); + table.find('tbody').append(filter_row); + } + } + + let row = frappe.get_doc(cdt, cdn); + + let wrapper = $(`[data-fieldname="filters_json"]`).empty(); + let table = $(` + + + + + + + + + +
${__('Filter')}${__('Condition')}${__('Value')}
`).appendTo(wrapper); + $(`

${__("Click table to edit")}

`).appendTo(wrapper); + + let filters = JSON.parse(row.filters_json || '[]'); + _show_filters(filters, table); + + table.on('click', () => { + frappe.model.with_doctype(row.document_type, function() { + let dialog = new frappe.ui.Dialog({ + title: __('Set Filters'), + fields: [ + { + fieldtype: 'HTML', + label: 'Filters', + fieldname: 'filter_area', + } + ], + primary_action: function() { + let values = filter_group.get_filters(); + let flt = [] + if (values) { + values.forEach(function(value) { + flt.push([value[0], value[1], value[2], value[3]]); + }); + } + row.filters_json = JSON.stringify(flt); + _show_filters(flt, table); + dialog.hide(); + }, + primary_action_label: "Set" + }); + + let filter_group = new frappe.ui.FilterGroup({ + parent: dialog.get_field('filter_area').$wrapper, + doctype: row.document_type, + on_change: () => {}, + }); + filter_group.add_filters_to_filter_group(filters); + dialog.show(); + }) + }); + }, +}); \ No newline at end of file diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index e3b6468791..06edcc4ce0 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -18,14 +18,12 @@ def export_package(): package = [] for doctype in package_doc.export_package: - filters, or_filters = {}, {} + filters = [] - if doctype.get("filters"): - filters = json.loads(doctype.get("filters")) - if doctype.get("or_filters"): - or_filters = json.loads(doctype.get("or_filters")) + if doctype.get("filters_json"): + filters = json.loads(doctype.get("filters_json")) - docs = frappe.get_all(doctype.get("document_type"), filters=filters, or_filters=or_filters) + docs = frappe.get_all(doctype.get("document_type"), filters=filters) length = len(docs) for idx, doc in enumerate(docs): frappe.publish_realtime("exporting_package", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) @@ -36,18 +34,21 @@ def export_package(): }) @frappe.whitelist() -def import_package(): +def import_package(package=None): """Import package from JSON""" - package_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Package", - "attached_to_name": "Package" - }, limit=1, order_by="creation desc") + if not package: + package_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Package", + "attached_to_name": "Package" + }, limit=1, order_by="creation desc") - if not package_file: - return + if not package_file: + return - content = json.loads(frappe.get_doc("File", package_file[0].name).get_content()) + package = frappe.get_doc("File", package_file[0].name).get_content() + + content = json.loads(package) length = len(content) for idx, doc in enumerate(content): diff --git a/frappe/custom/doctype/package_doctype/package_doctype.json b/frappe/custom/doctype/package_doctype/package_doctype.json index cfc4cbbe8f..353f01a673 100644 --- a/frappe/custom/doctype/package_doctype/package_doctype.json +++ b/frappe/custom/doctype/package_doctype/package_doctype.json @@ -6,10 +6,8 @@ "engine": "InnoDB", "field_order": [ "document_type", - "sb_00", - "filters", - "cb_00", - "or_filters" + "section_break_4", + "filters_json" ], "fields": [ { @@ -21,29 +19,19 @@ "reqd": 1 }, { - "fieldname": "sb_00", + "fieldname": "filters_json", + "fieldtype": "Code", + "label": "Filters", + "options": "JSON" + }, + { + "fieldname": "section_break_4", "fieldtype": "Section Break" - }, - { - "fieldname": "filters", - "fieldtype": "Code", - "in_list_view": 1, - "label": "Filters" - }, - { - "fieldname": "cb_00", - "fieldtype": "Column Break" - }, - { - "fieldname": "or_filters", - "fieldtype": "Code", - "in_list_view": 1, - "label": "Or Filters" } ], "istable": 1, "links": [], - "modified": "2020-03-14 11:20:42.875446", + "modified": "2020-03-16 22:11:40.479498", "modified_by": "Administrator", "module": "Custom", "name": "Package DocType", From f0ec5cec0e0da7b07caf1172fbb8e20fa8cf2230 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 17 Mar 2020 11:37:47 +0530 Subject: [PATCH 035/498] feat: package migrator --- .../doctype/package_migration/__init__.py | 0 .../package_migration/package_migration.js | 51 +++++++++++++++++++ .../package_migration/package_migration.json | 42 +++++++++++++++ .../package_migration/package_migration.py | 28 ++++++++++ .../test_package_migration.py | 10 ++++ 5 files changed, 131 insertions(+) create mode 100644 frappe/custom/doctype/package_migration/__init__.py create mode 100644 frappe/custom/doctype/package_migration/package_migration.js create mode 100644 frappe/custom/doctype/package_migration/package_migration.json create mode 100644 frappe/custom/doctype/package_migration/package_migration.py create mode 100644 frappe/custom/doctype/package_migration/test_package_migration.py diff --git a/frappe/custom/doctype/package_migration/__init__.py b/frappe/custom/doctype/package_migration/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/package_migration/package_migration.js b/frappe/custom/doctype/package_migration/package_migration.js new file mode 100644 index 0000000000..020092f5f3 --- /dev/null +++ b/frappe/custom/doctype/package_migration/package_migration.js @@ -0,0 +1,51 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Package Migration', { + refresh: function(frm) { + if (frm.doc.attach) { + frm.add_custom_button(__("Connect"), function() { + let dialog = new frappe.ui.Dialog({ + title: __('Connect to Remote Instance'), + fields: [ + { + fieldtype: 'Data', + label: 'Remote Instance', + fieldname: 'remote_instance', + reqd: 1 + }, + { + fieldtype: 'Data', + label: 'User', + fieldname: 'user', + reqd: 1 + }, + { + fieldtype: 'Password', + label: 'Password', + fieldname: 'password', + reqd: 1 + }, + ], + primary_action: function() { + let values = dialog.get_values(); + frm.call("install_package_to_remote", { + "remote_instance": values.remote_instance, + "user": values.user, + "password": values.password + }).then((r) => { + console.log(r); + }) + dialog.hide(); + }, + primary_action_label: "Execute" + }); + dialog.show(); + }) + } + } +}); + +// "erpnext_support_password": "uYrMeEhb2NzuEGOk", +// "erpnext_support_url": "https://marketplace.erpnext.com", +// "erpnext_support_user": "erpnext_support_fQrhUujW@erpnext.com", \ No newline at end of file diff --git a/frappe/custom/doctype/package_migration/package_migration.json b/frappe/custom/doctype/package_migration/package_migration.json new file mode 100644 index 0000000000..a3c230502b --- /dev/null +++ b/frappe/custom/doctype/package_migration/package_migration.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "creation": "2020-03-17 00:13:31.851579", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "attach" + ], + "fields": [ + { + "fieldname": "attach", + "fieldtype": "Attach", + "in_list_view": 1, + "label": "Attach", + "reqd": 1 + } + ], + "issingle": 1, + "links": [], + "modified": "2020-03-17 10:00:26.676328", + "modified_by": "Administrator", + "module": "Custom", + "name": "Package Migration", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/package_migration/package_migration.py b/frappe/custom/doctype/package_migration/package_migration.py new file mode 100644 index 0000000000..cdcbc0bd19 --- /dev/null +++ b/frappe/custom/doctype/package_migration/package_migration.py @@ -0,0 +1,28 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe.frappeclient import FrappeClient +from frappe.model.document import Document +from frappe import _ + +class PackageMigration(Document): + + def install_package_to_remote(self, remote_instance, user, password): + connection = FrappeClient(remote_instance, user, password) + + package_file = frappe.get_all("File", filters={ + "attached_to_doctype": "Package Migration", + "attached_to_name": "Package Migration" + }, limit=1, order_by="creation desc") + + try: + connection.post_request({ + "cmd": "frappe.custom.doctype.package.package.import_package", + "package": frappe.get_doc("File", package_file[0].name).get_content() + }) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Could not connect to Remote Site.")) diff --git a/frappe/custom/doctype/package_migration/test_package_migration.py b/frappe/custom/doctype/package_migration/test_package_migration.py new file mode 100644 index 0000000000..8b438145e8 --- /dev/null +++ b/frappe/custom/doctype/package_migration/test_package_migration.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestPackageMigration(unittest.TestCase): + pass From 230d78df2511032f7bc93021b004ab1786911726 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 18 Mar 2020 16:16:41 +0530 Subject: [PATCH 036/498] feat: deploy remote instance with package --- frappe/custom/doctype/package/package.js | 42 ++--------- frappe/custom/doctype/package/package.json | 37 +-------- frappe/custom/doctype/package/package.py | 75 ++++++++++++------- .../package_doctype/package_doctype.json | 23 +++++- .../package_migration/package_migration.js | 51 ------------- .../package_migration/package_migration.py | 28 ------- .../__init__.py | 0 frappe/custom/doctype/release/release.js | 22 ++++++ .../release.json} | 17 ++--- frappe/custom/doctype/release/release.py | 45 +++++++++++ .../test_release.py} | 2 +- .../doctype/release_instance/__init__.py | 0 .../release_instance/release_instance.json | 31 ++++++++ .../release_instance/release_instance.py | 10 +++ .../doctype/remote_instance/__init__.py | 0 .../remote_instance/remote_instance.js | 8 ++ .../remote_instance/remote_instance.json | 67 +++++++++++++++++ .../remote_instance/remote_instance.py | 10 +++ .../remote_instance/test_remote_instance.py | 10 +++ 19 files changed, 290 insertions(+), 188 deletions(-) delete mode 100644 frappe/custom/doctype/package_migration/package_migration.js delete mode 100644 frappe/custom/doctype/package_migration/package_migration.py rename frappe/custom/doctype/{package_migration => release}/__init__.py (100%) create mode 100644 frappe/custom/doctype/release/release.js rename frappe/custom/doctype/{package_migration/package_migration.json => release/release.json} (67%) create mode 100644 frappe/custom/doctype/release/release.py rename frappe/custom/doctype/{package_migration/test_package_migration.py => release/test_release.py} (79%) create mode 100644 frappe/custom/doctype/release_instance/__init__.py create mode 100644 frappe/custom/doctype/release_instance/release_instance.json create mode 100644 frappe/custom/doctype/release_instance/release_instance.py create mode 100644 frappe/custom/doctype/remote_instance/__init__.py create mode 100644 frappe/custom/doctype/remote_instance/remote_instance.js create mode 100644 frappe/custom/doctype/remote_instance/remote_instance.json create mode 100644 frappe/custom/doctype/remote_instance/remote_instance.py create mode 100644 frappe/custom/doctype/remote_instance/test_remote_instance.py diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 08ec8ac96b..2fb0b8eb1d 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -3,44 +3,12 @@ frappe.ui.form.on('Package', { refresh: function(frm) { - frappe.realtime.on("exporting_package", (data) => { - if (data.progress !== (data.total-1)) { - frm.dashboard.show_progress("Exporting", data.progress / data.total * 100, __("{0}", [data.message])); - } else { - frm.dashboard.hide_progress(); - } - }); - - frappe.realtime.on("importing_package", (data) => { - if (data.progress !== (data.total-1)) { - frm.dashboard.show_progress("Importing", data.progress / data.total * 100, __("{0}", [data.message])); - } else { - frm.dashboard.hide_progress(); - } - }); + if (frm.doc.export_package) { + frm.add_custom_button(__("Go to Release"), function() { + frappe.set_route("Form", "Release", "Release"); + }); + } }, - export: function(frm) { - frappe.call({ - method: 'frappe.custom.doctype.package.package.export_package', - callback: function(r) { - if (r.message) { - const args = { - cmd: 'frappe.custom.doctype.package.package.download_package', - data: r.message.data - }; - open_url_post(frappe.request.url, args, true); - } - } - }); - }, - import: function(frm) { - frappe.call({ - method: 'frappe.custom.doctype.package.package.import_package', - callback: function() { - frappe.msgprint(__("Package Imported.")); - } - }); - } }); frappe.ui.form.on('Package DocType', { diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index cad2ca6788..1d71b744d1 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -5,51 +5,20 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "sb_00", - "export_package", - "export", - "sb_01", - "import_package", - "import" + "export_package" ], "fields": [ { - "fieldname": "sb_00", - "fieldtype": "Section Break", - "label": "Export Package" - }, - { - "depends_on": "export_package", - "fieldname": "export", - "fieldtype": "Button", - "label": "Export" - }, - { - "fieldname": "sb_01", - "fieldtype": "Section Break", - "label": "Import Package" - }, - { - "depends_on": "import_package", - "fieldname": "import", - "fieldtype": "Button", - "label": "Import" - }, - { + "description": "Click on the row for accessing filters.", "fieldname": "export_package", "fieldtype": "Table", "label": "Package", "options": "Package DocType" - }, - { - "fieldname": "import_package", - "fieldtype": "Attach", - "label": "Attach" } ], "issingle": 1, "links": [], - "modified": "2020-03-14 11:52:58.221081", + "modified": "2020-03-17 16:41:13.542896", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 06edcc4ce0..a51386acfc 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -4,8 +4,10 @@ from __future__ import unicode_literals import frappe -from frappe.model.document import Document import json +from frappe.model.document import Document +from frappe.core.doctype.version.version import get_diff +from frappe.utils.file_manager import save_file class Package(Document): pass @@ -25,9 +27,29 @@ def export_package(): docs = frappe.get_all(doctype.get("document_type"), filters=filters) length = len(docs) + for idx, doc in enumerate(docs): - frappe.publish_realtime("exporting_package", dict(progress=idx, total=length, message=doctype.get("document_type")), user=frappe.session.user) - package.append(frappe.get_doc(doctype.get("document_type"), doc.name).as_dict()) + frappe.publish_realtime("exporting_package", {"progress":idx, "total":length, "message":doctype.get("document_type")}, + user=frappe.session.user) + + document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() + + if doctype.attachments: + attachments = [] + filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("doctype")} + for f in frappe.get_list("File", filters=filters): + attachments.append({ + "fname": f.name, + "content": frappe.get_doc("File", f.name).get_content() + }) + + document.update({"attachments": json.dumps(attachments)}) + + if doctype.overwrite: + document.update({"overwrite": 1}) + + document.update({"modified": frappe.utils.get_datetime_str(document.get("modified"))}) + package.append(document) return frappe._dict({ "data": post_process(package) @@ -37,26 +59,33 @@ def export_package(): def import_package(package=None): """Import package from JSON""" - if not package: - package_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Package", - "attached_to_name": "Package" - }, limit=1, order_by="creation desc") - - if not package_file: - return - - package = frappe.get_doc("File", package_file[0].name).get_content() - content = json.loads(package) length = len(content) - for idx, doc in enumerate(content): - frappe.publish_realtime("importing_package", dict(progress=idx, total=length, message=doc.get("doctype")), user=frappe.session.user) - frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) + for doc in content: + docname = doc.pop("name") + modified = doc.pop("modified") + overwrite = doc.pop("overwrite") + exists = frappe.db.exists(doc.get("doctype"), docname) + + if not exists: + d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) + if doc.get("attachments"): + add_attachment(doc.get("attachments"), d) + elif exists and overwrite: + document = frappe.get_doc(doc.get("doctype"), doc.pop("name")) + if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): + document.update(doc) + document.save() + if doc.get("attachments"): + add_attachment(doc.get("attachments"), document) + +def add_attachment(attachments, doc): + for attachment in attachments: + save_file(attachment.get("fname"), attachment.get("content"), doc.get("doctype"), doc.get("name")) def post_process(package): - del_keys = ('modified_by', 'creation', 'owner', 'idx', 'name', 'modified', 'docstatus') + del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') for doc in package: for key in del_keys: @@ -68,16 +97,8 @@ def post_process(package): continue for child in value: - for key in del_keys: + for key in del_keys + ('name'): if key in child: del child[key] return package - -@frappe.whitelist() -def download_package(): - data = frappe._dict(frappe.local.form_dict) - frappe.response['filename'] = 'Package.json' - frappe.response['filecontent'] = data.get("data") - frappe.response['content_type'] = 'application/json' - frappe.response['type'] = 'download' diff --git a/frappe/custom/doctype/package_doctype/package_doctype.json b/frappe/custom/doctype/package_doctype/package_doctype.json index 353f01a673..058cff6480 100644 --- a/frappe/custom/doctype/package_doctype/package_doctype.json +++ b/frappe/custom/doctype/package_doctype/package_doctype.json @@ -6,6 +6,9 @@ "engine": "InnoDB", "field_order": [ "document_type", + "column_break_2", + "attachments", + "overwrite", "section_break_4", "filters_json" ], @@ -27,11 +30,29 @@ { "fieldname": "section_break_4", "fieldtype": "Section Break" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "fieldname": "attachments", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Include Attachments" + }, + { + "default": "0", + "fieldname": "overwrite", + "fieldtype": "Check", + "in_list_view": 1, + "label": "Overwrite" } ], "istable": 1, "links": [], - "modified": "2020-03-16 22:11:40.479498", + "modified": "2020-03-17 17:27:58.859896", "modified_by": "Administrator", "module": "Custom", "name": "Package DocType", diff --git a/frappe/custom/doctype/package_migration/package_migration.js b/frappe/custom/doctype/package_migration/package_migration.js deleted file mode 100644 index 020092f5f3..0000000000 --- a/frappe/custom/doctype/package_migration/package_migration.js +++ /dev/null @@ -1,51 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Package Migration', { - refresh: function(frm) { - if (frm.doc.attach) { - frm.add_custom_button(__("Connect"), function() { - let dialog = new frappe.ui.Dialog({ - title: __('Connect to Remote Instance'), - fields: [ - { - fieldtype: 'Data', - label: 'Remote Instance', - fieldname: 'remote_instance', - reqd: 1 - }, - { - fieldtype: 'Data', - label: 'User', - fieldname: 'user', - reqd: 1 - }, - { - fieldtype: 'Password', - label: 'Password', - fieldname: 'password', - reqd: 1 - }, - ], - primary_action: function() { - let values = dialog.get_values(); - frm.call("install_package_to_remote", { - "remote_instance": values.remote_instance, - "user": values.user, - "password": values.password - }).then((r) => { - console.log(r); - }) - dialog.hide(); - }, - primary_action_label: "Execute" - }); - dialog.show(); - }) - } - } -}); - -// "erpnext_support_password": "uYrMeEhb2NzuEGOk", -// "erpnext_support_url": "https://marketplace.erpnext.com", -// "erpnext_support_user": "erpnext_support_fQrhUujW@erpnext.com", \ No newline at end of file diff --git a/frappe/custom/doctype/package_migration/package_migration.py b/frappe/custom/doctype/package_migration/package_migration.py deleted file mode 100644 index cdcbc0bd19..0000000000 --- a/frappe/custom/doctype/package_migration/package_migration.py +++ /dev/null @@ -1,28 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe.frappeclient import FrappeClient -from frappe.model.document import Document -from frappe import _ - -class PackageMigration(Document): - - def install_package_to_remote(self, remote_instance, user, password): - connection = FrappeClient(remote_instance, user, password) - - package_file = frappe.get_all("File", filters={ - "attached_to_doctype": "Package Migration", - "attached_to_name": "Package Migration" - }, limit=1, order_by="creation desc") - - try: - connection.post_request({ - "cmd": "frappe.custom.doctype.package.package.import_package", - "package": frappe.get_doc("File", package_file[0].name).get_content() - }) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Could not connect to Remote Site.")) diff --git a/frappe/custom/doctype/package_migration/__init__.py b/frappe/custom/doctype/release/__init__.py similarity index 100% rename from frappe/custom/doctype/package_migration/__init__.py rename to frappe/custom/doctype/release/__init__.py diff --git a/frappe/custom/doctype/release/release.js b/frappe/custom/doctype/release/release.js new file mode 100644 index 0000000000..b0919bef43 --- /dev/null +++ b/frappe/custom/doctype/release/release.js @@ -0,0 +1,22 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Release', { + refresh: function(frm) { + frm.add_custom_button(__("Go to Package"), function() { + frappe.set_route("Form", "Package", "Package"); + }); + + frm.add_custom_button(__("Release"), function() { + frm.call("create_release").then(() => {}); + }); + + frappe.realtime.on("exporting_package", (data) => { + if (data.progress !== (data.total-1)) { + frm.dashboard.show_progress("Releasing Package", data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + } +}); diff --git a/frappe/custom/doctype/package_migration/package_migration.json b/frappe/custom/doctype/release/release.json similarity index 67% rename from frappe/custom/doctype/package_migration/package_migration.json rename to frappe/custom/doctype/release/release.json index a3c230502b..ffe7c599cc 100644 --- a/frappe/custom/doctype/package_migration/package_migration.json +++ b/frappe/custom/doctype/release/release.json @@ -1,27 +1,26 @@ { "actions": [], - "creation": "2020-03-17 00:13:31.851579", + "creation": "2020-03-17 13:27:30.158389", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "attach" + "instances" ], "fields": [ { - "fieldname": "attach", - "fieldtype": "Attach", - "in_list_view": 1, - "label": "Attach", - "reqd": 1 + "fieldname": "instances", + "fieldtype": "Table", + "label": "Instances", + "options": "Release Instance" } ], "issingle": 1, "links": [], - "modified": "2020-03-17 10:00:26.676328", + "modified": "2020-03-17 15:37:21.813063", "modified_by": "Administrator", "module": "Custom", - "name": "Package Migration", + "name": "Release", "owner": "Administrator", "permissions": [ { diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py new file mode 100644 index 0000000000..27ecf5c459 --- /dev/null +++ b/frappe/custom/doctype/release/release.py @@ -0,0 +1,45 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +import json +from frappe import _ +from frappe.model.document import Document +from frappe.custom.doctype.package.package import export_package +from frappe.frappeclient import FrappeClient +from frappe.utils.file_manager import save_file +from frappe.model.naming import make_autoname +from frappe.utils.password import get_decrypted_password + +class Release(Document): + + def create_release(self): + package = export_package().get("data") + + for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): + frappe.delete_doc_if_exists("File", dt_file.name) + + file_name = make_autoname("Package") + save_file(file_name, json.dumps(package), "Release", "Release") + + length = len(self.instances) + for instance in self.instances: + message = _("Releasing to {0}").format(instance.instance) + frappe.publish_realtime("exporting_package", {"progress": idx, "total": length, "message": message}, + user=frappe.session.user) + remote = frappe.get_doc("Remote Instance", instance.instance) + self.install_package_to_remote(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) + + def install_package_to_remote(self, remote_instance, user, password): + connection = FrappeClient(remote_instance, user, password) + + try: + connection.post_request({ + "cmd": "frappe.custom.doctype.package.package.import_package", + "package": package + }) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Could not connect to Site {0}.").format(remote_instance)) diff --git a/frappe/custom/doctype/package_migration/test_package_migration.py b/frappe/custom/doctype/release/test_release.py similarity index 79% rename from frappe/custom/doctype/package_migration/test_package_migration.py rename to frappe/custom/doctype/release/test_release.py index 8b438145e8..13e4e26ac0 100644 --- a/frappe/custom/doctype/package_migration/test_package_migration.py +++ b/frappe/custom/doctype/release/test_release.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestPackageMigration(unittest.TestCase): +class TestRelease(unittest.TestCase): pass diff --git a/frappe/custom/doctype/release_instance/__init__.py b/frappe/custom/doctype/release_instance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json new file mode 100644 index 0000000000..a91b00666f --- /dev/null +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -0,0 +1,31 @@ +{ + "actions": [], + "creation": "2020-03-17 12:24:29.615432", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instance", + "options": "Remote Instance" + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-17 13:35:38.456830", + "modified_by": "Administrator", + "module": "Custom", + "name": "Release Instance", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance/release_instance.py b/frappe/custom/doctype/release_instance/release_instance.py new file mode 100644 index 0000000000..f5b745a9b4 --- /dev/null +++ b/frappe/custom/doctype/release_instance/release_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class ReleaseInstance(Document): + pass diff --git a/frappe/custom/doctype/remote_instance/__init__.py b/frappe/custom/doctype/remote_instance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/remote_instance/remote_instance.js b/frappe/custom/doctype/remote_instance/remote_instance.js new file mode 100644 index 0000000000..50096efe4a --- /dev/null +++ b/frappe/custom/doctype/remote_instance/remote_instance.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Remote Instance', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/custom/doctype/remote_instance/remote_instance.json b/frappe/custom/doctype/remote_instance/remote_instance.json new file mode 100644 index 0000000000..4224d40c4c --- /dev/null +++ b/frappe/custom/doctype/remote_instance/remote_instance.json @@ -0,0 +1,67 @@ +{ + "actions": [], + "autoname": "field:instance", + "creation": "2020-03-17 12:08:33.304631", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance", + "user", + "password" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Data", + "label": "Instance", + "unique": 1 + }, + { + "fieldname": "user", + "fieldtype": "Data", + "label": "User" + }, + { + "fieldname": "password", + "fieldtype": "Password", + "label": "Password" + } + ], + "links": [], + "modified": "2020-03-17 13:34:40.924501", + "modified_by": "Administrator", + "module": "Custom", + "name": "Remote Instance", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "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 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/remote_instance/remote_instance.py b/frappe/custom/doctype/remote_instance/remote_instance.py new file mode 100644 index 0000000000..5fd513a2f6 --- /dev/null +++ b/frappe/custom/doctype/remote_instance/remote_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class RemoteInstance(Document): + pass diff --git a/frappe/custom/doctype/remote_instance/test_remote_instance.py b/frappe/custom/doctype/remote_instance/test_remote_instance.py new file mode 100644 index 0000000000..0eb7561bf5 --- /dev/null +++ b/frappe/custom/doctype/remote_instance/test_remote_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestRemoteInstance(unittest.TestCase): + pass From a02847f12676f310b637ad1cfb89436c24896613 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 18 Mar 2020 23:18:06 +0530 Subject: [PATCH 037/498] fix: remove release instance doctype --- frappe/custom/doctype/package/package.py | 17 +++-- frappe/custom/doctype/release/release.js | 9 +-- frappe/custom/doctype/release/release.json | 4 +- frappe/custom/doctype/release/release.py | 25 +++---- .../release_instance.js} | 2 +- .../release_instance/release_instance.json | 57 ++++++++++++++-- .../test_release_instance.py} | 2 +- .../__init__.py | 0 .../release_instance_list.json | 32 +++++++++ .../release_instance_list.py} | 2 +- .../remote_instance/remote_instance.json | 67 ------------------- 11 files changed, 116 insertions(+), 101 deletions(-) rename frappe/custom/doctype/{remote_instance/remote_instance.js => release_instance/release_instance.js} (79%) rename frappe/custom/doctype/{remote_instance/test_remote_instance.py => release_instance/test_release_instance.py} (79%) rename frappe/custom/doctype/{remote_instance => release_instance_list}/__init__.py (100%) create mode 100644 frappe/custom/doctype/release_instance_list/release_instance_list.json rename frappe/custom/doctype/{remote_instance/remote_instance.py => release_instance_list/release_instance_list.py} (86%) delete mode 100644 frappe/custom/doctype/remote_instance/remote_instance.json diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index a51386acfc..bedc567066 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -5,9 +5,11 @@ from __future__ import unicode_literals import frappe import json +import pickle from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file +from frappe import _ class Package(Document): pass @@ -29,7 +31,7 @@ def export_package(): length = len(docs) for idx, doc in enumerate(docs): - frappe.publish_realtime("exporting_package", {"progress":idx, "total":length, "message":doctype.get("document_type")}, + frappe.publish_realtime("package", {"progress":idx, "total":length, "message":doctype.get("document_type"), "prefix": _("Exporting")}, user=frappe.session.user) document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() @@ -51,15 +53,13 @@ def export_package(): document.update({"modified": frappe.utils.get_datetime_str(document.get("modified"))}) package.append(document) - return frappe._dict({ - "data": post_process(package) - }) + return post_process(package) @frappe.whitelist() def import_package(package=None): """Import package from JSON""" - content = json.loads(package) + content = pickle.loads(package) length = len(content) for doc in content: @@ -85,7 +85,12 @@ def add_attachment(attachments, doc): save_file(attachment.get("fname"), attachment.get("content"), doc.get("doctype"), doc.get("name")) def post_process(package): + """ + Remove the keys from Document and Child Document + Convert datetime, date, time to str + """ del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') + child_del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus', 'name') for doc in package: for key in del_keys: @@ -97,7 +102,7 @@ def post_process(package): continue for child in value: - for key in del_keys + ('name'): + for key in child_del_keys: if key in child: del child[key] diff --git a/frappe/custom/doctype/release/release.js b/frappe/custom/doctype/release/release.js index b0919bef43..709ae18d36 100644 --- a/frappe/custom/doctype/release/release.js +++ b/frappe/custom/doctype/release/release.js @@ -8,12 +8,13 @@ frappe.ui.form.on('Release', { }); frm.add_custom_button(__("Release"), function() { - frm.call("create_release").then(() => {}); + frm.call("create_release"); }); - frappe.realtime.on("exporting_package", (data) => { - if (data.progress !== (data.total-1)) { - frm.dashboard.show_progress("Releasing Package", data.progress / data.total * 100, __("{0}", [data.message])); + frappe.realtime.on("package", (data) => { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); + if ((data.progress+1) != data.total) { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); } else { frm.dashboard.hide_progress(); } diff --git a/frappe/custom/doctype/release/release.json b/frappe/custom/doctype/release/release.json index ffe7c599cc..ba6c69c49f 100644 --- a/frappe/custom/doctype/release/release.json +++ b/frappe/custom/doctype/release/release.json @@ -12,12 +12,12 @@ "fieldname": "instances", "fieldtype": "Table", "label": "Instances", - "options": "Release Instance" + "options": "Release Instance List" } ], "issingle": 1, "links": [], - "modified": "2020-03-17 15:37:21.813063", + "modified": "2020-03-18 18:25:23.539372", "modified_by": "Administrator", "module": "Custom", "name": "Release", diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index 27ecf5c459..ae798f7d85 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe import json +import pickle from frappe import _ from frappe.model.document import Document from frappe.custom.doctype.package.package import export_package @@ -16,30 +17,30 @@ from frappe.utils.password import get_decrypted_password class Release(Document): def create_release(self): - package = export_package().get("data") + package = export_package() for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): frappe.delete_doc_if_exists("File", dt_file.name) file_name = make_autoname("Package") - save_file(file_name, json.dumps(package), "Release", "Release") + save_file(file_name, pickle.dumps(package), "Release", "Release") length = len(self.instances) - for instance in self.instances: - message = _("Releasing to {0}").format(instance.instance) - frappe.publish_realtime("exporting_package", {"progress": idx, "total": length, "message": message}, + for idx, instance in enumerate(self.instances): + frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance, "prefix": _("Deploying")}, user=frappe.session.user) - remote = frappe.get_doc("Remote Instance", instance.instance) - self.install_package_to_remote(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) - def install_package_to_remote(self, remote_instance, user, password): - connection = FrappeClient(remote_instance, user, password) + self.install_package_to_remote(package, instance) + + def install_package_to_remote(self, package, instance): + remote = frappe.get_doc("Release Instance", instance.instance) + connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) try: connection.post_request({ "cmd": "frappe.custom.doctype.package.package.import_package", - "package": package + "package": pickle.dumps(package) }) - except Exception: + except Exception as e: frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Could not connect to Site {0}.").format(remote_instance)) + frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(remote.instance)) diff --git a/frappe/custom/doctype/remote_instance/remote_instance.js b/frappe/custom/doctype/release_instance/release_instance.js similarity index 79% rename from frappe/custom/doctype/remote_instance/remote_instance.js rename to frappe/custom/doctype/release_instance/release_instance.js index 50096efe4a..7447c9c706 100644 --- a/frappe/custom/doctype/remote_instance/remote_instance.js +++ b/frappe/custom/doctype/release_instance/release_instance.js @@ -1,7 +1,7 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Remote Instance', { +frappe.ui.form.on('Release Instance', { // refresh: function(frm) { // } diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index a91b00666f..d1bf07db59 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -1,29 +1,72 @@ { "actions": [], + "autoname": "field:instance", "creation": "2020-03-17 12:24:29.615432", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "instance" + "instance", + "user", + "password" ], "fields": [ { "fieldname": "instance", - "fieldtype": "Link", + "fieldtype": "Data", "in_list_view": 1, - "label": "Instance", - "options": "Remote Instance" + "in_standard_filter": 1, + "label": "Instance URL", + "unique": 1 + }, + { + "fieldname": "user", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "User", + "options": "Email" + }, + { + "fieldname": "password", + "fieldtype": "Password", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Password" } ], - "istable": 1, "links": [], - "modified": "2020-03-17 13:35:38.456830", + "modified": "2020-03-18 18:54:55.801749", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", "owner": "Administrator", - "permissions": [], + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "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 + } + ], "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/custom/doctype/remote_instance/test_remote_instance.py b/frappe/custom/doctype/release_instance/test_release_instance.py similarity index 79% rename from frappe/custom/doctype/remote_instance/test_remote_instance.py rename to frappe/custom/doctype/release_instance/test_release_instance.py index 0eb7561bf5..95df304262 100644 --- a/frappe/custom/doctype/remote_instance/test_remote_instance.py +++ b/frappe/custom/doctype/release_instance/test_release_instance.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestRemoteInstance(unittest.TestCase): +class TestReleaseInstance(unittest.TestCase): pass diff --git a/frappe/custom/doctype/remote_instance/__init__.py b/frappe/custom/doctype/release_instance_list/__init__.py similarity index 100% rename from frappe/custom/doctype/remote_instance/__init__.py rename to frappe/custom/doctype/release_instance_list/__init__.py diff --git a/frappe/custom/doctype/release_instance_list/release_instance_list.json b/frappe/custom/doctype/release_instance_list/release_instance_list.json new file mode 100644 index 0000000000..d9b9b63340 --- /dev/null +++ b/frappe/custom/doctype/release_instance_list/release_instance_list.json @@ -0,0 +1,32 @@ +{ + "actions": [], + "creation": "2020-03-18 18:25:02.024237", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Instance", + "options": "Release Instance", + "reqd": 1 + } + ], + "istable": 1, + "links": [], + "modified": "2020-03-18 22:55:20.130597", + "modified_by": "Administrator", + "module": "Custom", + "name": "Release Instance List", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/remote_instance/remote_instance.py b/frappe/custom/doctype/release_instance_list/release_instance_list.py similarity index 86% rename from frappe/custom/doctype/remote_instance/remote_instance.py rename to frappe/custom/doctype/release_instance_list/release_instance_list.py index 5fd513a2f6..f6eb68a535 100644 --- a/frappe/custom/doctype/remote_instance/remote_instance.py +++ b/frappe/custom/doctype/release_instance_list/release_instance_list.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class RemoteInstance(Document): +class ReleaseInstanceList(Document): pass diff --git a/frappe/custom/doctype/remote_instance/remote_instance.json b/frappe/custom/doctype/remote_instance/remote_instance.json deleted file mode 100644 index 4224d40c4c..0000000000 --- a/frappe/custom/doctype/remote_instance/remote_instance.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "actions": [], - "autoname": "field:instance", - "creation": "2020-03-17 12:08:33.304631", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instance", - "user", - "password" - ], - "fields": [ - { - "fieldname": "instance", - "fieldtype": "Data", - "label": "Instance", - "unique": 1 - }, - { - "fieldname": "user", - "fieldtype": "Data", - "label": "User" - }, - { - "fieldname": "password", - "fieldtype": "Password", - "label": "Password" - } - ], - "links": [], - "modified": "2020-03-17 13:34:40.924501", - "modified_by": "Administrator", - "module": "Custom", - "name": "Remote Instance", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "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 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file From 1901650514037d29786e2d1841681a39fd7f4648 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 11:33:25 +0530 Subject: [PATCH 038/498] fix: add utils for converting to string --- frappe/custom/doctype/package/package.py | 31 ++++++++++++++++++++---- frappe/utils/data.py | 13 ++++++++++ 2 files changed, 39 insertions(+), 5 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index bedc567066..5ee781c608 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -5,7 +5,7 @@ from __future__ import unicode_literals import frappe import json -import pickle +import datetime from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file @@ -59,7 +59,7 @@ def export_package(): def import_package(package=None): """Import package from JSON""" - content = pickle.loads(package) + content = json.loads(package) length = len(content) for doc in content: @@ -98,12 +98,33 @@ def post_process(package): del doc[key] for key, value in doc.items(): + stringified_value = get_stringified_value(value) + if stringified_value: + doc[key] = stringified_value + if not isinstance(value, list): continue for child in value: - for key in child_del_keys: - if key in child: - del child[key] + for child_key in child_del_keys: + if child_key in child: + del child[child_key] + + for child_key, child_value in child.items(): + stringified_value = get_stringified_value(child_value) + if stringified_value: + child[child_key] = stringified_value return package + +def get_stringified_value(value): + if isinstance(value, datetime.datetime): + return frappe.utils.get_datetime_str(value) + + if isinstance(value, datetime.date): + return frappe.utils.get_date_str(value) + + if isinstance(value, datetime.timedelta): + return frappe.utils.get_time_str(value) + + return None \ No newline at end of file diff --git a/frappe/utils/data.py b/frappe/utils/data.py index acebfa2271..4c64624098 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -209,6 +209,19 @@ def get_datetime_str(datetime_obj): datetime_obj = get_datetime(datetime_obj) return datetime_obj.strftime(DATETIME_FORMAT) +def get_date_str(date_obj): + if isinstance(date_obj, string_types): + date_obj = get_datetime(date_obj) + return date_obj.strftime(DATE_FORMAT) + +def get_time_str(timedelta_obj): + if isinstance(timedelta_obj, string_types): + timedelta_obj = to_timedelta(date_obj) + + hours, remainder = divmod(timedelta_obj.seconds, 3600) + minutes, seconds = divmod(remainder, 60) + return "{0}:{1}:{2}".format(hours, minutes, seconds) + def get_user_date_format(): """Get the current user date format. The result will be cached.""" if getattr(frappe.local, "user_date_format", None) is None: From a47efa8d96c98ba8202755bdf80cdc9292a7f493 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 18:09:10 +0530 Subject: [PATCH 039/498] fix: file attachments --- frappe/custom/doctype/package/package.py | 35 ++++++++++++------------ frappe/custom/doctype/release/release.py | 5 ++-- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 5ee781c608..88549b644e 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -6,9 +6,10 @@ from __future__ import unicode_literals import frappe import json import datetime +import base64 from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff -from frappe.utils.file_manager import save_file +from frappe.utils.file_manager import save_file, get_file from frappe import _ class Package(Document): @@ -35,22 +36,19 @@ def export_package(): user=frappe.session.user) document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() + attachments = [] if doctype.attachments: - attachments = [] - filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("doctype")} + filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("name")} for f in frappe.get_list("File", filters=filters): - attachments.append({ - "fname": f.name, - "content": frappe.get_doc("File", f.name).get_content() - }) + fname, fcontents = get_file(f.name) + attachments.append({"fname": fname, "content": base64.b64encode(fcontents).decode('ascii')}) - document.update({"attachments": json.dumps(attachments)}) + document.update({ + "attachments": attachments, + "overwrite": True if doctype.overwrite else False + }) - if doctype.overwrite: - document.update({"overwrite": 1}) - - document.update({"modified": frappe.utils.get_datetime_str(document.get("modified"))}) package.append(document) return post_process(package) @@ -66,23 +64,24 @@ def import_package(package=None): docname = doc.pop("name") modified = doc.pop("modified") overwrite = doc.pop("overwrite") + attachments = doc.get("attachments") exists = frappe.db.exists(doc.get("doctype"), docname) if not exists: d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) - if doc.get("attachments"): - add_attachment(doc.get("attachments"), d) + if attachments: + add_attachment(attachments, d) elif exists and overwrite: - document = frappe.get_doc(doc.get("doctype"), doc.pop("name")) + document = frappe.get_doc(doc.get("doctype"), docname) if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): document.update(doc) document.save() - if doc.get("attachments"): - add_attachment(doc.get("attachments"), document) + if attachments: + add_attachment(attachments, document) def add_attachment(attachments, doc): for attachment in attachments: - save_file(attachment.get("fname"), attachment.get("content"), doc.get("doctype"), doc.get("name")) + save_file(attachment.get("fname"), base64.b64decode(attachment.get("content")), doc.get("doctype"), doc.get("name")) def post_process(package): """ diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index ae798f7d85..099e98a4fb 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -5,7 +5,6 @@ from __future__ import unicode_literals import frappe import json -import pickle from frappe import _ from frappe.model.document import Document from frappe.custom.doctype.package.package import export_package @@ -23,7 +22,7 @@ class Release(Document): frappe.delete_doc_if_exists("File", dt_file.name) file_name = make_autoname("Package") - save_file(file_name, pickle.dumps(package), "Release", "Release") + save_file(file_name, json.dumps(package), "Release", "Release") length = len(self.instances) for idx, instance in enumerate(self.instances): @@ -39,7 +38,7 @@ class Release(Document): try: connection.post_request({ "cmd": "frappe.custom.doctype.package.package.import_package", - "package": pickle.dumps(package) + "package": json.dumps(package) }) except Exception as e: frappe.log_error(frappe.get_traceback()) From 4a38cf09838db5c7f64b5f8e1a8e55e509954e52 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 19:45:21 +0530 Subject: [PATCH 040/498] fix: package import fixes --- frappe/custom/doctype/package/package.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 88549b644e..31f681486f 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -61,19 +61,19 @@ def import_package(package=None): length = len(content) for doc in content: - docname = doc.pop("name") modified = doc.pop("modified") overwrite = doc.pop("overwrite") - attachments = doc.get("attachments") - exists = frappe.db.exists(doc.get("doctype"), docname) + attachments = doc.pop("attachments") + exists = frappe.db.exists(doc.get("doctype"), doc.get("name")) if not exists: d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) if attachments: add_attachment(attachments, d) - elif exists and overwrite: + elif exists: + docname = doc.pop("name") document = frappe.get_doc(doc.get("doctype"), docname) - if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): + if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified) and not overwrite: document.update(doc) document.save() if attachments: From b77d93d22af1425fe7ea2a43d08d1a2fb7369f67 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 19 Mar 2020 21:32:34 +0530 Subject: [PATCH 041/498] fix: add filters for document type in package --- frappe/custom/doctype/package/package.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 2fb0b8eb1d..f6b908bcf1 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -8,6 +8,14 @@ frappe.ui.form.on('Package', { frappe.set_route("Form", "Release", "Release"); }); } + + frm.set_query("document_type", "export_package", function () { + return { + filters: { + "istable": 0, + } + }; + }) }, }); @@ -54,6 +62,11 @@ frappe.ui.form.on('Package DocType', { _show_filters(filters, table); table.on('click', () => { + if (!row.document_type) { + frappe.msgprint(__("Select Document Type.")); + return; + } + frappe.model.with_doctype(row.document_type, function() { let dialog = new frappe.ui.Dialog({ title: __('Set Filters'), From 0f1eaee1be856a4df4f2734a9d584cf1e701eebd Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 20 Mar 2020 13:28:35 +0530 Subject: [PATCH 042/498] feat: add manual import --- frappe/custom/doctype/package/package.js | 3 +++ frappe/custom/doctype/package/package.json | 24 ++++++++++++++++++++-- frappe/custom/doctype/package/package.py | 21 +++++++++++++++---- frappe/utils/data.py | 2 +- 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index f6b908bcf1..aa6fdbe635 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -17,6 +17,9 @@ frappe.ui.form.on('Package', { }; }) }, + import: function(frm) { + frm.call("import_from_package"); + } }); frappe.ui.form.on('Package DocType', { diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index 1d71b744d1..7bfdf719cf 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -5,7 +5,10 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "export_package" + "export_package", + "import_section", + "attach", + "import" ], "fields": [ { @@ -14,11 +17,28 @@ "fieldtype": "Table", "label": "Package", "options": "Package DocType" + }, + { + "collapsible": 1, + "collapsible_depends_on": "attach", + "fieldname": "import_section", + "fieldtype": "Section Break", + "label": "Import Package" + }, + { + "fieldname": "import", + "fieldtype": "Button", + "label": "Import" + }, + { + "fieldname": "attach", + "fieldtype": "Attach", + "label": "Attach Package" } ], "issingle": 1, "links": [], - "modified": "2020-03-17 16:41:13.542896", + "modified": "2020-03-19 22:03:06.012473", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 31f681486f..2bf5a5f7cc 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -11,9 +11,22 @@ from frappe.model.document import Document from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file, get_file from frappe import _ +from six import string_types class Package(Document): - pass + + def import_from_package(self): + filters = {"attached_to_doctype": "Package", "attached_to_name": "Package"} + files = frappe.get_list("File", filters=filters, limit=1, order_by="creation desc") + if not files: + frappe.msgprint(_("No file attach for Importing.")) + return + + for f in files: + fname, fcontents = get_file(f.name) + import_package(fcontents) + + frappe.msgprint(_("Package Imported.")) @frappe.whitelist() def export_package(): @@ -57,10 +70,10 @@ def export_package(): def import_package(package=None): """Import package from JSON""" - content = json.loads(package) - length = len(content) + if isinstance(package, string_types): + package = json.loads(package) - for doc in content: + for doc in package: modified = doc.pop("modified") overwrite = doc.pop("overwrite") attachments = doc.pop("attachments") diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 4c64624098..96bdb20841 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -216,7 +216,7 @@ def get_date_str(date_obj): def get_time_str(timedelta_obj): if isinstance(timedelta_obj, string_types): - timedelta_obj = to_timedelta(date_obj) + timedelta_obj = to_timedelta(timedelta_obj) hours, remainder = divmod(timedelta_obj.seconds, 3600) minutes, seconds = divmod(remainder, 60) From 8302f251762ea9362ca7b1d1b92f91542b781139 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Fri, 20 Mar 2020 15:21:21 +0530 Subject: [PATCH 043/498] chore: codacy fixes --- frappe/custom/doctype/package/package.js | 4 ++-- frappe/custom/doctype/package/package.py | 11 ++++------- frappe/custom/doctype/release/release.py | 9 +++++++-- 3 files changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index aa6fdbe635..a053b7d864 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -82,7 +82,7 @@ frappe.ui.form.on('Package DocType', { ], primary_action: function() { let values = filter_group.get_filters(); - let flt = [] + let flt = []; if (values) { values.forEach(function(value) { flt.push([value[0], value[1], value[2], value[3]]); @@ -102,7 +102,7 @@ frappe.ui.form.on('Package DocType', { }); filter_group.add_filters_to_filter_group(filters); dialog.show(); - }) + }); }); }, }); \ No newline at end of file diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 2bf5a5f7cc..d5da360089 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -8,7 +8,6 @@ import json import datetime import base64 from frappe.model.document import Document -from frappe.core.doctype.version.version import get_diff from frappe.utils.file_manager import save_file, get_file from frappe import _ from six import string_types @@ -30,7 +29,7 @@ class Package(Document): @frappe.whitelist() def export_package(): - """Export package as JSON""" + """Export package as JSON.""" package_doc = frappe.get_single("Package") package = [] @@ -68,7 +67,7 @@ def export_package(): @frappe.whitelist() def import_package(package=None): - """Import package from JSON""" + """Import package from JSON.""" if isinstance(package, string_types): package = json.loads(package) @@ -97,10 +96,8 @@ def add_attachment(attachments, doc): save_file(attachment.get("fname"), base64.b64decode(attachment.get("content")), doc.get("doctype"), doc.get("name")) def post_process(package): - """ - Remove the keys from Document and Child Document - Convert datetime, date, time to str - """ + """Remove the keys from Document and Child Document. Convert datetime, date, time to str.""" + del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') child_del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus', 'name') diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index 099e98a4fb..e68be15778 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -33,13 +33,18 @@ class Release(Document): def install_package_to_remote(self, package, instance): remote = frappe.get_doc("Release Instance", instance.instance) - connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) + + try: + connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(remote.instance)) try: connection.post_request({ "cmd": "frappe.custom.doctype.package.package.import_package", "package": json.dumps(package) }) - except Exception as e: + except Exception: frappe.log_error(frappe.get_traceback()) frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(remote.instance)) From ddb02bd7366ec83abe6abd9297b383595cde97da Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 31 Mar 2020 11:15:01 +0530 Subject: [PATCH 044/498] Update package.js --- frappe/custom/doctype/package/package.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index a053b7d864..54af424e4d 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -15,7 +15,7 @@ frappe.ui.form.on('Package', { "istable": 0, } }; - }) + }); }, import: function(frm) { frm.call("import_from_package"); @@ -105,4 +105,4 @@ frappe.ui.form.on('Package DocType', { }); }); }, -}); \ No newline at end of file +}); From c12cfffc84c1800807acebd4380fbd1f72dfc750 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 31 Mar 2020 11:45:59 +0530 Subject: [PATCH 045/498] Update package.py --- frappe/custom/doctype/package/package.py | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index d5da360089..a42de94896 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -30,7 +30,6 @@ class Package(Document): @frappe.whitelist() def export_package(): """Export package as JSON.""" - package_doc = frappe.get_single("Package") package = [] @@ -68,7 +67,6 @@ def export_package(): @frappe.whitelist() def import_package(package=None): """Import package from JSON.""" - if isinstance(package, string_types): package = json.loads(package) @@ -97,7 +95,6 @@ def add_attachment(attachments, doc): def post_process(package): """Remove the keys from Document and Child Document. Convert datetime, date, time to str.""" - del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus') child_del_keys = ('modified_by', 'creation', 'owner', 'idx', 'docstatus', 'name') @@ -136,4 +133,4 @@ def get_stringified_value(value): if isinstance(value, datetime.timedelta): return frappe.utils.get_time_str(value) - return None \ No newline at end of file + return None From 20d54e4588c74920bcbe8033ed2f9a9641f7e0e6 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 14:09:19 +0530 Subject: [PATCH 046/498] fix: child doctype naming --- frappe/custom/doctype/package/package.js | 4 +- frappe/custom/doctype/package/package.json | 18 ++--- frappe/custom/doctype/package/package.py | 19 ++++-- .../__init__.py | 0 .../package_detail/package_detail.json | 65 +++++++++++++++++++ .../package_detail.py} | 2 +- .../package_doctype/package_doctype.json | 65 ------------------- .../release_instance/release_instance.json | 14 +--- 8 files changed, 93 insertions(+), 94 deletions(-) rename frappe/custom/doctype/{package_doctype => package_detail}/__init__.py (100%) create mode 100644 frappe/custom/doctype/package_detail/package_detail.json rename frappe/custom/doctype/{package_doctype/package_doctype.py => package_detail/package_detail.py} (88%) delete mode 100644 frappe/custom/doctype/package_doctype/package_doctype.json diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 54af424e4d..adad9d7e52 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -3,7 +3,7 @@ frappe.ui.form.on('Package', { refresh: function(frm) { - if (frm.doc.export_package) { + if (frm.doc.package_details) { frm.add_custom_button(__("Go to Release"), function() { frappe.set_route("Form", "Release", "Release"); }); @@ -22,7 +22,7 @@ frappe.ui.form.on('Package', { } }); -frappe.ui.form.on('Package DocType', { +frappe.ui.form.on('Package Details', { form_render: function (frm, cdt, cdn) { function _show_filters(filters, table) { table.find('tbody').empty(); diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index 7bfdf719cf..0607abf6d7 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -5,19 +5,12 @@ "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "export_package", + "package_details", "import_section", "attach", "import" ], "fields": [ - { - "description": "Click on the row for accessing filters.", - "fieldname": "export_package", - "fieldtype": "Table", - "label": "Package", - "options": "Package DocType" - }, { "collapsible": 1, "collapsible_depends_on": "attach", @@ -34,11 +27,18 @@ "fieldname": "attach", "fieldtype": "Attach", "label": "Attach Package" + }, + { + "description": "Click on the row for accessing filters.", + "fieldname": "package_details", + "fieldtype": "Table", + "label": "Package Details", + "options": "Package Detail" } ], "issingle": 1, "links": [], - "modified": "2020-03-19 22:03:06.012473", + "modified": "2020-04-06 14:32:42.202640", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index a42de94896..ed9cb66957 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -33,7 +33,7 @@ def export_package(): package_doc = frappe.get_single("Package") package = [] - for doctype in package_doc.export_package: + for doctype in package_doc.package_details: filters = [] if doctype.get("filters_json"): @@ -43,17 +43,28 @@ def export_package(): length = len(docs) for idx, doc in enumerate(docs): - frappe.publish_realtime("package", {"progress":idx, "total":length, "message":doctype.get("document_type"), "prefix": _("Exporting")}, + frappe.publish_realtime("package", { + "progress":idx, "total":length, + "message":doctype.get("document_type"), + "prefix": _("Exporting") + }, user=frappe.session.user) document = frappe.get_doc(doctype.get("document_type"), doc.name).as_dict() attachments = [] if doctype.attachments: - filters = {"attached_to_doctype": document.get("doctype"), "attached_to_name": document.get("name")} + filters = { + "attached_to_doctype": document.get("doctype"), + "attached_to_name": document.get("name") + } + for f in frappe.get_list("File", filters=filters): fname, fcontents = get_file(f.name) - attachments.append({"fname": fname, "content": base64.b64encode(fcontents).decode('ascii')}) + attachments.append({ + "fname": fname, + "content": base64.b64encode(fcontents).decode('ascii') + }) document.update({ "attachments": attachments, diff --git a/frappe/custom/doctype/package_doctype/__init__.py b/frappe/custom/doctype/package_detail/__init__.py similarity index 100% rename from frappe/custom/doctype/package_doctype/__init__.py rename to frappe/custom/doctype/package_detail/__init__.py diff --git a/frappe/custom/doctype/package_detail/package_detail.json b/frappe/custom/doctype/package_detail/package_detail.json new file mode 100644 index 0000000000..f749587a42 --- /dev/null +++ b/frappe/custom/doctype/package_detail/package_detail.json @@ -0,0 +1,65 @@ +{ + "engine": "InnoDB", + "field_order": [ + "document_type", + "column_break_2", + "attachments", + "overwrite", + "section_break_4", + "filters_json" + ], + "istable": 1, + "modified_by": "Administrator", + "name": "Package Detail", + "links": [], + "fields": [ + { + "label": "Document Type", + "in_list_view": 1, + "fieldtype": "Link", + "reqd": 1, + "fieldname": "document_type", + "options": "DocType" + }, + { + "fieldname": "column_break_2", + "fieldtype": "Column Break" + }, + { + "default": "0", + "in_list_view": 1, + "fieldname": "attachments", + "fieldtype": "Check", + "label": "Include Attachments" + }, + { + "default": "0", + "in_list_view": 1, + "fieldname": "overwrite", + "fieldtype": "Check", + "label": "Overwrite" + }, + { + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "options": "JSON", + "fieldname": "filters_json", + "fieldtype": "Code", + "label": "Filters" + } + ], + "track_changes": 1, + "creation": "2020-04-06 12:59:59.657816", + "doctype": "DocType", + "actions": [], + "modified": "2020-04-06 12:59:59.657816", + "sort_order": "DESC", + "module": "Custom", + "owner": "Administrator", + "sort_field": "modified", + "editable_grid": 1, + "quick_entry": 1, + "permissions": [] +} \ No newline at end of file diff --git a/frappe/custom/doctype/package_doctype/package_doctype.py b/frappe/custom/doctype/package_detail/package_detail.py similarity index 88% rename from frappe/custom/doctype/package_doctype/package_doctype.py rename to frappe/custom/doctype/package_detail/package_detail.py index c185395965..0460329d85 100644 --- a/frappe/custom/doctype/package_doctype/package_doctype.py +++ b/frappe/custom/doctype/package_detail/package_detail.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class PackageDocType(Document): +class PackageDetail(Document): pass diff --git a/frappe/custom/doctype/package_doctype/package_doctype.json b/frappe/custom/doctype/package_doctype/package_doctype.json deleted file mode 100644 index 058cff6480..0000000000 --- a/frappe/custom/doctype/package_doctype/package_doctype.json +++ /dev/null @@ -1,65 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-14 11:20:42.875446", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "document_type", - "column_break_2", - "attachments", - "overwrite", - "section_break_4", - "filters_json" - ], - "fields": [ - { - "fieldname": "document_type", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Document Type", - "options": "DocType", - "reqd": 1 - }, - { - "fieldname": "filters_json", - "fieldtype": "Code", - "label": "Filters", - "options": "JSON" - }, - { - "fieldname": "section_break_4", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "attachments", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Include Attachments" - }, - { - "default": "0", - "fieldname": "overwrite", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Overwrite" - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-17 17:27:58.859896", - "modified_by": "Administrator", - "module": "Custom", - "name": "Package DocType", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index d1bf07db59..90484b16f4 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -36,7 +36,7 @@ } ], "links": [], - "modified": "2020-03-18 18:54:55.801749", + "modified": "2020-04-06 14:31:49.443654", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", @@ -53,18 +53,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 } ], "quick_entry": 1, From 1e7c4531e95e28b80cbacf65d2da6ce810ca1bd8 Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 14:52:20 +0530 Subject: [PATCH 047/498] fix: package overwriting --- frappe/custom/doctype/package/package.py | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index ed9cb66957..0cdfe4a87e 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -91,14 +91,22 @@ def import_package(package=None): d = frappe.get_doc(doc).insert(ignore_permissions=True, ignore_if_duplicate=True) if attachments: add_attachment(attachments, d) - elif exists: + else: docname = doc.pop("name") document = frappe.get_doc(doc.get("doctype"), docname) - if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified) and not overwrite: - document.update(doc) - document.save() - if attachments: - add_attachment(attachments, document) + + if overwrite: + update_document(document, doc, attachments) + + else: + if frappe.utils.get_datetime(document.modified) < frappe.utils.get_datetime(modified): + update_document(document, doc, attachments) + +def update_document(document, doc, attachments): + document.update(doc) + document.save() + if attachments: + add_attachment(attachments, document) def add_attachment(attachments, doc): for attachment in attachments: From 514f2bdae998b1bb5b37b90409b31086828252fa Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 15:32:44 +0530 Subject: [PATCH 048/498] fix: rename release related doctypes --- frappe/custom/doctype/package/package.js | 2 +- frappe/custom/doctype/release/release.json | 4 +- frappe/custom/doctype/release/release.py | 2 +- .../release_instance/release_instance.js | 8 ---- .../release_instance/release_instance.json | 48 ++++--------------- .../release_instance/test_release_instance.py | 10 ---- .../doctype/release_instance_list/__init__.py | 0 .../release_instance_list.json | 32 ------------- .../release_instance_list.py | 10 ---- 9 files changed, 13 insertions(+), 103 deletions(-) delete mode 100644 frappe/custom/doctype/release_instance/release_instance.js delete mode 100644 frappe/custom/doctype/release_instance/test_release_instance.py delete mode 100644 frappe/custom/doctype/release_instance_list/__init__.py delete mode 100644 frappe/custom/doctype/release_instance_list/release_instance_list.json delete mode 100644 frappe/custom/doctype/release_instance_list/release_instance_list.py diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index adad9d7e52..1743f38dd3 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -9,7 +9,7 @@ frappe.ui.form.on('Package', { }); } - frm.set_query("document_type", "export_package", function () { + frm.set_query("document_type", "package_details", function () { return { filters: { "istable": 0, diff --git a/frappe/custom/doctype/release/release.json b/frappe/custom/doctype/release/release.json index ba6c69c49f..b3f44196d0 100644 --- a/frappe/custom/doctype/release/release.json +++ b/frappe/custom/doctype/release/release.json @@ -12,12 +12,12 @@ "fieldname": "instances", "fieldtype": "Table", "label": "Instances", - "options": "Release Instance List" + "options": "Release Instance" } ], "issingle": 1, "links": [], - "modified": "2020-03-18 18:25:23.539372", + "modified": "2020-04-06 14:56:56.206720", "modified_by": "Administrator", "module": "Custom", "name": "Release", diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index e68be15778..c02aa0605d 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -32,7 +32,7 @@ class Release(Document): self.install_package_to_remote(package, instance) def install_package_to_remote(self, package, instance): - remote = frappe.get_doc("Release Instance", instance.instance) + remote = frappe.get_doc("Instance", instance.instance) try: connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) diff --git a/frappe/custom/doctype/release_instance/release_instance.js b/frappe/custom/doctype/release_instance/release_instance.js deleted file mode 100644 index 7447c9c706..0000000000 --- a/frappe/custom/doctype/release_instance/release_instance.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Release Instance', { - // refresh: function(frm) { - - // } -}); diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index 90484b16f4..2ebdc93954 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -1,60 +1,30 @@ { "actions": [], - "autoname": "field:instance", - "creation": "2020-03-17 12:24:29.615432", + "creation": "2020-03-18 18:25:02.024237", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "instance", - "user", - "password" + "instance" ], "fields": [ { "fieldname": "instance", - "fieldtype": "Data", + "fieldtype": "Link", "in_list_view": 1, - "in_standard_filter": 1, - "label": "Instance URL", - "unique": 1 - }, - { - "fieldname": "user", - "fieldtype": "Data", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "User", - "options": "Email" - }, - { - "fieldname": "password", - "fieldtype": "Password", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Password" + "label": "Instance", + "options": "Instance", + "reqd": 1 } ], + "istable": 1, "links": [], - "modified": "2020-04-06 14:31:49.443654", + "modified": "2020-04-06 15:30:08.966941", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", diff --git a/frappe/custom/doctype/release_instance/test_release_instance.py b/frappe/custom/doctype/release_instance/test_release_instance.py deleted file mode 100644 index 95df304262..0000000000 --- a/frappe/custom/doctype/release_instance/test_release_instance.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestReleaseInstance(unittest.TestCase): - pass diff --git a/frappe/custom/doctype/release_instance_list/__init__.py b/frappe/custom/doctype/release_instance_list/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/custom/doctype/release_instance_list/release_instance_list.json b/frappe/custom/doctype/release_instance_list/release_instance_list.json deleted file mode 100644 index d9b9b63340..0000000000 --- a/frappe/custom/doctype/release_instance_list/release_instance_list.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-18 18:25:02.024237", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instance" - ], - "fields": [ - { - "fieldname": "instance", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Instance", - "options": "Release Instance", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-18 22:55:20.130597", - "modified_by": "Administrator", - "module": "Custom", - "name": "Release Instance List", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance_list/release_instance_list.py b/frappe/custom/doctype/release_instance_list/release_instance_list.py deleted file mode 100644 index f6eb68a535..0000000000 --- a/frappe/custom/doctype/release_instance_list/release_instance_list.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -# import frappe -from frappe.model.document import Document - -class ReleaseInstanceList(Document): - pass From 23a76cea202588b6d450057cba7a454837f035cd Mon Sep 17 00:00:00 2001 From: Saurabh Date: Mon, 6 Apr 2020 15:42:16 +0530 Subject: [PATCH 049/498] fix: rename instance to deploy instance --- .../doctype/deploy_instance/__init__.py | 0 .../deploy_instance/deploy_instance.js | 8 +++ .../deploy_instance/deploy_instance.json | 63 +++++++++++++++++++ .../deploy_instance/deploy_instance.py | 10 +++ .../deploy_instance/test_deploy_instance.py | 10 +++ frappe/custom/doctype/release/release.py | 2 +- .../release_instance/release_instance.json | 4 +- 7 files changed, 94 insertions(+), 3 deletions(-) create mode 100644 frappe/custom/doctype/deploy_instance/__init__.py create mode 100644 frappe/custom/doctype/deploy_instance/deploy_instance.js create mode 100644 frappe/custom/doctype/deploy_instance/deploy_instance.json create mode 100644 frappe/custom/doctype/deploy_instance/deploy_instance.py create mode 100644 frappe/custom/doctype/deploy_instance/test_deploy_instance.py diff --git a/frappe/custom/doctype/deploy_instance/__init__.py b/frappe/custom/doctype/deploy_instance/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.js b/frappe/custom/doctype/deploy_instance/deploy_instance.js new file mode 100644 index 0000000000..d4b6e75155 --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.js @@ -0,0 +1,8 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('DeployInstance', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.json b/frappe/custom/doctype/deploy_instance/deploy_instance.json new file mode 100644 index 0000000000..2822ab81f9 --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.json @@ -0,0 +1,63 @@ +{ + "actions": [], + "autoname": "field:instance", + "creation": "2020-04-06 15:34:44.428007", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "instance", + "user", + "password" + ], + "fields": [ + { + "fieldname": "instance", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Instance URL", + "unique": 1 + }, + { + "fieldname": "user", + "fieldtype": "Data", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "User", + "options": "Email" + }, + { + "fieldname": "password", + "fieldtype": "Password", + "in_list_view": 1, + "in_standard_filter": 1, + "label": "Password" + } + ], + "links": [], + "modified": "2020-04-06 15:40:39.242881", + "modified_by": "Administrator", + "module": "Custom", + "name": "Deploy Instance", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1, + "track_views": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.py b/frappe/custom/doctype/deploy_instance/deploy_instance.py new file mode 100644 index 0000000000..9bc2e9192e --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class DeployInstance(Document): + pass diff --git a/frappe/custom/doctype/deploy_instance/test_deploy_instance.py b/frappe/custom/doctype/deploy_instance/test_deploy_instance.py new file mode 100644 index 0000000000..cdacc3a1ca --- /dev/null +++ b/frappe/custom/doctype/deploy_instance/test_deploy_instance.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestDeployInstance(unittest.TestCase): + pass diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py index c02aa0605d..05b99f3e57 100644 --- a/frappe/custom/doctype/release/release.py +++ b/frappe/custom/doctype/release/release.py @@ -32,7 +32,7 @@ class Release(Document): self.install_package_to_remote(package, instance) def install_package_to_remote(self, package, instance): - remote = frappe.get_doc("Instance", instance.instance) + remote = frappe.get_doc("Deploy Instance", instance.instance) try: connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json index 2ebdc93954..4ac4f30246 100644 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ b/frappe/custom/doctype/release_instance/release_instance.json @@ -13,13 +13,13 @@ "fieldtype": "Link", "in_list_view": 1, "label": "Instance", - "options": "Instance", + "options": "Deploy Instance", "reqd": 1 } ], "istable": 1, "links": [], - "modified": "2020-04-06 15:30:08.966941", + "modified": "2020-04-06 15:37:07.099322", "modified_by": "Administrator", "module": "Custom", "name": "Release Instance", From 08d7f905b57060ae4d6418ee2e681bef4830e96e Mon Sep 17 00:00:00 2001 From: Saurabh Date: Wed, 15 Apr 2020 16:36:41 +0530 Subject: [PATCH 050/498] feat: provision to deploy packages --- .../deploy_instance/deploy_instance.json | 45 ++++++----------- frappe/custom/doctype/package/package.js | 24 ++++++--- frappe/custom/doctype/package/package.json | 14 +++++- frappe/custom/doctype/package/package.py | 37 +++++++++++++- frappe/custom/doctype/release/__init__.py | 0 frappe/custom/doctype/release/release.js | 23 --------- frappe/custom/doctype/release/release.json | 41 --------------- frappe/custom/doctype/release/release.py | 50 ------------------- frappe/custom/doctype/release/test_release.py | 10 ---- .../doctype/release_instance/__init__.py | 0 .../release_instance/release_instance.json | 32 ------------ .../release_instance/release_instance.py | 10 ---- 12 files changed, 80 insertions(+), 206 deletions(-) delete mode 100644 frappe/custom/doctype/release/__init__.py delete mode 100644 frappe/custom/doctype/release/release.js delete mode 100644 frappe/custom/doctype/release/release.json delete mode 100644 frappe/custom/doctype/release/release.py delete mode 100644 frappe/custom/doctype/release/test_release.py delete mode 100644 frappe/custom/doctype/release_instance/__init__.py delete mode 100644 frappe/custom/doctype/release_instance/release_instance.json delete mode 100644 frappe/custom/doctype/release_instance/release_instance.py diff --git a/frappe/custom/doctype/deploy_instance/deploy_instance.json b/frappe/custom/doctype/deploy_instance/deploy_instance.json index 2822ab81f9..4bb140f088 100644 --- a/frappe/custom/doctype/deploy_instance/deploy_instance.json +++ b/frappe/custom/doctype/deploy_instance/deploy_instance.json @@ -1,63 +1,46 @@ { "actions": [], - "autoname": "field:instance", - "creation": "2020-04-06 15:34:44.428007", + "creation": "2020-03-18 18:25:02.024237", "doctype": "DocType", "editable_grid": 1, "engine": "InnoDB", "field_order": [ - "instance", - "user", + "instance_url", + "username", "password" ], "fields": [ { - "fieldname": "instance", + "fieldname": "instance_url", "fieldtype": "Data", "in_list_view": 1, - "in_standard_filter": 1, "label": "Instance URL", - "unique": 1 + "reqd": 1 }, { - "fieldname": "user", + "fieldname": "username", "fieldtype": "Data", "in_list_view": 1, - "in_standard_filter": 1, - "label": "User", - "options": "Email" + "label": "Username", + "reqd": 1 }, { "fieldname": "password", "fieldtype": "Password", - "in_list_view": 1, - "in_standard_filter": 1, - "label": "Password" + "label": "Password", + "reqd": 1 } ], + "istable": 1, "links": [], - "modified": "2020-04-06 15:40:39.242881", + "modified": "2020-04-14 13:56:31.167730", "modified_by": "Administrator", "module": "Custom", "name": "Deploy Instance", "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], + "permissions": [], "quick_entry": 1, "sort_field": "modified", "sort_order": "DESC", - "track_changes": 1, - "track_views": 1 + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 1743f38dd3..47a7bce0df 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -3,12 +3,6 @@ frappe.ui.form.on('Package', { refresh: function(frm) { - if (frm.doc.package_details) { - frm.add_custom_button(__("Go to Release"), function() { - frappe.set_route("Form", "Release", "Release"); - }); - } - frm.set_query("document_type", "package_details", function () { return { filters: { @@ -16,13 +10,29 @@ frappe.ui.form.on('Package', { } }; }); + + frappe.realtime.on("package", (data) => { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); + if ((data.progress+1) != data.total) { + frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); + } else { + frm.dashboard.hide_progress(); + } + }); + + if(frm.doc.instances){ + frm.add_custom_button(__("Deploy"), function() { + frm.call("deploy_package"); + }); + } + }, import: function(frm) { frm.call("import_from_package"); } }); -frappe.ui.form.on('Package Details', { +frappe.ui.form.on('Package Detail', { form_render: function (frm, cdt, cdn) { function _show_filters(filters, table) { table.find('tbody').empty(); diff --git a/frappe/custom/doctype/package/package.json b/frappe/custom/doctype/package/package.json index 0607abf6d7..2bec7f9b79 100644 --- a/frappe/custom/doctype/package/package.json +++ b/frappe/custom/doctype/package/package.json @@ -6,6 +6,8 @@ "engine": "InnoDB", "field_order": [ "package_details", + "section_break_2", + "instances", "import_section", "attach", "import" @@ -34,11 +36,21 @@ "fieldtype": "Table", "label": "Package Details", "options": "Package Detail" + }, + { + "fieldname": "section_break_2", + "fieldtype": "Section Break" + }, + { + "fieldname": "instances", + "fieldtype": "Table", + "label": "Instances", + "options": "Deploy Instance" } ], "issingle": 1, "links": [], - "modified": "2020-04-06 14:32:42.202640", + "modified": "2020-04-14 13:50:50.779396", "modified_by": "Administrator", "module": "Custom", "name": "Package", diff --git a/frappe/custom/doctype/package/package.py b/frappe/custom/doctype/package/package.py index 0cdfe4a87e..b4427d55bc 100644 --- a/frappe/custom/doctype/package/package.py +++ b/frappe/custom/doctype/package/package.py @@ -11,9 +11,11 @@ from frappe.model.document import Document from frappe.utils.file_manager import save_file, get_file from frappe import _ from six import string_types +from frappe.frappeclient import FrappeClient +from frappe.model.naming import make_autoname +from frappe.utils.password import get_decrypted_password class Package(Document): - def import_from_package(self): filters = {"attached_to_doctype": "Package", "attached_to_name": "Package"} files = frappe.get_list("File", filters=filters, limit=1, order_by="creation desc") @@ -27,6 +29,39 @@ class Package(Document): frappe.msgprint(_("Package Imported.")) + def deploy_package(self): + package = export_package() + + for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): + frappe.delete_doc_if_exists("File", dt_file.name) + + file_name = make_autoname("Package") + save_file(file_name, json.dumps(package), "Package", "Package") + + length = len(self.instances) + for idx, instance in enumerate(self.instances): + frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance_url, "prefix": _("Deploying")}, + user=frappe.session.user) + + self.install_package_to_remote(package, instance) + + def install_package_to_remote(self, package, instance): + print((instance.doctype, instance.name)) + try: + connection = FrappeClient(instance.instance_url, instance.username, get_decrypted_password(instance.doctype, instance.name)) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(instance.instance_url)) + + try: + connection.post_request({ + "cmd": "frappe.custom.doctype.package.package.import_package", + "package": json.dumps(package) + }) + except Exception: + frappe.log_error(frappe.get_traceback()) + frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(instance.instance_url)) + @frappe.whitelist() def export_package(): """Export package as JSON.""" diff --git a/frappe/custom/doctype/release/__init__.py b/frappe/custom/doctype/release/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/custom/doctype/release/release.js b/frappe/custom/doctype/release/release.js deleted file mode 100644 index 709ae18d36..0000000000 --- a/frappe/custom/doctype/release/release.js +++ /dev/null @@ -1,23 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Release', { - refresh: function(frm) { - frm.add_custom_button(__("Go to Package"), function() { - frappe.set_route("Form", "Package", "Package"); - }); - - frm.add_custom_button(__("Release"), function() { - frm.call("create_release"); - }); - - frappe.realtime.on("package", (data) => { - frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); - if ((data.progress+1) != data.total) { - frm.dashboard.show_progress(data.prefix, data.progress / data.total * 100, __("{0}", [data.message])); - } else { - frm.dashboard.hide_progress(); - } - }); - } -}); diff --git a/frappe/custom/doctype/release/release.json b/frappe/custom/doctype/release/release.json deleted file mode 100644 index b3f44196d0..0000000000 --- a/frappe/custom/doctype/release/release.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-17 13:27:30.158389", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instances" - ], - "fields": [ - { - "fieldname": "instances", - "fieldtype": "Table", - "label": "Instances", - "options": "Release Instance" - } - ], - "issingle": 1, - "links": [], - "modified": "2020-04-06 14:56:56.206720", - "modified_by": "Administrator", - "module": "Custom", - "name": "Release", - "owner": "Administrator", - "permissions": [ - { - "create": 1, - "delete": 1, - "email": 1, - "print": 1, - "read": 1, - "role": "System Manager", - "share": 1, - "write": 1 - } - ], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release/release.py b/frappe/custom/doctype/release/release.py deleted file mode 100644 index 05b99f3e57..0000000000 --- a/frappe/custom/doctype/release/release.py +++ /dev/null @@ -1,50 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -import json -from frappe import _ -from frappe.model.document import Document -from frappe.custom.doctype.package.package import export_package -from frappe.frappeclient import FrappeClient -from frappe.utils.file_manager import save_file -from frappe.model.naming import make_autoname -from frappe.utils.password import get_decrypted_password - -class Release(Document): - - def create_release(self): - package = export_package() - - for dt_file in frappe.get_list("File", filters={"attached_to_doctype": "Release", "attached_to_name": "Release"}): - frappe.delete_doc_if_exists("File", dt_file.name) - - file_name = make_autoname("Package") - save_file(file_name, json.dumps(package), "Release", "Release") - - length = len(self.instances) - for idx, instance in enumerate(self.instances): - frappe.publish_realtime("package", {"progress": idx, "total": length, "message": instance.instance, "prefix": _("Deploying")}, - user=frappe.session.user) - - self.install_package_to_remote(package, instance) - - def install_package_to_remote(self, package, instance): - remote = frappe.get_doc("Deploy Instance", instance.instance) - - try: - connection = FrappeClient(remote.instance, remote.user, get_decrypted_password(remote.doctype, remote.name)) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Couldn't connect to site {0}. Please check Error Logs.").format(remote.instance)) - - try: - connection.post_request({ - "cmd": "frappe.custom.doctype.package.package.import_package", - "package": json.dumps(package) - }) - except Exception: - frappe.log_error(frappe.get_traceback()) - frappe.throw(_("Error while installing package to site {0}. Please check Error Logs.").format(remote.instance)) diff --git a/frappe/custom/doctype/release/test_release.py b/frappe/custom/doctype/release/test_release.py deleted file mode 100644 index 13e4e26ac0..0000000000 --- a/frappe/custom/doctype/release/test_release.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and Contributors -# See license.txt -from __future__ import unicode_literals - -# import frappe -import unittest - -class TestRelease(unittest.TestCase): - pass diff --git a/frappe/custom/doctype/release_instance/__init__.py b/frappe/custom/doctype/release_instance/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/custom/doctype/release_instance/release_instance.json b/frappe/custom/doctype/release_instance/release_instance.json deleted file mode 100644 index 4ac4f30246..0000000000 --- a/frappe/custom/doctype/release_instance/release_instance.json +++ /dev/null @@ -1,32 +0,0 @@ -{ - "actions": [], - "creation": "2020-03-18 18:25:02.024237", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "instance" - ], - "fields": [ - { - "fieldname": "instance", - "fieldtype": "Link", - "in_list_view": 1, - "label": "Instance", - "options": "Deploy Instance", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-04-06 15:37:07.099322", - "modified_by": "Administrator", - "module": "Custom", - "name": "Release Instance", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/custom/doctype/release_instance/release_instance.py b/frappe/custom/doctype/release_instance/release_instance.py deleted file mode 100644 index f5b745a9b4..0000000000 --- a/frappe/custom/doctype/release_instance/release_instance.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -# import frappe -from frappe.model.document import Document - -class ReleaseInstance(Document): - pass From 2ed2e9f98b6e226f770794b18657b7456d9ed6c9 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Fri, 1 May 2020 04:32:34 +0530 Subject: [PATCH 051/498] fix: .snyk & package.json to reduce vulnerabilities The following vulnerabilities are fixed with a Snyk patch: - https://snyk.io/vuln/SNYK-JS-LODASH-567746 --- .snyk | 43 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/.snyk b/.snyk index b39169dcee..0dfecc6136 100644 --- a/.snyk +++ b/.snyk @@ -1,5 +1,5 @@ # Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities. -version: v1.13.5 +version: v1.14.1 # ignores vulnerabilities until expiry date; change duration by modifying expiry date ignore: SNYK-JS-AWESOMPLETE-174474: @@ -22,3 +22,44 @@ patch: SNYK-JS-LODASH-450202: - frappe-datatable > lodash: patched: '2020-01-31T01:33:09.889Z' + SNYK-JS-LODASH-567746: + - frappe-datatable > lodash: + patched: '2020-04-30T23:02:32.330Z' + - quagga > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > lodash: + patched: '2020-04-30T23:02:32.330Z' + - tailwindcss > lodash: + patched: '2020-04-30T23:02:32.330Z' + - '@tailwindcss/ui > @tailwindcss/custom-forms > lodash': + patched: '2020-04-30T23:02:32.330Z' + - snyk > @snyk/dep-graph > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > inquirer > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-config > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-mvn-plugin > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-nodejs-lockfile-parser > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-nuget-plugin > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > @snyk/dep-graph > graphlib > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-go-plugin > graphlib > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-nodejs-lockfile-parser > graphlib > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-nuget-plugin > dotnet-deps-parser > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > snyk-php-plugin > @snyk/composer-lockfile-parser > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > @snyk/snyk-cocoapods-plugin > @snyk/dep-graph > graphlib > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/ruby-semver > lodash: + patched: '2020-04-30T23:02:32.330Z' + - snyk > @snyk/snyk-cocoapods-plugin > @snyk/cocoapods-lockfile-parser > @snyk/dep-graph > graphlib > lodash: + patched: '2020-04-30T23:02:32.330Z' From d11463dfd65b26f6198ade410224e6f970d5e5ef Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Fri, 1 May 2020 04:32:35 +0530 Subject: [PATCH 052/498] fix: .snyk & package.json to reduce vulnerabilities The following vulnerabilities are fixed with a Snyk patch: - https://snyk.io/vuln/SNYK-JS-LODASH-567746 --- package.json | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index e735beee9b..583a9a00d3 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,8 @@ "production": "FRAPPE_ENV=production node rollup/build.js", "watch": "node rollup/watch.js", "cypress:open": "cypress open", - "snyk-protect": "snyk protect" + "snyk-protect": "snyk protect", + "prepare": "yarn run snyk-protect" }, "repository": { "type": "git", @@ -42,7 +43,7 @@ "qz-tray": "^2.0.8", "redis": "^2.8.0", "showdown": "^1.9.1", - "snyk": "^1.297.4", + "snyk": "^1.316.1", "socket.io": "^2.3.0", "superagent": "^3.8.2", "tailwindcss": "^1.3.3", From 58b90e72f35a6b004230b3197ca624ea70c5a5dd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 13:20:27 +0530 Subject: [PATCH 053/498] feat: duration control --- frappe/core/doctype/docfield/docfield.json | 4 +- .../doctype/custom_field/custom_field.json | 4 +- .../customize_form_field.json | 4 +- frappe/database/mariadb/database.py | 3 +- frappe/database/postgres/database.py | 3 +- frappe/model/__init__.py | 3 +- frappe/public/build.json | 4 +- .../public/js/frappe/form/controls/control.js | 1 + .../js/frappe/form/controls/duration.js | 159 ++++++++++++++++++ frappe/public/less/controls.less | 63 +++++++ .../website_theme/standard/standard.json | 2 +- 11 files changed, 239 insertions(+), 11 deletions(-) create mode 100644 frappe/public/js/frappe/form/controls/duration.js diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 6d8ee41a5a..04f31375d6 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -86,7 +86,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRead Only\nRating\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", "reqd": 1, "search_index": 1 }, @@ -453,7 +453,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-19 21:54:13.783908", + "modified": "2020-04-30 09:00:38.012601", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 394f38b56c..6534bfd90e 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -116,7 +116,7 @@ "label": "Field Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nGeolocation\nHTML\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime\nSignature", "reqd": 1 }, { @@ -383,7 +383,7 @@ "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-04-10 11:57:10.392218", + "modified": "2020-04-30 09:15:24.394782", "modified_by": "Administrator", "module": "Custom", "name": "Custom Field", diff --git a/frappe/custom/doctype/customize_form_field/customize_form_field.json b/frappe/custom/doctype/customize_form_field/customize_form_field.json index d7887cf8bd..d1510e0858 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -78,7 +78,7 @@ "label": "Type", "oldfieldname": "fieldtype", "oldfieldtype": "Select", - "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", + "options": "Attach\nAttach Image\nBarcode\nButton\nCheck\nCode\nColor\nColumn Break\nCurrency\nData\nDate\nDatetime\nDuration\nDynamic Link\nFloat\nFold\nGeolocation\nHeading\nHTML\nHTML Editor\nImage\nInt\nLink\nLong Text\nMarkdown Editor\nPassword\nPercent\nRating\nRead Only\nSection Break\nSelect\nSignature\nSmall Text\nTable\nTable MultiSelect\nText\nText Editor\nTime", "reqd": 1, "search_index": 1 }, @@ -393,7 +393,7 @@ "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-10 11:58:44.573537", + "modified": "2020-04-30 09:15:51.094586", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index cd053569f0..7350e0aaef 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -55,7 +55,8 @@ class MariaDBDatabase(Database): 'Signature': ('longtext', ''), 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('longtext', ''), - 'Geolocation': ('longtext', '') + 'Geolocation': ('longtext', ''), + 'Duration': ('bigint', '20') } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index e30ef3293f..78dc6d42ec 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -60,7 +60,8 @@ class PostgresDatabase(Database): 'Signature': ('text', ''), 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('text', ''), - 'Geolocation': ('text', '') + 'Geolocation': ('text', ''), + 'Duration': ('bigint', '20') } def get_connection(self): diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index 93ef78df7b..3c5d996439 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -34,7 +34,8 @@ data_fieldtypes = ( 'Signature', 'Color', 'Barcode', - 'Geolocation' + 'Geolocation', + 'Duration' ) no_value_fields = ('Section Break', 'Column Break', 'HTML', 'Table', 'Table MultiSelect', 'Button', 'Image', diff --git a/frappe/public/build.json b/frappe/public/build.json index 7f55924a6b..fe89e3bcc9 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -69,7 +69,8 @@ "node_modules/air-datepicker/dist/js/i18n/datepicker.sk.js", "node_modules/air-datepicker/dist/js/i18n/datepicker.zh.js", "public/js/frappe/ui/capture.js", - "public/js/frappe/form/controls/control.js" + "public/js/frappe/form/controls/control.js", + "public/js/frappe/form/controls/duration.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -259,6 +260,7 @@ "public/js/frappe/form/templates/timeline.html", "public/js/frappe/form/templates/timeline_item.html", "public/js/frappe/form/controls/control.js", + "public/js/frappe/form/controls/duration.js", "public/js/frappe/views/formview.js", "public/js/frappe/form/form.js", "public/js/frappe/meta_tag.js" diff --git a/frappe/public/js/frappe/form/controls/control.js b/frappe/public/js/frappe/form/controls/control.js index 2bf6292abc..168da2717c 100644 --- a/frappe/public/js/frappe/form/controls/control.js +++ b/frappe/public/js/frappe/form/controls/control.js @@ -38,6 +38,7 @@ import './table_multiselect'; import './multiselect_pills'; import './multiselect_list'; import './rating'; +import './duration'; frappe.ui.form.make_control = function (opts) { var control_class_name = "Control" + opts.df.fieldtype.replace(/ /g, ""); diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js new file mode 100644 index 0000000000..8c9aa68b5b --- /dev/null +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -0,0 +1,159 @@ +frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ + make_input: function() { + this._super(); + this.make_picker(); + }, + + make_picker: function() { + this.inputs = []; + this.$picker = $( + `
+
+
` + ); + this.$wrapper.append(this.$picker); + this.build_numeric_input('days', false); + this.build_numeric_input('hrs', false, 23); + this.build_numeric_input('mins', false, 59); + this.build_numeric_input('secs', false, 59); + this.set_duration_picker(); + this.$picker.hide(); + this.bind_events(); + this.refresh(); + }, + + build_numeric_input: function(label, hidden, max) { + let $duration_input = $(` + + `) + + let $input = $(`
`).prepend($duration_input); + + if (max) { + $duration_input.attr('max', max); + } + + this.inputs[label] = $duration_input; + + let $control = $(` +
+
${label}
+
` + ) + + if (hidden) { + $control.addClass('hidden'); + } + $control.prepend($input); + $control.appendTo($('.picker-row')); + }, + + set_duration_picker() { + let total_duration = this.seconds_to_duration(this.value); + if (total_duration.days()) { + this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days()); + } + if (total_duration.hours()) { + this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours()); + } + if (total_duration.minutes()) { + this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes()); + } + if (total_duration.seconds()) { + this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds()); + } + }, + + bind_events: function() { + let me = this; + let clicked = false; + + this.$picker.on('change', '.duration-input', () => { + clicked = false; + me.set_value(me.duration_to_seconds()); + me.set_focus(); + }); + + this.$wrapper.find(".duration-input").mousedown(() => { + clicked = true; + }); + + this.$input.on("focus", () => { + this.$picker.show(); + }); + + this.$input.on("blur", () => { + if (clicked) { + clicked = false; + } else { + this.$picker.hide(); + } + }); + }, + + refresh_input: function() { + this._super(); + this.set_duration_picker(); + }, + + format_for_input: function(value) { + let input_string = ''; + if (value) { + let total_duration = this.seconds_to_duration(value); + + if (total_duration.days()) { + input_string += total_duration.days() + 'd'; + } + if (total_duration.hours()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.hours() + 'h'; + } + if (total_duration.minutes()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.minutes() + 'm'; + } + if (total_duration.seconds()) { + input_string += (input_string.length ? " " : ""); + input_string += total_duration.seconds() + 's'; + } + } + return input_string; + }, + + seconds_to_duration(value) { + let secs = value; + let total_duration = moment.duration({ + days: Math.floor(secs / (3600 * 24)), + hours: Math.floor(secs % (3600 * 24) / 3600), + minutes: Math.floor(secs % 3600 / 60), + seconds : Math.floor(secs % 60) + }); + return total_duration; + }, + + duration_to_seconds() { + let value = 0; + if (this.inputs) { + let total_duration = moment.duration({ + seconds : parseInt(this.inputs.secs.val()), + minutes : parseInt(this.inputs.mins.val()), + hours : parseInt(this.inputs.hrs.val()), + days : parseInt(this.inputs.days.val()) + }); + + if (total_duration.days()) { + value += total_duration.days() * 24 * 60 * 60; + } + if (total_duration.hours()) { + value += total_duration.hours() * 60 * 60; + } + if (total_duration.minutes()) { + value += total_duration.minutes() * 60; + } + if (total_duration.seconds()) { + value += total_duration.seconds(); + } + } + return value; + } +}); \ No newline at end of file diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index d88e6adaec..9b585a58f3 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -165,3 +165,66 @@ top: 8px; } } + +/* duration control */ + +.duration-picker { + position: relative; + z-index: 999; + + border-radius: 4px; + box-shadow: 0 4px 12px rgba(0,0,0,.15); + background: #fff; + border: 1px solid @border-color; + padding-top: 10px; + padding-left: 5px; + position: absolute; + + &:after, + &:before { + border: solid transparent; + content: " "; + height: 0; + width: 0; + pointer-events: none; + position: absolute; + bottom: 100%; + left: 30px; + } + &:after { + border-color: rgba(255, 255, 255, 0); + border-bottom-color: #fff; + border-width: 8px; + margin-left: -8px; + } + &:before { + border-color: rgba(221, 221, 221, 0); + border-bottom-color: @border-color; + border-width: 9px; + margin-left: -9px; + } + + .duration-row { + margin: 7px; + display: flex; + } + + .duration-col { + margin-left: 2px; + } + + .duration-input { + width: 60px; + } + + .duration-label { + justify-content: center; + } + + .picker-row { + display: flex; + width: 335px; + margin-left: 5px; + margin-bottom: 3px; + } +} \ No newline at end of file diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json index a799f25425..799eee72ab 100644 --- a/frappe/website/website_theme/standard/standard.json +++ b/frappe/website/website_theme/standard/standard.json @@ -9,7 +9,7 @@ "font_properties": "300,600", "font_size": "", "idx": 27, - "modified": "2020-04-21 02:10:31.761219", + "modified": "2020-04-24 10:02:44.993836", "modified_by": "Administrator", "module": "Website", "name": "Standard", From b7408102411e4375aa1a155b3ee2bc809782aebc Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 4 May 2020 14:00:14 +0530 Subject: [PATCH 054/498] feat(blog settings): social share settings --- .../doctype/blog_settings/blog_settings.json | 182 ++++++------------ .../doctype/social_link_settings/__init__.py | 0 .../social_link_settings.json | 43 +++++ .../social_link_settings.py | 10 + 4 files changed, 107 insertions(+), 128 deletions(-) create mode 100644 frappe/website/doctype/social_link_settings/__init__.py create mode 100644 frappe/website/doctype/social_link_settings/social_link_settings.json create mode 100644 frappe/website/doctype/social_link_settings/social_link_settings.py diff --git a/frappe/website/doctype/blog_settings/blog_settings.json b/frappe/website/doctype/blog_settings/blog_settings.json index 1ee974d850..f0e51de170 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.json +++ b/frappe/website/doctype/blog_settings/blog_settings.json @@ -1,139 +1,65 @@ { - "allow_copy": 0, - "allow_import": 0, - "allow_rename": 0, - "beta": 0, - "creation": "2013-03-11 17:48:16", - "custom": 0, - "description": "Blog Settings", - "docstatus": 0, - "doctype": "DocType", - "editable_grid": 0, + "actions": [], + "creation": "2013-03-11 17:48:16", + "description": "Blog Settings", + "doctype": "DocType", + "engine": "InnoDB", + "field_order": [ + "blog_title", + "blog_introduction", + "writers_introduction", + "section_break_4", + "social_share_settings" + ], "fields": [ { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "blog_title", - "fieldtype": "Data", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blog Title", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "blog_title", + "fieldtype": "Data", + "label": "Blog Title" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "blog_introduction", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Blog Introduction", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 - }, + "fieldname": "blog_introduction", + "fieldtype": "Small Text", + "label": "Blog Introduction" + }, { - "allow_on_submit": 0, - "bold": 0, - "collapsible": 0, - "columns": 0, - "fieldname": "writers_introduction", - "fieldtype": "Small Text", - "hidden": 0, - "ignore_user_permissions": 0, - "ignore_xss_filter": 0, - "in_filter": 0, - "in_list_view": 0, - "in_standard_filter": 0, - "label": "Writers Introduction", - "length": 0, - "no_copy": 0, - "permlevel": 0, - "print_hide": 0, - "print_hide_if_no_value": 0, - "read_only": 0, - "remember_last_selected_value": 0, - "report_hide": 0, - "reqd": 0, - "search_index": 0, - "set_only_once": 0, - "unique": 0 + "fieldname": "writers_introduction", + "fieldtype": "Small Text", + "label": "Writers Introduction" + }, + { + "collapsible": 1, + "fieldname": "section_break_4", + "fieldtype": "Section Break" + }, + { + "fieldname": "social_share_settings", + "fieldtype": "Table", + "label": "Social Share Settings", + "options": "Social Link Settings" } - ], - "hide_heading": 0, - "hide_toolbar": 0, - "icon": "fa fa-cog", - "idx": 1, - "image_view": 0, - "in_create": 0, - - "is_submittable": 0, - "issingle": 1, - "istable": 0, - "max_attachments": 0, - "modified": "2016-12-29 14:40:41.629468", - "modified_by": "Administrator", - "module": "Website", - "name": "Blog Settings", - "owner": "Administrator", + ], + "icon": "fa fa-cog", + "idx": 1, + "issingle": 1, + "links": [], + "modified": "2020-05-04 09:10:41.815238", + "modified_by": "Administrator", + "module": "Website", + "name": "Blog Settings", + "owner": "Administrator", "permissions": [ { - "amend": 0, - "apply_user_permissions": 0, - "cancel": 0, - "create": 1, - "delete": 0, - "email": 1, - "export": 0, - "if_owner": 0, - "import": 0, - "is_custom": 0, - "permlevel": 0, - "print": 1, - "read": 1, - "report": 0, - "role": "Website Manager", - "set_user_permissions": 0, - "share": 1, - "submit": 0, + "create": 1, + "email": 1, + "print": 1, + "read": 1, + "role": "Website Manager", + "share": 1, "write": 1 } - ], - "quick_entry": 0, - "read_only": 0, - "read_only_onload": 0, - "track_changes": 1, - "track_seen": 0 + ], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 } \ No newline at end of file diff --git a/frappe/website/doctype/social_link_settings/__init__.py b/frappe/website/doctype/social_link_settings/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/website/doctype/social_link_settings/social_link_settings.json b/frappe/website/doctype/social_link_settings/social_link_settings.json new file mode 100644 index 0000000000..459c98eed6 --- /dev/null +++ b/frappe/website/doctype/social_link_settings/social_link_settings.json @@ -0,0 +1,43 @@ +{ + "actions": [], + "creation": "2020-04-30 07:39:33.095554", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "social_link_type", + "color", + "background_color" + ], + "fields": [ + { + "fieldname": "social_link_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Social Link Type", + "options": "\nfacebook\nlinkedin\ntwitter\nemail" + }, + { + "fieldname": "color", + "fieldtype": "Color", + "in_list_view": 1, + "label": "Color" + }, + { + "fieldname": "background_color", + "fieldtype": "Color", + "in_list_view": 1, + "label": "Background Color" + } + ], + "istable": 1, + "links": [], + "modified": "2020-05-01 23:55:04.731518", + "modified_by": "Administrator", + "module": "Website", + "name": "Social Link Settings", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC" +} \ No newline at end of file diff --git a/frappe/website/doctype/social_link_settings/social_link_settings.py b/frappe/website/doctype/social_link_settings/social_link_settings.py new file mode 100644 index 0000000000..b382afac99 --- /dev/null +++ b/frappe/website/doctype/social_link_settings/social_link_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class SocialLinkSettings(Document): + pass From ff89abd930c8c07d5424c4d43459213df1466f63 Mon Sep 17 00:00:00 2001 From: Sagar Vora Date: Mon, 4 May 2020 14:23:45 +0530 Subject: [PATCH 055/498] fix(v13): use newer ipython compatible with python 3.8 --- frappe/commands/utils.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index da9d67be3b..3610393d9a 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -443,7 +443,7 @@ def console(context): for app in all_apps: locals()[app] = __import__(app) print("Apps in this namespace:\n{}".format(", ".join(all_apps))) - IPython.embed(display_banner="", header="") + IPython.embed(display_banner="", header="", colors="neutral") @click.command('run-tests') diff --git a/requirements.txt b/requirements.txt index 58f923d880..33239bb13b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -23,7 +23,7 @@ googlemaps==3.1.1 gunicorn==19.10.0 html2text==2016.9.19 html5lib==1.0.1 -ipython==5.9.0 +ipython==7.14.0 Jinja2==2.10.3 ldap3==2.7 markdown2==2.3.8 From 2cd39a1bc4aa4eff44b482b9d786b224b7ac0923 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 14:36:32 +0530 Subject: [PATCH 056/498] feat: add duration options --- .../js/frappe/form/controls/duration.js | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 8c9aa68b5b..c615dd6eb8 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -6,16 +6,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ make_picker: function() { this.inputs = []; + this.set_duration_options(); this.$picker = $( `
` ); this.$wrapper.append(this.$picker); - this.build_numeric_input('days', false); - this.build_numeric_input('hrs', false, 23); - this.build_numeric_input('mins', false, 59); - this.build_numeric_input('secs', false, 59); + this.build_numeric_input('days', !this.duration_options.showDays); + this.build_numeric_input('hrs', false); + this.build_numeric_input('mins', false); + this.build_numeric_input('secs', !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -48,6 +49,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ $control.appendTo($('.picker-row')); }, + set_duration_options() { + let lang = 'en'; + frappe.boot.user && (lang = frappe.boot.user.language); + + this.duration_options = { + lang: lang, + max: 59, + checkRanges: false, + showSeconds: true, + showDays: true, + }; + }, + set_duration_picker() { let total_duration = this.seconds_to_duration(this.value); if (total_duration.days()) { From ef3e3685e90449f3379102833f40d6c7d6a50674 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Mon, 4 May 2020 18:01:14 +0530 Subject: [PATCH 057/498] feat(about): add social media links --- frappe/public/js/frappe/ui/toolbar/about.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/public/js/frappe/ui/toolbar/about.js b/frappe/public/js/frappe/ui/toolbar/about.js index 13ba4836fb..87462916c2 100644 --- a/frappe/public/js/frappe/ui/toolbar/about.js +++ b/frappe/public/js/frappe/ui/toolbar/about.js @@ -9,6 +9,12 @@ frappe.ui.misc.about = function() { Website: https://frappe.io

\

\ Source: https://github.com/frappe

\ +

\ + Linkedin: https://linkedin.com/company/frappe-tech

\ +

\ + Facebook: https://facebook.com/erpnext

\ +

\ + Twitter: https://twitter.com/erpnext

\
\

Installed Apps

\
Loading versions...
\ From 94bfe471a56ea51a11224a68e5f87d0e85c81c15 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 19:12:19 +0530 Subject: [PATCH 058/498] fix: wrong days setting --- .../js/frappe/form/controls/duration.js | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c615dd6eb8..ade49e2293 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -64,17 +64,17 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ set_duration_picker() { let total_duration = this.seconds_to_duration(this.value); - if (total_duration.days()) { - this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days()); + if (total_duration.days) { + this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } - if (total_duration.hours()) { - this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours()); + if (total_duration.hours) { + this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours); } - if (total_duration.minutes()) { - this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes()); + if (total_duration.minutes) { + this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes); } - if (total_duration.seconds()) { - this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds()); + if (total_duration.seconds) { + this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds); } }, @@ -115,20 +115,20 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ if (value) { let total_duration = this.seconds_to_duration(value); - if (total_duration.days()) { - input_string += total_duration.days() + 'd'; + if (total_duration.days) { + input_string += total_duration.days + 'd'; } - if (total_duration.hours()) { + if (total_duration.hours) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.hours() + 'h'; + input_string += total_duration.hours + 'h'; } - if (total_duration.minutes()) { + if (total_duration.minutes) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.minutes() + 'm'; + input_string += total_duration.minutes + 'm'; } - if (total_duration.seconds()) { + if (total_duration.seconds) { input_string += (input_string.length ? " " : ""); - input_string += total_duration.seconds() + 's'; + input_string += total_duration.seconds + 's'; } } return input_string; @@ -136,36 +136,36 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ seconds_to_duration(value) { let secs = value; - let total_duration = moment.duration({ + let total_duration = { days: Math.floor(secs / (3600 * 24)), hours: Math.floor(secs % (3600 * 24) / 3600), minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) - }); + }; return total_duration; }, duration_to_seconds() { let value = 0; if (this.inputs) { - let total_duration = moment.duration({ + let total_duration = { seconds : parseInt(this.inputs.secs.val()), minutes : parseInt(this.inputs.mins.val()), hours : parseInt(this.inputs.hrs.val()), days : parseInt(this.inputs.days.val()) - }); + }; - if (total_duration.days()) { - value += total_duration.days() * 24 * 60 * 60; + if (total_duration.days) { + value += total_duration.days * 24 * 60 * 60; } - if (total_duration.hours()) { - value += total_duration.hours() * 60 * 60; + if (total_duration.hours) { + value += total_duration.hours * 60 * 60; } - if (total_duration.minutes()) { - value += total_duration.minutes() * 60; + if (total_duration.minutes) { + value += total_duration.minutes * 60; } - if (total_duration.seconds()) { - value += total_duration.seconds(); + if (total_duration.seconds) { + value += total_duration.seconds; } } return value; From dc555864dd544c168d38b77c1a814991772bb572 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 21:40:35 +0530 Subject: [PATCH 059/498] fix: add formatter and util methods for duration fieldtype --- .../js/frappe/form/controls/duration.js | 35 ++----------------- frappe/public/js/frappe/form/formatters.js | 7 ++++ frappe/public/js/frappe/utils/utils.js | 34 ++++++++++++++++++ 3 files changed, 43 insertions(+), 33 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index ade49e2293..c0ca37dea3 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -63,7 +63,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_picker() { - let total_duration = this.seconds_to_duration(this.value); + let total_duration = frappe.utils.seconds_to_duration(this.value); if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } @@ -111,38 +111,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - let input_string = ''; - if (value) { - let total_duration = this.seconds_to_duration(value); - - if (total_duration.days) { - input_string += total_duration.days + 'd'; - } - if (total_duration.hours) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.hours + 'h'; - } - if (total_duration.minutes) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.minutes + 'm'; - } - if (total_duration.seconds) { - input_string += (input_string.length ? " " : ""); - input_string += total_duration.seconds + 's'; - } - } - return input_string; - }, - - seconds_to_duration(value) { - let secs = value; - let total_duration = { - days: Math.floor(secs / (3600 * 24)), - hours: Math.floor(secs % (3600 * 24) / 3600), - minutes: Math.floor(secs % 3600 / 60), - seconds : Math.floor(secs % 60) - }; - return total_duration; + return frappe.utils.get_formatted_duration(value); }, duration_to_seconds() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index d178c59100..cc24c48a1f 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -188,6 +188,13 @@ frappe.form.formatters = { return value || ""; }, + Duration: function(value) { + if (value) { + value = frappe.utils.get_formatted_duration(value); + } + + return value || ""; + }, LikedBy: function(value) { var html = ""; $.each(JSON.parse(value || "[]"), function(i, v) { diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 1afdbfd81c..2584248711 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -778,6 +778,40 @@ Object.assign(frappe.utils, { version: M[1], }; }, + + get_formatted_duration(value) { + let duration = ''; + if (value) { + let total_duration = frappe.utils.seconds_to_duration(value); + + if (total_duration.days) { + duration += total_duration.days + 'd'; + } + if (total_duration.hours) { + duration += (duration.length ? " " : ""); + duration += total_duration.hours + 'h'; + } + if (total_duration.minutes) { + duration += (duration.length ? " " : ""); + duration += total_duration.minutes + 'm'; + } + if (total_duration.seconds) { + duration += (duration.length ? " " : ""); + duration += total_duration.seconds + 's'; + } + } + return duration; + }, + seconds_to_duration(value) { + let secs = value; + let total_duration = { + days: Math.floor(secs / (3600 * 24)), + hours: Math.floor(secs % (3600 * 24) / 3600), + minutes: Math.floor(secs % 3600 / 60), + seconds : Math.floor(secs % 60) + }; + return total_duration; + }, }); // Array de duplicate From 003225f20c4c776f794cb3935a69c59933675dc1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 21:59:46 +0530 Subject: [PATCH 060/498] fix: filters for duration fieldtype --- frappe/public/js/frappe/form/controls/duration.js | 4 ++++ frappe/public/js/frappe/ui/filters/filters.js | 2 ++ 2 files changed, 6 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c0ca37dea3..791882185c 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -105,6 +105,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }); }, + get_value() { + return cint(this.value); + }, + refresh_input: function() { this._super(); this.set_duration_picker(); diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 3646dc6b6e..94e2b59d22 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -200,6 +200,8 @@ frappe.ui.FilterList = Class.extend({ value = {0:"Draft", 1:"Submitted", 2:"Cancelled"}[value] || value; } else if(field.df.original_type==="Check") { value = {0:"No", 1:"Yes"}[cint(value)]; + } else if (field.df.original_type === "Duration") { + value = frappe.utils.get_formatted_duration(value); } value = frappe.format(value, field.df, {only_value: 1}); From 58e4ed06037b7ea45a88a087db0e941b1196d9a5 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 May 2020 00:59:03 +0530 Subject: [PATCH 061/498] fix: control styling in child table --- frappe/public/js/frappe/form/controls/duration.js | 4 ++-- frappe/public/less/controls.less | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 791882185c..86dee3866f 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -25,7 +25,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ build_numeric_input: function(label, hidden, max) { let $duration_input = $(` - + `) let $input = $(`
`).prepend($duration_input); @@ -46,7 +46,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ $control.addClass('hidden'); } $control.prepend($input); - $control.appendTo($('.picker-row')); + $control.appendTo(this.$picker.find('.picker-row')); }, set_duration_options() { diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index 9b585a58f3..ac2936b149 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -204,6 +204,12 @@ margin-left: -9px; } + .row .col { + // for fixing layout in child table + padding-left: 0px !important; + padding-right: 0px !important; + } + .duration-row { margin: 7px; display: flex; @@ -215,6 +221,7 @@ .duration-input { width: 60px; + border: 1px solid rgba(0, 0, 0, 0.25) !important; } .duration-label { From 77d2c3d8cfaaa61aa7c8984f6e5ec3640c6ab020 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 13:43:34 +0530 Subject: [PATCH 062/498] feat: added patch for is unique --- frappe/patches.txt | 1 + .../patches/v13_0/set_unique_for_page_view.py | 6 +++++ .../doctype/web_page_view/web_page_view.json | 24 ++++++++++++++----- 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 frappe/patches/v13_0/set_unique_for_page_view.py diff --git a/frappe/patches.txt b/frappe/patches.txt index a086fa6f4a..3d8b0732d2 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.set_unique_for_page_view \ No newline at end of file diff --git a/frappe/patches/v13_0/set_unique_for_page_view.py b/frappe/patches/v13_0/set_unique_for_page_view.py new file mode 100644 index 0000000000..2a084e52e3 --- /dev/null +++ b/frappe/patches/v13_0/set_unique_for_page_view.py @@ -0,0 +1,6 @@ +import frappe + +def execute(): + frappe.reload_doc('website', 'doctype', 'web_page_view', force=True) + site_url = frappe.utils.get_site_url(frappe.local.site) + frappe.db.sql("""UPDATE `tabWeb Page View` set is_unique=1 where referrer LIKE '%{0}%'""".format(site_url)) diff --git a/frappe/website/doctype/web_page_view/web_page_view.json b/frappe/website/doctype/web_page_view/web_page_view.json index 7a1a210d62..4243df39b1 100644 --- a/frappe/website/doctype/web_page_view/web_page_view.json +++ b/frappe/website/doctype/web_page_view/web_page_view.json @@ -1,5 +1,6 @@ { "actions": [], + "allow_import": 1, "creation": "2020-04-15 22:54:46.009703", "doctype": "DocType", "editable_grid": 1, @@ -9,7 +10,9 @@ "referrer", "browser", "browser_version", - "date" + "is_unique", + "time_zone", + "user_agent" ], "fields": [ { @@ -39,15 +42,24 @@ "set_only_once": 1 }, { - "fieldname": "date", - "fieldtype": "Datetime", - "label": "Date", - "set_only_once": 1 + "fieldname": "is_unique", + "fieldtype": "Data", + "label": "Is Unique" + }, + { + "fieldname": "time_zone", + "fieldtype": "Data", + "label": "Time Zone" + }, + { + "fieldname": "user_agent", + "fieldtype": "Data", + "label": "User Agent" } ], "in_create": 1, "links": [], - "modified": "2020-04-15 23:31:27.517793", + "modified": "2020-05-05 14:11:24.718770", "modified_by": "Administrator", "module": "Website", "name": "Web Page View", From ab9e596f627dc70dab273d1b8485796559ea23a3 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 15:06:14 +0530 Subject: [PATCH 063/498] feat: do not track 404 --- frappe/www/404.html | 6 ++++-- frappe/www/website_script.js | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/www/404.html b/frappe/www/404.html index 47685c45d0..dc178dbdc8 100644 --- a/frappe/www/404.html +++ b/frappe/www/404.html @@ -15,7 +15,9 @@ html, body { } {% include "templates/styles/card_style.css" %} - +
{{_("Page Missing or Moved")}} @@ -29,4 +31,4 @@ html, body { background-color: #f5f7fa; } -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/frappe/www/website_script.js b/frappe/www/website_script.js index 7fdc2e94d6..e31b6812d5 100644 --- a/frappe/www/website_script.js +++ b/frappe/www/website_script.js @@ -14,7 +14,7 @@ ga('send', 'pageview'); {%- endif %} {% if enable_view_tracking %} - if (navigator.doNotTrack != 1) { + if (navigator.doNotTrack != 1 && !window.is_404) { frappe.ready(() => { let browser = frappe.utils.get_browser(); frappe.call("frappe.website.doctype.web_page_view.web_page_view.make_view_log", { From fb66f85d6364f26b4d1cc4281aed6ac80ee8bdb7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 15:06:48 +0530 Subject: [PATCH 064/498] feat: update website analytics report --- .../website_analytics/website_analytics.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index 8b2d5b3806..b821cbff1f 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -17,8 +17,8 @@ class WebsiteAnalytics(object): def run(self): columns = self.get_columns() data = self.get_data() - summary = self.get_report_summary() chart = self.get_chart_data() + summary = self.get_report_summary() return columns, data, None, chart, summary def get_columns(self): @@ -56,7 +56,7 @@ class WebsiteAnalytics(object): field, date_format = _get_field_for_chart(self.filters.range) - data = frappe.db.sql(""" + self.chart_data = frappe.db.sql(""" SELECT DATE_FORMAT({0}, %s) as date, COUNT(*) as count, @@ -65,9 +65,9 @@ class WebsiteAnalytics(object): WHERE creation BETWEEN %s AND %s GROUP BY DATE_FORMAT({0}, %s) ORDER BY creation - """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1, debug=1) + """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1) - return self.prepare_chart_data(data) + return self.prepare_chart_data(self.chart_data) def prepare_chart_data(self, data): date_range = get_date_range(self.filters.from_date, self.filters.to_date, self.filters.range) @@ -121,14 +121,12 @@ class WebsiteAnalytics(object): def get_report_summary(self): - summary_data = frappe.get_all("Web Page View", fields=['is_unique', 'count(*) as count'], filters=self.query_filters, group_by="is_unique") - total_count = 0 unique_count = 0 - for data in summary_data: - if data.get('is_unique'): - unique_count = data.get('count') + for data in self.chart_data: + unique_count += data.get('unique_count') total_count += data.get('count') + report_summary = [ { "value": total_count, From b9bfc6b2f51e0359f1a4c524d43f13d371d5d8b1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 5 May 2020 15:30:55 +0530 Subject: [PATCH 065/498] 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 066/498] 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 606c57be5d849335513025d6ef12e1ab50613c1b Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 5 May 2020 17:35:26 +0530 Subject: [PATCH 067/498] fix: error message --- frappe/desk/form/assign_to.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index be83dc957e..6178690b1f 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -41,6 +41,9 @@ def add(args=None): if not args: args = frappe.local.form_dict + users_with_duplicate_todo = [] + shared_with_users = [] + for assign_to in json.loads(args.get("assign_to")): filters = { "reference_type": args['doctype'], @@ -50,8 +53,7 @@ def add(args=None): } if frappe.get_all("ToDo", filters=filters): - if not args.get("bulk_assign"): - frappe.throw(_("Already in user's ToDo list"), DuplicateToDoError) + users_with_duplicate_todo.append(assign_to) else: from frappe.utils import nowdate @@ -80,7 +82,7 @@ def add(args=None): # if assignee does not have permissions, share if not frappe.has_permission(doc=doc, user=assign_to): frappe.share.add(doc.doctype, doc.name, assign_to) - frappe.msgprint(_('Shared with user {0} with read access').format(assign_to, alert=True)) + shared_with_users.append(assign_to) # make this document followed by assigned user follow_document(args['doctype'], args['name'], assign_to) @@ -89,6 +91,14 @@ def add(args=None): notify_assignment(d.assigned_by, d.owner, d.reference_type, d.reference_name, action='ASSIGN', description=args.get("description")) + if shared_with_users: + user_list = format_message_for_assign_to(shared_with_users) + frappe.msgprint(_("Shared with the following Users with Read access:{0}").format(user_list, alert=True)) + + if users_with_duplicate_todo: + user_list = format_message_for_assign_to(users_with_duplicate_todo) + frappe.msgprint(_("Already in the following Users ToDo list:{0}").format(user_list, alert=True)) + return get(args) @frappe.whitelist() @@ -186,3 +196,5 @@ def notify_assignment(assigned_by, owner, doc_type, doc_name, action='CLOSE', enqueue_create_notification(owner, notification_doc) +def format_message_for_assign_to(users): + return "

" + "
".join(users) \ No newline at end of file From f684756fba34fcd5ac1099ea7938f64fe08e574d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 18:22:04 +0530 Subject: [PATCH 068/498] feat: added website analytics report --- .../website_analytics/website_analytics.js | 2 - .../website_analytics/website_analytics.py | 116 +++++++++++++++--- 2 files changed, 98 insertions(+), 20 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.js b/frappe/website/report/website_analytics/website_analytics.js index b607a16eb4..7e051afa8c 100644 --- a/frappe/website/report/website_analytics/website_analytics.js +++ b/frappe/website/report/website_analytics/website_analytics.js @@ -9,14 +9,12 @@ frappe.query_reports["Website Analytics"] = { label: __("From Date"), fieldtype: "Date", default: frappe.datetime.add_days(frappe.datetime.now_date(true), -7), - reqd: 1 }, { fieldname:"to_date", label: __("To Date"), fieldtype: "Date", default: frappe.datetime.now_date(true), - reqd: 1 }, { fieldname: "range", diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index b821cbff1f..694bc9e797 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -3,6 +3,7 @@ from __future__ import unicode_literals import frappe +from datetime import datetime from frappe.utils.dateutils import get_date_range def execute(filters=None): @@ -11,6 +12,16 @@ def execute(filters=None): class WebsiteAnalytics(object): def __init__(self, filters=None): self.filters = frappe._dict(filters or {}) + + if not self.filters.to_date: + self.filters.to_date = datetime.now() + + if not self.filters.from_date: + self.filters.from_date = frappe.utils.add_days(self.filters.to_date, -7) + + if not self.filters.range: + self.filters.range = "D" + self.filters.to_date = frappe.utils.add_days(self.filters.to_date, 1) self.query_filters = {'creation': ['between', [self.filters.from_date, self.filters.to_date]]} @@ -19,7 +30,8 @@ class WebsiteAnalytics(object): data = self.get_data() chart = self.get_chart_data() summary = self.get_report_summary() - return columns, data, None, chart, summary + + return columns, data[:250], None, chart, summary def get_columns(self): return [ @@ -34,38 +46,105 @@ class WebsiteAnalytics(object): "label": "Page Views", "fieldtype": "Int", "width": 150 + }, + { + "fieldname": "unique_count", + "label": "Unique Visitors", + "fieldtype": "Int", + "width": 150 } ] def get_data(self): - data = frappe.get_all("Web Page View", fields=['path', 'count(*) as count'], filters=self.query_filters, group_by="path", order_by='count desc') + pg_query = """ + SELECT + path, + COUNT(*) as count, + COUNT(CASE WHEN CAST(is_unique as Integer) = 1 THEN 1 END) as unique_count + FROM `tabWeb Page View` + WHERE coalesce("tabWeb Page View".creation, '0001-01-01') BETWEEN %s AND %s + GROUP BY path + ORDER BY count desc + """ + + mariadb_query = """ + SELECT + path, + COUNT(*) as count, + COUNT(CASE WHEN is_unique = 1 THEN 1 END) as unique_count + FROM `tabWeb Page View` + WHERE creation BETWEEN %s AND %s + GROUP BY path + ORDER BY count desc + """ + + data = frappe.db.multisql({ + "mariadb": mariadb_query, + "postgres": pg_query + }, (self.filters.from_date, self.filters.to_date)) return data - def get_chart_data(self): - def _get_field_for_chart(filters_range): - field = 'creation' - date_format = '%Y-%m-%d' + def _get_query_for_mariadb(self): + filters_range = self.filters.range + field = 'creation' + date_format = '%Y-%m-%d' - if filters_range == "W": - field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' + if filters_range == "W": + field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' - elif filters_range == "M": - date_format = '%Y-%m-01' + elif filters_range == "M": + date_format = '%Y-%m-01' - return field, date_format - - field, date_format = _get_field_for_chart(self.filters.range) - - self.chart_data = frappe.db.sql(""" + query = """ SELECT DATE_FORMAT({0}, %s) as date, COUNT(*) as count, - count(CASE WHEN is_unique = 1 THEN 1 END) as unique_count + COUNT(CASE WHEN is_unique = 1 THEN 1 END) as unique_count FROM `tabWeb Page View` WHERE creation BETWEEN %s AND %s GROUP BY DATE_FORMAT({0}, %s) ORDER BY creation - """.format(field), (date_format, self.filters.from_date, self.filters.to_date, date_format), as_dict=1) + """.format(field) + + values = (date_format, self.filters.from_date, self.filters.to_date, date_format) + + return query, values + + def _get_query_for_postgres(self): + filters_range = self.filters.range + field = 'creation' + granularity = 'day' + + if filters_range == "W": + granularity = 'week' + + elif filters_range == "M": + granularity = 'day' + + query = """ + SELECT + DATE_TRUNC(%s, {0}) as date, + COUNT(*) as count, + COUNT(CASE WHEN CAST(is_unique as Integer) = 1 THEN 1 END) as unique_count + FROM "tabWeb Page View" + WHERE coalesce("tabWeb Page View".{0}, '0001-01-01') BETWEEN %s AND %s + GROUP BY date_trunc(%s, {0}) + ORDER BY date + """.format(field) + + values = (granularity, self.filters.from_date, self.filters.to_date, granularity) + + return query, values + + def get_chart_data(self): + current_dialect = frappe.db.db_type or 'mariadb' + + if current_dialect == 'mariadb': + query, values = self._get_query_for_mariadb() + else: + query, values = self._get_query_for_postgres() + + self.chart_data = frappe.db.sql(query, values=values, as_dict=1) return self.prepare_chart_data(self.chart_data) @@ -114,7 +193,8 @@ class WebsiteAnalytics(object): }, 'axisOptions': { 'xIsSeries': 1 - } + }, + 'colors': ['#7cd6fd', '#5e64ff'] } return chart From 83380f25475edfb3bec8d79cad7013d78443b9b4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 18:22:47 +0530 Subject: [PATCH 069/498] feat: added website analytics chart as JSON fixture --- frappe/model/sync.py | 5 ++-- .../website_analytics/website_analytics.json | 24 +++++++++++++++++++ 2 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 frappe/website/dashboard_chart/website_analytics/website_analytics.json diff --git a/frappe/model/sync.py b/frappe/model/sync.py index c2acb59f63..3dba994134 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -52,7 +52,8 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("desk", "desk_card"), ("desk", "desk_chart"), ("desk", "desk_shortcut"), - ("desk", "desk_page")): + ("desk", "desk_page"), + ("desk", "dashboard_chart")): files.append(os.path.join(frappe.get_app_path("frappe"), d[0], "doctype", d[1], d[1] + ".json")) @@ -82,7 +83,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', 'data_migration_mapping', 'data_migration_plan', 'desk_page', - 'onboarding_step', 'onboarding'] + 'onboarding_step', 'onboarding', 'dashboard_chart'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) diff --git a/frappe/website/dashboard_chart/website_analytics/website_analytics.json b/frappe/website/dashboard_chart/website_analytics/website_analytics.json new file mode 100644 index 0000000000..eeeb1a11f9 --- /dev/null +++ b/frappe/website/dashboard_chart/website_analytics/website_analytics.json @@ -0,0 +1,24 @@ +{ + "chart_name": "Website Analytics", + "chart_type": "Report", + "creation": "2020-05-05 18:14:19.369181", + "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"colors\": [\"#7cd6fd\", \"#5e64ff\"], \"tooltipOptions\": {}}", + "docstatus": 0, + "doctype": "Dashboard Chart", + "filters_json": "{}", + "group_by_type": "Count", + "idx": 0, + "is_custom": 1, + "is_public": 1, + "modified": "2020-05-05 18:16:47.383649", + "modified_by": "Administrator", + "name": "Website Analytics", + "number_of_groups": 0, + "owner": "Administrator", + "report_name": "Website Analytics", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Line", + "y_axis": [] +} \ No newline at end of file From 06e4e6a3b933b28e29bcd7e01bf1027939a236a1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Tue, 5 May 2020 18:29:42 +0530 Subject: [PATCH 070/498] feat: add analytics chart to desk --- frappe/website/desk_page/website/website.json | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/frappe/website/desk_page/website/website.json b/frappe/website/desk_page/website/website.json index 1c6066d21e..c42a17d404 100644 --- a/frappe/website/desk_page/website/website.json +++ b/frappe/website/desk_page/website/website.json @@ -27,7 +27,11 @@ } ], "category": "Modules", - "charts": [], + "charts": [ + { + "chart_name": "Website Analytics" + } + ], "creation": "2020-03-02 14:13:51.089373", "developer_mode_only": 0, "disable_user_customization": 0, @@ -37,7 +41,7 @@ "idx": 0, "is_standard": 1, "label": "Website", - "modified": "2020-04-26 13:03:49.094728", + "modified": "2020-05-05 18:17:13.232473", "modified_by": "Administrator", "module": "Website", "name": "Website", From 627940c56b7fa435726d79f355ca8d96fd329d57 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 5 May 2020 18:58:38 +0530 Subject: [PATCH 071/498] 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 072/498] 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 e4dbbfd13f201dcd1bc1314d7a3898d8acff1145 Mon Sep 17 00:00:00 2001 From: Deepesh Garg Date: Tue, 5 May 2020 19:24:34 +0530 Subject: [PATCH 073/498] fix: Ignore energy point log perm on doc cancel --- frappe/social/doctype/energy_point_log/energy_point_log.py | 6 ++++-- .../social/doctype/energy_point_rule/energy_point_rule.py | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/social/doctype/energy_point_log/energy_point_log.py b/frappe/social/doctype/energy_point_log/energy_point_log.py index 31de1b8a60..3fc8b8cbfe 100644 --- a/frappe/social/doctype/energy_point_log/energy_point_log.py +++ b/frappe/social/doctype/energy_point_log/energy_point_log.py @@ -52,8 +52,10 @@ class EnergyPointLog(Document): reference_log.reverted = 0 reference_log.save() - def revert(self, reason): - frappe.only_for('System Manager') + def revert(self, reason, ignore_permissions=False): + if not ignore_permissions: + frappe.only_for('System Manager') + if self.type != 'Auto': frappe.throw(_('This document cannot be reverted')) diff --git a/frappe/social/doctype/energy_point_rule/energy_point_rule.py b/frappe/social/doctype/energy_point_rule/energy_point_rule.py index b603cb2b24..d04448dc0f 100644 --- a/frappe/social/doctype/energy_point_rule/energy_point_rule.py +++ b/frappe/social/doctype/energy_point_rule/energy_point_rule.py @@ -110,7 +110,7 @@ def revert_points_for_cancelled_doc(doc): }) for log in energy_point_logs: reference_log = frappe.get_doc('Energy Point Log', log.name) - reference_log.revert(_('Reference document has been cancelled')) + reference_log.revert(_('Reference document has been cancelled'), ignore_permissions=True) def get_energy_point_doctypes(): From e71b14c17dc9bf6b5ba7f22f8ed2899fa175e5f6 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Tue, 5 May 2020 19:53:50 +0530 Subject: [PATCH 074/498] fix: Schedule Date for Daily and Weekly frequency --- frappe/automation/doctype/auto_repeat/auto_repeat.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.py b/frappe/automation/doctype/auto_repeat/auto_repeat.py index bfcaf684d6..c447c55727 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.py +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.py @@ -299,17 +299,20 @@ def get_next_schedule_date(schedule_date, frequency, start_date, repeat_on_day=N next_date = get_next_date(start_date, month_count) else: days = 7 if frequency == 'Weekly' else 1 - next_date = add_days(start_date, days) + next_date = add_days(schedule_date, days) # next schedule date should be after or on current date if not for_full_schedule: while getdate(next_date) < getdate(today()): if month_count: month_count += month_map.get(frequency) - next_date = get_next_date(start_date, month_count, day_count) + next_date = get_next_date(start_date, month_count, day_count) + elif days: + next_date = add_days(next_date, days) return next_date + def get_next_date(dt, mcount, day=None): dt = getdate(dt) dt += relativedelta(months=mcount, day=day) From 87f2c66e0f3b13359b94819017c3252033089a4d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 00:09:55 +0530 Subject: [PATCH 075/498] feat: options to hide days and seconds for duration control --- .../js/frappe/form/controls/duration.js | 21 ++++++++++--------- frappe/public/js/frappe/utils/utils.js | 10 ++++++--- frappe/public/less/controls.less | 2 +- 3 files changed, 19 insertions(+), 14 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 86dee3866f..49c1e46530 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,20 +50,16 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - let lang = 'en'; - frappe.boot.user && (lang = frappe.boot.user.language); - this.duration_options = { - lang: lang, - max: 59, - checkRanges: false, showSeconds: true, showDays: true, }; }, set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value); + let total_duration = frappe.utils.seconds_to_duration(this.value, + this.duration_options.showDays, this.set_duration_options.showSeconds); + if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); } @@ -115,18 +111,23 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - return frappe.utils.get_formatted_duration(value); + return frappe.utils.get_formatted_duration(value, + this.duration_options.showDays, this.duration_options.showSeconds); }, duration_to_seconds() { let value = 0; if (this.inputs) { let total_duration = { - seconds : parseInt(this.inputs.secs.val()), minutes : parseInt(this.inputs.mins.val()), hours : parseInt(this.inputs.hrs.val()), - days : parseInt(this.inputs.days.val()) }; + if (this.duration_options.showDays) { + total_duration.days = parseInt(this.inputs.days.val()); + } + if (this.duration_options.showSeconds) { + total_duration.seconds = parseInt(this.inputs.secs.val()); + } if (total_duration.days) { value += total_duration.days * 24 * 60 * 60; diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 2584248711..559598de5b 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -779,10 +779,10 @@ Object.assign(frappe.utils, { }; }, - get_formatted_duration(value) { + get_formatted_duration(value, showDays=true, showSeconds=true) { let duration = ''; if (value) { - let total_duration = frappe.utils.seconds_to_duration(value); + let total_duration = frappe.utils.seconds_to_duration(value, showDays, showSeconds); if (total_duration.days) { duration += total_duration.days + 'd'; @@ -802,7 +802,7 @@ Object.assign(frappe.utils, { } return duration; }, - seconds_to_duration(value) { + seconds_to_duration(value, showDays=true, showSeconds=true) { let secs = value; let total_duration = { days: Math.floor(secs / (3600 * 24)), @@ -810,6 +810,10 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) }; + if (!showDays) { + total_duration.hours = Math.floor(secs / 3600) + total_duration.days = 0 + } return total_duration; }, }); diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index ac2936b149..b6ac3cf359 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -230,8 +230,8 @@ .picker-row { display: flex; - width: 335px; margin-left: 5px; margin-bottom: 3px; + margin-right: 12px; } } \ No newline at end of file From 156a00724fd6adbeb65471e99d4fb0744c6e7c3f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 09:39:57 +0530 Subject: [PATCH 076/498] feat: add duration options to Duration docfield --- frappe/core/doctype/docfield/docfield.json | 18 +++++++++++++++++- .../public/js/frappe/form/controls/duration.js | 11 +++-------- frappe/public/js/frappe/form/formatters.js | 5 +++-- frappe/public/js/frappe/model/meta.js | 8 ++++++++ frappe/public/js/frappe/utils/utils.js | 8 ++++---- 5 files changed, 35 insertions(+), 15 deletions(-) diff --git a/frappe/core/doctype/docfield/docfield.json b/frappe/core/doctype/docfield/docfield.json index 04f31375d6..8ae2b740b5 100644 --- a/frappe/core/doctype/docfield/docfield.json +++ b/frappe/core/doctype/docfield/docfield.json @@ -13,6 +13,8 @@ "fieldname", "precision", "length", + "show_days", + "show_seconds", "reqd", "search_index", "in_list_view", @@ -448,12 +450,26 @@ { "fieldname": "column_break_38", "fieldtype": "Column Break" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds" } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-30 09:00:38.012601", + "modified": "2020-05-06 09:06:25.224411", "modified_by": "Administrator", "module": "Core", "name": "DocField", diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 49c1e46530..de8aa96c95 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -50,15 +50,11 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = { - showSeconds: true, - showDays: true, - }; + this.duration_options = frappe.meta.get_duration_options(this.df, this.get_doc()); }, set_duration_picker() { - let total_duration = frappe.utils.seconds_to_duration(this.value, - this.duration_options.showDays, this.set_duration_options.showSeconds); + let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (total_duration.days) { this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); @@ -111,8 +107,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, format_for_input: function(value) { - return frappe.utils.get_formatted_duration(value, - this.duration_options.showDays, this.duration_options.showSeconds); + return frappe.utils.get_formatted_duration(value, this.duration_options); }, duration_to_seconds() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index cc24c48a1f..a9abd1c601 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -188,9 +188,10 @@ frappe.form.formatters = { return value || ""; }, - Duration: function(value) { + Duration: function(value, docfield, doc) { if (value) { - value = frappe.utils.get_formatted_duration(value); + let duration_options = frappe.meta.get_duration_options(docfield, doc); + value = frappe.utils.get_formatted_duration(value, duration_options); } return value || ""; diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index b7ad52838c..acde2a367f 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -267,4 +267,12 @@ $.extend(frappe.meta, { } return precision; }, + + get_duration_options: function(df, doc) { + let duration_options = { + showDays: df.show_days, + showSeconds: df.show_seconds + }; + return duration_options; + } }); diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 559598de5b..9281ba6c43 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -779,10 +779,10 @@ Object.assign(frappe.utils, { }; }, - get_formatted_duration(value, showDays=true, showSeconds=true) { + get_formatted_duration(value, duration_options) { let duration = ''; if (value) { - let total_duration = frappe.utils.seconds_to_duration(value, showDays, showSeconds); + let total_duration = frappe.utils.seconds_to_duration(value, duration_options); if (total_duration.days) { duration += total_duration.days + 'd'; @@ -802,7 +802,7 @@ Object.assign(frappe.utils, { } return duration; }, - seconds_to_duration(value, showDays=true, showSeconds=true) { + seconds_to_duration(value, duration_options) { let secs = value; let total_duration = { days: Math.floor(secs / (3600 * 24)), @@ -810,7 +810,7 @@ Object.assign(frappe.utils, { minutes: Math.floor(secs % 3600 / 60), seconds : Math.floor(secs % 60) }; - if (!showDays) { + if (!duration_options.showDays) { total_duration.hours = Math.floor(secs / 3600) total_duration.days = 0 } From 94116d322784098f2a215ceb134efd1a24e73589 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 09:51:16 +0530 Subject: [PATCH 077/498] fix: duration options for filters --- frappe/public/js/frappe/ui/filters/filters.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filters.js b/frappe/public/js/frappe/ui/filters/filters.js index 94e2b59d22..9a394eade3 100644 --- a/frappe/public/js/frappe/ui/filters/filters.js +++ b/frappe/public/js/frappe/ui/filters/filters.js @@ -201,7 +201,11 @@ frappe.ui.FilterList = Class.extend({ } else if(field.df.original_type==="Check") { value = {0:"No", 1:"Yes"}[cint(value)]; } else if (field.df.original_type === "Duration") { - value = frappe.utils.get_formatted_duration(value); + let duration_options = { + showDays: field.df.show_days, + showSeconds: field.df.show_seconds + }; + value = frappe.utils.get_formatted_duration(value, duration_options); } value = frappe.format(value, field.df, {only_value: 1}); From b696306edc65bb96a4ed450451c26ca61937b6c9 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 10:15:11 +0530 Subject: [PATCH 078/498] fix: set duration options in refresh_input --- .../js/frappe/form/controls/duration.js | 25 ++++++++++--------- 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index de8aa96c95..c177ae6f17 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -13,10 +13,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
` ); this.$wrapper.append(this.$picker); - this.build_numeric_input('days', !this.duration_options.showDays); - this.build_numeric_input('hrs', false); - this.build_numeric_input('mins', false); - this.build_numeric_input('secs', !this.duration_options.showSeconds); + this.build_numeric_input("days", !this.duration_options.showDays); + this.build_numeric_input("hrs", false); + this.build_numeric_input("mins", false); + this.build_numeric_input("secs", !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -31,7 +31,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let $input = $(`
`).prepend($duration_input); if (max) { - $duration_input.attr('max', max); + $duration_input.attr("max", max); } this.inputs[label] = $duration_input; @@ -43,10 +43,10 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ ) if (hidden) { - $control.addClass('hidden'); + $control.addClass("hidden"); } $control.prepend($input); - $control.appendTo(this.$picker.find('.picker-row')); + $control.appendTo(this.$picker.find(".picker-row")); }, set_duration_options() { @@ -57,16 +57,16 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); if (total_duration.days) { - this.$picker.find(`[data-duration='days']`).prop('value', total_duration.days); + this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); } if (total_duration.hours) { - this.$picker.find(`[data-duration='hrs']`).prop('value', total_duration.hours); + this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); } if (total_duration.minutes) { - this.$picker.find(`[data-duration='mins']`).prop('value', total_duration.minutes); + this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); } if (total_duration.seconds) { - this.$picker.find(`[data-duration='secs']`).prop('value', total_duration.seconds); + this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); } }, @@ -74,7 +74,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let me = this; let clicked = false; - this.$picker.on('change', '.duration-input', () => { + this.$picker.on("change", ".duration-input", () => { clicked = false; me.set_value(me.duration_to_seconds()); me.set_focus(); @@ -103,6 +103,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ refresh_input: function() { this._super(); + this.set_duration_options(); this.set_duration_picker(); }, From 972833d13fb20deebe95acb47d0495baed655831 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 12:36:22 +0530 Subject: [PATCH 079/498] fix: change duration fieldtype to decimal --- frappe/database/mariadb/database.py | 2 +- frappe/database/postgres/database.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 7350e0aaef..4ec89c126d 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -56,7 +56,7 @@ class MariaDBDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('longtext', ''), 'Geolocation': ('longtext', ''), - 'Duration': ('bigint', '20') + 'Duration': ('decimal', '18,6') } def get_connection(self): diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index 78dc6d42ec..e348916705 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -61,7 +61,7 @@ class PostgresDatabase(Database): 'Color': ('varchar', self.VARCHAR_LEN), 'Barcode': ('text', ''), 'Geolocation': ('text', ''), - 'Duration': ('bigint', '20') + 'Duration': ('decimal', '18,6') } def get_connection(self): From 6ea8881b29106edf45155b9ac565d95f9672ded2 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 12:48:15 +0530 Subject: [PATCH 080/498] fix: remove exact phrase search --- frappe/utils/global_search.py | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/frappe/utils/global_search.py b/frappe/utils/global_search.py index 3c4b9583f8..0272ae16f4 100644 --- a/frappe/utils/global_search.py +++ b/frappe/utils/global_search.py @@ -81,10 +81,10 @@ def rebuild_for_doctype(doctype): return filters meta = frappe.get_meta(doctype) - + if cint(meta.issingle) == 1: return - + if cint(meta.istable) == 1: parent_doctypes = frappe.get_all("DocField", fields="parent", filters={ "fieldtype": ["in", frappe.model.table_fields], @@ -506,15 +506,13 @@ def web_search(text, scope=None, start=0, limit=20): mariadb_conditions = postgres_conditions = ' '.join([published_condition, scope_condition]) # https://mariadb.com/kb/en/library/full-text-index-overview/#in-boolean-mode - text = '"{}"'.format(text) - mariadb_conditions += 'MATCH(`content`) AGAINST (%(text)s IN BOOLEAN MODE)' - postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY(%(text)s)' + mariadb_conditions += 'MATCH(`content`) AGAINST ({} IN BOOLEAN MODE)'.format(frappe.db.escape('+' + text + '*')) + postgres_conditions += 'TO_TSVECTOR("content") @@ PLAINTO_TSQUERY({})'.format(frappe.db.escape(text)) values = { "scope": "".join([scope, "%"]) if scope else '', "limit": limit, - "start": start, - "text": text + "start": start } result = frappe.db.multisql({ From e91050b0e496d872d08a7898fb2124b3a64424ef Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 13:14:22 +0530 Subject: [PATCH 081/498] fix: add admin to email unsubscribe --- frappe/desk/page/setup_wizard/install_fixtures.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index c857bd077f..b74153f1c7 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -12,6 +12,7 @@ def install(): update_salutations() update_global_search_doctypes() setup_email_linking() + add_unsubscribe() @frappe.whitelist() def update_genders(): @@ -35,4 +36,12 @@ def setup_email_linking(): "email_id": "email_linking@example.com", }) doc.insert(ignore_permissions=True, ignore_if_duplicate=True) - \ No newline at end of file + +def add_unsubscribe(): + email_unsubscribe = [ + {"email": "admin@example.com", "global_unsubscribe": 1}, + {"email": "guest@example.com", "global_unsubscribe": 1} + ] + + for unsubscribe in email_unsubscribe: + frappe.get_doc(unsubscribe).insert() \ No newline at end of file From 0ff51a257d723ebd19bb89ca44f8b0644b4abcb8 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 13:17:41 +0530 Subject: [PATCH 082/498] refactor: move dashboard utilities to frappe.utils --- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 2 +- frappe/{core/page/dashboard => utils}/dashboard.py | 0 2 files changed, 1 insertion(+), 1 deletion(-) rename frappe/{core/page/dashboard => utils}/dashboard.py (100%) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 7ddb3d98f0..417ef2ba82 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -7,7 +7,7 @@ import frappe from frappe import _ import datetime import json -from frappe.core.page.dashboard.dashboard import cache_source, get_from_date_from_timespan +from frappe.utils.dashboard import cache_source, get_from_date_from_timespan from frappe.utils import nowdate, add_to_date, getdate, get_last_day, formatdate, get_datetime from frappe.model.naming import append_number_if_name_exists from frappe.boot import get_allowed_reports diff --git a/frappe/core/page/dashboard/dashboard.py b/frappe/utils/dashboard.py similarity index 100% rename from frappe/core/page/dashboard/dashboard.py rename to frappe/utils/dashboard.py From 37e81e33a52da50ff16ed3e476afe97124a75000 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 13:20:59 +0530 Subject: [PATCH 083/498] fix: add patch --- frappe/desk/page/setup_wizard/install_fixtures.py | 5 ++++- frappe/patches.txt | 1 + frappe/patches/v13_0/email_unsubscribe.py | 13 +++++++++++++ 3 files changed, 18 insertions(+), 1 deletion(-) create mode 100644 frappe/patches/v13_0/email_unsubscribe.py diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index b74153f1c7..692c1a117a 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -44,4 +44,7 @@ def add_unsubscribe(): ] for unsubscribe in email_unsubscribe: - frappe.get_doc(unsubscribe).insert() \ No newline at end of file + if not frappe.get_all("Email Unsubscribe", filters=unsubscribe): + doc = frappe.new_doc("Email Unsubscribe") + doc.update(unsubscribe) + doc.insert(ignore_permissions=True) \ No newline at end of file diff --git a/frappe/patches.txt b/frappe/patches.txt index cbda8cf677..5d45cbec66 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -273,3 +273,4 @@ execute:frappe.delete_doc_if_exists('DocType', 'GCalendar Settings') frappe.patches.v12_0.remove_parent_and_parenttype_from_print_formats execute:from frappe.desk.page.setup_wizard.install_fixtures import update_genders;update_genders() frappe.patches.v13_0.website_theme_custom_scss +frappe.patches.v13_0.email_unsubscribe diff --git a/frappe/patches/v13_0/email_unsubscribe.py b/frappe/patches/v13_0/email_unsubscribe.py new file mode 100644 index 0000000000..69ed1be948 --- /dev/null +++ b/frappe/patches/v13_0/email_unsubscribe.py @@ -0,0 +1,13 @@ +import frappe + +def execute(): + email_unsubscribe = [ + {"email": "admin@example.com", "global_unsubscribe": 1}, + {"email": "guest@example.com", "global_unsubscribe": 1} + ] + + for unsubscribe in email_unsubscribe: + if not frappe.get_all("Email Unsubscribe", filters=unsubscribe): + doc = frappe.new_doc("Email Unsubscribe") + doc.update(unsubscribe) + doc.insert(ignore_permissions=True) \ No newline at end of file From 18ce053a7475759f378038bb7017099c6db989a1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 13:39:40 +0530 Subject: [PATCH 084/498] fix: refresh input --- .../js/frappe/form/controls/duration.js | 24 ++++++++++--------- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c177ae6f17..c764c9b041 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -56,17 +56,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ set_duration_picker() { let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); - if (total_duration.days) { - this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); - } - if (total_duration.hours) { - this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); - } - if (total_duration.minutes) { - this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); - } - if (total_duration.seconds) { - this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); + if (this.$picker) { + if (total_duration.days) { + this.$picker.find(`[data-duration="days"]`).prop("value", total_duration.days); + } + if (total_duration.hours) { + this.$picker.find(`[data-duration="hrs"]`).prop("value", total_duration.hours); + } + if (total_duration.minutes) { + this.$picker.find(`[data-duration="mins"]`).prop("value", total_duration.minutes); + } + if (total_duration.seconds) { + this.$picker.find(`[data-duration="secs"]`).prop("value", total_duration.seconds); + } } }, From 74eba09e526e521e503ac10b0597dfe6677cbd56 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 14:06:34 +0530 Subject: [PATCH 085/498] fix(style): remove borders on focus for individual inputs --- frappe/public/less/controls.less | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/public/less/controls.less b/frappe/public/less/controls.less index b6ac3cf359..161293b558 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -224,6 +224,10 @@ border: 1px solid rgba(0, 0, 0, 0.25) !important; } + .duration-input:focus { + outline: None; + } + .duration-label { justify-content: center; } From 8383b9fc3ac18485d2379b12a0b2d97f34c96f37 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Wed, 6 May 2020 14:21:44 +0530 Subject: [PATCH 086/498] fix: clear_sessions should by default include mobile whenever password was changed, the system only logged out user for the current device type; which is incorrect. suppose if the user logs in through mobile and selects "logout all devices" on password reset on desktop, the system would log the user out of all desktop instances, leaving the mobile instance active. Signed-off-by: Chinmay D. Pai --- frappe/sessions.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/frappe/sessions.py b/frappe/sessions.py index cca40cbc55..d317d6caf3 100644 --- a/frappe/sessions.py +++ b/frappe/sessions.py @@ -33,7 +33,7 @@ def clear_sessions(user=None, keep_current=False, device=None, force=False): :param user: user name (default: current user) :param keep_current: keep current session (default: false) - :param device: delete sessions of this device (default: desktop) + :param device: delete sessions of this device (default: desktop, mobile) :param force: triggered by the user (default false) ''' @@ -49,13 +49,16 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None): :param user: user name (default: current user) :param keep_current: keep current session (default: false) - :param device: delete sessions of this device (default: desktop) + :param device: delete sessions of this device (default: desktop, mobile) ''' if not user: user = frappe.session.user if not device: - device = frappe.session.data.device or "desktop" + device = ("desktop", "mobile") + + if not isinstance(device, (tuple, list)): + device = (device,) offset = 0 if user == frappe.session.user: @@ -68,12 +71,12 @@ def get_sessions_to_clear(user=None, keep_current=False, device=None): return frappe.db.sql_list(""" SELECT `sid` FROM `tabSessions` - WHERE user=%s - AND device=%s + WHERE user=%(user)s + AND device in %(device)s {condition} ORDER BY `lastupdate` DESC LIMIT 100 OFFSET {offset}""".format(condition=condition, offset=offset), - (user, device)) + {"user": user, "device": device}) def delete_session(sid=None, user=None, reason="Session Expired"): from frappe.core.doctype.activity_log.feed import logout_feed From 52eee67217b4238886661d5664046d3a6d89958d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 14:44:41 +0530 Subject: [PATCH 087/498] feat: added sync dashboard utility --- frappe/utils/dashboard.py | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index 17f46a24d2..43057be79a 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -4,8 +4,10 @@ from __future__ import unicode_literals import json import frappe from frappe import _ +import os from functools import wraps from frappe.utils import add_to_date, get_link_to_form +from frappe.modules.import_file import import_doc def cache_source(function): @@ -72,3 +74,38 @@ def get_from_date_from_timespan(to_date, timespan): years = -50 return add_to_date(to_date, years=years, months=months, days=days, as_datetime=True) + +def sync_dashboards(app=None): + """Import, overwrite fixtures from `[app]/fixtures`""" + if app: + apps = [app] + else: + apps = frappe.get_installed_apps() + + for app_name in apps: + print("Updating Dashboard for {app}".format(app=app_name)) + for module_name in frappe.local.app_modules.get(app_name) or []: + config = get_config(app_name, module_name) + if config: + frappe.flags.in_import = True + make_records(config.charts, "Dashboard Chart") + make_records(config.dashboards, "Dashboard") + frappe.flags.in_import = False + +def make_records(config, doctype): + if not config: + return + + for item in config: + item["doctype"] = doctype + import_doc(item) + frappe.db.commit() + +def get_config(app, module): + try: + module_dashboards = frappe.get_module('{app}.{module}.dashboard_fixtures'.format(app=app, module=module)) + if hasattr(module_dashboards, 'get_data'): + return frappe._dict(module_dashboards.get_data()) + return None + except ImportError: + return None From db152b28de1c7dee0cf11cbe353022ce9fb3c75e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 14:45:07 +0530 Subject: [PATCH 088/498] feat: sync dashboards after setup and migrate --- frappe/desk/page/setup_wizard/install_fixtures.py | 3 ++- frappe/migrate.py | 3 +++ frappe/model/sync.py | 2 ++ 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/frappe/desk/page/setup_wizard/install_fixtures.py b/frappe/desk/page/setup_wizard/install_fixtures.py index c857bd077f..6917ef0426 100644 --- a/frappe/desk/page/setup_wizard/install_fixtures.py +++ b/frappe/desk/page/setup_wizard/install_fixtures.py @@ -6,12 +6,14 @@ from __future__ import unicode_literals import frappe from frappe import _ from frappe.desk.doctype.global_search_settings.global_search_settings import update_global_search_doctypes +from frappe.utils.dashboard import sync_dashboards def install(): update_genders() update_salutations() update_global_search_doctypes() setup_email_linking() + sync_dashboards() @frappe.whitelist() def update_genders(): @@ -35,4 +37,3 @@ def setup_email_linking(): "email_id": "email_linking@example.com", }) doc.insert(ignore_permissions=True, ignore_if_duplicate=True) - \ No newline at end of file diff --git a/frappe/migrate.py b/frappe/migrate.py index 043b6817d7..094abbe099 100644 --- a/frappe/migrate.py +++ b/frappe/migrate.py @@ -10,6 +10,7 @@ import frappe.translate import frappe.modules.patch_handler import frappe.model.sync from frappe.utils.fixtures import sync_fixtures +from frappe.utils.dashboard import sync_dashboards from frappe.cache_manager import clear_global_cache from frappe.desk.notifications import clear_notifications from frappe.website import render @@ -23,6 +24,7 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False): - run before migrate hooks - run patches - sync doctypes (schema) + - sync dashboards - sync fixtures - sync desktop icons - sync web pages (from /www) @@ -53,6 +55,7 @@ def migrate(verbose=True, rebuild_website=False, skip_failing=False): frappe.translate.clear_cache() sync_jobs() sync_fixtures() + sync_dashboards() sync_customizations() sync_languages() diff --git a/frappe/model/sync.py b/frappe/model/sync.py index c2acb59f63..c753f64e89 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -45,6 +45,8 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("data_migration", "data_migration_mapping"), ("data_migration", "data_migration_plan_mapping"), ("data_migration", "data_migration_plan"), + ("desk", "dashboard_chart"), + ("desk", "dashboard"), ("desk", "onboarding_permission"), ("desk", "onboarding_step"), ("desk", "onboarding_step_map"), From fffa3d5114d6200e99f5c76ae81b50054509a6dd Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Wed, 6 May 2020 15:47:54 +0530 Subject: [PATCH 089/498] fix: sanitize comment content before saving comments are handled fine while being created from the frontend, but when comments are made through a POST request, there's a chance of rendering javascript in the comment, since there's no way to sanitize the input on the server side. Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/comment/comment.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index e07266dc4d..a2105c1511 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -26,6 +26,7 @@ class Comment(Document): def validate(self): if not self.comment_email: self.comment_email = frappe.session.user + self.content = frappe.utils.sanitize_html(self.content) def on_update(self): update_comment_in_doc(self) From 0caf571fa8558934b805ba1a5ae58cea41268770 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Wed, 6 May 2020 15:58:50 +0530 Subject: [PATCH 090/498] fix: do not sanitize if no html is found return default string if no html tags are found Signed-off-by: Chinmay D. Pai --- frappe/utils/html_utils.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/utils/html_utils.py b/frappe/utils/html_utils.py index 62161408eb..c740252b5c 100644 --- a/frappe/utils/html_utils.py +++ b/frappe/utils/html_utils.py @@ -59,6 +59,9 @@ def sanitize_html(html, linkify=False): elif is_json(html): return html + if not bool(BeautifulSoup(html, 'html.parser').find()): + return html + tags = (acceptable_elements + svg_elements + mathml_elements + ["html", "head", "meta", "link", "body", "style", "o:p"]) attributes = {"*": acceptable_attributes, 'svg': svg_attributes} From 61d07941b8d269ac243823c5d808a22f79d91d23 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 16:46:49 +0530 Subject: [PATCH 091/498] feat: allow number cards in sync --- frappe/utils/dashboard.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index 43057be79a..9d31c8aa2d 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -90,6 +90,7 @@ def sync_dashboards(app=None): frappe.flags.in_import = True make_records(config.charts, "Dashboard Chart") make_records(config.dashboards, "Dashboard") + make_records(config.number_cards, "Number Cards") frappe.flags.in_import = False def make_records(config, doctype): From 7df3d2e585fbcbd1ea5fd5a1def18a9635a224da Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 16:47:27 +0530 Subject: [PATCH 092/498] fix: sync number cards before dashbaords --- frappe/utils/dashboard.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index 9d31c8aa2d..ceb5ea758a 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -89,8 +89,8 @@ def sync_dashboards(app=None): if config: frappe.flags.in_import = True make_records(config.charts, "Dashboard Chart") - make_records(config.dashboards, "Dashboard") make_records(config.number_cards, "Number Cards") + make_records(config.dashboards, "Dashboard") frappe.flags.in_import = False def make_records(config, doctype): From 213b0a228847b32c6eaad35a1eac40a3e4d8ae66 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 16:50:44 +0530 Subject: [PATCH 093/498] style: remove unused import --- frappe/utils/dashboard.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index ceb5ea758a..cf64d9da61 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -1,10 +1,8 @@ # Copyright (c) 2019, Frappe Technologies Pvt. Ltd. and Contributors # MIT License. See license.txt from __future__ import unicode_literals -import json import frappe from frappe import _ -import os from functools import wraps from frappe.utils import add_to_date, get_link_to_form from frappe.modules.import_file import import_doc From 961fbf6594c3f97af482f5f673c3f960e29315d3 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 17:26:28 +0530 Subject: [PATCH 094/498] feat: sync number cards --- frappe/model/sync.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index c753f64e89..320cc24677 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -45,6 +45,7 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("data_migration", "data_migration_mapping"), ("data_migration", "data_migration_plan_mapping"), ("data_migration", "data_migration_plan"), + ("desk", "number_card"), ("desk", "dashboard_chart"), ("desk", "dashboard"), ("desk", "onboarding_permission"), From 04a59352f7ccaf66758632e15415395844246280 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 17:40:50 +0530 Subject: [PATCH 095/498] fix: test cases --- frappe/desk/form/assign_to.py | 2 +- frappe/public/js/frappe/form/sidebar/assign_to.js | 2 +- frappe/tests/test_assign.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 6178690b1f..6c8cd5dfbd 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -44,7 +44,7 @@ def add(args=None): users_with_duplicate_todo = [] shared_with_users = [] - for assign_to in json.loads(args.get("assign_to")): + for assign_to in frappe.parse_json(args.get("assign_to")): filters = { "reference_type": args['doctype'], "reference_name": args['name'], diff --git a/frappe/public/js/frappe/form/sidebar/assign_to.js b/frappe/public/js/frappe/form/sidebar/assign_to.js index 84fde564c0..95ceb246e6 100644 --- a/frappe/public/js/frappe/form/sidebar/assign_to.js +++ b/frappe/public/js/frappe/form/sidebar/assign_to.js @@ -130,7 +130,7 @@ frappe.ui.form.AssignToDialog = Class.extend({ this.set_description_from_doc(); }, make: function() { - let me = this + let me = this; me.dialog = new frappe.ui.Dialog({ title: __('Add to ToDo'), diff --git a/frappe/tests/test_assign.py b/frappe/tests/test_assign.py index f32e3c9272..439e1546c0 100644 --- a/frappe/tests/test_assign.py +++ b/frappe/tests/test_assign.py @@ -60,7 +60,7 @@ class TestAssign(unittest.TestCase): def assign(doc, user): return frappe.desk.form.assign_to.add({ - "assign_to": user, + "assign_to": [user], "doctype": doc.doctype, "name": doc.name, "description": 'test', From e183d16a83ee72d1d34680d715c87fd8fb6fc779 Mon Sep 17 00:00:00 2001 From: prssanna Date: Wed, 6 May 2020 18:04:55 +0530 Subject: [PATCH 096/498] fix: headers not being exported for non dict columns --- frappe/desk/query_report.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/query_report.py b/frappe/desk/query_report.py index 164f6389eb..74e841f107 100644 --- a/frappe/desk/query_report.py +++ b/frappe/desk/query_report.py @@ -299,7 +299,6 @@ def export_query(): _("You can try changing the filters of your report.")) return - data.columns = [col for col in data.columns if isinstance(col, dict) and not col.get('hidden')] columns = get_columns_dict(data.columns) from frappe.utils.xlsxutils import make_xlsx @@ -316,7 +315,8 @@ def build_xlsx_data(columns, data, visible_idx, include_indentation): # add column headings for idx in range(len(data.columns)): - result[0].append(columns[idx]["label"]) + if not columns[idx].get("hidden"): + result[0].append(columns[idx]["label"]) # build table from result for i, row in enumerate(data.result): From 7dbf38e3ee49eaaa519bce1beff65bc2956574cd Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 19:02:48 +0530 Subject: [PATCH 097/498] feat: prompt user for name --- frappe/desk/doctype/number_card/number_card.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/number_card/number_card.json b/frappe/desk/doctype/number_card/number_card.json index 5fb058d8ce..698ad1ed35 100644 --- a/frappe/desk/doctype/number_card/number_card.json +++ b/frappe/desk/doctype/number_card/number_card.json @@ -1,6 +1,6 @@ { "actions": [], - "autoname": "CARD.#####", + "autoname": "Prompt", "creation": "2020-04-15 18:06:39.444683", "doctype": "DocType", "editable_grid": 1, @@ -99,7 +99,7 @@ } ], "links": [], - "modified": "2020-05-01 15:23:29.550243", + "modified": "2020-05-06 19:01:38.401035", "modified_by": "Administrator", "module": "Desk", "name": "Number Card", From b5d88e42ee321ac32b2d57154e98429eebf0b5d6 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 19:02:56 +0530 Subject: [PATCH 098/498] feat: autoname based on label --- frappe/desk/doctype/number_card/number_card.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 2c5655beda..64f517bffc 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -6,10 +6,15 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document from frappe.utils import cint +from frappe.model.naming import append_number_if_name_exists class NumberCard(Document): - pass + def autoname(self): + if not self.name: + self.name = self.label + if frappe.db.exists("Number Card", self.name): + self.name = append_number_if_name_exists('Number Card', self.name) def get_permission_query_conditions(user=None): if not user: From 192ab2a8368b49da6d92e37996d2af8d8ba2bd36 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 6 May 2020 19:51:13 +0530 Subject: [PATCH 099/498] refactor: use autoname --- frappe/desk/doctype/number_card/number_card.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/desk/doctype/number_card/number_card.json b/frappe/desk/doctype/number_card/number_card.json index 698ad1ed35..ec6a1e9190 100644 --- a/frappe/desk/doctype/number_card/number_card.json +++ b/frappe/desk/doctype/number_card/number_card.json @@ -1,6 +1,5 @@ { "actions": [], - "autoname": "Prompt", "creation": "2020-04-15 18:06:39.444683", "doctype": "DocType", "editable_grid": 1, @@ -99,7 +98,7 @@ } ], "links": [], - "modified": "2020-05-06 19:01:38.401035", + "modified": "2020-05-06 19:47:57.753574", "modified_by": "Administrator", "module": "Desk", "name": "Number Card", From 8f9cfcb55db447a105d72efd7559ada7fe9dc8da Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Wed, 6 May 2020 21:42:05 +0530 Subject: [PATCH 100/498] chore: Use xenial distribution --- .travis.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e9c2ee5262..488c3074f1 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: python -dist: trusty +dist: xenial addons: hosts: @@ -9,6 +9,10 @@ addons: postgresql: 9.5 chrome: stable +services: + - xvfb + - mysql + git: depth: 1 From 2c12af23191518a625b77c7cf6e715826b709db8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 12:12:04 +0530 Subject: [PATCH 101/498] fix: consider permissions for charts and cards --- frappe/desk/doctype/dashboard_chart/dashboard_chart.py | 4 ++-- frappe/desk/doctype/number_card/number_card.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 7ddb3d98f0..c03f6f8156 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -144,7 +144,7 @@ def get_chart_config(chart, filters, timespan, timegrain, from_date, to_date): filters.append([doctype, datefield, '>=', from_date, False]) filters.append([doctype, datefield, '<=', to_date, False]) - data = frappe.db.get_all( + data = frappe.db.get_list( doctype, fields = [ 'extract(year from `tab{doctype}`.{datefield}) as _year'.format(doctype=doctype, datefield=datefield), @@ -182,7 +182,7 @@ def get_group_by_chart_config(chart, filters): group_by_field = chart.group_by_based_on doctype = chart.document_type - data = frappe.db.get_all( + data = frappe.db.get_list( doctype, fields = [ '{} as name'.format(group_by_field), diff --git a/frappe/desk/doctype/number_card/number_card.py b/frappe/desk/doctype/number_card/number_card.py index 2c5655beda..2c072f44c4 100644 --- a/frappe/desk/doctype/number_card/number_card.py +++ b/frappe/desk/doctype/number_card/number_card.py @@ -65,7 +65,7 @@ def get_result(doc, to_date=None): if to_date: filters.append([doc.document_type, 'creation', '<', to_date, False]) - res = frappe.db.get_all(doc.document_type, fields=fields, filters=filters) + res = frappe.db.get_list(doc.document_type, fields=fields, filters=filters) number = res[0]['result'] if res else 0 return cint(number) From fff07548fd0e05b5e4232cbda25237a5137ff012 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 14:00:32 +0530 Subject: [PATCH 102/498] fix: show restricted button to indicate permission restrictions --- frappe/core/page/dashboard/dashboard.css | 5 +++++ frappe/core/page/dashboard/dashboard.js | 7 +++++++ frappe/public/js/frappe/list/list_view.js | 2 +- .../frappe/views/dashboard/dashboard_view.js | 8 +++++++- frappe/public/less/dashboard_view.less | 12 ++++++++++++ frappe/public/less/desk.less | 17 +++++++++++++++++ frappe/public/less/list.less | 18 +----------------- 7 files changed, 50 insertions(+), 19 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.css b/frappe/core/page/dashboard/dashboard.css index e69de29bb2..e1687a0675 100644 --- a/frappe/core/page/dashboard/dashboard.css +++ b/frappe/core/page/dashboard/dashboard.css @@ -0,0 +1,5 @@ +.restricted-button { + cursor: default; + position: relative; + right: -5px; +} \ No newline at end of file diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 222a31a863..0634d6518c 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -26,6 +26,13 @@ class Dashboard {
`).appendTo(this.wrapper.find(".page-content").empty()); this.container = this.wrapper.find(".dashboard-graph"); this.page = wrapper.page; + + this.page.set_title_sub( + $(``) + ) } show() { diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index af9a3c0221..dd9362d664 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -139,7 +139,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { show_restricted_list_indicator_if_applicable() { const match_rules_list = frappe.perm.get_match_rules(this.doctype); if (match_rules_list.length) { - this.restricted_list = $(``) + this.restricted_list = $(``) .prepend('') .click(() => this.show_restrictions(match_rules_list)) .appendTo(this.page.page_form); diff --git a/frappe/public/js/frappe/views/dashboard/dashboard_view.js b/frappe/public/js/frappe/views/dashboard/dashboard_view.js index 13c44d2130..83f45da5be 100644 --- a/frappe/public/js/frappe/views/dashboard/dashboard_view.js +++ b/frappe/public/js/frappe/views/dashboard/dashboard_view.js @@ -41,7 +41,13 @@ frappe.views.DashboardView = class DashboardView extends frappe.views.ListView { this.$dashboard_page = this.$page.find('.layout-main-section-wrapper').addClass('dashboard-page'); this.$page.find('.page-form').empty().html( `
-
${dashboard_name}
+
+ ${dashboard_name} +
+
${__('Customize')}
${__('Reset')} diff --git a/frappe/public/less/dashboard_view.less b/frappe/public/less/dashboard_view.less index 874e4e2e36..5af532b9c7 100644 --- a/frappe/public/less/dashboard_view.less +++ b/frappe/public/less/dashboard_view.less @@ -28,6 +28,18 @@ display: flex; justify-content: space-between; width: 100%; + + .header-title { + line-height: 1.5em; + vertical-align: text-bottom; + } + + .restricted-button { + cursor: default; + position: relative; + right: 5px; + top: -3px; + } } .customize-dashboard { diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index 6d44fc5192..e85367435e 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -316,6 +316,23 @@ li.user-progress { } } +.restricted-button { + padding: 0px 10px; + border: 1px solid @yellow; + height: 25px; + font-weight: 600; + border-radius: 5px; + background: lightyellow; + color: @text-light; + margin: auto 5px auto auto; + font-size: @text-small; + outline: 0; + .octicon { + padding-right: 5px; + font-size: 12px; + } +} + /* on small screens, show only icons on top */ @media (max-width: 767px) { .module-view-layout .nav-stacked > li { diff --git a/frappe/public/less/list.less b/frappe/public/less/list.less index 3294e24bf3..639e67e3a7 100644 --- a/frappe/public/less/list.less +++ b/frappe/public/less/list.less @@ -298,26 +298,10 @@ input.list-check-all, input.list-row-checkbox { .awesomplete > ul { min-width: 300px; } - .restricted-list { - padding: 0px 10px; - border: 1px solid @yellow; - height: 25px; - font-weight: 600; - border-radius: 5px; - background: lightyellow; - color: @text-light; - margin: auto 5px auto auto; - font-size: @text-small; - outline: 0; - .octicon { - padding-right: 5px; - font-size: 12px; - } - } } .frappe-rtl { - .restricted-list { + .restricted-button { margin: auto auto auto 5px; direction: ltr; } From 9b4e5c5a14898cf74de581e58340e5ac1edd17fc Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 7 May 2020 15:26:29 +0530 Subject: [PATCH 103/498] fix: do not create contacts if unchecked --- .../core/doctype/communication/communication.py | 3 +++ .../doctype/email_account/email_account.json | 15 +++++++++++---- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index abd24fb468..12a9a8962c 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -337,6 +337,9 @@ def get_permission_query_conditions_for_communication(user): .format(email_accounts=','.join(email_accounts)) def get_contacts(email_strings): + if (not self.email_account) or (self.email_account and not frappe.db.get_value("Email Account", self.email_account, "create_contact")): + return [] + email_addrs = [] for email_string in email_strings: diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 6bde0291a0..d664e0f9fb 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -29,6 +29,7 @@ "default_incoming", "email_sync_option", "initial_sync_count", + "create_contact", "section_break_12", "enable_automatic_linking", "section_break_13", @@ -114,9 +115,9 @@ "depends_on": "eval:!doc.service", "fieldname": "domain", "fieldtype": "Link", - "label": "Domain", "in_list_view": 1, "in_standard_filter": 1, + "label": "Domain", "options": "Email Domain" }, { @@ -408,11 +409,17 @@ "fieldname": "use_ssl_for_outgoing", "fieldtype": "Check", "label": "Use SSL for Outgoing" + }, + { + "default": "0", + "fieldname": "create_contact", + "fieldtype": "Check", + "label": "Create Contacts from Incoming Emails" } ], "icon": "fa fa-inbox", "links": [], - "modified": "2020-04-06 19:20:50.491146", + "modified": "2020-05-07 15:18:43.931499", "modified_by": "Administrator", "module": "Email", "name": "Email Account", @@ -427,8 +434,8 @@ "write": 1 }, { - "read": 1, - "role": "Inbox User" + "read": 1, + "role": "Inbox User" } ], "sort_field": "modified", From eeaca0968f210c288e82961f0305b9316840f555 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Thu, 7 May 2020 16:03:04 +0530 Subject: [PATCH 104/498] feat: add styling and links for sharing blogs --- frappe/website/doctype/blog_post/blog_post.py | 23 +++++++++ .../blog_post/templates/blog_post.html | 50 ++++++++++++++----- .../doctype/blog_settings/blog_settings.js | 2 +- .../website_theme/standard/standard.json | 2 +- 4 files changed, 63 insertions(+), 14 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index be58bec842..d24c3a5e8e 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -61,6 +61,7 @@ class BlogPost(WebsiteGenerator): # temp fields context.full_name = get_fullname(self.owner) context.updated = global_date_format(self.published_on) + context.social_links = self.fetch_social_links_info() if self.blogger: context.blogger_info = frappe.get_doc("Blogger", self.blogger).as_dict() @@ -89,6 +90,28 @@ class BlogPost(WebsiteGenerator): {"name": "Blog", "route": "/blog"}, {"label": context.category.title, "route":context.category.route}] + + def fetch_social_links_info(self): + url = frappe.local.site + "/" +self.route + social_url_map = { + "twitter": "https://twitter.com/intent/tweet?text=" +self.title + "&url=" + url, + "facebook": "https://www.facebook.com/sharer.php?u=" + url, + "linkedin": "https://www.linkedin.com/sharing/share-offsite/?url=" + url, + "email": "mailto:?subject=" + self.title + "&body=" + url, + } + + social_link = [] + for link in frappe.get_cached_doc("Blog Settings").social_share_settings: + social_media = link.social_link_type + + social_link.append({ + 'icon': social_media if not social_media == 'email' else 'envelope', + 'url': social_url_map.get(social_media), + 'color': link.color, + 'background': link.background_color + }) + return social_link + def load_comments(self, context): context.comment_list = get_comment_list(self.doctype, self.name) diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index 4c4219ea54..f01d3a7e9b 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -9,18 +9,30 @@
-

{{ title }}

-

- {{ frappe.format_date(published_on) }} - {% if read_time %} - · - {{ read_time }} min read - {% endif %} -

+ +

{{ title }}

+

+ {{ blog_intro }} +

+
+ +
+ {{ frappe.format_date(published_on) }} + {% if read_time %} + · + {{ read_time }} min read + {% endif %} + {% if social_links %} + + {% endif %} +
-

- {{ blog_intro }} -

{{ content }}
@@ -51,6 +63,20 @@ margin: 0 auto; font-size: 1.2rem; } - +.meta-info { + display: flex; + justify-content: space-between; + align-items: flex-end; +} +.social-links { + margin-right: 0px; +} +.social-links a { + font-size: 1.25rem; + margin: 0 5px 0 0; + padding: 5px; + width: 2.5rem; + text-align: center; +} {% endblock %} diff --git a/frappe/website/doctype/blog_settings/blog_settings.js b/frappe/website/doctype/blog_settings/blog_settings.js index b52c8862c5..90421b9550 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.js +++ b/frappe/website/doctype/blog_settings/blog_settings.js @@ -5,4 +5,4 @@ frappe.ui.form.on('Blog Settings', { refresh: function(frm) { } -}); +}) \ No newline at end of file diff --git a/frappe/website/website_theme/standard/standard.json b/frappe/website/website_theme/standard/standard.json index 1729e4616a..db79bde0a8 100644 --- a/frappe/website/website_theme/standard/standard.json +++ b/frappe/website/website_theme/standard/standard.json @@ -10,7 +10,7 @@ "font_properties": "300,600", "footer": [], "idx": 26, - "modified": "2020-04-29 12:26:48.399125", + "modified": "2020-05-06 17:36:19.830993", "modified_by": "Administrator", "module": "Website", "name": "Standard", From fe911aa0c59251b8ebf25cd95cf263ce5a073b08 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Thu, 7 May 2020 18:25:17 +0530 Subject: [PATCH 105/498] Update package.js --- frappe/custom/doctype/package/package.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/custom/doctype/package/package.js b/frappe/custom/doctype/package/package.js index 47a7bce0df..956bbfc05c 100644 --- a/frappe/custom/doctype/package/package.js +++ b/frappe/custom/doctype/package/package.js @@ -20,7 +20,7 @@ frappe.ui.form.on('Package', { } }); - if(frm.doc.instances){ + if (frm.doc.instances) { frm.add_custom_button(__("Deploy"), function() { frm.call("deploy_package"); }); From 413ff349ea31139931fdcf429f01b86aebfe0387 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Thu, 7 May 2020 18:54:55 +0530 Subject: [PATCH 106/498] chore: get_contacts only if email account is set --- frappe/core/doctype/communication/communication.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 12a9a8962c..07a06701e6 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -258,7 +258,10 @@ class Communication(Document): # Timeline Links def set_timeline_links(self): - contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc]) + if self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact"): + contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc]) + else: + contacts = [] for contact_name in contacts: self.add_link('Contact', contact_name) @@ -337,9 +340,6 @@ def get_permission_query_conditions_for_communication(user): .format(email_accounts=','.join(email_accounts)) def get_contacts(email_strings): - if (not self.email_account) or (self.email_account and not frappe.db.get_value("Email Account", self.email_account, "create_contact")): - return [] - email_addrs = [] for email_string in email_strings: From baa0d5d26c81cb798048d34917c66561d3b756ac Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Thu, 7 May 2020 19:02:54 +0530 Subject: [PATCH 107/498] chore: simplify contacts condition --- frappe/core/doctype/communication/communication.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index 07a06701e6..aecf35fcdf 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -258,10 +258,9 @@ class Communication(Document): # Timeline Links def set_timeline_links(self): + contacts = [] if self.email_account and frappe.db.get_value("Email Account", self.email_account, "create_contact"): contacts = get_contacts([self.sender, self.recipients, self.cc, self.bcc]) - else: - contacts = [] for contact_name in contacts: self.add_link('Contact', contact_name) From 8a918fd54dce8c0ab4ad2326d2f6dc8257412d33 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 7 May 2020 19:18:08 +0530 Subject: [PATCH 108/498] feat: use new sync API --- frappe/model/sync.py | 5 ++- .../website_analytics/website_analytics.json | 24 ------------- frappe/website/dashboard_fixtures.py | 36 +++++++++++++++++++ 3 files changed, 38 insertions(+), 27 deletions(-) delete mode 100644 frappe/website/dashboard_chart/website_analytics/website_analytics.json create mode 100644 frappe/website/dashboard_fixtures.py diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 7991d11d15..320cc24677 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -55,8 +55,7 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("desk", "desk_card"), ("desk", "desk_chart"), ("desk", "desk_shortcut"), - ("desk", "desk_page"), - ("desk", "dashboard_chart")): + ("desk", "desk_page")): files.append(os.path.join(frappe.get_app_path("frappe"), d[0], "doctype", d[1], d[1] + ".json")) @@ -86,7 +85,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', 'data_migration_mapping', 'data_migration_plan', 'desk_page', - 'onboarding_step', 'onboarding', 'dashboard_chart'] + 'onboarding_step', 'onboarding'] for doctype in document_types: doctype_path = os.path.join(start_path, doctype) diff --git a/frappe/website/dashboard_chart/website_analytics/website_analytics.json b/frappe/website/dashboard_chart/website_analytics/website_analytics.json deleted file mode 100644 index eeeb1a11f9..0000000000 --- a/frappe/website/dashboard_chart/website_analytics/website_analytics.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "chart_name": "Website Analytics", - "chart_type": "Report", - "creation": "2020-05-05 18:14:19.369181", - "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"colors\": [\"#7cd6fd\", \"#5e64ff\"], \"tooltipOptions\": {}}", - "docstatus": 0, - "doctype": "Dashboard Chart", - "filters_json": "{}", - "group_by_type": "Count", - "idx": 0, - "is_custom": 1, - "is_public": 1, - "modified": "2020-05-05 18:16:47.383649", - "modified_by": "Administrator", - "name": "Website Analytics", - "number_of_groups": 0, - "owner": "Administrator", - "report_name": "Website Analytics", - "time_interval": "Yearly", - "timeseries": 0, - "timespan": "Last Year", - "type": "Line", - "y_axis": [] -} \ No newline at end of file diff --git a/frappe/website/dashboard_fixtures.py b/frappe/website/dashboard_fixtures.py new file mode 100644 index 0000000000..4e5b5454bb --- /dev/null +++ b/frappe/website/dashboard_fixtures.py @@ -0,0 +1,36 @@ +import frappe + +def get_data(): + return frappe._dict({ + "dashboards": get_dashboards(), + "charts": get_charts(), + "number_cards": get_number_cards(), + }) + +def get_dashboards(): + return [{ + "name": "Website", + "dashboard_name": "Website", + "charts": [ + { "chart": "Website Analytics", "width": "Full" } + ] + }] + +def get_charts(): + return [{ + "chart_name": "Website Analytics", + "chart_type": "Report", + "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"colors\": [\"#7cd6fd\", \"#5e64ff\"], \"tooltipOptions\": {}}", + "doctype": "Dashboard Chart", + "filters_json": "{}", + "group_by_type": "Count", + "is_custom": 1, + "is_public": 1, + "name": "Website Analytics", + "number_of_groups": 0, + "report_name": "Website Analytics", + "time_interval": "Yearly", + "timeseries": 0, + "timespan": "Last Year", + "type": "Line" + }] \ No newline at end of file From a7ba5c9eb01df23d6fdd342538ae67e57d184907 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Thu, 7 May 2020 22:26:07 +0530 Subject: [PATCH 109/498] chore: Try Bionic --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 488c3074f1..866b5889bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,5 +1,5 @@ language: python -dist: xenial +dist: bionic addons: hosts: From 80f6ff7f17c95f1046d7b377e5395a46ff403e42 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 23:34:37 +0530 Subject: [PATCH 110/498] style: formatting fixes --- frappe/core/page/dashboard/dashboard.js | 2 +- frappe/public/less/desk.less | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard/dashboard.js index 0634d6518c..db2f8f8988 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard/dashboard.js @@ -32,7 +32,7 @@ class Dashboard { ${__('Restricted')} `) - ) + ); } show() { diff --git a/frappe/public/less/desk.less b/frappe/public/less/desk.less index e85367435e..4c2c37c785 100644 --- a/frappe/public/less/desk.less +++ b/frappe/public/less/desk.less @@ -327,7 +327,7 @@ li.user-progress { margin: auto 5px auto auto; font-size: @text-small; outline: 0; - .octicon { + .octicon-lock { padding-right: 5px; font-size: 12px; } From 198041a5ef57b45dabd2125126f06e39c3bf08a9 Mon Sep 17 00:00:00 2001 From: Rohit Waghchaure Date: Fri, 8 May 2020 02:58:23 +0530 Subject: [PATCH 111/498] fix: dashboard sync issue --- frappe/utils/dashboard.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/frappe/utils/dashboard.py b/frappe/utils/dashboard.py index cf64d9da61..7cc97ed3f9 100644 --- a/frappe/utils/dashboard.py +++ b/frappe/utils/dashboard.py @@ -87,7 +87,7 @@ def sync_dashboards(app=None): if config: frappe.flags.in_import = True make_records(config.charts, "Dashboard Chart") - make_records(config.number_cards, "Number Cards") + make_records(config.number_cards, "Number Card") make_records(config.dashboards, "Dashboard") frappe.flags.in_import = False @@ -95,10 +95,13 @@ def make_records(config, doctype): if not config: return - for item in config: - item["doctype"] = doctype - import_doc(item) - frappe.db.commit() + try: + for item in config: + item["doctype"] = doctype + import_doc(item) + frappe.db.commit() + except frappe.DuplicateEntryError: + pass def get_config(app, module): try: From 3296687178a1849b133e68eed4d14c30c436e9e6 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Fri, 8 May 2020 11:00:03 +0530 Subject: [PATCH 112/498] chore: Use python 3.7 --- .travis.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.travis.yml b/.travis.yml index 866b5889bc..174f92ea11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -27,18 +27,18 @@ cache: matrix: include: - - name: "Python 3.6 MariaDB" - python: 3.6 + - name: "Python 3.7 MariaDB" + python: 3.7 env: DB=mariadb TYPE=server script: bench --site test_site run-tests --coverage - - name: "Python 3.6 PostgreSQL" - python: 3.6 + - name: "Python 3.7 PostgreSQL" + python: 3.7 env: DB=postgres TYPE=server script: bench --site test_site run-tests --coverage - name: "Cypress" - python: 3.6 + python: 3.7 env: DB=mariadb TYPE=ui before_script: - bench --site test_site execute frappe.utils.install.complete_setup_wizard From aae45ed98c50cf5f64caf5dc465f31d116e2deb8 Mon Sep 17 00:00:00 2001 From: sahil28297 <37302950+sahil28297@users.noreply.github.com> Date: Fri, 8 May 2020 12:18:09 +0530 Subject: [PATCH 113/498] fix: bump frappe develop to version 13-dev --- frappe/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 7664ac4c61..eae8b0d76f 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -23,7 +23,7 @@ if PY2: reload(sys) sys.setdefaultencoding("utf-8") -__version__ = '12.0.0-dev' +__version__ = '13.0.0-dev' __title__ = "Frappe Framework" local = Local() From bae02075d69eae7737471cde4e6c967fe7cd230a Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 8 May 2020 12:30:52 +0530 Subject: [PATCH 114/498] fix: remove reviews label on disabling energy points settings --- frappe/public/js/frappe/form/controls/rating.js | 2 +- frappe/public/js/frappe/form/sidebar/review.js | 6 ++++++ frappe/public/js/frappe/form/templates/form_sidebar.html | 5 +---- .../doctype/energy_point_settings/energy_point_settings.js | 4 +++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/rating.js b/frappe/public/js/frappe/form/controls/rating.js index 9a68cec2be..34e890d45c 100644 --- a/frappe/public/js/frappe/form/controls/rating.js +++ b/frappe/public/js/frappe/form/controls/rating.js @@ -16,7 +16,7 @@ frappe.ui.form.ControlRating = frappe.ui.form.ControlInt.extend({ $(this.input_area).find('i').hover((ev) => { const el = $(ev.currentTarget); let star_value = el.data('rating'); - el.parent().children('i.fa').each( function(e){ + el.parent().children('i.fa').each( function(e) { if (e < star_value) { $(this).addClass('star-hover'); } else { diff --git a/frappe/public/js/frappe/form/sidebar/review.js b/frappe/public/js/frappe/form/sidebar/review.js index e187ca4693..2cf2980bf7 100644 --- a/frappe/public/js/frappe/form/sidebar/review.js +++ b/frappe/public/js/frappe/form/sidebar/review.js @@ -21,6 +21,12 @@ frappe.ui.form.Review = class Review { }); } make_review_container() { + this.parent.append(` + + `); this.review_list_wrapper = this.parent.find('.review-list'); } add_review_button() { diff --git a/frappe/public/js/frappe/form/templates/form_sidebar.html b/frappe/public/js/frappe/form/templates/form_sidebar.html index 30b2205bae..c3f2de9c7e 100644 --- a/frappe/public/js/frappe/form/templates/form_sidebar.html +++ b/frappe/public/js/frappe/form/templates/form_sidebar.html @@ -69,10 +69,7 @@
- +