From 97ae23c2a1f098fc38a408fac8273c3b4e4130c9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 23 Oct 2019 15:42:24 +0530 Subject: [PATCH 001/572] 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/572] 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/572] 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/572] 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/572] 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/572] 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/572] 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/572] 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/572] 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/572] 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/572] 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/572] fix: remove child table and use html --- .../list_view_column_order/__init__.py | 0 .../list_view_column_order.json | 35 ------------------- .../list_view_column_order.py | 10 ------ .../list_view_settings/list_view_columns.html | 9 +++++ .../list_view_settings/list_view_settings.js | 19 +++++----- .../list_view_settings.json | 13 ++++--- .../test_list_view_settings.py | 10 ++++++ 7 files changed, 38 insertions(+), 58 deletions(-) delete mode 100644 frappe/desk/doctype/list_view_column_order/__init__.py delete mode 100644 frappe/desk/doctype/list_view_column_order/list_view_column_order.json delete mode 100644 frappe/desk/doctype/list_view_column_order/list_view_column_order.py create mode 100644 frappe/desk/doctype/list_view_settings/list_view_columns.html create mode 100644 frappe/desk/doctype/list_view_settings/test_list_view_settings.py diff --git a/frappe/desk/doctype/list_view_column_order/__init__.py b/frappe/desk/doctype/list_view_column_order/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/desk/doctype/list_view_column_order/list_view_column_order.json b/frappe/desk/doctype/list_view_column_order/list_view_column_order.json deleted file mode 100644 index 2db000e706..0000000000 --- a/frappe/desk/doctype/list_view_column_order/list_view_column_order.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "creation": "2019-10-24 07:54:42.901997", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label", - "fieldname" - ], - "fields": [ - { - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "read_only": 1 - }, - { - "fieldname": "fieldname", - "fieldtype": "Data", - "label": "Fieldname", - "read_only": 1 - } - ], - "istable": 1, - "modified": "2019-10-24 18:08:40.740953", - "modified_by": "Administrator", - "module": "Desk", - "name": "List View Column Order", - "owner": "Administrator", - "permissions": [], - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_column_order/list_view_column_order.py b/frappe/desk/doctype/list_view_column_order/list_view_column_order.py deleted file mode 100644 index 598e6398b4..0000000000 --- a/frappe/desk/doctype/list_view_column_order/list_view_column_order.py +++ /dev/null @@ -1,10 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -# import frappe -from frappe.model.document import Document - -class ListViewColumnOrder(Document): - pass diff --git a/frappe/desk/doctype/list_view_settings/list_view_columns.html b/frappe/desk/doctype/list_view_settings/list_view_columns.html new file mode 100644 index 0000000000..50474c5169 --- /dev/null +++ b/frappe/desk/doctype/list_view_settings/list_view_columns.html @@ -0,0 +1,9 @@ +
+ {% for (var i=0, l=fields.length; i < l; i++) { var field = fields[i]; %} +
+ + {%= field.label %} + +
+ {% } %} +
diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 8109c6d45b..3b8654e349 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -19,18 +19,19 @@ frappe.ui.form.on('List View Settings', { }, callback: function (r) { if (r && r.message) { - let i = 0; - frm.clear_table("column_order"); - while (i < frm.doc.column_count) { - if (r.message[i]) { - frm.add_child("column_order", r.message[i]); - } - i++; - } - frm.refresh(); + render_fields(frm, r.message); } } }); }); + + if (frm.doc.column_order) { + render_fields(frm, frm.doc.column_order); + } } }); + +function render_fields(frm, fields) { + let columns_html = frm.get_field("column_order"); + columns_html.html(frappe.render_template("list_view_columns", {fields: fields})); +} \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 3911467980..95ef3b2acb 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -9,6 +9,7 @@ "disable_sidebar_stats", "disable_auto_refresh", "column_count", + "columns", "column_order" ], "fields": [ @@ -38,12 +39,16 @@ }, { "fieldname": "column_order", - "fieldtype": "Table", - "label": "Column Order", - "options": "List View Column Order" + "fieldtype": "Data", + "label": "Column Order" + }, + { + "fieldname": "columns", + "fieldtype": "HTML", + "label": "Columns" } ], - "modified": "2019-10-25 11:57:00.314570", + "modified": "2019-11-18 10:05:11.859157", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", diff --git a/frappe/desk/doctype/list_view_settings/test_list_view_settings.py b/frappe/desk/doctype/list_view_settings/test_list_view_settings.py new file mode 100644 index 0000000000..c1b2f4a0da --- /dev/null +++ b/frappe/desk/doctype/list_view_settings/test_list_view_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2019, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestListViewSettings(unittest.TestCase): + pass From 4775d1b6ee5761a1ffa4988b463f5cc7854c5f05 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 14 Apr 2020 11:41:31 +0530 Subject: [PATCH 013/572] feat: new UI for list view settings --- .../list_view_settings/list_view_settings.js | 37 +- .../list_view_settings.json | 41 ++- .../list_view_settings/list_view_settings.py | 84 +++-- frappe/public/js/frappe/list/list_settings.js | 320 ++++++++++++++++++ frappe/public/js/frappe/list/list_view.js | 64 ++-- 5 files changed, 442 insertions(+), 104 deletions(-) create mode 100644 frappe/public/js/frappe/list/list_settings.js diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.js b/frappe/desk/doctype/list_view_settings/list_view_settings.js index 3b8654e349..db33f71675 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.js +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.js @@ -1,37 +1,8 @@ -// Copyright (c) 2019, Frappe Technologies and contributors +// Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt frappe.ui.form.on('List View Settings', { - refresh: function(frm) { - if (frm.is_new()) { - return; - } + // refresh: function(frm) { - frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => { - frappe.set_route('List', frm.doc.name, 'List'); - }); - - frm.add_custom_button('Get List View Columns', function () { - frappe.call({ - method: "frappe.desk.doctype.list_view_settings.list_view_settings.get_listview_columns", - args: { - doctype: frm.doc.name - }, - callback: function (r) { - if (r && r.message) { - render_fields(frm, r.message); - } - } - }); - }); - - if (frm.doc.column_order) { - render_fields(frm, frm.doc.column_order); - } - } -}); - -function render_fields(frm, fields) { - let columns_html = frm.get_field("column_order"); - columns_html.html(frappe.render_template("list_view_columns", {fields: fields})); -} \ No newline at end of file + // } +}); \ No newline at end of file diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 95ef3b2acb..e59991c52e 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "Prompt", "creation": "2019-10-23 15:00:48.392374", "doctype": "DocType", @@ -8,9 +9,9 @@ "disable_count", "disable_sidebar_stats", "disable_auto_refresh", - "column_count", - "columns", - "column_order" + "total_fields", + "fields_html", + "fields" ], "fields": [ { @@ -32,23 +33,26 @@ "label": "Disable Auto Refresh" }, { - "fieldname": "column_count", + "fieldname": "total_fields", "fieldtype": "Select", - "label": "Column Count", + "label": "Total Fields", "options": "\n4\n5\n6\n7\n8\n9\n10" }, { - "fieldname": "column_order", - "fieldtype": "Data", - "label": "Column Order" + "fieldname": "fields_html", + "fieldtype": "HTML", + "label": "Fields" }, { - "fieldname": "columns", - "fieldtype": "HTML", - "label": "Columns" + "fieldname": "fields", + "fieldtype": "Code", + "hidden": 1, + "label": "Fields", + "read_only": 1 } ], - "modified": "2019-11-18 10:05:11.859157", + "links": [], + "modified": "2020-04-13 23:31:15.411147", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", @@ -63,8 +67,21 @@ "role": "System Manager", "share": 1, "write": 1 + }, + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "All", + "share": 1, + "write": 1 } ], + "read_only": 1, "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index adf92e8ec6..acd8f8a7b6 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -1,40 +1,78 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2019, Frappe Technologies and contributors +# Copyright (c) 2020, Frappe Technologies and contributors # For license information, please see license.txt from __future__ import unicode_literals import frappe from frappe.model.document import Document +from six import string_types +from frappe.exceptions import DoesNotExistError +import json class ListViewSettings(Document): def on_update(self): frappe.clear_document_cache(self.doctype, self.name) - frappe.get_cached_doc(self.doctype, self.name) @frappe.whitelist() -def get_listview_columns(doctype): - meta = frappe.get_meta(doctype) - listview_columns = [] +def save_listview_settings(doctype, listview_settings, removed_listview_fields): - subject_field = { - "label": "Name", - "fieldname": "name" - } + if isinstance(listview_settings, string_types): + listview_settings = json.loads(listview_settings) + + if isinstance(removed_listview_fields, string_types): + removed_listview_fields = json.loads(removed_listview_fields) + + try: + doc = frappe.get_doc("List View Settings", doctype) + doc.update(listview_settings) + doc.save() + except DoesNotExistError: + doc = frappe.new_doc("List View Settings") + doc.name = doctype + doc.update(listview_settings) + doc.insert() + + set_listview_fields(doctype, listview_settings.get("fields"), removed_listview_fields) + +def set_listview_fields(doctype, listview_fields, removed_listview_fields): + meta = frappe.get_meta(doctype) + + if isinstance(listview_fields, string_types): + listview_fields = [f.get("fieldname") for f in json.loads(listview_fields)] + + for field in listview_fields: + set_in_list_view_property(doctype, meta.get_field(field), "1") + + for field in removed_listview_fields: + set_in_list_view_property(doctype, meta.get_field(field), "0") + +def set_in_list_view_property(doctype, field, value): + property_setter = frappe.db.get_value("Property Setter", {"doc_type": doctype, "field_name": field.fieldname, "property": "in_list_view"}) + if property_setter: + doc = frappe.get_doc("Property Setter", property_setter) + doc.value = value + doc.save() + else: + frappe.make_property_setter({ + "doctype": doctype, + "doctype_or_field": "DocField", + "fieldname": field.fieldname, + "property": "in_list_view", + "value": value, + "property_type": "Check" + }, ignore_validate=True) + +@frappe.whitelist() +def get_default_listview_fields(doctype): + meta = frappe.get_meta(doctype) + path = frappe.get_module_path(frappe.scrub(meta.module), "doctype", frappe.scrub(meta.name), frappe.scrub(meta.name) + ".json") + json = frappe.get_file_json(path) + + fields = [f.get("fieldname") for f in json.get("fields") if f.get("in_list_view")] if meta.title_field: - title_field = meta.get_field(meta.title_field) + if not meta.title_field.strip() in fields: + fields.append(meta.title_field.strip()) - subject_field["label"] = title_field.label - subject_field["fieldname"] = title_field.fieldname - - listview_columns.append(subject_field) - - for field in meta.fields: - if field.in_list_view and field.label and field.fieldname: - listview_columns.append({ - "label": field.label, - "fieldname": field.fieldname - }) - - return listview_columns + return fields diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js new file mode 100644 index 0000000000..252309dd88 --- /dev/null +++ b/frappe/public/js/frappe/list/list_settings.js @@ -0,0 +1,320 @@ +export default class ListSettings { + constructor({ doctype, meta, settings }) { + if (!doctype) { + frappe.throw(__('Doctype required')); + } + + this.doctype = doctype; + this.meta = meta; + this.settings = settings; + this.dialog = null; + this.fields = []; + this.subject_field = null; + + frappe.model.with_doctype("List View Settings", () => { + this.make(); + this.get_listview_fields(meta); + this.setup_fields(); + this.setup_remove_fields(); + this.add_new_fields(); + this.show_dialog(); + }); + } + + make() { + let me = this; + + let list_view_settings = frappe.get_meta("List View Settings"); + + me.dialog = new frappe.ui.Dialog({ + title: __("{0} List View Settings", [__(me.doctype)]), + fields: list_view_settings.fields + }); + me.dialog.set_values(me.settings); + me.dialog.set_primary_action(__('Save'), () => { + let values = me.dialog.get_values(); + frappe.call("frappe.desk.doctype.list_view_settings.list_view_settings.save_listview_settings", { + doctype: me.doctype, + listview_settings: values, + removed_listview_fields: me.removed_fields || [] + }); + me.dialog.hide(); + frappe.ui.toolbar.clear_cache(); + }); + + me.dialog.fields_dict["total_fields"].df.onchange = () => me.refresh(); + } + + refresh() { + let me = this; + + me.setup_fields(); + me.update_fields(); + me.add_new_fields(); + me.setup_remove_fields(); + } + + show_dialog() { + let me = this; + me.dialog.show(); + } + + setup_fields() { + let me = this; + + let fields_html = me.dialog.get_field("fields_html"); + let $wrapper = fields_html.$wrapper[0]; + let fields = ``; + let total_fields = me.dialog.get_values().total_fields ? me.dialog.get_values().total_fields : me.settings.total_fields; + + for (let idx in me.fields) { + if (idx == parseInt(total_fields)) { + break; + } + let is_sortable = (idx == 0) ? `` : `sortable`; + let can_remove = (idx == 0) ? `hide` : ``; + + fields += ` +
+ +
+
+ +
+
+ ${me.fields[idx].label} +
+
+ + + +
+
+
`; + } + + fields_html.html(` +
+
+ +
+
+ ${fields} +
+ +
+ `); + + new Sortable($wrapper.getElementsByClassName("control-input-wrapper")[0], { + handle: '.sortable-handle', + draggable: '.sortable', + onUpdate: () => { + me.refresh(); + } + }); + } + + add_new_fields() { + let me = this + + let fields_html = me.dialog.get_field("fields_html"); + let add_new_fields = fields_html.$wrapper[0].getElementsByClassName("add-new-fields")[0]; + add_new_fields.onclick = () => me.column_selector(); + } + + setup_remove_fields() { + let me = this; + + let fields_html = me.dialog.get_field("fields_html"); + let remove_fields = fields_html.$wrapper[0].getElementsByClassName("remove-field"); + + for (let idx = 0; idx < remove_fields.length; idx++) { + remove_fields.item(idx).onclick = () => me.remove_fields(remove_fields.item(idx).getAttribute("data-fieldname")); + } + } + + remove_fields(fieldname) { + let me = this; + + for (let idx in me.fields) { + let field = me.fields[idx]; + + if (field.fieldname == fieldname) { + console.log(idx); + me.fields.splice(idx, 1); + break; + } + } + + me.refresh(); + } + + update_fields() { + let me = this; + + let fields_html = me.dialog.get_field("fields_html"); + let $wrapper = fields_html.$wrapper[0]; + + let fields_order = $wrapper.getElementsByClassName("fields_order"); + me.fields = []; + + for (let idx = 0; idx < fields_order.length; idx++) { + me.fields.push({ + fieldname: fields_order.item(idx).getAttribute("data-fieldname"), + label: fields_order.item(idx).getAttribute("data-label") + }); + } + + me.dialog.set_value("fields", JSON.stringify(me.fields)); + } + + column_selector() { + let me = this; + + let d = new frappe.ui.Dialog({ + title: __("{0} Fields", [__(me.doctype)]), + fields: [ + { + label: __("Reset Fields"), + fieldtype: "Button", + fieldname: "reset_fields", + click: () => me.reset_listview_fields(d) + }, + { + label: __("Select Fields"), + fieldtype: "MultiCheck", + fieldname: "fields", + options: me.get_doctype_fields(me.meta, me.fields.map(f => f.fieldname)), + columns: 2 + } + ] + }); + d.set_primary_action(__('Save'), () => { + let values = d.get_values().fields; + me.removed_fields = me.get_removed_listview_fields(values) + me.new_fields = values + + me.fields = []; + me.set_subject_field(me.meta); + + for (let idx in values) { + let value = values[idx]; + + if (me.fields.length === parseInt(me.dialog.get_values().total_fields)) { + break; + } else if (value != me.subject_field.fieldname) { + let field = frappe.meta.get_docfield(me.doctype, value); + me.fields.push({ + label: field.label, + fieldname: field.fieldname + }) + } + } + + me.refresh(); + me.dialog.set_value("fields", JSON.stringify(me.fields)); + d.hide(); + }); + d.show(); + } + + reset_listview_fields(dialog) { + let me = this; + + frappe.xcall("frappe.desk.doctype.list_view_settings.list_view_settings.get_default_listview_fields", { + doctype: me.doctype + }).then((fields) => { + let field = dialog.get_field("fields"); + field.df.options = me.get_doctype_fields(me.meta, fields); + dialog.refresh(); + }); + + } + + get_listview_fields(meta) { + let me = this; + + if (!me.settings.fields) { + me.set_list_view_fields(meta); + } else { + me.fields = JSON.parse(this.settings.fields); + } + + me.fields.uniqBy(f => f.fieldname); + } + + set_list_view_fields(meta) { + let me = this; + + me.set_subject_field(meta); + + meta.fields.forEach(field => { + if (field.in_list_view && !in_list(frappe.model.no_value_type, field.fieldtype) && + me.subject_field.fieldname != field.fieldname) { + + me.fields.push({ + label: field.label, + fieldname: field.fieldname + }) + } + }) + } + + set_subject_field(meta) { + let me = this; + + me.subject_field = { + label: "Name", + fieldname: "name" + } + + if (meta.title_field) { + let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()) + + me.subject_field = { + label: field.label, + fieldname: field.fieldname + } + } + + me.fields.push(me.subject_field); + } + + get_doctype_fields(meta, fields) { + let me = this; + let multiselect_fields = [] + + meta.fields.forEach(field => { + if (!in_list(frappe.model.no_value_type, field.fieldtype)) { + multiselect_fields.push({ + label: field.label, + value: field.fieldname, + checked: in_list(fields, field.fieldname) + }) + } + }) + + return multiselect_fields + } + + get_removed_listview_fields(new_fields) { + let me = this; + + let removed_fields = [] + let existing_fields = me.fields.map(f => f.fieldname); + + existing_fields.forEach(column => { + if (!in_list(new_fields, column)) { + removed_fields.push(column); + } + }); + + return removed_fields; + } +} \ No newline at end of file diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index faa6797d43..4913e29d11 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -1,4 +1,5 @@ import BulkOperations from "./bulk_operations"; +import ListSettings from "./list_settings"; frappe.provide('frappe.views'); @@ -287,41 +288,47 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { })) ); - if (this.list_view_settings.column_order && this.list_view_settings.column_order.length > 0 && - this.list_view_settings.column_order.length === this.list_view_settings.column_count) { - let custom_column_order = []; + if (this.list_view_settings.fields) { + let fields_order = []; + let fields = JSON.parse(this.list_view_settings.fields); + let is_status_field_set = false; //title_field is fixed - custom_column_order.push(this.columns[0]); + fields_order.push(this.columns[0]); this.columns.splice(0, 1); - for (let i in this.list_view_settings.column_order) { - let fieldname = this.list_view_settings.column_order[i].fieldname; - for (let j in this.columns) { - let df = this.columns[j].df.fieldname; - if (fieldname === df) { - custom_column_order.push(this.columns[j]); + for (let fld in fields) { + for (let col in this.columns) { + let field = fields[fld]; + let column = this.columns[col]; + + if (column.type == "Status" && !is_status_field_set) { + fields_order.push(column); + is_status_field_set = true; + break; + } else if (column.type == "Field" && field.fieldname === column.df.fieldname) { + fields_order.push(column); break; } } } - this.columns = custom_column_order; + this.columns = fields_order; } - // limit max to 8 columns if no column_count is set in List View Settings + // limit max to 8 columns if no total_fields is set in List View Settings // Screen with low density no of columns 4 // Screen with medium density no of columns 6 // Screen with high density no of columns 8 - let column_count = 6; + let total_fields = 6; if (window.innerWidth <= 1366) { - column_count = 4; + total_fields = 4; } else if (window.innerWidth >= 1920) { - column_count = 8; + total_fields = 8; } - this.columns = this.columns.slice(0, this.list_view_settings.column_count || column_count); + this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields); } get_documentation_link() { @@ -1300,26 +1307,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } show_list_settings() { - frappe.model.with_doctype("List View Settings", () => { - let list_view_settings = frappe.get_meta("List View Settings"); - - let d = new frappe.ui.Dialog({ - title: __("Settings"), - fields: [ - list_view_settings.fields[0], - list_view_settings.fields[1], - list_view_settings.fields[2], - list_view_settings.fields[3], - ] - }); - d.set_values(this.list_view_settings); - d.show(); - d.set_primary_action(__('Save'), () => { - let values = d.get_values(); - frappe.call("frappe.desk.listview.set_list_settings", {doctype: this.doctype, values: values}); - Object.assign(this.list_view_settings, values); - d.hide(); - frappe.ui.toolbar.clear_cache(); + frappe.model.with_doctype(this.doctype, () => { + new ListSettings({ + doctype: this.doctype, + settings: this.list_view_settings, + meta: frappe.get_meta(this.doctype) }); }); } From ebf87e430448aad9205105a231bbc4a2bcbdcc76 Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 14 Apr 2020 11:44:20 +0530 Subject: [PATCH 014/572] 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 015/572] 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 016/572] 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 017/572] 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 018/572] 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 019/572] 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 020/572] 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 021/572] 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 9b185802aaab09aa84dff1fce76292ea1edcdc54 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 27 Apr 2020 18:56:12 +0530 Subject: [PATCH 022/572] 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 58b90e72f35a6b004230b3197ca624ea70c5a5dd Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 13:20:27 +0530 Subject: [PATCH 023/572] 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 024/572] 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 2cd39a1bc4aa4eff44b482b9d786b224b7ac0923 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Mon, 4 May 2020 14:36:32 +0530 Subject: [PATCH 025/572] 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 026/572] 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 027/572] 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 028/572] 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 029/572] 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 030/572] 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 031/572] 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 032/572] 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 033/572] 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 034/572] 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 035/572] 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 036/572] 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 037/572] 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 038/572] 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 039/572] 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 040/572] 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 041/572] 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 87f2c66e0f3b13359b94819017c3252033089a4d Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 00:09:55 +0530 Subject: [PATCH 042/572] 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 043/572] 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 044/572] 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 045/572] 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 046/572] 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 18ce053a7475759f378038bb7017099c6db989a1 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 6 May 2020 13:39:40 +0530 Subject: [PATCH 047/572] 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 048/572] 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 049/572] 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 04a59352f7ccaf66758632e15415395844246280 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 6 May 2020 17:40:50 +0530 Subject: [PATCH 050/572] 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 2c12af23191518a625b77c7cf6e715826b709db8 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 12:12:04 +0530 Subject: [PATCH 051/572] 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 052/572] 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 053/572] 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 054/572] 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 763e8cfcaecdfe1c3bb997a6d8e96a15962b5344 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 7 May 2020 17:29:35 +0530 Subject: [PATCH 055/572] fix: remove unnecessary eval from error snapshot Signed-off-by: Chinmay D. Pai --- frappe/utils/error.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/frappe/utils/error.py b/frappe/utils/error.py index c124410a7f..dd6ae05c51 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -123,22 +123,13 @@ def get_snapshot(exception, context=10): # add exception type, value and attributes if isinstance(evalue, BaseException): for name in dir(evalue): - # prevent py26 DeprecationWarning - if (name != 'messages' or sys.version_info < (2.6)) and not name.startswith('__'): + if name != 'messages' and not name.startswith('__'): value = pydoc.text.repr(getattr(evalue, name)) - - # render multilingual string properly - if isinstance(value, six.text_type): - value = eval(value) - s['exception'][name] = encode(value) # add all local values (of last frame) to the snapshot for name, value in locals.items(): - if isinstance(value, six.text_type): - value = eval(value) - - s['locals'][name] = pydoc.text.repr(value) + s['locals'][name] = value if isinstance(value, six.text_type) else pydoc.text.repr(value) return s From 413ff349ea31139931fdcf429f01b86aebfe0387 Mon Sep 17 00:00:00 2001 From: Chinmay Pai Date: Thu, 7 May 2020 18:54:55 +0530 Subject: [PATCH 056/572] 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 057/572] 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 058/572] 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 80f6ff7f17c95f1046d7b377e5395a46ff403e42 Mon Sep 17 00:00:00 2001 From: prssanna Date: Thu, 7 May 2020 23:34:37 +0530 Subject: [PATCH 059/572] 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 50bde698bb5848f3303a9fbb84634a0aac53d4d5 Mon Sep 17 00:00:00 2001 From: Mangesh-Khairnar Date: Fri, 8 May 2020 15:11:21 +0530 Subject: [PATCH 060/572] fix: alignment issues on mobile --- frappe/website/doctype/blog_post/templates/blog_post.html | 4 +++- frappe/website/doctype/blog_settings/blog_settings.js | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index f01d3a7e9b..12e5ccf2d7 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -67,14 +67,16 @@ display: flex; justify-content: space-between; align-items: flex-end; + flex-wrap: wrap; } .social-links { margin-right: 0px; + margin-top: 1rem; } .social-links a { font-size: 1.25rem; margin: 0 5px 0 0; - padding: 5px; + padding: 5px 0; width: 2.5rem; text-align: center; } diff --git a/frappe/website/doctype/blog_settings/blog_settings.js b/frappe/website/doctype/blog_settings/blog_settings.js index 90421b9550..5ffc4ffc44 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 +}); \ No newline at end of file From 9e0b96c5c90239442a3240a74ae8d4378c1d9387 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 8 May 2020 18:21:18 +0530 Subject: [PATCH 061/572] fix: function not found --- frappe/website/dashboard_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/dashboard_fixtures.py b/frappe/website/dashboard_fixtures.py index 4e5b5454bb..01f1376d43 100644 --- a/frappe/website/dashboard_fixtures.py +++ b/frappe/website/dashboard_fixtures.py @@ -4,7 +4,7 @@ def get_data(): return frappe._dict({ "dashboards": get_dashboards(), "charts": get_charts(), - "number_cards": get_number_cards(), + "number_cards": None, }) def get_dashboards(): From b685412b369dac57fa853a8c0e33a0f5d0764328 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 8 May 2020 18:29:45 +0530 Subject: [PATCH 062/572] feat: allow notifications created via Notification in system notifications --- .../notification_log/notification_log.json | 8 ++++--- .../notification_log/notification_log.py | 2 +- .../notification_settings.json | 12 ++++++++++- .../notification_settings.py | 3 +++ .../doctype/notification/notification.json | 12 ++++++++++- .../doctype/notification/notification.py | 21 +++++++++++++++++++ 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index ecb746df64..86213da242 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-08-26 13:37:34.165254", "doctype": "DocType", "editable_grid": 1, @@ -35,7 +36,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Type", - "options": "Mention\nEnergy Point\nAssignment\nShare", + "options": "Mention\nEnergy Point\nAssignment\nShare\nAlert", "read_only": 1, "search_index": 1 }, @@ -82,8 +83,9 @@ } ], "in_create": 1, - "modified": "2019-11-12 15:22:35.283678", - "modified_by": "umair@erpnext.com", + "links": [], + "modified": "2020-05-08 15:16:36.365285", + "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 17eb6371b1..0112e68b1d 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -68,7 +68,7 @@ def make_notification_logs(doc, users): _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') - if _doc.for_user != _doc.from_user or doc.type == 'Energy Point': + if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': _doc.insert(ignore_permissions=True) def send_notification_email(doc): diff --git a/frappe/desk/doctype/notification_settings/notification_settings.json b/frappe/desk/doctype/notification_settings/notification_settings.json index 6af325507b..190e827ab5 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.json +++ b/frappe/desk/doctype/notification_settings/notification_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "Prompt", "creation": "2019-09-11 22:15:44.851526", "doctype": "DocType", @@ -13,6 +14,7 @@ "enable_email_assignment", "enable_email_energy_point", "enable_email_share", + "enable_email_alert", "user", "seen" ], @@ -83,10 +85,18 @@ "fieldtype": "Check", "hidden": 1, "label": "Seen" + }, + { + "default": "0", + "fieldname": "enable_email_alert", + "fieldtype": "Check", + "hidden": 1, + "label": "Alert" } ], "in_create": 1, - "modified": "2019-11-19 12:57:59.356786", + "links": [], + "modified": "2020-05-08 15:15:53.925897", "modified_by": "Administrator", "module": "Desk", "name": "Notification Settings", diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 6b5a13ee27..9b124cd6f4 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -28,6 +28,9 @@ def is_email_notifications_enabled_for_type(user, notification_type): if not is_email_notifications_enabled(user): return False + if notification_type == 'Alert': + return False + fieldname = 'enable_email_' + frappe.scrub(notification_type) enabled = frappe.db.get_value('Notification Settings', user, fieldname) if enabled is None: diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 14eff2251a..d83305ec5b 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_rename": 1, "autoname": "Prompt", "creation": "2014-07-11 17:18:09.923399", @@ -23,6 +24,7 @@ "value_changed", "sender", "sender_email", + "show_in_notifications_dropdown", "section_break_9", "condition", "column_break_6", @@ -260,10 +262,18 @@ "fieldtype": "Link", "label": "Print Format", "options": "Print Format" + }, + { + "default": "0", + "description": "If enabled, the notification will also show up in the notifications dropdown on the top right corner of the navigation bar.", + "fieldname": "show_in_notifications_dropdown", + "fieldtype": "Check", + "label": "Show in Notifications Dropdown" } ], "icon": "fa fa-envelope", - "modified": "2019-07-15 13:17:02.585013", + "links": [], + "modified": "2020-05-06 13:46:21.155535", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 8c011ade65..cac4bbddc2 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -13,6 +13,7 @@ from frappe.utils.jinja import validate_template from frappe.modules.utils import export_module_json, get_doc_module from six import string_types from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification class Notification(Document): def onload(self): @@ -125,6 +126,9 @@ def get_context(context): if self.channel == 'Slack': self.send_a_slack_msg(doc, context) + if self.show_in_notifications_dropdown: + self.create_drodown_notification(doc, context) + if self.set_property_after_alert: allow_update = True if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit: @@ -143,6 +147,23 @@ def get_context(context): except Exception: frappe.log_error(title='Document update failed', message=frappe.get_traceback()) + def create_drodown_notification(self, doc, context): + subject = self.subject + if "{" in subject: + subject = frappe.render_template(self.subject, context) + subject = '[Alert] ' + subject + + recipients, cc, bcc = self.get_list_of_recipients(doc, context) + users = recipients + cc + bcc + + notification_doc = { + 'type': 'Alert', + 'document_type': doc.doctype, + 'document_name': doc.name, + 'subject': subject, + } + enqueue_create_notification(recipients, notification_doc) + def send_an_email(self, doc, context): from email.utils import formataddr subject = self.subject From e8f30259c6cdf419129f2a820cbd7b215eeee596 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 15:23:18 +0530 Subject: [PATCH 063/572] fix: link notification log document and show message --- .../notification_log/notification_log.json | 19 +++++++++---------- .../notification_log/notification_log.py | 15 +++++++++++++++ .../doctype/notification/notification.py | 4 +++- .../frappe/ui/notifications/notifications.js | 14 ++++++++++---- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index 86213da242..e8225956b8 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -9,7 +9,6 @@ "for_user", "type", "email_content", - "column_break_4", "document_type", "read", "document_name", @@ -26,6 +25,7 @@ { "fieldname": "for_user", "fieldtype": "Link", + "hidden": 1, "label": "For User", "options": "User", "read_only": 1 @@ -33,6 +33,7 @@ { "fieldname": "type", "fieldtype": "Select", + "hidden": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Type", @@ -42,17 +43,14 @@ }, { "fieldname": "email_content", - "fieldtype": "Text", - "label": "Email Content", + "fieldtype": "Text Editor", + "label": "Message", "read_only": 1 }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, { "fieldname": "document_type", "fieldtype": "Link", + "hidden": 1, "label": "Document Type", "options": "DocType", "read_only": 1, @@ -61,13 +59,14 @@ { "fieldname": "document_name", "fieldtype": "Data", - "label": "Document Name", + "label": "Document Link", "read_only": 1, "search_index": 1 }, { "fieldname": "from_user", "fieldtype": "Link", + "hidden": 1, "label": "From User", "options": "User", "read_only": 1, @@ -84,8 +83,8 @@ ], "in_create": 1, "links": [], - "modified": "2020-05-08 15:16:36.365285", - "modified_by": "Administrator", + "modified": "2020-05-11 15:18:20.893718", + "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 0112e68b1d..2db552e7de 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -65,11 +65,26 @@ def make_notification_logs(doc, users): return _doc = frappe.new_doc('Notification Log') + + attachments = None + if doc.attachments: + attachments = doc.pop('attachments') + _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': _doc.insert(ignore_permissions=True) + if attachments: + attach_file_to_doc(attachments, _doc.name) + + +def attach_file_to_doc(attachments, docname): + from frappe.utils.file_manager import save_file + for attachment in attachments: + attachment.pop("print_format_attachment", None) + print_format_file = frappe._dict(frappe.attach_print(**attachment)) + save_file(print_format_file.fname, print_format_file.fcontent, 'Notification Log', docname) def send_notification_email(doc): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index cac4bbddc2..6d74e499d9 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -151,8 +151,8 @@ def get_context(context): subject = self.subject if "{" in subject: subject = frappe.render_template(self.subject, context) - subject = '[Alert] ' + subject + attachments = self.get_attachment(doc) recipients, cc, bcc = self.get_list_of_recipients(doc, context) users = recipients + cc + bcc @@ -161,6 +161,8 @@ def get_context(context): 'document_type': doc.doctype, 'document_name': doc.name, 'subject': subject, + 'email_content': frappe.render_template(self.message, context), + 'attachments': attachments } enqueue_create_notification(recipients, notification_doc) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index 2420d6772e..14d3b9bbd0 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -304,10 +304,7 @@ frappe.ui.Notifications = class Notifications { } get_dropdown_item_html(field) { - let doc_link = frappe.utils.get_form_link( - field.document_type, - field.document_name - ); + let doc_link = this.get_item_link(field); let read_class = field.read ? '' : 'unread'; let mark_read_action = field.read ? '': 'data-action="mark_as_read"'; let message = field.subject; @@ -336,6 +333,15 @@ frappe.ui.Notifications = class Notifications { return item_html; } + get_item_link(field) { + const link_doctype = field.type == 'Alert'? 'Notification Log': field.document_type; + const link_docname = field.type == 'Alert'? field.name: field.document_name; + return frappe.utils.get_form_link( + link_doctype, + link_docname + ); + } + render_dropdown_headers() { this.categories = [ { From d40b60d95b454b9b253689c6ba69b04b7edb9da9 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 18:15:38 +0530 Subject: [PATCH 064/572] fix(Report): hide chart if group by is applied to report --- frappe/public/js/frappe/ui/group_by/group_by.js | 1 + frappe/public/js/frappe/views/reports/report_view.js | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index 831bafa8e8..5053ad71f4 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -241,6 +241,7 @@ frappe.ui.GroupBy = class { this.order_by = ''; this.group_by = null; + this.report_view.group_by = null; this.aggregate_function = null; this.aggregate_on = null; $(".groupby").val(""); diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 856061f1f0..9399acf39e 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -113,7 +113,9 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { } else { this.save_report_settings(); } - this.init_chart(); + if (!this.group_by) { + this.init_chart(); + } } set_dirty_state_for_custom_report() { @@ -177,9 +179,12 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { this.render_count(); this.setup_columns(); - if (this.chart) { + if (this.group_by) { + this.$charts_wrapper.addClass('hidden'); + } else if (this.chart) { this.refresh_charts(); } + if (this.datatable && !force) { this.datatable.refresh(this.get_data(this.data), this.columns); return; From 4e79dc98e02f039bfa826dbaf4c70a5370d9eb07 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 18:22:13 +0530 Subject: [PATCH 065/572] fix: show chart if hidden --- frappe/public/js/frappe/views/reports/report_view.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 9399acf39e..f794248ad9 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -182,6 +182,7 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { if (this.group_by) { this.$charts_wrapper.addClass('hidden'); } else if (this.chart) { + this.$charts_wrapper.removeClass('hidden'); this.refresh_charts(); } From a6a3919ac61cd9217abd32155d5e95e3637c3767 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 19:27:01 +0530 Subject: [PATCH 066/572] fix: add button to route to reference doc form --- frappe/desk/doctype/notification_log/notification_log.js | 6 ++---- .../desk/doctype/notification_log/notification_log.json | 9 ++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.js b/frappe/desk/doctype/notification_log/notification_log.js index 654b2b2b06..2bb48ccdc1 100644 --- a/frappe/desk/doctype/notification_log/notification_log.js +++ b/frappe/desk/doctype/notification_log/notification_log.js @@ -2,11 +2,9 @@ // For license information, please see license.txt frappe.ui.form.on('Notification Log', { - refresh: function(frm) { + open_reference_document: function(frm) { let dt = frm.doc.document_type; let dn = frm.doc.document_name; - frm.fields_dict.document_name.$input_wrapper - .find('.control-value') - .wrapInner(``); + frappe.set_route('Form', dt, dn); } }); diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index e8225956b8..e676bfc1f9 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -12,6 +12,7 @@ "document_type", "read", "document_name", + "open_reference_document", "from_user" ], "fields": [ @@ -59,6 +60,7 @@ { "fieldname": "document_name", "fieldtype": "Data", + "hidden": 1, "label": "Document Link", "read_only": 1, "search_index": 1 @@ -79,11 +81,16 @@ "hidden": 1, "ignore_user_permissions": 1, "label": "Read" + }, + { + "fieldname": "open_reference_document", + "fieldtype": "Button", + "label": "Open Reference Document" } ], "in_create": 1, "links": [], - "modified": "2020-05-11 15:18:20.893718", + "modified": "2020-05-11 19:19:58.578245", "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", From 2a68af05f5bf8e4bfa30a8f714a226fb4b089d69 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 19:36:43 +0530 Subject: [PATCH 067/572] fix: option to disable channel if show in dropdown is enabled --- frappe/email/doctype/notification/notification.json | 13 +++++++++++-- frappe/email/doctype/notification/notification.py | 6 ++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index d83305ec5b..736056056f 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -25,6 +25,7 @@ "sender", "sender_email", "show_in_notifications_dropdown", + "disable_channel", "section_break_9", "condition", "column_break_6", @@ -265,15 +266,23 @@ }, { "default": "0", - "description": "If enabled, the notification will also show up in the notifications dropdown on the top right corner of the navigation bar.", + "description": "If enabled, the notification will show up in the notifications dropdown on the top right corner of the navigation bar.", "fieldname": "show_in_notifications_dropdown", "fieldtype": "Check", "label": "Show in Notifications Dropdown" + }, + { + "default": "0", + "depends_on": "eval: doc.show_in_notifications_dropdown", + "description": "If enabled, the channel set for the Notification will be disabled. The Notification will only be received in the Notifications Dropdown.", + "fieldname": "disable_channel", + "fieldtype": "Check", + "label": "Disable Channel" } ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-05-06 13:46:21.155535", + "modified": "2020-05-11 15:33:28.904019", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 6d74e499d9..8ed0bae067 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -120,10 +120,12 @@ def get_context(context): if self.is_standard: self.load_standard_properties(context) - if self.channel == 'Email': + channel_disabled = self.disable_channel + + if self.channel == 'Email' and not channel_disabled: self.send_an_email(doc, context) - if self.channel == 'Slack': + if self.channel == 'Slack' and not channel_disabled: self.send_a_slack_msg(doc, context) if self.show_in_notifications_dropdown: From 38b51dbf4b4bb2e65ac31a14322c5d2c36d3b892 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 12:44:33 +0530 Subject: [PATCH 068/572] fix: resetting fields --- frappe/public/js/frappe/list/list_settings.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index a2eaaa6689..9487269efa 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -247,10 +247,12 @@ export default class ListSettings { break; } else if (value != me.subject_field.fieldname) { let field = frappe.meta.get_docfield(me.doctype, value); - me.fields.push({ - label: field.label, - fieldname: field.fieldname - }) + if (field) { + me.fields.push({ + label: field.label, + fieldname: field.fieldname + }) + } } } From 77fdb55d106c00b2742c352b9b814d6fa0e5d5ce Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 12:53:59 +0530 Subject: [PATCH 069/572] fix: sider --- .../list_view_settings/list_view_settings.py | 7 ++---- frappe/public/js/frappe/list/list_settings.js | 24 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index db3be47402..74e029f499 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -5,9 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from six import string_types -from frappe.exceptions import DoesNotExistError -import json class ListViewSettings(Document): @@ -71,9 +68,9 @@ def set_in_list_view_property(doctype, field, value): def get_default_listview_fields(doctype): meta = frappe.get_meta(doctype) path = frappe.get_module_path(frappe.scrub(meta.module), "doctype", frappe.scrub(meta.name), frappe.scrub(meta.name) + ".json") - json = frappe.get_file_json(path) + doctype_json = frappe.get_file_json(path) - fields = [f.get("fieldname") for f in json.get("fields") if f.get("in_list_view")] + fields = [f.get("fieldname") for f in doctype_json.get("fields") if f.get("in_list_view")] if meta.title_field: if not meta.title_field.strip() in fields: diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index fa6736a1cd..6cebe421d7 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -155,7 +155,7 @@ export default class ListSettings { } add_new_fields() { - let me = this + let me = this; let fields_html = me.dialog.get_field("fields_html"); let add_new_fields = fields_html.$wrapper[0].getElementsByClassName("add-new-fields")[0]; @@ -301,9 +301,9 @@ export default class ListSettings { me.fields.push({ label: field.label, fieldname: field.fieldname - }) + }); } - }) + }); } set_subject_field(meta) { @@ -312,15 +312,15 @@ export default class ListSettings { me.subject_field = { label: "Name", fieldname: "name" - } + }; if (meta.title_field) { - let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()) + let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()); me.subject_field = { label: field.label, fieldname: field.fieldname - } + }; } me.fields.push(me.subject_field); @@ -334,13 +334,13 @@ export default class ListSettings { type: "Status", label: "Status", fieldname: "status_field" - }) + }); } } get_doctype_fields(meta, fields) { let me = this; - let multiselect_fields = [] + let multiselect_fields = []; meta.fields.forEach(field => { if (!in_list(frappe.model.no_value_type, field.fieldtype)) { @@ -348,16 +348,16 @@ export default class ListSettings { label: field.label, value: field.fieldname, checked: in_list(fields, field.fieldname) - }) + }); } - }) + }); - return multiselect_fields + return multiselect_fields; } get_removed_listview_fields(new_fields, existing_fields) { let me = this; - let removed_fields = [] + let removed_fields = []; if (frappe.has_indicator(me.doctype)) { new_fields.push("status_field"); From 00f7194069369977cce66d1b43a4d115cc9486fa Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 16:28:37 +0530 Subject: [PATCH 070/572] fix: link attachment to doc --- .../notification_log/notification_log.js | 39 ++++++++++++++++++- .../notification_log/notification_log.json | 18 ++++++++- .../notification_log/notification_log.py | 23 ++++------- .../doctype/notification/notification.py | 2 +- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.js b/frappe/desk/doctype/notification_log/notification_log.js index 2bb48ccdc1..1f381d115b 100644 --- a/frappe/desk/doctype/notification_log/notification_log.js +++ b/frappe/desk/doctype/notification_log/notification_log.js @@ -2,9 +2,44 @@ // For license information, please see license.txt frappe.ui.form.on('Notification Log', { + refresh: function(frm) { + if (frm.doc.attached_file) { + frm.trigger('set_attachment'); + } else { + frm.get_field('attachment_link').$wrapper.empty(); + } + }, + open_reference_document: function(frm) { - let dt = frm.doc.document_type; - let dn = frm.doc.document_name; + const dt = frm.doc.document_type; + const dn = frm.doc.document_name; frappe.set_route('Form', dt, dn); + }, + + set_attachment: function(frm) { + const attachment = JSON.parse(frm.doc.attached_file); + + const $wrapper = frm.get_field('attachment_link').$wrapper; + $wrapper.html(` + + `); + + $wrapper.find(".attached-file-link").click(() => { + const w = window.open( + frappe.urllib.get_full_url(`/api/method/frappe.utils.print_format.download_pdf? + doctype=${encodeURIComponent(attachment.doctype)} + &name=${encodeURIComponent(attachment.name)} + &format=${encodeURIComponent(attachment.print_format)} + &lang=${encodeURIComponent(attachment.lang)}`) + ); + if (!w) { + frappe.msgprint(__("Please enable pop-ups")); + } + }); } }); diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index e676bfc1f9..35869f8c2e 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -12,6 +12,8 @@ "document_type", "read", "document_name", + "attached_file", + "attachment_link", "open_reference_document", "from_user" ], @@ -86,12 +88,24 @@ "fieldname": "open_reference_document", "fieldtype": "Button", "label": "Open Reference Document" + }, + { + "fieldname": "attached_file", + "fieldtype": "Code", + "hidden": 1, + "label": "Attached File", + "options": "JSON" + }, + { + "fieldname": "attachment_link", + "fieldtype": "HTML", + "label": "Attachment Link" } ], "in_create": 1, "links": [], - "modified": "2020-05-11 19:19:58.578245", - "modified_by": "umair@erpnext.com", + "modified": "2020-05-12 15:56:46.278677", + "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 2db552e7de..244d0e150c 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +import json from frappe.model.document import Document from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value) @@ -58,6 +59,11 @@ def enqueue_create_notification(users, doc): def make_notification_logs(doc, users): from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled + + attachment = None + if doc.attachment: + attachment = doc.pop('attachment') + for user in users: if frappe.db.exists('User', user): if is_notifications_enabled(user): @@ -65,26 +71,13 @@ def make_notification_logs(doc, users): return _doc = frappe.new_doc('Notification Log') - - attachments = None - if doc.attachments: - attachments = doc.pop('attachments') - _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': + if attachment: + _doc.attached_file = json.dumps(attachment) _doc.insert(ignore_permissions=True) - if attachments: - attach_file_to_doc(attachments, _doc.name) - - -def attach_file_to_doc(attachments, docname): - from frappe.utils.file_manager import save_file - for attachment in attachments: - attachment.pop("print_format_attachment", None) - print_format_file = frappe._dict(frappe.attach_print(**attachment)) - save_file(print_format_file.fname, print_format_file.fcontent, 'Notification Log', docname) def send_notification_email(doc): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 8ed0bae067..3940625e1d 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -164,7 +164,7 @@ def get_context(context): 'document_name': doc.name, 'subject': subject, 'email_content': frappe.render_template(self.message, context), - 'attachments': attachments + 'attachment': attachments[0] } enqueue_create_notification(recipients, notification_doc) From c60a3ebe6a6eb679857a05806d0d138662d1fa29 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 16:32:49 +0530 Subject: [PATCH 071/572] fix: send notification to all users --- frappe/desk/doctype/notification_log/notification_log.py | 1 + frappe/email/doctype/notification/notification.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 244d0e150c..90ae5a437e 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -49,6 +49,7 @@ def enqueue_create_notification(users, doc): if isinstance(users, frappe.string_types): users = [user.strip() for user in users.split(',') if user.strip()] + users = list(set(users)) frappe.enqueue( 'frappe.desk.doctype.notification_log.notification_log.make_notification_logs', diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 3940625e1d..38077154cd 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -166,7 +166,7 @@ def get_context(context): 'email_content': frappe.render_template(self.message, context), 'attachment': attachments[0] } - enqueue_create_notification(recipients, notification_doc) + enqueue_create_notification(users, notification_doc) def send_an_email(self, doc, context): from email.utils import formataddr From 8761ce978667cdb542719cc24d33c818cb3afcf1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 16:48:05 +0530 Subject: [PATCH 072/572] fix: enable create contact by default --- frappe/core/doctype/communication/communication.py | 1 + frappe/email/doctype/email_account/email_account.json | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index aecf35fcdf..295d2464c8 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -261,6 +261,7 @@ class Communication(Document): 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]) + for contact_name in contacts: self.add_link('Contact', contact_name) diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index d664e0f9fb..37ecf4a4e6 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -411,7 +411,7 @@ "label": "Use SSL for Outgoing" }, { - "default": "0", + "default": "1", "fieldname": "create_contact", "fieldtype": "Check", "label": "Create Contacts from Incoming Emails" From 8a74b4051de54cab4779a1bd5715c1572977c7aa Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 12 May 2020 16:58:11 +0530 Subject: [PATCH 073/572] Update email_account.json --- frappe/email/doctype/email_account/email_account.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 37ecf4a4e6..057638697a 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -419,7 +419,7 @@ ], "icon": "fa fa-inbox", "links": [], - "modified": "2020-05-07 15:18:43.931499", + "modified": "2020-05-11 15:18:43.931499", "modified_by": "Administrator", "module": "Email", "name": "Email Account", @@ -441,4 +441,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} \ No newline at end of file +} From 080301925169d60fc9e02fbd1812d6d7d6e575fa Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 18:25:21 +0530 Subject: [PATCH 074/572] style: fix formatting --- frappe/public/js/frappe/list/list_settings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index 6cebe421d7..f2045c9c34 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -251,7 +251,7 @@ export default class ListSettings { me.fields.push({ label: field.label, fieldname: field.fieldname - }) + }); } } } @@ -339,7 +339,6 @@ export default class ListSettings { } get_doctype_fields(meta, fields) { - let me = this; let multiselect_fields = []; meta.fields.forEach(field => { From b05d0705d11e68bdde30fe93fd925580b64d7878 Mon Sep 17 00:00:00 2001 From: Neha Sacher Date: Mon, 11 May 2020 18:25:59 +0530 Subject: [PATCH 075/572] fix: fixed the reports print format --- frappe/templates/styles/standard.css | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/templates/styles/standard.css b/frappe/templates/styles/standard.css index f5eeb1c7fb..b87aa46d23 100644 --- a/frappe/templates/styles/standard.css +++ b/frappe/templates/styles/standard.css @@ -145,6 +145,11 @@ table.no-border, table.no-border td { margin: 3px 0px 3px; } +.print-format table td pre { + white-space: normal; + word-break: normal; +} + table td div { {% if not print_settings.allow_page_break_inside_tables %} /* needed to avoid partial cutting of text between page break in wkhtmltopdf */ From 68e5a1d3e6575460122834786901dabe640846d2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 18:29:07 +0530 Subject: [PATCH 076/572] fix: remove permission for all in list view settings --- .../list_view_settings/list_view_settings.json | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 486d2c1c44..44761992f1 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -52,7 +52,7 @@ } ], "links": [], - "modified": "2020-05-05 15:15:14.306665", + "modified": "2020-05-12 18:27:15.568199", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", @@ -67,18 +67,6 @@ "role": "System Manager", "share": 1, "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "share": 1, - "write": 1 } ], "read_only": 1, From 77b59a14937ce79b778400e6d26feba8079e10c6 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 19:21:29 +0530 Subject: [PATCH 077/572] fix: hide channel field if disabled --- frappe/email/doctype/notification/notification.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 736056056f..9e52a8b7a8 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -57,6 +57,7 @@ }, { "default": "Email", + "depends_on": "eval: !doc.disable_channel", "fieldname": "channel", "fieldtype": "Select", "label": "Channel", @@ -282,7 +283,7 @@ ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-05-11 15:33:28.904019", + "modified": "2020-05-12 19:20:50.304928", "modified_by": "Administrator", "module": "Email", "name": "Notification", From 39ecc61b61d75bec0bfadf655de08e6a6c9dae69 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 12:34:34 +0530 Subject: [PATCH 078/572] feat: added tour for web page --- frappe/website/doctype/web_page/web_page.js | 55 +++++++++++++++++++++ 1 file changed, 55 insertions(+) diff --git a/frappe/website/doctype/web_page/web_page.js b/frappe/website/doctype/web_page/web_page.js index c0a3bcdc20..c714b98241 100644 --- a/frappe/website/doctype/web_page/web_page.js +++ b/frappe/website/doctype/web_page/web_page.js @@ -48,3 +48,58 @@ frappe.ui.form.on('Web Page', { frappe.utils.set_meta_tag(frm.doc.route); } }); + +frappe.tour['Web Page'] = [ + { + fieldname: "title", + title: "Title of the page", + description: "This title will be used as the title of the webpage as well as in meta tags", + }, + { + fieldname: "published", + title: "Makes the page public", + description: "Checking this will publish the page on your website and it'll be visible to everyone.", + }, + { + fieldname: "route", + title: "URL of the page", + description: "This will be automatically generated when you publish the page, you can also enter a route yourself if you wish", + }, + { + fieldname: "content_type", + title: "Content type for building the page", + description: `You can select one from the following,
+
    +
  • Rich Text: Standard rich text editor with controls
  • +
  • Markdown: Github flavoured markdown syntax
  • +
  • HTML: HTML with jinja support
  • +
  • Page Builder: Frappe page builder using components
  • +
+ ` + }, + { + fieldname: "insert_code", + title: "Client Script", + description: "Checking this will show a text area where you can write custom javascript that will run on this page.", + }, + { + fieldname: "meta_title", + title: "Meta title for SEO", + description: "By default the title is used as meta title, adding a value here will override it.", + }, + { + fieldname: "meta_title", + title: "Meta Title", + description: "By default the title is used as meta title, adding a value here will override it.", + }, + { + fieldname: "meta_description", + title: "Meta Description", + description: "The meta description is an HTML attribute that provides a brief summary of a web page. Search engines such as Google often display the meta description in search results, which can influence click-through rates." + }, + { + fieldname: "meta_image", + title: "Meta Image", + description: "The meta image is unique image representing the content of the page. Images for this Card should be at least 280px in width, and at least 150px in height." + }, +]; \ No newline at end of file From aa76a03d266d085080dcfdc389badaea4f69ef07 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 12:34:55 +0530 Subject: [PATCH 079/572] feat: added web page tour to onboarding --- .../module_onboarding/website/website.json | 5 ++++- .../add_blog_category/add_blog_category.json | 1 + .../create_blogger/create_blogger.json | 1 + .../enable_website_tracking.json | 1 + .../introduction_to_website.json | 1 + .../web_page_tour/web_page_tour.json | 17 +++++++++++++++++ 6 files changed, 25 insertions(+), 1 deletion(-) create mode 100644 frappe/website/onboarding_step/web_page_tour/web_page_tour.json diff --git a/frappe/website/module_onboarding/website/website.json b/frappe/website/module_onboarding/website/website.json index b849a809ed..606ef09811 100644 --- a/frappe/website/module_onboarding/website/website.json +++ b/frappe/website/module_onboarding/website/website.json @@ -10,7 +10,7 @@ "documentation_url": "https://docs.erpnext.com/docs/user/manual/en/website", "idx": 0, "is_complete": 0, - "modified": "2020-04-30 20:23:06.438314", + "modified": "2020-05-13 12:32:35.269025", "modified_by": "Administrator", "module": "Website", "name": "Website", @@ -27,6 +27,9 @@ }, { "step": "Enable Website Tracking" + }, + { + "step": "Web Page Tour" } ], "subtitle": "Blogs, website view tracking, and more.", diff --git a/frappe/website/onboarding_step/add_blog_category/add_blog_category.json b/frappe/website/onboarding_step/add_blog_category/add_blog_category.json index a0d07c8464..5936e78c68 100644 --- a/frappe/website/onboarding_step/add_blog_category/add_blog_category.json +++ b/frappe/website/onboarding_step/add_blog_category/add_blog_category.json @@ -6,6 +6,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 19:06:10.750976", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/create_blogger/create_blogger.json b/frappe/website/onboarding_step/create_blogger/create_blogger.json index 5162e7e895..841325ca6a 100644 --- a/frappe/website/onboarding_step/create_blogger/create_blogger.json +++ b/frappe/website/onboarding_step/create_blogger/create_blogger.json @@ -6,6 +6,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 19:06:10.694419", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json b/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json index 56a4fa58b6..b37a704b1e 100644 --- a/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json +++ b/frappe/website/onboarding_step/enable_website_tracking/enable_website_tracking.json @@ -7,6 +7,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 0, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 20:22:50.778590", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json b/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json index 683d0a889e..f3c95657e8 100644 --- a/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json +++ b/frappe/website/onboarding_step/introduction_to_website/introduction_to_website.json @@ -6,6 +6,7 @@ "idx": 0, "is_complete": 0, "is_mandatory": 1, + "is_single": 0, "is_skipped": 0, "modified": "2020-04-30 19:06:10.578218", "modified_by": "Administrator", diff --git a/frappe/website/onboarding_step/web_page_tour/web_page_tour.json b/frappe/website/onboarding_step/web_page_tour/web_page_tour.json new file mode 100644 index 0000000000..1ee98d69ee --- /dev/null +++ b/frappe/website/onboarding_step/web_page_tour/web_page_tour.json @@ -0,0 +1,17 @@ +{ + "action": "Show Form Tour", + "creation": "2020-05-13 12:32:15.966570", + "docstatus": 0, + "doctype": "Onboarding Step", + "idx": 0, + "is_complete": 0, + "is_mandatory": 0, + "is_single": 0, + "is_skipped": 0, + "modified": "2020-05-13 12:32:15.966570", + "modified_by": "Administrator", + "name": "Web Page Tour", + "owner": "Administrator", + "reference_document": "Web Page", + "title": "Learn about Web Pages" +} \ No newline at end of file From 8d79230be2a4e570b311ee1808a9b6c4518d98a7 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 13:33:45 +0530 Subject: [PATCH 080/572] fix: code clean-up --- frappe/public/build.json | 4 +-- .../js/frappe/form/controls/duration.js | 34 +++++++------------ 2 files changed, 13 insertions(+), 25 deletions(-) diff --git a/frappe/public/build.json b/frappe/public/build.json index 86f3d23c99..d56907b558 100755 --- a/frappe/public/build.json +++ b/frappe/public/build.json @@ -72,8 +72,7 @@ "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/duration.js" + "public/js/frappe/form/controls/control.js" ], "js/dialog.min.js": [ "public/js/frappe/dom.js", @@ -264,7 +263,6 @@ "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/duration.js b/frappe/public/js/frappe/form/controls/duration.js index c764c9b041..01e18de731 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -14,9 +14,9 @@ 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("hours", false); + this.build_numeric_input("minutes", false); + this.build_numeric_input("seconds", !this.duration_options.showSeconds); this.set_duration_picker(); this.$picker.hide(); this.bind_events(); @@ -38,7 +38,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let $control = $(`
-
${label}
+
${__(label)}
` ) @@ -57,29 +57,19 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let total_duration = frappe.utils.seconds_to_duration(this.value, this.duration_options); 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); - } + Object.keys(total_duration).forEach(duration => { + this.inputs[duration].prop("value", total_duration[duration]); + }); } }, 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.set_value(this.duration_to_seconds()); + this.set_focus(); }); this.$wrapper.find(".duration-input").mousedown(() => { @@ -117,14 +107,14 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let value = 0; if (this.inputs) { let total_duration = { - minutes : parseInt(this.inputs.mins.val()), - hours : parseInt(this.inputs.hrs.val()), + minutes : parseInt(this.inputs.minutes.val()), + hours : parseInt(this.inputs.hours.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()); + total_duration.seconds = parseInt(this.inputs.seconds.val()); } if (total_duration.days) { From 776da4cc907e9b3a332d82ea7d1cda2898b2f02f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 14:00:10 +0530 Subject: [PATCH 081/572] feat: add duration field in web form --- frappe/website/doctype/web_form_field/web_form_field.json | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/web_form_field/web_form_field.json b/frappe/website/doctype/web_form_field/web_form_field.json index 90f9b24a16..ef7b1c4ddf 100644 --- a/frappe/website/doctype/web_form_field/web_form_field.json +++ b/frappe/website/doctype/web_form_field/web_form_field.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2014-09-01 14:14:14.292173", "doctype": "DocType", "editable_grid": 1, @@ -34,7 +35,7 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Fieldtype", - "options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break" + "options": "Attach\nAttach Image\nCheck\nCurrency\nData\nDate\nDatetime\nDuration\nFloat\nHTML\nInt\nLink\nRating\nSelect\nSmall Text\nText\nText Editor\nTable\nSection Break\nColumn Break" }, { "fieldname": "label", @@ -119,7 +120,8 @@ } ], "istable": 1, - "modified": "2019-06-07 12:17:10.547133", + "links": [], + "modified": "2020-05-13 13:35:08.454427", "modified_by": "Administrator", "module": "Website", "name": "Web Form Field", From 3174d16ab33f0965e91f55cc38944b9f8ce6d6b2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 18:52:05 +0530 Subject: [PATCH 082/572] fix: sider issues, add comments --- .../js/frappe/form/controls/duration.js | 23 +++++++++++-------- frappe/public/js/frappe/form/formatters.js | 2 +- frappe/public/js/frappe/model/meta.js | 2 +- frappe/public/js/frappe/utils/utils.js | 6 ++--- frappe/public/less/controls.less | 3 +-- 5 files changed, 20 insertions(+), 16 deletions(-) diff --git a/frappe/public/js/frappe/form/controls/duration.js b/frappe/public/js/frappe/form/controls/duration.js index 01e18de731..d6d512da9c 100644 --- a/frappe/public/js/frappe/form/controls/duration.js +++ b/frappe/public/js/frappe/form/controls/duration.js @@ -26,7 +26,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); @@ -40,7 +40,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({
${__(label)}
` - ) + ); if (hidden) { $control.addClass("hidden"); @@ -50,7 +50,7 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, set_duration_options() { - this.duration_options = frappe.meta.get_duration_options(this.df, this.get_doc()); + this.duration_options = frappe.meta.get_duration_options(this.df); }, set_duration_picker() { @@ -64,26 +64,31 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ }, bind_events: function() { + // flag to handle the display property of the picker let clicked = false; + this.$wrapper.find(".duration-input").mousedown(() => { + // input in individual duration boxes + clicked = true; + }); + this.$picker.on("change", ".duration-input", () => { + // duration changed in individual boxes clicked = false; this.set_value(this.duration_to_seconds()); this.set_focus(); }); - this.$wrapper.find(".duration-input").mousedown(() => { - clicked = true; - }); - this.$input.on("focus", () => { this.$picker.show(); }); this.$input.on("blur", () => { + // input in duration boxes, don't close the picker if (clicked) { clicked = false; } else { + // blur event was not due to duration inputs this.$picker.hide(); } }); @@ -107,8 +112,8 @@ frappe.ui.form.ControlDuration = frappe.ui.form.ControlData.extend({ let value = 0; if (this.inputs) { let total_duration = { - minutes : parseInt(this.inputs.minutes.val()), - hours : parseInt(this.inputs.hours.val()), + minutes: parseInt(this.inputs.minutes.val()), + hours: parseInt(this.inputs.hours.val()), }; if (this.duration_options.showDays) { total_duration.days = parseInt(this.inputs.days.val()); diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index a9abd1c601..23c0ad5d5d 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -190,7 +190,7 @@ frappe.form.formatters = { }, Duration: function(value, docfield, doc) { if (value) { - let duration_options = frappe.meta.get_duration_options(docfield, doc); + let duration_options = frappe.meta.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); } diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index acde2a367f..c6b5ca0b4a 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -268,7 +268,7 @@ $.extend(frappe.meta, { return precision; }, - get_duration_options: function(df, doc) { + get_duration_options: function(df) { let duration_options = { showDays: df.show_days, showSeconds: df.show_seconds diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 3ddfa0f4dd..68628276a8 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -833,11 +833,11 @@ Object.assign(frappe.utils, { days: Math.floor(secs / (3600 * 24)), hours: Math.floor(secs % (3600 * 24) / 3600), minutes: Math.floor(secs % 3600 / 60), - seconds : Math.floor(secs % 60) + seconds: Math.floor(secs % 60) }; if (!duration_options.showDays) { - total_duration.hours = Math.floor(secs / 3600) - total_duration.days = 0 + 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 33d8594535..2b03b93f56 100644 --- a/frappe/public/less/controls.less +++ b/frappe/public/less/controls.less @@ -169,7 +169,7 @@ /* duration control */ .duration-picker { - position: relative; + position: absolute; z-index: 999; border-radius: 4px; @@ -178,7 +178,6 @@ border: 1px solid @border-color; padding-top: 10px; padding-left: 5px; - position: absolute; &:after, &:before { From 8933e71d906c6e16daa002ab478d123afd53842f Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 18:52:34 +0530 Subject: [PATCH 083/572] test(ui): duration control --- cypress/integration/control_duration.js | 45 +++++++++++++++++++++++++ 1 file changed, 45 insertions(+) create mode 100644 cypress/integration/control_duration.js diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js new file mode 100644 index 0000000000..20634acb80 --- /dev/null +++ b/cypress/integration/control_duration.js @@ -0,0 +1,45 @@ +context('Control Duration', () => { + before(() => { + cy.login(); + cy.visit('/desk#workspace/Website'); + }); + + function get_dialog_with_duration(show_days=1, show_seconds=1) { + return cy.dialog({ + title: 'Duration', + fields: [{ + 'fieldname': 'duration', + 'fieldtype': 'Duration', + 'show_seconds': show_days, + 'show_days': show_seconds + }] + }); + } + + it('should set duration', () => { + get_dialog_with_duration().as('dialog') + cy.get('.frappe-control[data-fieldname=duration] input') + .first() + .click(); + cy.get('.duration-input[data-duration=days]') + .type(45) + .blur(); + cy.get('.duration-input[data-duration=minutes]') + .type(30) + .blur(); + cy.get('.frappe-control[data-fieldname=duration] input').should('have.value', '45d 30m'); + cy.get('.frappe-control[data-fieldname=duration] input').first().blur(); + cy.get('.duration-picker').should('not.be.visible'); + cy.get('@dialog').then(dialog => { + let value = dialog.get_value('duration'); + expect(value).to.equal(3889800); + }); + }); + + it('should hide days or seconds according to duration options', () => { + get_dialog_with_duration(0, 0).as('dialog'); + cy.get('.frappe-control[data-fieldname=duration] input').first().click(); + cy.get('.duration-input[data-duration=days]').should('not.be.visible'); + cy.get('.duration-input[data-duration=seconds]').should('not.be.visible'); + }); +}); \ No newline at end of file From 5a47f2da7fa2417f2c1624c7b6933d6dedce31e2 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 13 May 2020 19:17:51 +0530 Subject: [PATCH 084/572] fix: sider and translations issues --- cypress/integration/control_duration.js | 2 +- frappe/public/js/frappe/form/formatters.js | 7 ++----- frappe/public/js/frappe/model/meta.js | 3 +-- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 20634acb80..8ff6b8668d 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -17,7 +17,7 @@ context('Control Duration', () => { } it('should set duration', () => { - get_dialog_with_duration().as('dialog') + get_dialog_with_duration().as('dialog'); cy.get('.frappe-control[data-fieldname=duration] input') .first() .click(); diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 23c0ad5d5d..37c4db7428 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -142,10 +142,7 @@ frappe.form.formatters = { }, DateRange: function(value) { if($.isArray(value)) { - return __("{0} to {1}", [ - frappe.datetime.str_to_user(value[0]), - frappe.datetime.str_to_user(value[1]) - ]); + return __("{0} to {1}", [frappe.datetime.str_to_user(value[0]), frappe.datetime.str_to_user(value[1])]); } else { return value || ""; } @@ -188,7 +185,7 @@ frappe.form.formatters = { return value || ""; }, - Duration: function(value, docfield, doc) { + Duration: function(value, docfield) { if (value) { let duration_options = frappe.meta.get_duration_options(docfield); value = frappe.utils.get_formatted_duration(value, duration_options); diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index c6b5ca0b4a..f71689e409 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -161,8 +161,7 @@ $.extend(frappe.meta, { if(!out) { // eslint-disable-next-line - console.log(__('Warning: Unable to find {0} in any table related to {1}', [ - key, __(doctype)])); + console.log(__('Warning: Unable to find {0} in any table related to {1}', [key, __(doctype)])); } } return out; From 218e945b2614b0b2a72d59bfaf74ca2862a4794f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:56:00 +0530 Subject: [PATCH 085/572] feat: add get_last_day_of_week function --- frappe/utils/data.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 58c74a905d..906b07fd08 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -190,6 +190,10 @@ def get_first_day(dt, d_years=0, d_months=0): def get_first_day_of_week(dt): return dt - datetime.timedelta(days=dt.weekday()) +def get_last_day_of_week(dt): + dt = get_first_day_of_week(dt) + return dt + datetime.timedelta(days=6) + def get_last_day(dt): """ Returns last day of the month using: From 8de7f7602e7aa6ba8ac10b60b5a83886fd77ab93 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:56:16 +0530 Subject: [PATCH 086/572] refactor: replace pandas date range with native function --- frappe/utils/dateutils.py | 32 ++++++++++++++++++++++++++++---- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/frappe/utils/dateutils.py b/frappe/utils/dateutils.py index 907cab3d02..b98c1c12fc 100644 --- a/frappe/utils/dateutils.py +++ b/frappe/utils/dateutils.py @@ -6,6 +6,9 @@ import frappe import frappe.defaults import datetime from frappe.utils import get_datetime +from frappe.utils import nowdate, add_to_date, getdate, get_datetime +from frappe.utils.data import get_last_day_of_week +from frappe.desk.doctype.dashboard_chart.dashboard_chart import get_period_ending from six import string_types # global values -- used for caching @@ -74,8 +77,29 @@ def datetime_in_user_format(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() +def get_dates_from_timegrain(from_date, to_date, timegrain="Daily"): + from_date = getdate(from_date) + to_date = getdate(to_date) - return date_range \ No newline at end of file + days = months = years = 0 + if "Daily" == timegrain: + days = 1 + elif "Weekly" == timegrain: + days = 7 + elif "Monthly" == timegrain: + months = 1 + elif "Quarterly" == timegrain: + months = 3 + + if "Weekly" == timegrain: + dates = [get_last_day_of_week(from_date)] + else: + dates = [get_period_ending(from_date, timegrain)] + + while getdate(dates[-1]) < getdate(to_date): + if "Weekly" == timegrain: + date = get_last_day_of_week(add_to_date(dates[-1], years=years, months=months, days=days)) + else: + date = get_period_ending(add_to_date(dates[-1], years=years, months=months, days=days), timegrain) + dates.append(date) + return dates \ No newline at end of file From 0eaf778fe24911970f9e1b02e0e743fc63082103 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:56:33 +0530 Subject: [PATCH 087/572] refactor: change values for report range options --- .../report/website_analytics/website_analytics.js | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.js b/frappe/website/report/website_analytics/website_analytics.js index 7e051afa8c..9079949724 100644 --- a/frappe/website/report/website_analytics/website_analytics.js +++ b/frappe/website/report/website_analytics/website_analytics.js @@ -8,7 +8,7 @@ frappe.query_reports["Website Analytics"] = { fieldname: "from_date", label: __("From Date"), fieldtype: "Date", - default: frappe.datetime.add_days(frappe.datetime.now_date(true), -7), + default: frappe.datetime.add_days(frappe.datetime.now_date(true), -100), }, { fieldname:"to_date", @@ -21,11 +21,11 @@ frappe.query_reports["Website Analytics"] = { label: __("Range"), fieldtype: "Select", options: [ - { "value": "D", "label": __("Daily") }, - { "value": "W", "label": __("Weekly") }, - { "value": "M", "label": __("Monthly") }, + { "value": "Daily", "label": __("Daily") }, + { "value": "Weekly", "label": __("Weekly") }, + { "value": "Monthly", "label": __("Monthly") }, ], - default: "D", + default: "Daily", reqd: 1 } ] From 0bb10609989a71bcde291cb7cfaec43b428cb978 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:57:30 +0530 Subject: [PATCH 088/572] feat: use new date range function --- .../website_analytics/website_analytics.py | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/frappe/website/report/website_analytics/website_analytics.py b/frappe/website/report/website_analytics/website_analytics.py index 694bc9e797..97c330fed9 100644 --- a/frappe/website/report/website_analytics/website_analytics.py +++ b/frappe/website/report/website_analytics/website_analytics.py @@ -4,7 +4,8 @@ from __future__ import unicode_literals import frappe from datetime import datetime -from frappe.utils.dateutils import get_date_range +from frappe.utils import getdate +from frappe.utils.dateutils import get_dates_from_timegrain def execute(filters=None): return WebsiteAnalytics(filters).run() @@ -20,7 +21,7 @@ class WebsiteAnalytics(object): self.filters.from_date = frappe.utils.add_days(self.filters.to_date, -7) if not self.filters.range: - self.filters.range = "D" + self.filters.range = "Daily" 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]]} @@ -89,10 +90,10 @@ class WebsiteAnalytics(object): field = 'creation' date_format = '%Y-%m-%d' - if filters_range == "W": + if filters_range == "Weekly": field = 'ADDDATE(creation, INTERVAL 1-DAYOFWEEK(creation) DAY)' - elif filters_range == "M": + elif filters_range == "Monthly": date_format = '%Y-%m-01' query = """ @@ -115,10 +116,10 @@ class WebsiteAnalytics(object): field = 'creation' granularity = 'day' - if filters_range == "W": + if filters_range == "Weekly": granularity = 'week' - elif filters_range == "M": + elif filters_range == "Monthly": granularity = 'day' query = """ @@ -149,8 +150,8 @@ class WebsiteAnalytics(object): 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) - if self.filters.range == "M": + date_range = get_dates_from_timegrain(self.filters.from_date, self.filters.to_date, self.filters.range) + if self.filters.range == "Monthly": date_range = [frappe.utils.add_days(dd, 1) for dd in date_range] labels = [] @@ -159,8 +160,8 @@ class WebsiteAnalytics(object): 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(): + item_date = getdate(item.get("date")) + if item_date == date: return item return {'count': 0, 'unique_count': 0} From e2b5fcb6abe46d4b3da13263aca427d316d068ae Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Wed, 13 May 2020 19:58:33 +0530 Subject: [PATCH 089/572] chore: update fixtures --- frappe/website/dashboard_fixtures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/dashboard_fixtures.py b/frappe/website/dashboard_fixtures.py index 01f1376d43..1ac7ca60ec 100644 --- a/frappe/website/dashboard_fixtures.py +++ b/frappe/website/dashboard_fixtures.py @@ -20,7 +20,7 @@ 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\": {}}", + "custom_options": "{\"type\": \"line\", \"lineOptions\": {\"regionFill\": 1}, \"axisOptions\": {\"shortenYAxisNumbers\": 1}, \"tooltipOptions\": {}}", "doctype": "Dashboard Chart", "filters_json": "{}", "group_by_type": "Count", From e1fd1c0144a471029eae4834577b6d93855fd085 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 11:32:32 +0530 Subject: [PATCH 090/572] fix: test --- cypress/integration/control_duration.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 8ff6b8668d..195200e824 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -23,11 +23,11 @@ context('Control Duration', () => { .click(); cy.get('.duration-input[data-duration=days]') .type(45) - .blur(); + .blur({force: true}); cy.get('.duration-input[data-duration=minutes]') .type(30) - .blur(); - cy.get('.frappe-control[data-fieldname=duration] input').should('have.value', '45d 30m'); + .blur({force: true}); + cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m'); cy.get('.frappe-control[data-fieldname=duration] input').first().blur(); cy.get('.duration-picker').should('not.be.visible'); cy.get('@dialog').then(dialog => { From cfd7d6e09ffea9dbcfbfb30668fa896e408a3f35 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 12:21:21 +0530 Subject: [PATCH 091/572] fix: test --- cypress/integration/control_duration.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 195200e824..f304abd3d9 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -22,7 +22,7 @@ context('Control Duration', () => { .first() .click(); cy.get('.duration-input[data-duration=days]') - .type(45) + .type(45, {force: true}) .blur({force: true}); cy.get('.duration-input[data-duration=minutes]') .type(30) From 080a8b9ce0b51ea5adbd33a9244b48c16b39b163 Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Thu, 14 May 2020 23:48:17 +0530 Subject: [PATCH 092/572] fix: add duration field options in custom field and customize form field --- .../doctype/custom_field/custom_field.json | 22 ++++++++++++++++++- .../customize_form_field.json | 22 ++++++++++++++++++- 2 files changed, 42 insertions(+), 2 deletions(-) diff --git a/frappe/custom/doctype/custom_field/custom_field.json b/frappe/custom/doctype/custom_field/custom_field.json index 6534bfd90e..d220e448df 100644 --- a/frappe/custom/doctype/custom_field/custom_field.json +++ b/frappe/custom/doctype/custom_field/custom_field.json @@ -16,6 +16,8 @@ "column_break_6", "fieldtype", "precision", + "show_seconds", + "show_days", "options", "fetch_from", "fetch_if_empty", @@ -378,12 +380,30 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days", + "show_days": 1, + "show_seconds": 1 } ], "icon": "fa fa-glass", "idx": 1, "links": [], - "modified": "2020-04-30 09:15:24.394782", + "modified": "2020-05-14 23:43:00.123572", "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 d1510e0858..5876666485 100644 --- a/frappe/custom/doctype/customize_form_field/customize_form_field.json +++ b/frappe/custom/doctype/customize_form_field/customize_form_field.json @@ -11,6 +11,8 @@ "label", "fieldtype", "fieldname", + "show_seconds", + "show_days", "reqd", "unique", "in_list_view", @@ -388,12 +390,30 @@ "fieldname": "in_preview", "fieldtype": "Check", "label": "In Preview" + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_seconds", + "fieldtype": "Check", + "label": "Show Seconds", + "show_days": 1, + "show_seconds": 1 + }, + { + "default": "1", + "depends_on": "eval:doc.fieldtype === \"Duration\";", + "fieldname": "show_days", + "fieldtype": "Check", + "label": "Show Days", + "show_days": 1, + "show_seconds": 1 } ], "idx": 1, "istable": 1, "links": [], - "modified": "2020-04-30 09:15:51.094586", + "modified": "2020-05-14 23:45:46.810869", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form Field", From 2fb27a7ceb9f2aa575832b68c8562d84b3939b37 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 13:23:53 +0530 Subject: [PATCH 093/572] refactor: explicitly hide amend button in toolbar --- frappe/public/js/frappe/form/toolbar.js | 29 +++++++++++++++---------- 1 file changed, 17 insertions(+), 12 deletions(-) diff --git a/frappe/public/js/frappe/form/toolbar.js b/frappe/public/js/frappe/form/toolbar.js index 528c874935..6f475fa9e5 100644 --- a/frappe/public/js/frappe/form/toolbar.js +++ b/frappe/public/js/frappe/form/toolbar.js @@ -374,19 +374,24 @@ frappe.ui.form.Toolbar = Class.extend({ var status = this.get_action_status(); if (status) { - if (status !== this.current_status) { - if (status === 'Amend') { - let doc = this.frm.doc; - frappe.xcall('frappe.client.is_document_amended', { - 'doctype': doc.doctype, - 'docname': doc.name - }).then(is_amended => { - if (is_amended) return; - this.set_page_actions(status); - }); - } else { + // When moving from a page with status amend to another page with status amend + // We need to check if document is already amened specifcally and hide + // or clear the menu actions accordingly + + if (status !== this.current_status || status === 'Amend') { + let doc = this.frm.doc; + frappe.xcall('frappe.client.is_document_amended', { + 'doctype': doc.doctype, + 'docname': doc.name + }).then(is_amended => { + if (is_amended) { + this.page.clear_actions(); + return; + } this.set_page_actions(status); - } + }); + } else { + this.set_page_actions(status); } } else { this.page.clear_actions(); From bfaf2fcf7740de19c8568235e80c5eb3ac3cfbf4 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 13:24:06 +0530 Subject: [PATCH 094/572] feat: do not allow amending already amended docs --- frappe/public/js/frappe/form/form.js | 27 ++++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 01dfbf81f9..58a1be73c1 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -780,15 +780,24 @@ frappe.ui.form.Form = class FrappeForm { frappe.msgprint(__('"amended_from" field must be present to do an amendment.')); return; } - this.validate_form_action("Amend"); - var me = this; - var fn = function(newdoc) { - newdoc.amended_from = me.docname; - if(me.fields_dict && me.fields_dict['amendment_date']) - newdoc.amendment_date = frappe.datetime.obj_to_str(new Date()); - }; - this.copy_doc(fn, 1); - frappe.utils.play_sound("click"); + + frappe.xcall('frappe.client.is_document_amended', { + 'doctype': this.doc.doctype, + 'docname': this.doc.name + }).then(is_amended => { + if (is_amended) { + frappe.throw(__('This document is already amended, you cannot ammend it again')) + }; + this.validate_form_action("Amend"); + var me = this; + var fn = function(newdoc) { + newdoc.amended_from = me.docname; + if(me.fields_dict && me.fields_dict['amendment_date']) + newdoc.amendment_date = frappe.datetime.obj_to_str(new Date()); + }; + this.copy_doc(fn, 1); + frappe.utils.play_sound("click"); + }); } validate_form_action(action, resolve) { From 9d4a4ef6f4c1a92ec5f0918f4773cb605586cbc9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 14:03:04 +0530 Subject: [PATCH 095/572] feat: collapse sections by default for page builder dialog --- frappe/public/js/frappe/utils/web_page_block.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/web_page_block.js b/frappe/public/js/frappe/utils/web_page_block.js index bcf821bcfa..acc7943671 100644 --- a/frappe/public/js/frappe/utils/web_page_block.js +++ b/frappe/public/js/frappe/utils/web_page_block.js @@ -4,7 +4,12 @@ frappe.ui.form.on('Web Page Block', { frappe.model.with_doc('Web Template', row.web_template).then(doc => { let d = new frappe.ui.Dialog({ title: __('Edit Values'), - fields: doc.fields, + fields: doc.fields.map(df => { + if (df.fieldtype == "Section Break") { + df.collapsible = 1; + } + return df + }), primary_action(values) { frappe.model.set_value( cdt, From a7b431465d127360f995f45acd6bb605b58e111f Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 14:03:37 +0530 Subject: [PATCH 096/572] feat: allow 9 cards in card grid --- .../section_with_cards.html | 2 +- .../section_with_cards.json | 26 ++++++++++++++++++- 2 files changed, 26 insertions(+), 2 deletions(-) diff --git a/frappe/website/web_template/section_with_cards/section_with_cards.html b/frappe/website/web_template/section_with_cards/section_with_cards.html index 1f0f71a15b..431a64a86d 100644 --- a/frappe/website/web_template/section_with_cards/section_with_cards.html +++ b/frappe/website/web_template/section_with_cards/section_with_cards.html @@ -29,7 +29,7 @@ {%- set card_size = card_size or 'Small' -%}
- {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8'] -%} + {%- for index in ['1', '2', '3', '4', '5', '6', '7', '8', '9'] -%} {%- set title = values['card_' + index + '_title'] -%} {%- set content = values['card_' + index + '_content'] -%} {%- set url = values['card_' + index + '_url'] -%} diff --git a/frappe/website/web_template/section_with_cards/section_with_cards.json b/frappe/website/web_template/section_with_cards/section_with_cards.json index 9ec430ae60..311cced829 100644 --- a/frappe/website/web_template/section_with_cards/section_with_cards.json +++ b/frappe/website/web_template/section_with_cards/section_with_cards.json @@ -213,10 +213,34 @@ "fieldtype": "Data", "label": "URL", "reqd": 0 + }, + { + "fieldname": "card_9", + "fieldtype": "Section Break", + "label": "Card 9", + "reqd": 0 + }, + { + "fieldname": "card_9_title", + "fieldtype": "Data", + "label": "Title", + "reqd": 0 + }, + { + "fieldname": "card_9_content", + "fieldtype": "Small Text", + "label": "Content", + "reqd": 0 + }, + { + "fieldname": "card_9_url", + "fieldtype": "Data", + "label": "URL", + "reqd": 0 } ], "idx": 0, - "modified": "2020-04-29 22:40:03.362229", + "modified": "2020-05-15 13:52:02.984001", "modified_by": "Administrator", "name": "Section with Cards", "owner": "Administrator", From 7721d540167d30f76308a2bc557afb29f021b33a Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 15 May 2020 14:15:59 +0530 Subject: [PATCH 097/572] feat: add hide login to website settings --- frappe/templates/includes/navbar/navbar_login.html | 2 +- .../doctype/website_settings/website_settings.json | 9 ++++++++- .../website/doctype/website_settings/website_settings.py | 2 ++ 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/frappe/templates/includes/navbar/navbar_login.html b/frappe/templates/includes/navbar/navbar_login.html index 2a58efe039..4e2c6dc93b 100644 --- a/frappe/templates/includes/navbar/navbar_login.html +++ b/frappe/templates/includes/navbar/navbar_login.html @@ -1,5 +1,5 @@ -{% if not only_static %} +{% if not only_static and not hide_login %} {% if frappe.session.user != 'Guest' %}