From 97ae23c2a1f098fc38a408fac8273c3b4e4130c9 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Wed, 23 Oct 2019 15:42:24 +0530 Subject: [PATCH 001/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] 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/222] Delete list_view_columns.html --- .../doctype/list_view_settings/list_view_columns.html | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 frappe/desk/doctype/list_view_settings/list_view_columns.html diff --git a/frappe/desk/doctype/list_view_settings/list_view_columns.html b/frappe/desk/doctype/list_view_settings/list_view_columns.html deleted file mode 100644 index 50474c5169..0000000000 --- a/frappe/desk/doctype/list_view_settings/list_view_columns.html +++ /dev/null @@ -1,9 +0,0 @@ -
- {% for (var i=0, l=fields.length; i < l; i++) { var field = fields[i]; %} -
- - {%= field.label %} - -
- {% } %} -
From 7483bf79683430e54a9ce73390fce75dc5b04b7d Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Thu, 16 Apr 2020 20:20:13 +0530 Subject: [PATCH 015/222] feat: do not refresh when changing columns --- .../list_view_settings/list_view_settings.py | 11 ++-- frappe/public/js/frappe/list/list_settings.js | 51 +++++++++++++------ frappe/public/js/frappe/list/list_view.js | 34 ++++++++++++- 3 files changed, 77 insertions(+), 19 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index acd8f8a7b6..6cc7d11950 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -35,18 +35,23 @@ def save_listview_settings(doctype, listview_settings, removed_listview_fields): set_listview_fields(doctype, listview_settings.get("fields"), removed_listview_fields) + return { + "meta": frappe.get_meta(doctype, False), + "listview_settings": doc + } + def set_listview_fields(doctype, listview_fields, removed_listview_fields): meta = frappe.get_meta(doctype) if isinstance(listview_fields, string_types): listview_fields = [f.get("fieldname") for f in json.loads(listview_fields)] - for field in listview_fields: - set_in_list_view_property(doctype, meta.get_field(field), "1") - for field in removed_listview_fields: set_in_list_view_property(doctype, meta.get_field(field), "0") + for field in listview_fields: + set_in_list_view_property(doctype, meta.get_field(field), "1") + def set_in_list_view_property(doctype, field, value): property_setter = frappe.db.get_value("Property Setter", {"doc_type": doctype, "field_name": field.fieldname, "property": "in_list_view"}) if property_setter: diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index 252309dd88..e078a106b1 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -1,9 +1,10 @@ export default class ListSettings { - constructor({ doctype, meta, settings }) { + constructor({ listview, doctype, meta, settings }) { if (!doctype) { frappe.throw(__('Doctype required')); } + this.listview = listview; this.doctype = doctype; this.meta = meta; this.settings = settings; @@ -33,13 +34,24 @@ export default class ListSettings { me.dialog.set_values(me.settings); me.dialog.set_primary_action(__('Save'), () => { let values = me.dialog.get_values(); - frappe.call("frappe.desk.doctype.list_view_settings.list_view_settings.save_listview_settings", { - doctype: me.doctype, - listview_settings: values, - removed_listview_fields: me.removed_fields || [] + + frappe.show_alert({ + message: __("Saving"), + indicator: "green" + }); + + frappe.call({ + method: "frappe.desk.doctype.list_view_settings.list_view_settings.save_listview_settings", + args: { + doctype: me.doctype, + listview_settings: values, + removed_listview_fields: me.removed_fields || [] + }, + callback: function (r) { + me.listview.refresh_columns(r.message.meta, r.message.listview_settings); + me.dialog.hide(); + } }); - me.dialog.hide(); - frappe.ui.toolbar.clear_cache(); }); me.dialog.fields_dict["total_fields"].df.onchange = () => me.refresh(); @@ -49,7 +61,6 @@ export default class ListSettings { let me = this; me.setup_fields(); - me.update_fields(); me.add_new_fields(); me.setup_remove_fields(); } @@ -115,6 +126,7 @@ export default class ListSettings { handle: '.sortable-handle', draggable: '.sortable', onUpdate: () => { + me.update_fields(); me.refresh(); } }); @@ -141,18 +153,19 @@ export default class ListSettings { remove_fields(fieldname) { let me = this; + let existing_fields = me.fields.map(f => f.fieldname); for (let idx in me.fields) { let field = me.fields[idx]; if (field.fieldname == fieldname) { - console.log(idx); me.fields.splice(idx, 1); break; } } - + me.set_removed_fields(me.get_removed_listview_fields(me.fields.map(f => f.fieldname), existing_fields)); me.refresh(); + me.update_fields(); } update_fields() { @@ -197,8 +210,8 @@ export default class ListSettings { }); d.set_primary_action(__('Save'), () => { let values = d.get_values().fields; - me.removed_fields = me.get_removed_listview_fields(values) - me.new_fields = values + + me.set_removed_fields(me.get_removed_listview_fields(values, me.fields.map(f => f.fieldname))); me.fields = []; me.set_subject_field(me.meta); @@ -303,11 +316,9 @@ export default class ListSettings { return multiselect_fields } - get_removed_listview_fields(new_fields) { + get_removed_listview_fields(new_fields, existing_fields) { let me = this; - let removed_fields = [] - let existing_fields = me.fields.map(f => f.fieldname); existing_fields.forEach(column => { if (!in_list(new_fields, column)) { @@ -317,4 +328,14 @@ export default class ListSettings { return removed_fields; } + + set_removed_fields(fields) { + let me = this; + + if (me.removed_fields) { + me.removed_fields.concat(fields); + } else { + me.removed_fields = fields; + } + } } \ No newline at end of file diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index 3d629d3fda..b784e7ba92 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -232,6 +232,32 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } + refresh_columns(meta, list_view_settings) { + this.meta = meta; + this.list_view_settings = list_view_settings; + + this.setup_columns(); + this.refresh(true); + } + + refresh(refresh_header=false) { + this.freeze(true); + // fetch data from server + return frappe.call(this.get_call_args()).then(r => { + // render + this.prepare_data(r); + this.toggle_result_area(); + this.before_render(); + this.render_header(refresh_header); + this.render(); + this.after_render(); + this.freeze(false); + if (this.settings.refresh) { + this.settings.refresh(this); + } + }); + } + setup_freeze_area() { this.$freeze = $(`
${__('Loading')}...
`) @@ -329,6 +355,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields); + console.log(this.columns); } get_documentation_link() { @@ -415,7 +442,11 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } } - render_header() { + render_header(refresh_header=false) { + if (refresh_header) { + this.$result.find('.list-row-head').remove(); + } + if (this.$result.find('.list-row-head').length === 0) { // append header once this.$result.prepend(this.get_header_html()); @@ -1315,6 +1346,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { show_list_settings() { frappe.model.with_doctype(this.doctype, () => { new ListSettings({ + listview: this, doctype: this.doctype, settings: this.list_view_settings, meta: frappe.get_meta(this.doctype) From b9bfc6b2f51e0359f1a4c524d43f13d371d5d8b1 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 5 May 2020 15:30:55 +0530 Subject: [PATCH 016/222] fix: review changes --- .../list_view_settings.json | 4 +- .../list_view_settings/list_view_settings.py | 17 +++--- frappe/public/js/frappe/list/list_settings.js | 59 ++++++++++++++++--- frappe/public/js/frappe/list/list_view.js | 53 +++++++++-------- 4 files changed, 87 insertions(+), 46 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index e59991c52e..486d2c1c44 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -35,7 +35,7 @@ { "fieldname": "total_fields", "fieldtype": "Select", - "label": "Total Fields", + "label": "Maximum Number of Fields", "options": "\n4\n5\n6\n7\n8\n9\n10" }, { @@ -52,7 +52,7 @@ } ], "links": [], - "modified": "2020-04-13 23:31:15.411147", + "modified": "2020-05-05 15:15:14.306665", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index 6cc7d11950..db3be47402 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -17,17 +17,14 @@ class ListViewSettings(Document): @frappe.whitelist() def save_listview_settings(doctype, listview_settings, removed_listview_fields): - if isinstance(listview_settings, string_types): - listview_settings = json.loads(listview_settings) + listview_settings = frappe.parse_json(listview_settings) + removed_listview_fields = frappe.parse_json(removed_listview_fields) - if isinstance(removed_listview_fields, string_types): - removed_listview_fields = json.loads(removed_listview_fields) - - try: + if frappe.get_all("List View Settings", filters={"name": doctype}): doc = frappe.get_doc("List View Settings", doctype) doc.update(listview_settings) doc.save() - except DoesNotExistError: + else: doc = frappe.new_doc("List View Settings") doc.name = doctype doc.update(listview_settings) @@ -43,8 +40,7 @@ def save_listview_settings(doctype, listview_settings, removed_listview_fields): def set_listview_fields(doctype, listview_fields, removed_listview_fields): meta = frappe.get_meta(doctype) - if isinstance(listview_fields, string_types): - listview_fields = [f.get("fieldname") for f in json.loads(listview_fields)] + listview_fields = [f.get("fieldname") for f in frappe.parse_json(listview_fields) if f.get("fieldname")] for field in removed_listview_fields: set_in_list_view_property(doctype, meta.get_field(field), "0") @@ -53,6 +49,9 @@ def set_listview_fields(doctype, listview_fields, removed_listview_fields): set_in_list_view_property(doctype, meta.get_field(field), "1") def set_in_list_view_property(doctype, field, value): + if not field or field.fieldname == "status_field": + return + property_setter = frappe.db.get_value("Property Setter", {"doc_type": doctype, "field_name": field.fieldname, "property": "in_list_view"}) if property_setter: doc = frappe.get_doc("Property Setter", property_setter) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index e078a106b1..a2eaaa6689 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -9,7 +9,7 @@ export default class ListSettings { this.meta = meta; this.settings = settings; this.dialog = null; - this.fields = []; + this.fields = this.settings && this.settings.fields ? JSON.parse(this.settings.fields) : []; this.subject_field = null; frappe.model.with_doctype("List View Settings", () => { @@ -28,7 +28,7 @@ export default class ListSettings { let list_view_settings = frappe.get_meta("List View Settings"); me.dialog = new frappe.ui.Dialog({ - title: __("{0} List View Settings", [__(me.doctype)]), + title: __("{0} Settings", [__(me.doctype)]), fields: list_view_settings.fields }); me.dialog.set_values(me.settings); @@ -67,14 +67,35 @@ export default class ListSettings { show_dialog() { let me = this; + + if (!this.settings.fields) { + me.update_fields(); + } + + if (!me.dialog.get_value("total_fields")) { + let field_count = me.fields.length; + + if (field_count < 4) { + field_count = 4; + } else if (field_count > 10) { + field_count = 4; + } + + me.dialog.set_value("total_fields", field_count); + } + me.dialog.show(); } setup_fields() { + function is_status_field(field) { + return field.fieldname === "status_field"; + } + let me = this; let fields_html = me.dialog.get_field("fields_html"); - let $wrapper = fields_html.$wrapper[0]; + let wrapper = fields_html.$wrapper[0]; let fields = ``; let total_fields = me.dialog.get_values().total_fields ? me.dialog.get_values().total_fields : me.settings.total_fields; @@ -83,16 +104,17 @@ export default class ListSettings { break; } let is_sortable = (idx == 0) ? `` : `sortable`; - let can_remove = (idx == 0) ? `hide` : ``; + let show_sortable_handle = (idx == 0) ? `hide` : ``; + let can_remove = (idx == 0 || is_status_field(me.fields[idx])) ? `hide` : ``; fields += `
+ data-label="${me.fields[idx].label}" data-type="${me.fields[idx].type}">
- +
${me.fields[idx].label} @@ -122,7 +144,7 @@ export default class ListSettings {
`); - new Sortable($wrapper.getElementsByClassName("control-input-wrapper")[0], { + new Sortable(wrapper.getElementsByClassName("control-input-wrapper")[0], { handle: '.sortable-handle', draggable: '.sortable', onUpdate: () => { @@ -172,9 +194,9 @@ export default class ListSettings { let me = this; let fields_html = me.dialog.get_field("fields_html"); - let $wrapper = fields_html.$wrapper[0]; + let wrapper = fields_html.$wrapper[0]; - let fields_order = $wrapper.getElementsByClassName("fields_order"); + let fields_order = wrapper.getElementsByClassName("fields_order"); me.fields = []; for (let idx = 0; idx < fields_order.length; idx++) { @@ -185,6 +207,7 @@ export default class ListSettings { } me.dialog.set_value("fields", JSON.stringify(me.fields)); + me.dialog.get_value("fields"); } column_selector() { @@ -215,6 +238,7 @@ export default class ListSettings { me.fields = []; me.set_subject_field(me.meta); + me.set_status_field(); for (let idx in values) { let value = values[idx]; @@ -266,6 +290,7 @@ export default class ListSettings { let me = this; me.set_subject_field(meta); + me.set_status_field(); meta.fields.forEach(field => { if (field.in_list_view && !in_list(frappe.model.no_value_type, field.fieldtype) && @@ -299,6 +324,18 @@ export default class ListSettings { me.fields.push(me.subject_field); } + set_status_field() { + let me = this; + + if (frappe.has_indicator(me.doctype)) { + me.fields.push({ + type: "Status", + label: "Status", + fieldname: "status_field" + }) + } + } + get_doctype_fields(meta, fields) { let me = this; let multiselect_fields = [] @@ -320,6 +357,10 @@ export default class ListSettings { let me = this; let removed_fields = [] + if (frappe.has_indicator(me.doctype)) { + new_fields.push("status_field"); + } + existing_fields.forEach(column => { if (!in_list(new_fields, column)) { removed_fields.push(column); diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index b784e7ba92..98d21f905d 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -315,31 +315,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { ); if (this.list_view_settings.fields) { - let fields_order = []; - let fields = JSON.parse(this.list_view_settings.fields); - let is_status_field_set = false; - - //title_field is fixed - fields_order.push(this.columns[0]); - this.columns.splice(0, 1); - - for (let fld in fields) { - for (let col in this.columns) { - let field = fields[fld]; - let column = this.columns[col]; - - if (column.type == "Status" && !is_status_field_set) { - fields_order.push(column); - is_status_field_set = true; - break; - } else if (column.type == "Field" && field.fieldname === column.df.fieldname) { - fields_order.push(column); - break; - } - } - } - - this.columns = fields_order; + this.columns = this.reorder_listview_fields(); } // limit max to 8 columns if no total_fields is set in List View Settings @@ -355,7 +331,32 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { } this.columns = this.columns.slice(0, this.list_view_settings.total_fields || total_fields); - console.log(this.columns); + } + + reorder_listview_fields() { + let fields_order = []; + let fields = JSON.parse(this.list_view_settings.fields); + + //title_field is fixed + fields_order.push(this.columns[0]); + this.columns.splice(0, 1); + + for (let fld in fields) { + for (let col in this.columns) { + let field = fields[fld]; + let column = this.columns[col]; + + if (column.type == "Status" && field.fieldname == "status_field") { + fields_order.push(column); + break; + } else if (column.type == "Field" && field.fieldname === column.df.fieldname) { + fields_order.push(column); + break; + } + } + } + + return fields_order; } get_documentation_link() { From c4320fb22f71e12539d71f0f6374aa3aa9d99e8b Mon Sep 17 00:00:00 2001 From: Himanshu Date: Tue, 5 May 2020 16:05:23 +0530 Subject: [PATCH 017/222] Update frappe/public/js/frappe/list/list_settings.js Co-authored-by: Prssanna Desai --- frappe/public/js/frappe/list/list_settings.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index a2eaaa6689..e1978cc721 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -97,7 +97,7 @@ export default class ListSettings { let fields_html = me.dialog.get_field("fields_html"); let wrapper = fields_html.$wrapper[0]; let fields = ``; - let total_fields = me.dialog.get_values().total_fields ? me.dialog.get_values().total_fields : me.settings.total_fields; + let total_fields = me.dialog.get_values().total_fields || me.settings.total_fields; for (let idx in me.fields) { if (idx == parseInt(total_fields)) { @@ -379,4 +379,4 @@ export default class ListSettings { me.removed_fields = fields; } } -} \ No newline at end of file +} From 627940c56b7fa435726d79f355ca8d96fd329d57 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 5 May 2020 18:58:38 +0530 Subject: [PATCH 018/222] feat: site wise logs under sites/{sitename}/logs/frappe.log --- frappe/commands/site.py | 2 +- frappe/utils/logger.py | 31 +++++++++++++++++++++++++------ 2 files changed, 26 insertions(+), 7 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 52994ccec3..362d02c0d2 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -409,7 +409,7 @@ def _drop_site(site, root_login='root', root_password=None, archived_sites_path= else: click.echo("="*80) click.echo("Error: The operation has stopped because backup of {s}'s database failed.".format(s=site)) - click.echo("Reason: {reason}{sep}".format(reason=err[1], sep="\n")) + click.echo("Reason: {reason}{sep}".format(reason=str(err), sep="\n")) click.echo("Fix the issue and try again.") click.echo( "Hint: Use 'bench drop-site {s} --force' to force the removal of {s}".format(sep="\n", tab="\t", s=site) diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 5a77434cde..e446b39ee0 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -1,23 +1,42 @@ +# imports - compatibility imports from __future__ import unicode_literals -import frappe + +# imports - standard imports import logging +import os from logging.handlers import RotatingFileHandler + +# imports - third party imports from six import text_type +# imports - module imports +import frappe + + default_log_level = logging.DEBUG -LOG_FILENAME = '../logs/{}-frappe.log'.format(frappe.local.site) +site = getattr(frappe.local, 'site', None) +LOG_FILENAME = os.path.join('..', 'logs', 'frappe.log') + def get_logger(module, with_more_info=True): if module in frappe.loggers: return frappe.loggers[module] formatter = logging.Formatter('[%(levelname)s] %(asctime)s | %(pathname)s:\n%(message)s') - # handler = logging.StreamHandler() - handler = RotatingFileHandler( - LOG_FILENAME, maxBytes=100000, backupCount=20) + handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) handler.setFormatter(formatter) + if site: + SITELOG_FOLDER = os.path.join(site, 'logs') + SITELOG_FILENAME = os.path.join(SITELOG_FOLDER, 'frappe.log') + + if not os.path.exists(SITELOG_FOLDER): + os.mkdir(SITELOG_FOLDER) + + handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) + handler.setFormatter(formatter) + if with_more_info: handler.addFilter(SiteContextFilter()) @@ -39,7 +58,7 @@ class SiteContextFilter(logging.Filter): def get_more_info_for_log(): '''Adds Site, Form Dict into log entry''' more_info = [] - site = getattr(frappe.local, 'site', None) + if site: more_info.append('Site: {0}'.format(site)) From ca70bd364cc125be533ec63bcdb2f87dc71096e7 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 5 May 2020 18:59:38 +0530 Subject: [PATCH 019/222] fix: create sites/{site}/logs folder --- frappe/installer.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/installer.py b/frappe/installer.py index 54402f0087..565fc9d36b 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -269,6 +269,7 @@ def make_site_dirs(): os.path.join(site_private_path, 'backups'), os.path.join(site_public_path, 'files'), os.path.join(site_private_path, 'files'), + os.path.join(frappe.local.site_path, 'logs'), os.path.join(frappe.local.site_path, 'task-logs')): if not os.path.exists(dir_path): os.makedirs(dir_path) From 763e8cfcaecdfe1c3bb997a6d8e96a15962b5344 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Thu, 7 May 2020 17:29:35 +0530 Subject: [PATCH 020/222] fix: remove unnecessary eval from error snapshot Signed-off-by: Chinmay D. Pai --- frappe/utils/error.py | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/frappe/utils/error.py b/frappe/utils/error.py index c124410a7f..dd6ae05c51 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -123,22 +123,13 @@ def get_snapshot(exception, context=10): # add exception type, value and attributes if isinstance(evalue, BaseException): for name in dir(evalue): - # prevent py26 DeprecationWarning - if (name != 'messages' or sys.version_info < (2.6)) and not name.startswith('__'): + if name != 'messages' and not name.startswith('__'): value = pydoc.text.repr(getattr(evalue, name)) - - # render multilingual string properly - if isinstance(value, six.text_type): - value = eval(value) - s['exception'][name] = encode(value) # add all local values (of last frame) to the snapshot for name, value in locals.items(): - if isinstance(value, six.text_type): - value = eval(value) - - s['locals'][name] = pydoc.text.repr(value) + s['locals'][name] = value if isinstance(value, six.text_type) else pydoc.text.repr(value) return s From b685412b369dac57fa853a8c0e33a0f5d0764328 Mon Sep 17 00:00:00 2001 From: prssanna Date: Fri, 8 May 2020 18:29:45 +0530 Subject: [PATCH 021/222] feat: allow notifications created via Notification in system notifications --- .../notification_log/notification_log.json | 8 ++++--- .../notification_log/notification_log.py | 2 +- .../notification_settings.json | 12 ++++++++++- .../notification_settings.py | 3 +++ .../doctype/notification/notification.json | 12 ++++++++++- .../doctype/notification/notification.py | 21 +++++++++++++++++++ 6 files changed, 52 insertions(+), 6 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index ecb746df64..86213da242 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -1,4 +1,5 @@ { + "actions": [], "creation": "2019-08-26 13:37:34.165254", "doctype": "DocType", "editable_grid": 1, @@ -35,7 +36,7 @@ "in_list_view": 1, "in_standard_filter": 1, "label": "Type", - "options": "Mention\nEnergy Point\nAssignment\nShare", + "options": "Mention\nEnergy Point\nAssignment\nShare\nAlert", "read_only": 1, "search_index": 1 }, @@ -82,8 +83,9 @@ } ], "in_create": 1, - "modified": "2019-11-12 15:22:35.283678", - "modified_by": "umair@erpnext.com", + "links": [], + "modified": "2020-05-08 15:16:36.365285", + "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 17eb6371b1..0112e68b1d 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -68,7 +68,7 @@ def make_notification_logs(doc, users): _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') - if _doc.for_user != _doc.from_user or doc.type == 'Energy Point': + if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': _doc.insert(ignore_permissions=True) def send_notification_email(doc): diff --git a/frappe/desk/doctype/notification_settings/notification_settings.json b/frappe/desk/doctype/notification_settings/notification_settings.json index 6af325507b..190e827ab5 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.json +++ b/frappe/desk/doctype/notification_settings/notification_settings.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "Prompt", "creation": "2019-09-11 22:15:44.851526", "doctype": "DocType", @@ -13,6 +14,7 @@ "enable_email_assignment", "enable_email_energy_point", "enable_email_share", + "enable_email_alert", "user", "seen" ], @@ -83,10 +85,18 @@ "fieldtype": "Check", "hidden": 1, "label": "Seen" + }, + { + "default": "0", + "fieldname": "enable_email_alert", + "fieldtype": "Check", + "hidden": 1, + "label": "Alert" } ], "in_create": 1, - "modified": "2019-11-19 12:57:59.356786", + "links": [], + "modified": "2020-05-08 15:15:53.925897", "modified_by": "Administrator", "module": "Desk", "name": "Notification Settings", diff --git a/frappe/desk/doctype/notification_settings/notification_settings.py b/frappe/desk/doctype/notification_settings/notification_settings.py index 6b5a13ee27..9b124cd6f4 100644 --- a/frappe/desk/doctype/notification_settings/notification_settings.py +++ b/frappe/desk/doctype/notification_settings/notification_settings.py @@ -28,6 +28,9 @@ def is_email_notifications_enabled_for_type(user, notification_type): if not is_email_notifications_enabled(user): return False + if notification_type == 'Alert': + return False + fieldname = 'enable_email_' + frappe.scrub(notification_type) enabled = frappe.db.get_value('Notification Settings', user, fieldname) if enabled is None: diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 14eff2251a..d83305ec5b 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_rename": 1, "autoname": "Prompt", "creation": "2014-07-11 17:18:09.923399", @@ -23,6 +24,7 @@ "value_changed", "sender", "sender_email", + "show_in_notifications_dropdown", "section_break_9", "condition", "column_break_6", @@ -260,10 +262,18 @@ "fieldtype": "Link", "label": "Print Format", "options": "Print Format" + }, + { + "default": "0", + "description": "If enabled, the notification will also show up in the notifications dropdown on the top right corner of the navigation bar.", + "fieldname": "show_in_notifications_dropdown", + "fieldtype": "Check", + "label": "Show in Notifications Dropdown" } ], "icon": "fa fa-envelope", - "modified": "2019-07-15 13:17:02.585013", + "links": [], + "modified": "2020-05-06 13:46:21.155535", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 8c011ade65..cac4bbddc2 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -13,6 +13,7 @@ from frappe.utils.jinja import validate_template from frappe.modules.utils import export_module_json, get_doc_module from six import string_types from frappe.integrations.doctype.slack_webhook_url.slack_webhook_url import send_slack_message +from frappe.desk.doctype.notification_log.notification_log import enqueue_create_notification class Notification(Document): def onload(self): @@ -125,6 +126,9 @@ def get_context(context): if self.channel == 'Slack': self.send_a_slack_msg(doc, context) + if self.show_in_notifications_dropdown: + self.create_drodown_notification(doc, context) + if self.set_property_after_alert: allow_update = True if doc.docstatus == 1 and not doc.meta.get_field(self.set_property_after_alert).allow_on_submit: @@ -143,6 +147,23 @@ def get_context(context): except Exception: frappe.log_error(title='Document update failed', message=frappe.get_traceback()) + def create_drodown_notification(self, doc, context): + subject = self.subject + if "{" in subject: + subject = frappe.render_template(self.subject, context) + subject = '[Alert] ' + subject + + recipients, cc, bcc = self.get_list_of_recipients(doc, context) + users = recipients + cc + bcc + + notification_doc = { + 'type': 'Alert', + 'document_type': doc.doctype, + 'document_name': doc.name, + 'subject': subject, + } + enqueue_create_notification(recipients, notification_doc) + def send_an_email(self, doc, context): from email.utils import formataddr subject = self.subject From e8f30259c6cdf419129f2a820cbd7b215eeee596 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 15:23:18 +0530 Subject: [PATCH 022/222] fix: link notification log document and show message --- .../notification_log/notification_log.json | 19 +++++++++---------- .../notification_log/notification_log.py | 15 +++++++++++++++ .../doctype/notification/notification.py | 4 +++- .../frappe/ui/notifications/notifications.js | 14 ++++++++++---- 4 files changed, 37 insertions(+), 15 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index 86213da242..e8225956b8 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -9,7 +9,6 @@ "for_user", "type", "email_content", - "column_break_4", "document_type", "read", "document_name", @@ -26,6 +25,7 @@ { "fieldname": "for_user", "fieldtype": "Link", + "hidden": 1, "label": "For User", "options": "User", "read_only": 1 @@ -33,6 +33,7 @@ { "fieldname": "type", "fieldtype": "Select", + "hidden": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Type", @@ -42,17 +43,14 @@ }, { "fieldname": "email_content", - "fieldtype": "Text", - "label": "Email Content", + "fieldtype": "Text Editor", + "label": "Message", "read_only": 1 }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, { "fieldname": "document_type", "fieldtype": "Link", + "hidden": 1, "label": "Document Type", "options": "DocType", "read_only": 1, @@ -61,13 +59,14 @@ { "fieldname": "document_name", "fieldtype": "Data", - "label": "Document Name", + "label": "Document Link", "read_only": 1, "search_index": 1 }, { "fieldname": "from_user", "fieldtype": "Link", + "hidden": 1, "label": "From User", "options": "User", "read_only": 1, @@ -84,8 +83,8 @@ ], "in_create": 1, "links": [], - "modified": "2020-05-08 15:16:36.365285", - "modified_by": "Administrator", + "modified": "2020-05-11 15:18:20.893718", + "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 0112e68b1d..2db552e7de 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -65,11 +65,26 @@ def make_notification_logs(doc, users): return _doc = frappe.new_doc('Notification Log') + + attachments = None + if doc.attachments: + attachments = doc.pop('attachments') + _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': _doc.insert(ignore_permissions=True) + if attachments: + attach_file_to_doc(attachments, _doc.name) + + +def attach_file_to_doc(attachments, docname): + from frappe.utils.file_manager import save_file + for attachment in attachments: + attachment.pop("print_format_attachment", None) + print_format_file = frappe._dict(frappe.attach_print(**attachment)) + save_file(print_format_file.fname, print_format_file.fcontent, 'Notification Log', docname) def send_notification_email(doc): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index cac4bbddc2..6d74e499d9 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -151,8 +151,8 @@ def get_context(context): subject = self.subject if "{" in subject: subject = frappe.render_template(self.subject, context) - subject = '[Alert] ' + subject + attachments = self.get_attachment(doc) recipients, cc, bcc = self.get_list_of_recipients(doc, context) users = recipients + cc + bcc @@ -161,6 +161,8 @@ def get_context(context): 'document_type': doc.doctype, 'document_name': doc.name, 'subject': subject, + 'email_content': frappe.render_template(self.message, context), + 'attachments': attachments } enqueue_create_notification(recipients, notification_doc) diff --git a/frappe/public/js/frappe/ui/notifications/notifications.js b/frappe/public/js/frappe/ui/notifications/notifications.js index 2420d6772e..14d3b9bbd0 100644 --- a/frappe/public/js/frappe/ui/notifications/notifications.js +++ b/frappe/public/js/frappe/ui/notifications/notifications.js @@ -304,10 +304,7 @@ frappe.ui.Notifications = class Notifications { } get_dropdown_item_html(field) { - let doc_link = frappe.utils.get_form_link( - field.document_type, - field.document_name - ); + let doc_link = this.get_item_link(field); let read_class = field.read ? '' : 'unread'; let mark_read_action = field.read ? '': 'data-action="mark_as_read"'; let message = field.subject; @@ -336,6 +333,15 @@ frappe.ui.Notifications = class Notifications { return item_html; } + get_item_link(field) { + const link_doctype = field.type == 'Alert'? 'Notification Log': field.document_type; + const link_docname = field.type == 'Alert'? field.name: field.document_name; + return frappe.utils.get_form_link( + link_doctype, + link_docname + ); + } + render_dropdown_headers() { this.categories = [ { From a6a3919ac61cd9217abd32155d5e95e3637c3767 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 19:27:01 +0530 Subject: [PATCH 023/222] fix: add button to route to reference doc form --- frappe/desk/doctype/notification_log/notification_log.js | 6 ++---- .../desk/doctype/notification_log/notification_log.json | 9 ++++++++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.js b/frappe/desk/doctype/notification_log/notification_log.js index 654b2b2b06..2bb48ccdc1 100644 --- a/frappe/desk/doctype/notification_log/notification_log.js +++ b/frappe/desk/doctype/notification_log/notification_log.js @@ -2,11 +2,9 @@ // For license information, please see license.txt frappe.ui.form.on('Notification Log', { - refresh: function(frm) { + open_reference_document: function(frm) { let dt = frm.doc.document_type; let dn = frm.doc.document_name; - frm.fields_dict.document_name.$input_wrapper - .find('.control-value') - .wrapInner(``); + frappe.set_route('Form', dt, dn); } }); diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index e8225956b8..e676bfc1f9 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -12,6 +12,7 @@ "document_type", "read", "document_name", + "open_reference_document", "from_user" ], "fields": [ @@ -59,6 +60,7 @@ { "fieldname": "document_name", "fieldtype": "Data", + "hidden": 1, "label": "Document Link", "read_only": 1, "search_index": 1 @@ -79,11 +81,16 @@ "hidden": 1, "ignore_user_permissions": 1, "label": "Read" + }, + { + "fieldname": "open_reference_document", + "fieldtype": "Button", + "label": "Open Reference Document" } ], "in_create": 1, "links": [], - "modified": "2020-05-11 15:18:20.893718", + "modified": "2020-05-11 19:19:58.578245", "modified_by": "umair@erpnext.com", "module": "Desk", "name": "Notification Log", From 2a68af05f5bf8e4bfa30a8f714a226fb4b089d69 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 11 May 2020 19:36:43 +0530 Subject: [PATCH 024/222] fix: option to disable channel if show in dropdown is enabled --- frappe/email/doctype/notification/notification.json | 13 +++++++++++-- frappe/email/doctype/notification/notification.py | 6 ++++-- 2 files changed, 15 insertions(+), 4 deletions(-) diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index d83305ec5b..736056056f 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -25,6 +25,7 @@ "sender", "sender_email", "show_in_notifications_dropdown", + "disable_channel", "section_break_9", "condition", "column_break_6", @@ -265,15 +266,23 @@ }, { "default": "0", - "description": "If enabled, the notification will also show up in the notifications dropdown on the top right corner of the navigation bar.", + "description": "If enabled, the notification will show up in the notifications dropdown on the top right corner of the navigation bar.", "fieldname": "show_in_notifications_dropdown", "fieldtype": "Check", "label": "Show in Notifications Dropdown" + }, + { + "default": "0", + "depends_on": "eval: doc.show_in_notifications_dropdown", + "description": "If enabled, the channel set for the Notification will be disabled. The Notification will only be received in the Notifications Dropdown.", + "fieldname": "disable_channel", + "fieldtype": "Check", + "label": "Disable Channel" } ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-05-06 13:46:21.155535", + "modified": "2020-05-11 15:33:28.904019", "modified_by": "Administrator", "module": "Email", "name": "Notification", diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 6d74e499d9..8ed0bae067 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -120,10 +120,12 @@ def get_context(context): if self.is_standard: self.load_standard_properties(context) - if self.channel == 'Email': + channel_disabled = self.disable_channel + + if self.channel == 'Email' and not channel_disabled: self.send_an_email(doc, context) - if self.channel == 'Slack': + if self.channel == 'Slack' and not channel_disabled: self.send_a_slack_msg(doc, context) if self.show_in_notifications_dropdown: From 38b51dbf4b4bb2e65ac31a14322c5d2c36d3b892 Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 12:44:33 +0530 Subject: [PATCH 025/222] fix: resetting fields --- frappe/public/js/frappe/list/list_settings.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index a2eaaa6689..9487269efa 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -247,10 +247,12 @@ export default class ListSettings { break; } else if (value != me.subject_field.fieldname) { let field = frappe.meta.get_docfield(me.doctype, value); - me.fields.push({ - label: field.label, - fieldname: field.fieldname - }) + if (field) { + me.fields.push({ + label: field.label, + fieldname: field.fieldname + }) + } } } From 77fdb55d106c00b2742c352b9b814d6fa0e5d5ce Mon Sep 17 00:00:00 2001 From: Himanshu Warekar Date: Tue, 12 May 2020 12:53:59 +0530 Subject: [PATCH 026/222] fix: sider --- .../list_view_settings/list_view_settings.py | 7 ++---- frappe/public/js/frappe/list/list_settings.js | 24 +++++++++---------- 2 files changed, 14 insertions(+), 17 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.py b/frappe/desk/doctype/list_view_settings/list_view_settings.py index db3be47402..74e029f499 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.py +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.py @@ -5,9 +5,6 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from six import string_types -from frappe.exceptions import DoesNotExistError -import json class ListViewSettings(Document): @@ -71,9 +68,9 @@ def set_in_list_view_property(doctype, field, value): def get_default_listview_fields(doctype): meta = frappe.get_meta(doctype) path = frappe.get_module_path(frappe.scrub(meta.module), "doctype", frappe.scrub(meta.name), frappe.scrub(meta.name) + ".json") - json = frappe.get_file_json(path) + doctype_json = frappe.get_file_json(path) - fields = [f.get("fieldname") for f in json.get("fields") if f.get("in_list_view")] + fields = [f.get("fieldname") for f in doctype_json.get("fields") if f.get("in_list_view")] if meta.title_field: if not meta.title_field.strip() in fields: diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index fa6736a1cd..6cebe421d7 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -155,7 +155,7 @@ export default class ListSettings { } add_new_fields() { - let me = this + let me = this; let fields_html = me.dialog.get_field("fields_html"); let add_new_fields = fields_html.$wrapper[0].getElementsByClassName("add-new-fields")[0]; @@ -301,9 +301,9 @@ export default class ListSettings { me.fields.push({ label: field.label, fieldname: field.fieldname - }) + }); } - }) + }); } set_subject_field(meta) { @@ -312,15 +312,15 @@ export default class ListSettings { me.subject_field = { label: "Name", fieldname: "name" - } + }; if (meta.title_field) { - let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()) + let field = frappe.meta.get_docfield(me.doctype, meta.title_field.trim()); me.subject_field = { label: field.label, fieldname: field.fieldname - } + }; } me.fields.push(me.subject_field); @@ -334,13 +334,13 @@ export default class ListSettings { type: "Status", label: "Status", fieldname: "status_field" - }) + }); } } get_doctype_fields(meta, fields) { let me = this; - let multiselect_fields = [] + let multiselect_fields = []; meta.fields.forEach(field => { if (!in_list(frappe.model.no_value_type, field.fieldtype)) { @@ -348,16 +348,16 @@ export default class ListSettings { label: field.label, value: field.fieldname, checked: in_list(fields, field.fieldname) - }) + }); } - }) + }); - return multiselect_fields + return multiselect_fields; } get_removed_listview_fields(new_fields, existing_fields) { let me = this; - let removed_fields = [] + let removed_fields = []; if (frappe.has_indicator(me.doctype)) { new_fields.push("status_field"); From 00f7194069369977cce66d1b43a4d115cc9486fa Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 16:28:37 +0530 Subject: [PATCH 027/222] fix: link attachment to doc --- .../notification_log/notification_log.js | 39 ++++++++++++++++++- .../notification_log/notification_log.json | 18 ++++++++- .../notification_log/notification_log.py | 23 ++++------- .../doctype/notification/notification.py | 2 +- 4 files changed, 62 insertions(+), 20 deletions(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.js b/frappe/desk/doctype/notification_log/notification_log.js index 2bb48ccdc1..1f381d115b 100644 --- a/frappe/desk/doctype/notification_log/notification_log.js +++ b/frappe/desk/doctype/notification_log/notification_log.js @@ -2,9 +2,44 @@ // For license information, please see license.txt frappe.ui.form.on('Notification Log', { + refresh: function(frm) { + if (frm.doc.attached_file) { + frm.trigger('set_attachment'); + } else { + frm.get_field('attachment_link').$wrapper.empty(); + } + }, + open_reference_document: function(frm) { - let dt = frm.doc.document_type; - let dn = frm.doc.document_name; + const dt = frm.doc.document_type; + const dn = frm.doc.document_name; frappe.set_route('Form', dt, dn); + }, + + set_attachment: function(frm) { + const attachment = JSON.parse(frm.doc.attached_file); + + const $wrapper = frm.get_field('attachment_link').$wrapper; + $wrapper.html(` + + `); + + $wrapper.find(".attached-file-link").click(() => { + const w = window.open( + frappe.urllib.get_full_url(`/api/method/frappe.utils.print_format.download_pdf? + doctype=${encodeURIComponent(attachment.doctype)} + &name=${encodeURIComponent(attachment.name)} + &format=${encodeURIComponent(attachment.print_format)} + &lang=${encodeURIComponent(attachment.lang)}`) + ); + if (!w) { + frappe.msgprint(__("Please enable pop-ups")); + } + }); } }); diff --git a/frappe/desk/doctype/notification_log/notification_log.json b/frappe/desk/doctype/notification_log/notification_log.json index e676bfc1f9..35869f8c2e 100644 --- a/frappe/desk/doctype/notification_log/notification_log.json +++ b/frappe/desk/doctype/notification_log/notification_log.json @@ -12,6 +12,8 @@ "document_type", "read", "document_name", + "attached_file", + "attachment_link", "open_reference_document", "from_user" ], @@ -86,12 +88,24 @@ "fieldname": "open_reference_document", "fieldtype": "Button", "label": "Open Reference Document" + }, + { + "fieldname": "attached_file", + "fieldtype": "Code", + "hidden": 1, + "label": "Attached File", + "options": "JSON" + }, + { + "fieldname": "attachment_link", + "fieldtype": "HTML", + "label": "Attachment Link" } ], "in_create": 1, "links": [], - "modified": "2020-05-11 19:19:58.578245", - "modified_by": "umair@erpnext.com", + "modified": "2020-05-12 15:56:46.278677", + "modified_by": "Administrator", "module": "Desk", "name": "Notification Log", "owner": "Administrator", diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 2db552e7de..244d0e150c 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -5,6 +5,7 @@ from __future__ import unicode_literals import frappe from frappe import _ +import json from frappe.model.document import Document from frappe.desk.doctype.notification_settings.notification_settings import (is_notifications_enabled, is_email_notifications_enabled_for_type, set_seen_value) @@ -58,6 +59,11 @@ def enqueue_create_notification(users, doc): def make_notification_logs(doc, users): from frappe.social.doctype.energy_point_settings.energy_point_settings import is_energy_point_enabled + + attachment = None + if doc.attachment: + attachment = doc.pop('attachment') + for user in users: if frappe.db.exists('User', user): if is_notifications_enabled(user): @@ -65,26 +71,13 @@ def make_notification_logs(doc, users): return _doc = frappe.new_doc('Notification Log') - - attachments = None - if doc.attachments: - attachments = doc.pop('attachments') - _doc.update(doc) _doc.for_user = user _doc.subject = _doc.subject.replace('
', '').replace('
', '') if _doc.for_user != _doc.from_user or doc.type == 'Energy Point' or doc.type == 'Alert': + if attachment: + _doc.attached_file = json.dumps(attachment) _doc.insert(ignore_permissions=True) - if attachments: - attach_file_to_doc(attachments, _doc.name) - - -def attach_file_to_doc(attachments, docname): - from frappe.utils.file_manager import save_file - for attachment in attachments: - attachment.pop("print_format_attachment", None) - print_format_file = frappe._dict(frappe.attach_print(**attachment)) - save_file(print_format_file.fname, print_format_file.fcontent, 'Notification Log', docname) def send_notification_email(doc): diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 8ed0bae067..3940625e1d 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -164,7 +164,7 @@ def get_context(context): 'document_name': doc.name, 'subject': subject, 'email_content': frappe.render_template(self.message, context), - 'attachments': attachments + 'attachment': attachments[0] } enqueue_create_notification(recipients, notification_doc) From c60a3ebe6a6eb679857a05806d0d138662d1fa29 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 16:32:49 +0530 Subject: [PATCH 028/222] fix: send notification to all users --- frappe/desk/doctype/notification_log/notification_log.py | 1 + frappe/email/doctype/notification/notification.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/notification_log/notification_log.py b/frappe/desk/doctype/notification_log/notification_log.py index 244d0e150c..90ae5a437e 100644 --- a/frappe/desk/doctype/notification_log/notification_log.py +++ b/frappe/desk/doctype/notification_log/notification_log.py @@ -49,6 +49,7 @@ def enqueue_create_notification(users, doc): if isinstance(users, frappe.string_types): users = [user.strip() for user in users.split(',') if user.strip()] + users = list(set(users)) frappe.enqueue( 'frappe.desk.doctype.notification_log.notification_log.make_notification_logs', diff --git a/frappe/email/doctype/notification/notification.py b/frappe/email/doctype/notification/notification.py index 3940625e1d..38077154cd 100644 --- a/frappe/email/doctype/notification/notification.py +++ b/frappe/email/doctype/notification/notification.py @@ -166,7 +166,7 @@ def get_context(context): 'email_content': frappe.render_template(self.message, context), 'attachment': attachments[0] } - enqueue_create_notification(recipients, notification_doc) + enqueue_create_notification(users, notification_doc) def send_an_email(self, doc, context): from email.utils import formataddr From 080301925169d60fc9e02fbd1812d6d7d6e575fa Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 18:25:21 +0530 Subject: [PATCH 029/222] style: fix formatting --- frappe/public/js/frappe/list/list_settings.js | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index 6cebe421d7..f2045c9c34 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -251,7 +251,7 @@ export default class ListSettings { me.fields.push({ label: field.label, fieldname: field.fieldname - }) + }); } } } @@ -339,7 +339,6 @@ export default class ListSettings { } get_doctype_fields(meta, fields) { - let me = this; let multiselect_fields = []; meta.fields.forEach(field => { From 68e5a1d3e6575460122834786901dabe640846d2 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 18:29:07 +0530 Subject: [PATCH 030/222] fix: remove permission for all in list view settings --- .../list_view_settings/list_view_settings.json | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/frappe/desk/doctype/list_view_settings/list_view_settings.json b/frappe/desk/doctype/list_view_settings/list_view_settings.json index 486d2c1c44..44761992f1 100644 --- a/frappe/desk/doctype/list_view_settings/list_view_settings.json +++ b/frappe/desk/doctype/list_view_settings/list_view_settings.json @@ -52,7 +52,7 @@ } ], "links": [], - "modified": "2020-05-05 15:15:14.306665", + "modified": "2020-05-12 18:27:15.568199", "modified_by": "Administrator", "module": "Desk", "name": "List View Settings", @@ -67,18 +67,6 @@ "role": "System Manager", "share": 1, "write": 1 - }, - { - "create": 1, - "delete": 1, - "email": 1, - "export": 1, - "print": 1, - "read": 1, - "report": 1, - "role": "All", - "share": 1, - "write": 1 } ], "read_only": 1, From 77b59a14937ce79b778400e6d26feba8079e10c6 Mon Sep 17 00:00:00 2001 From: prssanna Date: Tue, 12 May 2020 19:21:29 +0530 Subject: [PATCH 031/222] fix: hide channel field if disabled --- frappe/email/doctype/notification/notification.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/email/doctype/notification/notification.json b/frappe/email/doctype/notification/notification.json index 736056056f..9e52a8b7a8 100644 --- a/frappe/email/doctype/notification/notification.json +++ b/frappe/email/doctype/notification/notification.json @@ -57,6 +57,7 @@ }, { "default": "Email", + "depends_on": "eval: !doc.disable_channel", "fieldname": "channel", "fieldtype": "Select", "label": "Channel", @@ -282,7 +283,7 @@ ], "icon": "fa fa-envelope", "links": [], - "modified": "2020-05-11 15:33:28.904019", + "modified": "2020-05-12 19:20:50.304928", "modified_by": "Administrator", "module": "Email", "name": "Notification", From a86ae948f7b35a4afff6594dbd1499eb6a516b03 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 18 May 2020 13:01:33 +0530 Subject: [PATCH 032/222] fix(commands): raise SiteNotSpecifiedError if site not found in context --- frappe/__init__.py | 3 +-- frappe/commands/__init__.py | 1 - frappe/commands/scheduler.py | 8 +++++++- frappe/commands/site.py | 35 +++++++++++++++++++++++++++++++++-- frappe/commands/translate.py | 2 ++ frappe/commands/utils.py | 26 ++++++++++++++++++++++++++ frappe/exceptions.py | 5 +++++ frappe/utils/bench_helper.py | 16 +++++++++------- 8 files changed, 83 insertions(+), 13 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index f0b6bfe41b..dbc16fa056 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -231,9 +231,8 @@ def get_site_config(sites_path=None, site_path=None): if os.path.exists(site_config): config.update(get_file_json(site_config)) elif local.site and not local.flags.new_site: - print("{0} does not exist".format(local.site)) + print("Site {0} does not exist".format(local.site)) sys.exit(1) - #raise IncorrectSitePath, "{0} does not exist".format(site_config) return _dict(config) diff --git a/frappe/commands/__init__.py b/frappe/commands/__init__.py index 8110f2ec19..7d36cbe28c 100644 --- a/frappe/commands/__init__.py +++ b/frappe/commands/__init__.py @@ -44,7 +44,6 @@ def get_site(context): site = context.sites[0] return site except (IndexError, TypeError): - print('Please specify --site sitename') sys.exit(1) def popen(command, *args, **kwargs): diff --git a/frappe/commands/scheduler.py b/frappe/commands/scheduler.py index 6f51c81211..d073c3d90e 100755 --- a/frappe/commands/scheduler.py +++ b/frappe/commands/scheduler.py @@ -4,6 +4,7 @@ import sys import frappe from frappe.utils import cint from frappe.commands import pass_context, get_site +from frappe.exceptions import SiteNotSpecifiedError def _is_scheduler_enabled(): enable_scheduler = False @@ -30,6 +31,8 @@ def trigger_scheduler_event(context, event): frappe.utils.scheduler.trigger(site, event, now=True) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('enable-scheduler') @pass_context @@ -45,6 +48,8 @@ def enable_scheduler(context): print("Enabled for", site) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('disable-scheduler') @pass_context @@ -60,7 +65,8 @@ def disable_scheduler(context): print("Disabled for", site) finally: frappe.destroy() - + else: + raise SiteNotSpecifiedError @click.command('scheduler') diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 82ed72dd5c..f50ec05af4 100755 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -15,6 +15,7 @@ import frappe from frappe import _ from frappe.commands import get_site, pass_context from frappe.commands.scheduler import _is_scheduler_enabled +from frappe.exceptions import SiteNotSpecifiedError from frappe.installer import update_site_config from frappe.utils import get_site_path, touch_file @@ -192,6 +193,8 @@ def install_app(context, apps): _install_app(app, verbose=context.verbose) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('list-apps') @pass_context @@ -221,7 +224,8 @@ def add_system_manager(context, email, first_name, last_name, send_welcome_email frappe.db.commit() finally: frappe.destroy() - + else: + raise SiteNotSpecifiedError @click.command('disable-user') @click.argument('email') @@ -252,6 +256,8 @@ def migrate(context, rebuild_website=False, skip_failing=False): migrate(context.verbose, rebuild_website=rebuild_website, skip_failing=skip_failing) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError print("Compiling Python Files...") compileall.compile_dir('../apps', quiet=1, rx=re.compile('.*node_modules.*')) @@ -264,6 +270,8 @@ def migrate_to(context, frappe_provider): from frappe.integrations.frappe_providers import migrate_to for site in context.sites: migrate_to(site, frappe_provider) + else: + raise SiteNotSpecifiedError @click.command('run-patch') @click.argument('module') @@ -278,6 +286,8 @@ def run_patch(context, module): frappe.modules.patch_handler.run_single(module, force=context.force) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('reload-doc') @click.argument('module') @@ -294,6 +304,8 @@ def reload_doc(context, module, doctype, docname): frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('reload-doctype') @click.argument('doctype') @@ -308,6 +320,8 @@ def reload_doctype(context, doctype): frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('add-to-hosts') @pass_context @@ -315,6 +329,8 @@ def add_to_hosts(context): "Add site to hosts" for site in context.sites: frappe.commands.popen('echo 127.0.0.1\t{0} | sudo tee -a /etc/hosts'.format(site)) + else: + raise SiteNotSpecifiedError @click.command('use') @click.argument('site') @@ -328,7 +344,7 @@ def use(site, sites_path='.'): sitefile.write(site) print("Current Site set to {}".format(site)) else: - print("{} does not exist".format(site)) + print("Site {} does not exist".format(site)) @click.command('backup') @click.option('--with-files', default=False, is_flag=True, help="Take backup with files") @@ -361,6 +377,9 @@ def backup(context, with_files=False, backup_path_db=None, backup_path_files=Non print("Private files: ", odb.backup_path_private_files) frappe.destroy() + else: + raise SiteNotSpecifiedError + sys.exit(exit_code) @click.command('remove-from-installed-apps') @@ -376,6 +395,8 @@ def remove_from_installed_apps(context, app): remove_from_installed_apps(app) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('uninstall-app') @click.argument('app') @@ -392,6 +413,8 @@ def uninstall(context, app, dry_run=False, yes=False): remove_app(app, dry_run, yes) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('drop-site') @@ -483,6 +506,8 @@ def set_admin_password(context, admin_password, logout_all_sessions=False): admin_password = None finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('set-last-active-for-user') @click.option('--user', help="Setup last active date for user") @@ -528,6 +553,8 @@ def publish_realtime(context, event, message, room, user, doctype, docname, afte frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('browse') @click.argument('site', required=False) @@ -555,6 +582,8 @@ def start_recording(context): for site in context.sites: frappe.init(site=site) frappe.recorder.start() + else: + raise SiteNotSpecifiedError @click.command('stop-recording') @@ -563,6 +592,8 @@ def stop_recording(context): for site in context.sites: frappe.init(site=site) frappe.recorder.stop() + else: + raise SiteNotSpecifiedError commands = [ diff --git a/frappe/commands/translate.py b/frappe/commands/translate.py index 5a48e2b409..de4d8aab0b 100644 --- a/frappe/commands/translate.py +++ b/frappe/commands/translate.py @@ -15,6 +15,8 @@ def build_message_files(context): frappe.translate.rebuild_all_translation_files() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('new-language') #, help="Create lang-code.csv for given app") @pass_context diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 3610393d9a..835fa2d40e 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -51,6 +51,8 @@ def clear_cache(context): frappe.website.render.clear_cache() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('clear-website-cache') @@ -65,6 +67,8 @@ def clear_website_cache(context): frappe.website.render.clear_cache() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('destroy-all-sessions') @@ -81,6 +85,8 @@ def destroy_all_sessions(context, reason=None): frappe.db.commit() finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('show-config') @@ -117,6 +123,8 @@ def reset_perms(context): reset_perms(d) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('execute') @@ -163,6 +171,8 @@ def execute(context, method, args=None, kwargs=None, profile=False): frappe.destroy() if ret: print(json.dumps(ret, default=json_handler)) + else: + raise SiteNotSpecifiedError @click.command('add-to-email-queue') @@ -197,6 +207,8 @@ def export_doc(context, doctype, docname): frappe.modules.export_doc(doctype, docname) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('export-json') @@ -214,6 +226,8 @@ def export_json(context, doctype, path, name=None): data_import.export_json(doctype, path, name=name) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('export-csv') @@ -230,6 +244,8 @@ def export_csv(context, doctype, path): data_import.export_csv(doctype, path) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('export-fixtures') @@ -245,6 +261,8 @@ def export_fixtures(context, app=None): export_fixtures(app=app) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('import-doc') @@ -267,6 +285,8 @@ def import_doc(context, path, force=False): data_import.import_doc(path, overwrite=context.force) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('import-csv') @@ -577,6 +597,8 @@ def request(context, args=None, path=None): print(frappe.response) finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('make-app') @@ -610,6 +632,8 @@ def set_config(context, key, value, global_ = False, as_dict=False): frappe.init(site=site) update_site_config(key, value, validate=False) frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('version') @@ -658,6 +682,8 @@ def rebuild_global_search(context, static_pages=False): finally: frappe.destroy() + else: + raise SiteNotSpecifiedError @click.command('auto-deploy') diff --git a/frappe/exceptions.py b/frappe/exceptions.py index 5a1181f31e..1aac339228 100644 --- a/frappe/exceptions.py +++ b/frappe/exceptions.py @@ -13,6 +13,11 @@ if sys.version_info.major == 2: else: from builtins import FileNotFoundError +class SiteNotSpecifiedError(Exception): + def __init__(self, *args, **kwargs): + self.message = "Please specify --site sitename" + super(Exception, self).__init__(self.message) + class ValidationError(Exception): http_status_code = 417 diff --git a/frappe/utils/bench_helper.py b/frappe/utils/bench_helper.py index 7c5d209179..c46b42b132 100644 --- a/frappe/utils/bench_helper.py +++ b/frappe/utils/bench_helper.py @@ -50,14 +50,16 @@ def app_group(ctx, site=False, force=False, verbose=False, profile=False): ctx.info_name = '' def get_sites(site_arg): - if site_arg and site_arg == 'all': + if site_arg == 'all': return frappe.utils.get_sites() - else: - if site_arg: - return [site_arg] - if os.path.exists('currentsite.txt'): - with open('currentsite.txt') as f: - return [f.read().strip()] + elif site_arg: + return [site_arg] + elif os.path.exists('currentsite.txt'): + with open('currentsite.txt') as f: + site = f.read().strip() + if site: + return [site] + return [] def get_app_commands(app): if os.path.exists(os.path.join('..', 'apps', app, app, 'commands.py'))\ From 75cafdc359503065decd8ffb07a36059dee04377 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 19 May 2020 02:14:44 +0530 Subject: [PATCH 033/222] wip --- frappe/modules/full_text_search.py | 38 ++++++++++++++++++++++++++++++ requirements.txt | 1 + 2 files changed, 39 insertions(+) create mode 100644 frappe/modules/full_text_search.py diff --git a/frappe/modules/full_text_search.py b/frappe/modules/full_text_search.py new file mode 100644 index 0000000000..de0ee375b5 --- /dev/null +++ b/frappe/modules/full_text_search.py @@ -0,0 +1,38 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +from __future__ import unicode_literals +import frappe +import os +from whoosh.index import create_in, open_dir +from whoosh.fields import TEXT, ID, Schema + + +def build_index(index_name, documents): + schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT) + + index_dir = os.path.join(frappe.utils.get_bench_path(), "indexes", index_name) + frappe.create_folder(index_dir) + + ix = create_in(index_dir, schema) + writer = ix.writer() + + for document in documents: + writer.add_document( + title=document.title, path=document.path, content=document.content + ) + + writer.commit() + + +def search(index_name, text): + from whoosh.qparser import QueryParser + + index_dir = os.path.join(frappe.utils.get_bench_path(), "indexes", index_name) + ix = open_dir(index_dir) + + with ix.searcher() as searcher: + query = QueryParser("content", ix.schema).parse(text) + results = searcher.search(query) + + return results diff --git a/requirements.txt b/requirements.txt index 431f216afa..5d46f799be 100644 --- a/requirements.txt +++ b/requirements.txt @@ -66,3 +66,4 @@ watchdog==0.8.0 Werkzeug==0.16.1 xlrd==1.2.0 zxcvbn-python==4.4.24 +Whoosh==2.7.4 From 4cbe236cf1420e638369b6b91df839158deaf2a4 Mon Sep 17 00:00:00 2001 From: "Chinmay D. Pai" Date: Tue, 19 May 2020 13:21:54 +0530 Subject: [PATCH 034/222] fix: make file name and url check stricter * disallow '/' in filenames * check if the file url points to a directory inside /files/ or /private/files - raises error otherwise Signed-off-by: Chinmay D. Pai --- frappe/core/doctype/file/file.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index b35abfa861..a17b3acd02 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -48,6 +48,8 @@ class File(Document): def before_insert(self): frappe.local.rollback_observers.append(self) self.set_folder_name() + if self.file_name: + self.file_name = re.sub(r'/', '', self.file_name) self.content = self.get("content", None) self.decode = self.get("decode", False) if self.content: @@ -192,6 +194,8 @@ class File(Document): def set_file_name(self): if not self.file_name and self.file_url: self.file_name = self.file_url.split('/')[-1] + else: + self.file_name = re.sub(r'/', '', self.file_name) def generate_content_hash(self): if self.content_hash or not self.file_url or self.file_url.startswith('http'): @@ -405,6 +409,12 @@ class File(Document): frappe.throw(_("URL must start with 'http://' or 'https://'")) return + if not self.file_url.startswith(("http://", "https://")): + # local file + root_files_path = get_files_path(is_private=self.is_private) + if not os.path.commonpath([root_files_path]) == os.path.commonpath([root_files_path, self.get_full_path()]): + # basically the file url is skewed to not point to /files/ or /private/files + frappe.throw(_("{0} is not a valid file url").format(self.file_url)) self.file_url = unquote(self.file_url) self.file_size = frappe.form_dict.file_size or self.file_size From 2813a0edc1a92607d4e90fb43b87c59514c6c909 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 22 May 2020 13:31:49 +0530 Subject: [PATCH 035/222] feat: added webhook hmac verification --- .../razorpay_settings/razorpay_settings.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py index 5e464d4882..98ac022fc4 100644 --- a/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py +++ b/frappe/integrations/doctype/razorpay_settings/razorpay_settings.py @@ -64,6 +64,9 @@ from __future__ import unicode_literals import frappe from frappe import _ import json +import hmac +import sys +import hashlib from six.moves.urllib.parse import urlencode from frappe.model.document import Document from frappe.utils import get_url, call_hook_method, cint, get_timestamp @@ -317,6 +320,27 @@ class RazorpaySettings(Document): except Exception: frappe.log_error(frappe.get_traceback()) + def verify_signature(self, body, signature, key): + if sys.version_info[0] == 3: + key = bytes(key, 'utf-8') + body = bytes(body, 'utf-8') + + dig = hmac.new(key=key, + msg=body, + digestmod=hashlib.sha256) + + generated_signature = dig.hexdigest() + + if sys.version_info[0:3] < (2, 7, 7): + result = self.compare_string(generated_signature, signature) + else: + result = hmac.compare_digest(generated_signature, signature) + + if not result: + frappe.throw(_('Razorpay Signature Verification Failed'), exc=frappe.PermissionError) + + return result + def capture_payment(is_sandbox=False, sanbox_response=None): """ Verifies the purchase as complete by the merchant. From a8720d9554ceade97dbd57d9ae80dc92c357e907 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 19 May 2020 14:24:20 +0530 Subject: [PATCH 036/222] fix: Extract page toc from markdown --- frappe/public/scss/markdown.scss | 38 ++++++++++++++++++++++++++++++++ frappe/utils/data.py | 1 + frappe/website/router.py | 3 +++ 3 files changed, 42 insertions(+) diff --git a/frappe/public/scss/markdown.scss b/frappe/public/scss/markdown.scss index 595b7f96a3..50f46eae19 100644 --- a/frappe/public/scss/markdown.scss +++ b/frappe/public/scss/markdown.scss @@ -122,3 +122,41 @@ main:not(.my-5) .from-markdown { margin-top: 5rem; } } + +.page-toc { + font-size: $font-size-sm; + + h5 { + font-size: $font-size-sm; + margin-bottom: 0.5rem; + color: $gray-500; + } + + > div { + padding-top: 2rem; + position: sticky; + top: 0; + } + + ul { + padding-left: 0; + list-style-type: none; + } + + li > ul { + padding-left: 0.5rem; + } + + a { + display: block; + padding: 0.25rem 0; + + color: $gray-600; + text-decoration: none; + font-weight: 500; + + &:hover { + color: $gray-700; + } + } +} diff --git a/frappe/utils/data.py b/frappe/utils/data.py index a0703c1465..d2ccb42cb8 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1129,6 +1129,7 @@ def md_to_html(markdown_text): 'fenced-code-blocks': None, 'tables': None, 'header-ids': None, + 'toc': None, 'highlightjs-lang': None, 'html-classes': { 'table': 'table table-bordered', diff --git a/frappe/website/router.py b/frappe/website/router.py index 4a9db0868f..1e999c1611 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -271,6 +271,9 @@ def setup_source(page_info): if page_info.template.endswith('.md'): source = frappe.utils.md_to_html(source) + if page_info.page_toc: + page_info.page_toc_html = source.toc_html + if not page_info.show_sidebar: source = '
' + source + '
' From 501f9a1cf875124ff1cfd614023a3f13504dbca2 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 19 May 2020 14:24:37 +0530 Subject: [PATCH 037/222] fix: Remove whitespace from breadcrumbs block --- frappe/templates/includes/breadcrumbs.html | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/templates/includes/breadcrumbs.html b/frappe/templates/includes/breadcrumbs.html index 3fda731372..4cb3ef5c0c 100644 --- a/frappe/templates/includes/breadcrumbs.html +++ b/frappe/templates/includes/breadcrumbs.html @@ -1,4 +1,4 @@ -{% if not no_breadcrumbs and parents %} +{%- if not no_breadcrumbs and parents -%}
-{% endif %} +{%- endif -%} From 256898e23e53960831fe4afa30e50b9d74e4ac59 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 19 May 2020 14:25:41 +0530 Subject: [PATCH 038/222] feat: Doc page layout (wip) --- frappe/templates/doc.html | 70 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) create mode 100644 frappe/templates/doc.html diff --git a/frappe/templates/doc.html b/frappe/templates/doc.html new file mode 100644 index 0000000000..edd85b975b --- /dev/null +++ b/frappe/templates/doc.html @@ -0,0 +1,70 @@ +{% extends base_template_path %} + +{% macro page_content() %} +{%- block page_content -%}{%- endblock -%} +{% endmacro %} + +{% block content %} + +{% macro main_content() %} +
+ + + + asdfasdf + + {% block page_container %} +
+
+ {{ page_content() }} +
+ {%- if page_toc -%} +
+
+
On this page
+ {{ page_toc_html }} +
+ {% include "templates/includes/web_sidebar.html" %} +
+ {%- endif -%} +
+ {% endblock %} +
+{% endmacro %} + +{% macro sidebar() %} +{%- if show_sidebar -%} + +{%- endif -%} +{% endmacro %} + +{% macro container_attributes() -%} +id="page-{{ name or route | e }}" data-path="{{ pathname | e }}" +{%- if page_or_generator=="Generator" %}source-type="Generator" data-doctype="{{ doctype }}"{%- endif %} +{%- if source_content_type %}source-content-type="{{ source_content_type }}"{%- endif %} +{%- endmacro %} + +
+
+ {%- set columns = (sidebar_columns or 2) if show_sidebar else 0 -%} + {%- if not sidebar_right -%} + {{ sidebar() }} + {%- endif -%} +
+ {{ main_content() }} +
+ {%- if sidebar_right -%} + {{ sidebar() }} + {%- endif -%} +
+
+ +{% endblock %} From 28c70a6aedfbf7b8aa61460ad0d7214111072994 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 19 May 2020 14:26:16 +0530 Subject: [PATCH 039/222] fix: Set font to Inter temporarily --- frappe/public/scss/base.scss | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/frappe/public/scss/base.scss b/frappe/public/scss/base.scss index 36a1df55ac..9f0797eb4e 100644 --- a/frappe/public/scss/base.scss +++ b/frappe/public/scss/base.scss @@ -1,3 +1,10 @@ +@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); + +html, body { + font-family: Inter; +} + + html { height: 100%; } From 5b1dacb6a177bd5a4db41652a080083984a2912a Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Sun, 24 May 2020 07:33:46 +0530 Subject: [PATCH 040/222] feat: Set base_template for routes by regex The default `base_template` for any web route was `templates/web.html` by default. Now, you can set the `base_template` for routes by a regex pattern via hooks (using the key `base_template_map`). For e.g, you can set `templates/doc.html` for routes that match the pattern `docs.*` --- frappe/hooks.py | 6 ++++++ frappe/website/context.py | 2 +- frappe/website/router.py | 39 +++++++++++++++++++++++---------------- 3 files changed, 30 insertions(+), 17 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index 200280f6de..9c63f05b99 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -56,6 +56,12 @@ website_route_rules = [ {"from_route": "/profile", "to_route": "me"}, ] +base_template = "templates/base.html" + +base_template_map = { + r".*": "templates/web.html" +} + write_file_keys = ["file_url", "file_name"] notification_config = "frappe.core.notifications.get_notification_config" diff --git a/frappe/website/context.py b/frappe/website/context.py index 5663199545..9d0f3d2067 100644 --- a/frappe/website/context.py +++ b/frappe/website/context.py @@ -120,7 +120,7 @@ def build_context(context): # determine templates to be used if not context.base_template_path: app_base = frappe.get_hooks("base_template") - context.base_template_path = app_base[0] if app_base else "templates/base.html" + context.base_template_path = app_base[-1] if app_base else "templates/base.html" if context.title_prefix and context.title and not context.title.startswith(context.title_prefix): context.title = '{0} - {1}'.format(context.title_prefix, context.title) diff --git a/frappe/website/router.py b/frappe/website/router.py index 1e999c1611..c915578300 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -277,9 +277,12 @@ def setup_source(page_info): if not page_info.show_sidebar: source = '
' + source + '
' + if not page_info.base_template: + page_info.base_template = get_base_template(page_info.route) + # if only content if page_info.template.endswith('.html') or page_info.template.endswith('.md'): - html = extend_from_base_template(page_info, source) + html = source # load css/js files js, css = '', '' @@ -303,22 +306,23 @@ def setup_source(page_info): # show table of contents setup_index(page_info) -def extend_from_base_template(page_info, source): - '''Extend the content with appropriate base template if required. - - For easy composition, the users will only add the content of the page, - not its template. But if the user has explicitly put Jinja blocks, or tags, - or comment tags like - then the system will not try and put it inside the "web.template" +def get_base_template(path=None): ''' + Returns the `base_template` for given `path`. + The default `base_template` for any web route is `templates/web.html` defined in `hooks.py`. + This can be overridden for certain routes in `custom_app/hooks.py` based on regex pattern. + ''' + if not path: + path = frappe.local.request.path - if (('' not in source) and ('{% block' not in source) - and (' - - - asdfasdf - {% block page_container %} -
-
+
+
+ {{ page_content() }}
- {%- if page_toc -%} -
-
-
On this page
- {{ page_toc_html }} -
- {% include "templates/includes/web_sidebar.html" %} -
- {%- endif -%}
{% endblock %}
{% endmacro %} -{% macro sidebar() %} -{%- if show_sidebar -%} - -{%- endif -%} -{% endmacro %} - {% macro container_attributes() -%} id="page-{{ name or route | e }}" data-path="{{ pathname | e }}" {%- if page_or_generator=="Generator" %}source-type="Generator" data-doctype="{{ doctype }}"{%- endif %} {%- if source_content_type %}source-content-type="{{ source_content_type }}"{%- endif %} {%- endmacro %} -
-
- {%- set columns = (sidebar_columns or 2) if show_sidebar else 0 -%} - {%- if not sidebar_right -%} - {{ sidebar() }} +
+
+ {%- if show_sidebar -%} + {%- endif -%} -
+
{{ main_content() }}
- {%- if sidebar_right -%} - {{ sidebar() }} - {%- endif -%} +
+ {%- if page_toc -%} +
+
On this page
+ {{ page_toc_html }} +
+ {%- endif -%} +
diff --git a/frappe/templates/includes/web_sidebar.html b/frappe/templates/includes/web_sidebar.html index d7816eff34..be762b0628 100644 --- a/frappe/templates/includes/web_sidebar.html +++ b/frappe/templates/includes/web_sidebar.html @@ -1,46 +1,71 @@ +{% macro render_sidebar_item(item) %} +
  • + {%- if item.group_title -%} + +
    {{ item.group_title }}
    + {{ render_sidebar_items(item.group_items) }} + + {%- else -%} + + {% if item.type != 'input' %} + {%- set item_route = item.route[1:] if item.route[0] == '/' else item.route -%} + + {{ _(item.title or item.label) }} + + {% else %} +
    + +
    + {% endif %} + + {%- endif -%} +
  • +{% endmacro %} + +{% macro render_sidebar_items(items) %} +{%- if items | len > 0 -%} +
      + {% for item in items -%} + {{ render_sidebar_item(item) }} + {%- endfor %} +
    +{%- endif -%} +{% endmacro %} + +{% macro my_account() %} +{% if frappe.user != 'Guest' %} + +{% endif %} +{% endmacro %} +
    + {% if sidebar_title %} +
  • + {{ sidebar_title }} +
  • + {% endif %}
    diff --git a/frappe/website/js/website.js b/frappe/website/js/website.js index d400e7633c..ac27b677a3 100644 --- a/frappe/website/js/website.js +++ b/frappe/website/js/website.js @@ -329,6 +329,22 @@ $.extend(frappe, { add_switch_to_desk: function() { $('.switch-to-desk').removeClass('hidden'); }, + add_link_to_headings: function() { + $('.from-markdown').find('h2, h3, h4, h5, h6').each((i, $heading) => { + let id = $heading.id; + let $a = $('') + .prop('href', '#' + id) + .attr('aria-hidden', 'true') + .html(` + + + + + `); + $($heading).append($a); + }); + }, setup_lazy_images: function() { // Use IntersectionObserver to only load images that are visible in the viewport // Fallback for browsers that don't support it @@ -445,6 +461,7 @@ $(document).on("page-change", function() { frappe.trigger_ready(); frappe.bind_filters(); frappe.highlight_code_blocks(); + frappe.add_link_to_headings(); frappe.make_navbar_active(); // scroll to hash if (window.location.hash) { From c329a2c1aff8395aaf7847500bb8521dedfca810 Mon Sep 17 00:00:00 2001 From: prssanna Date: Mon, 25 May 2020 12:22:19 +0530 Subject: [PATCH 042/222] fix: add update_after_submit to actions while setting worflow state --- frappe/model/workflow.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/model/workflow.py b/frappe/model/workflow.py index 4384e7c8f5..ea563dfc13 100644 --- a/frappe/model/workflow.py +++ b/frappe/model/workflow.py @@ -299,6 +299,7 @@ def set_workflow_state_on_action(doc, workflow_name, action): return action_map = { + 'update_after_submit': '1', 'submit': '1', 'cancel': '2' } From b17b0d9077a694b627a3de5d5abc1d9fbadcc09f Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Wed, 27 May 2020 18:02:16 +0530 Subject: [PATCH 043/222] feat: Doc search --- frappe/modules/full_text_search.py | 56 ++++++++++++++++++++--- frappe/templates/doc.html | 71 ++++++++++++++++++++++++++++-- frappe/www/website_script.py | 2 +- 3 files changed, 119 insertions(+), 10 deletions(-) diff --git a/frappe/modules/full_text_search.py b/frappe/modules/full_text_search.py index de0ee375b5..367c1481e6 100644 --- a/frappe/modules/full_text_search.py +++ b/frappe/modules/full_text_search.py @@ -6,10 +6,46 @@ import frappe import os from whoosh.index import create_in, open_dir from whoosh.fields import TEXT, ID, Schema +from bs4 import BeautifulSoup +from frappe.website.render import render_page +from frappe.utils import set_request +from frappe.utils.global_search import get_routes_to_index + + +def build_index_for_all_routes(): + routes = get_routes_to_index() + documents = [get_document_to_index(route) for route in routes] + build_index("web_routes", documents) + + +def get_document_to_index(route): + frappe.set_user("Guest") + frappe.local.no_cache = True + + try: + set_request(method="GET", path=route) + content = render_page(route) + soup = BeautifulSoup(content, "html.parser") + page_content = soup.find(class_="page_content") + text_content = page_content.text if page_content else "" + title = soup.title.text.strip() if soup.title else route + + frappe.set_user("Administrator") + + return frappe._dict(title=title, content=text_content, path=route) + except ( + frappe.PermissionError, + frappe.DoesNotExistError, + frappe.ValidationError, + Exception, + ): + pass + def build_index(index_name, documents): - schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT) + print("Building index " + index_name) + schema = Schema(title=TEXT(stored=True), path=ID(stored=True), content=TEXT(stored=True)) index_dir = os.path.join(frappe.utils.get_bench_path(), "indexes", index_name) frappe.create_folder(index_dir) @@ -18,11 +54,13 @@ def build_index(index_name, documents): writer = ix.writer() for document in documents: - writer.add_document( - title=document.title, path=document.path, content=document.content - ) + if document: + writer.add_document( + title=document.title, path=document.path, content=document.content + ) writer.commit() + print("Done.") def search(index_name, text): @@ -31,8 +69,16 @@ def search(index_name, text): index_dir = os.path.join(frappe.utils.get_bench_path(), "indexes", index_name) ix = open_dir(index_dir) + results = None + out = [] with ix.searcher() as searcher: query = QueryParser("content", ix.schema).parse(text) results = searcher.search(query) + for r in results: + out.append(frappe._dict(title=r['title'], path=r['path'], highlights=r.highlights('content'))) - return results + return out + +@frappe.whitelist(allow_guest=True) +def web_search(query): + return search("web_routes", query) diff --git a/frappe/templates/doc.html b/frappe/templates/doc.html index e1beecefd2..af36c88d0f 100644 --- a/frappe/templates/doc.html +++ b/frappe/templates/doc.html @@ -5,7 +5,7 @@ {% endmacro %} {%- block head_include %} - + {% endblock -%} {%- block navbar -%}{%- endblock -%} @@ -17,11 +17,20 @@ {% block page_container %}
    - @@ -67,3 +76,57 @@ id="page-{{ name or route | e }}" data-path="{{ pathname | e }}"
    {% endblock %} + +{%- block script -%} + +{%- endblock -%} diff --git a/frappe/www/website_script.py b/frappe/www/website_script.py index 9d6ba1065e..6ce17e62e5 100644 --- a/frappe/www/website_script.py +++ b/frappe/www/website_script.py @@ -6,7 +6,7 @@ import frappe from frappe.utils import strip from frappe.website.doctype.website_theme.website_theme import get_active_theme -base_template_path = "templates/www/website_script.js" +base_template_path = "frappe/www/website_script.js" def get_context(context): context.javascript = frappe.db.get_single_value('Website Script', From 5d83e94b098cf33b7096153fbde4755d728ee17b Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 11:56:09 +0530 Subject: [PATCH 044/222] fix(patch): create a logs folder under respective sites --- frappe/patches.txt | 1 + frappe/patches/v13_0/site_wise_logging.py | 10 ++++++++++ 2 files changed, 11 insertions(+) create mode 100644 frappe/patches/v13_0/site_wise_logging.py diff --git a/frappe/patches.txt b/frappe/patches.txt index a086fa6f4a..6b6bd173b8 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -278,3 +278,4 @@ frappe.patches.v13_0.set_path_for_homepage_in_web_page_view frappe.patches.v13_0.migrate_translation_column_data frappe.patches.v13_0.set_read_times frappe.patches.v13_0.remove_web_view +frappe.patches.v13_0.site_wise_logging diff --git a/frappe/patches/v13_0/site_wise_logging.py b/frappe/patches/v13_0/site_wise_logging.py new file mode 100644 index 0000000000..6f04e0c9dd --- /dev/null +++ b/frappe/patches/v13_0/site_wise_logging.py @@ -0,0 +1,10 @@ +import os +import frappe + + +def execute(): + site = frappe.local.site + + log_folder = os.path.join(site, 'logs') + if not os.path.exists(log_folder): + os.mkdir(log_folder) \ No newline at end of file From c39a52d76416d88c9e50f00dabfd6eecb70127ea Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 18:33:03 +0530 Subject: [PATCH 045/222] feat: seperate loggers into frappe, web, scheduler categories and not by module --- frappe/__init__.py | 2 +- .../scheduled_job_type/scheduled_job_type.py | 2 +- frappe/utils/error.py | 2 +- frappe/utils/logger.py | 34 ++++++++++--------- frappe/utils/scheduler.py | 34 +++++++++++++------ 5 files changed, 45 insertions(+), 29 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 7664ac4c61..56017e2bc3 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1562,7 +1562,7 @@ log_level = None def logger(module=None, with_more_info=True): '''Returns a python logger that uses StreamHandler''' from frappe.utils.logger import get_logger - return get_logger(module or 'default', with_more_info=with_more_info) + return get_logger(module=module, with_more_info=with_more_info) def log_error(message=None, title=_("Error")): '''Log error to Error Log''' diff --git a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py index c179054550..765ae5fe93 100644 --- a/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py +++ b/frappe/core/doctype/scheduled_job_type/scheduled_job_type.py @@ -84,7 +84,7 @@ class ScheduledJobType(Document): def log_status(self, status): # log file - frappe.logger(__name__).info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site)) + frappe.logger("scheduler").info('Scheduled Job {0}: {1} for {2}'.format(status, self.method, frappe.local.site)) self.update_scheduler_log(status) def update_scheduler_log(self, status): diff --git a/frappe/utils/error.py b/frappe/utils/error.py index c124410a7f..bb83815d18 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -21,7 +21,7 @@ def make_error_snapshot(exception): if frappe.conf.disable_error_snapshot: return - logger = frappe.logger(__name__, with_more_info=False) + logger = frappe.logger(with_more_info=False) try: error_id = '{timestamp:s}-{ip:s}-{hash:s}'.format( diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index e446b39ee0..5e202f87fb 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -15,35 +15,37 @@ import frappe default_log_level = logging.DEBUG site = getattr(frappe.local, 'site', None) -LOG_FILENAME = os.path.join('..', 'logs', 'frappe.log') +form_dict = getattr(frappe.local, 'form_dict', None) def get_logger(module, with_more_info=True): if module in frappe.loggers: return frappe.loggers[module] - formatter = logging.Formatter('[%(levelname)s] %(asctime)s | %(pathname)s:\n%(message)s') + if not module: + module = "frappe" + logfile = module + '.log' + LOG_FILENAME = os.path.join('..', 'logs', logfile) + + logger = logging.getLogger(module) + logger.setLevel(frappe.log_level or default_log_level) + logger.propagate = False + + formatter = logging.Formatter('%(asctime)s %(levelname)s %(name)s %(message)s') handler = RotatingFileHandler(LOG_FILENAME, maxBytes=100_000, backupCount=20) - handler.setFormatter(formatter) - + logger.addHandler(handler) +# if site: - SITELOG_FOLDER = os.path.join(site, 'logs') - SITELOG_FILENAME = os.path.join(SITELOG_FOLDER, 'frappe.log') - - if not os.path.exists(SITELOG_FOLDER): - os.mkdir(SITELOG_FOLDER) - - handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) - handler.setFormatter(formatter) + SITELOG_FILENAME = os.path.join(site, 'logs', logfile) + site_handler = RotatingFileHandler(SITELOG_FILENAME, maxBytes=100_000, backupCount=20) + site_handler.setFormatter(formatter) + logger.addHandler(site_handler) if with_more_info: handler.addFilter(SiteContextFilter()) - logger = logging.getLogger(module) - logger.setLevel(frappe.log_level or default_log_level) - logger.addHandler(handler) - logger.propagate = False + handler.setFormatter(formatter) frappe.loggers[module] = logger diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index 596595a160..e05419805c 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -7,17 +7,24 @@ Events: monthly weekly """ +# imports - compatibility imports +from __future__ import print_function, unicode_literals -from __future__ import unicode_literals, print_function +# imports - standard imports +import os +import time -import frappe, os, time +# imports - third party imports import schedule -from frappe.utils import now_datetime, get_datetime -from frappe.utils import get_sites -from frappe.installer import update_site_config + +# imports - module imports +import frappe from frappe.core.doctype.user.user import STANDARD_USERS +from frappe.installer import update_site_config +from frappe.utils import get_datetime, get_sites, now_datetime from frappe.utils.background_jobs import get_jobs + DATETIME_FORMAT = '%Y-%m-%d %H:%M:%S' def start_scheduler(): @@ -48,9 +55,16 @@ def enqueue_events_for_all_sites(): def enqueue_events_for_site(site): def log_and_raise(): - frappe.logger(__name__).error('Exception in Enqueue Events for Site {0}'.format(site) + - '\n' + frappe.get_traceback()) - raise # pylint: disable=misplaced-bare-raise + error_message = 'Exception in Enqueue Events for Site {0}\n{1}'.format(site, frappe.get_traceback()) + error_message = json.dumps({ + "type": "Exception", + "event": "Enqueue Events", + "site": site, + "traceback": frappe.get_traceback() + }) + + frappe.logger("scheduler").error(error_message) + SITELOG_FILENAME = os.path.join(site or "..", 'logs', 'schedule.log') try: frappe.init(site=site) @@ -60,10 +74,10 @@ def enqueue_events_for_site(site): enqueue_events(site=site) - frappe.logger(__name__).debug('Queued events for site {0}'.format(site)) + frappe.logger("scheduler").debug('Queued events for site {0}'.format(site)) except frappe.db.OperationalError as e: if frappe.db.is_access_denied(e): - frappe.logger(__name__).debug('Access denied for site {0}'.format(site)) + frappe.logger("scheduler").debug('Access denied for site {0}'.format(site)) else: log_and_raise() except: From 746b540af8845f495b8b19be5cf77f23a06fa6f2 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 19:09:18 +0530 Subject: [PATCH 046/222] fix: Add Form Dict information only for frappe.log --- frappe/__init__.py | 2 +- frappe/app.py | 11 ++++++++++- frappe/utils/error.py | 2 +- frappe/utils/logger.py | 28 +++++++--------------------- 4 files changed, 19 insertions(+), 24 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 56017e2bc3..d958f1ad86 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1559,7 +1559,7 @@ def get_doctype_app(doctype): loggers = {} log_level = None -def logger(module=None, with_more_info=True): +def logger(module=None, with_more_info=False): '''Returns a python logger that uses StreamHandler''' from frappe.utils.logger import get_logger return get_logger(module=module, with_more_info=with_more_info) diff --git a/frappe/app.py b/frappe/app.py index 41798b0bc4..5f9a22891f 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -96,6 +96,16 @@ def application(request): frappe.monitor.stop(response) frappe.recorder.dump() + frappe.logger("web").info({ + "site": get_site_name(request.host), + "remote_addr": request.remote_addr, + "base_url": request.base_url, + "full_path": request.full_path, + "method": request.method, + "scheme": request.scheme, + "http_status_code": response.status_code + }) + frappe.destroy() return response @@ -186,7 +196,6 @@ def handle_exception(e): frappe.local.login_manager.clear_cookies() if http_status_code >= 500: - frappe.logger().error('Request Error', exc_info=True) make_error_snapshot(e) if return_as_message: diff --git a/frappe/utils/error.py b/frappe/utils/error.py index bb83815d18..af8c0952e8 100644 --- a/frappe/utils/error.py +++ b/frappe/utils/error.py @@ -21,7 +21,7 @@ def make_error_snapshot(exception): if frappe.conf.disable_error_snapshot: return - logger = frappe.logger(with_more_info=False) + logger = frappe.logger(with_more_info=True) try: error_id = '{timestamp:s}-{ip:s}-{hash:s}'.format( diff --git a/frappe/utils/logger.py b/frappe/utils/logger.py index 5e202f87fb..89e3711b0f 100755 --- a/frappe/utils/logger.py +++ b/frappe/utils/logger.py @@ -15,17 +15,19 @@ import frappe default_log_level = logging.DEBUG site = getattr(frappe.local, 'site', None) -form_dict = getattr(frappe.local, 'form_dict', None) -def get_logger(module, with_more_info=True): +def get_logger(module, with_more_info=False): + global site if module in frappe.loggers: return frappe.loggers[module] if not module: module = "frappe" + with_more_info = True logfile = module + '.log' + site = getattr(frappe.local, 'site', None) LOG_FILENAME = os.path.join('..', 'logs', logfile) logger = logging.getLogger(module) @@ -54,25 +56,9 @@ def get_logger(module, with_more_info=True): class SiteContextFilter(logging.Filter): """This is a filter which injects request information (if available) into the log.""" def filter(self, record): - record.msg = get_more_info_for_log() + text_type(record.msg) - return True - -def get_more_info_for_log(): - '''Adds Site, Form Dict into log entry''' - more_info = [] - - if site: - more_info.append('Site: {0}'.format(site)) - - form_dict = getattr(frappe.local, 'form_dict', None) - if form_dict: - more_info.append('Form Dict: {0}'.format(frappe.as_json(form_dict))) - - if more_info: - # to append a \n - more_info = more_info + [''] - - return '\n'.join(more_info) + if "Form Dict" not in text_type(record.msg): + record.msg = text_type(record.msg) + "\nSite: {0}\nForm Dict: {1}".format(site, getattr(frappe.local, 'form_dict', None)) + return True def set_log_level(level): '''Use this method to set log level to something other than the default DEBUG''' From 9a6151ec32c8682b564a04bc52c6458748b8bd5c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 19:10:04 +0530 Subject: [PATCH 047/222] perf: memoize get_site_name function --- frappe/utils/__init__.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/utils/__init__.py b/frappe/utils/__init__.py index 956846d50c..1574bbb30c 100644 --- a/frappe/utils/__init__.py +++ b/frappe/utils/__init__.py @@ -6,6 +6,7 @@ from __future__ import unicode_literals, print_function from werkzeug.test import Client import os, re, sys, json, hashlib, requests, traceback +import functools from .html_utils import sanitize_html import frappe from frappe.utils.identicon import Identicon @@ -360,6 +361,7 @@ def decode_dict(d, encoding="utf-8"): return d +@functools.lru_cache() def get_site_name(hostname): return hostname.split(':')[0] From 6b88c5979e0ed56ccdcca7f5191b90ca00a2ae3e Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 27 May 2020 19:53:51 +0530 Subject: [PATCH 048/222] chore: drop unused code --- frappe/utils/scheduler.py | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/frappe/utils/scheduler.py b/frappe/utils/scheduler.py index e05419805c..749a41682f 100755 --- a/frappe/utils/scheduler.py +++ b/frappe/utils/scheduler.py @@ -21,7 +21,7 @@ import schedule import frappe from frappe.core.doctype.user.user import STANDARD_USERS from frappe.installer import update_site_config -from frappe.utils import get_datetime, get_sites, now_datetime +from frappe.utils import get_sites, now_datetime from frappe.utils.background_jobs import get_jobs @@ -56,15 +56,7 @@ def enqueue_events_for_all_sites(): def enqueue_events_for_site(site): def log_and_raise(): error_message = 'Exception in Enqueue Events for Site {0}\n{1}'.format(site, frappe.get_traceback()) - error_message = json.dumps({ - "type": "Exception", - "event": "Enqueue Events", - "site": site, - "traceback": frappe.get_traceback() - }) - frappe.logger("scheduler").error(error_message) - SITELOG_FILENAME = os.path.join(site or "..", 'logs', 'schedule.log') try: frappe.init(site=site) From aabbfb8df823e59ff7ea565254c08b1526c1a499 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 18:17:41 +0530 Subject: [PATCH 049/222] feat: switch teams if you are a part of multiple --- .../frappe_providers/frappecloud.py | 107 ++++++------------ frappe/utils/commands.py | 9 ++ 2 files changed, 45 insertions(+), 71 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 4f33c990f9..3503f9d684 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -13,7 +13,28 @@ import requests import frappe import frappe.utils.backups from frappe.utils import get_installed_apps_info -from frappe.utils.commands import render_table, add_line_after +from frappe.utils.commands import render_table, add_line_after, add_line_before + + +@add_line_before +def select_team(session): + # get team options + account_details_sc = session.post(account_details_url) + if account_details_sc.ok: + account_details = account_details_sc.json()["message"] + available_teams = account_details["teams"] + + # ask if they want to select, go ahead with if only one exists + if len(available_teams) == 1: + team = available_teams[0] + else: + render_teams_table(available_teams) + idx = click.prompt("Select Team", type=click.IntRange(1, len(available_teams))) - 1 + team = available_teams[idx] + + print("Team '{}' set for current session".format(team)) + + return team def get_new_site_options(): @@ -148,24 +169,6 @@ def filter_apps(app_groups): return selected_group["name"], filtered_apps -@add_line_after -def create_session(): - # take user input from STDIN - username = click.prompt("Username").strip() - password = getpass.unix_getpass() - - auth_credentials = {"usr": username, "pwd": password} - - session = requests.Session() - login_sc = session.post(login_url, auth_credentials) - - if login_sc.ok: - print("Authorization Successful! ✅") - session.headers.update({"X-Press-Team": username}) - return session - else: - print("Authorization Failed with Error Code {}".format(login_sc.status_code)) - @add_line_after def get_subdomain(domain): @@ -208,61 +211,23 @@ def upload_backup(local_site): return files_uploaded -def frappecloud_migrator(local_site, remote_site): - global login_url, upload_url, files_url, options_url, site_exists_url, session - - login_url = "https://{}/api/method/login".format(remote_site) - upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) - files_url = "https://{}/api/method/upload_file".format(remote_site) - options_url = "https://{}/api/method/press.api.site.options_for_new".format(remote_site) - site_exists_url = "https://{}/api/method/press.api.site.exists".format(remote_site) - +@add_line_after +def create_session(): print("Frappe Cloud credentials @ {}".format(remote_site)) - # get credentials + auth user + start session - session = create_session() + # take user input from STDIN + username = click.prompt("Username").strip() + password = getpass.unix_getpass() - if session: - # connect to site db - frappe.init(site=local_site) - frappe.connect() + auth_credentials = {"usr": username, "pwd": password} - # get new site options - site_options = get_new_site_options() - - # set preferences from site options - subdomain = get_subdomain(site_options["domain"]) - plan = choose_plan(site_options["plans"]) - - app_groups = site_options["groups"] - selected_group, filtered_apps = filter_apps(app_groups) - files_uploaded = upload_backup(local_site) - - # push to frappe_cloud - payload = json.dumps({ - "site": { - "apps": filtered_apps, - "files": files_uploaded, - "group": selected_group, - "name": subdomain, - "plan": plan - } - }) - - session.headers.update({"Content-Type": "application/json; charset=utf-8"}) - site_creation_request = session.post(upload_url, payload) - frappe.destroy() - - if site_creation_request.ok: - site_url = site_creation_request.json()["message"] - print("Your site {} is being migrated ✨".format(local_site)) - print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, site_url)) - print("Your site URL: {}".format(site_url)) - else: - print("Request failed with error code {}".format(site_creation_request.status_code)) - reason = html2text(site_creation_request.text) - print(reason) - sys.exit(1) + session = requests.Session() + login_sc = session.post(login_url, auth_credentials) + if login_sc.ok: + print("Authorization Successful! ✅") + team = select_team(session) + session.headers.update({"X-Press-Team": team }) + return session else: - sys.exit(1) + handle_request_failure(message="Authorization Failed with Error Code {}".format(login_sc.status_code), traceback=False) diff --git a/frappe/utils/commands.py b/frappe/utils/commands.py index 99322b50ba..113014c135 100644 --- a/frappe/utils/commands.py +++ b/frappe/utils/commands.py @@ -27,6 +27,15 @@ def add_line_after(function): return empty_line +def add_line_before(function): + """Adds an extra line to STDOUT before the execution of a function this decorates""" + def empty_line(*args, **kwargs): + print() + result = function(*args, **kwargs) + return result + return empty_line + + def log(message, colour=''): """Coloured log outputs to STDOUT""" colours = { From b8138004d9fcc500f9f4590ce5b10f3e1062d3c3 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 18:18:38 +0530 Subject: [PATCH 050/222] feat: allow restoring to existing FC sites --- .../frappe_providers/frappecloud.py | 225 +++++++++++++++--- 1 file changed, 197 insertions(+), 28 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 3503f9d684..39712ecc42 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -16,6 +16,107 @@ from frappe.utils import get_installed_apps_info from frappe.utils.commands import render_table, add_line_after, add_line_before +# TODO: check upgrade compatibility + + +def render_actions_table(): + actions_table = [["#", "Action"]] + actions = [] + + for n, action in enumerate(migrator_actions): + actions_table.append([n+1, action["title"]]) + actions.append(action["fn"]) + + render_table(actions_table) + return actions + + +def render_site_table(sites_info): + sites_table = [["#", "Site Name", "Status"]] + available_sites = [] + + for n, site_data in enumerate(sites_info): + name, status = site_data["name"], site_data["status"] + if status not in ("Inactive", "Suspended"): + sites_table.append([n + 1, name, status]) + available_sites.append(name) + + render_table(sites_table) + return available_sites + + +def render_teams_table(teams): + teams_table = [["#", "Team"]] + + for n, team in enumerate(teams): + teams_table.append([n+1, team]) + + render_table(teams_table) + + +def render_plan_table(plans_list): + plans_table = [["Plan", "CPU Time"]] + visible_headers = ["name", "cpu_time_per_day"] + + for plan in plans_list: + plan, cpu_time = [plan[header] for header in visible_headers] + plans_table.append([plan, "{} hour{}/day".format(cpu_time, "" if cpu_time < 2 else "s")]) + + render_table(plans_table) + + +def render_group_table(app_groups): + # title row + app_groups_table = [["#", "App Group", "Apps"]] + + # all rows + for idx, app_group in enumerate(app_groups): + apps_list = ", ".join(["{}:{}".format(app["scrubbed"], app["branch"]) for app in app_group["apps"]]) + row = [idx + 1, app_group["name"], apps_list] + app_groups_table.append(row) + + render_table(app_groups_table) + + +def handle_request_failure(request=None, message=None, traceback=True, exit_code=1): + message = message or "Request failed with error code {}".format(request.status_code) + response = html2text(request.text) if traceback else "" + + print("{0}{1}".format(message, "\n" + response)) + sys.exit(exit_code) + + +@add_line_after +def select_primary_action(): + actions = render_actions_table() + idx = click.prompt("What do you want to do?", type=click.IntRange(1, len(actions))) - 1 + + return actions[idx] + + +@add_line_after +def select_site(): + get_all_sites_request = session.post(all_site_url, headers={ + "accept": "application/json", + "accept-encoding": "gzip, deflate, br", + "content-type": "application/json; charset=utf-8" + }) + + if get_all_sites_request.ok: + all_sites = get_all_sites_request.json()["message"] + available_sites = render_site_table(all_sites) + + while True: + selected_site = click.prompt("Name of the site you want to restore to", type=str).strip() + if selected_site in available_sites: + return selected_site + else: + print("Site {} does not exist. Try again ❌".format(site)) + else: + print("Couldn't retrive sites list...Try again later") + sys.exit(1) + + @add_line_before def select_team(session): # get team options @@ -67,21 +168,6 @@ def is_subdomain_available(subdomain): return available -def render_plan_table(plans_list): - plans_table = [] - - # title row - visible_headers = ["name", "cpu_time_per_day"] - plans_table.append(["Plan", "CPU Time"]) - - # all rows - for plan in plans_list: - plan, cpu_time = [plan[header] for header in visible_headers] - plans_table.append([plan, "{} hour{}/day".format(cpu_time, "" if cpu_time < 2 else "s")]) - - render_table(plans_table) - - @add_line_after def choose_plan(plans_list): print("{} plans available".format(len(plans_list))) @@ -134,19 +220,6 @@ def check_app_compat(available_group): return is_compat, filtered_apps -def render_group_table(app_groups): - # title row - app_groups_table = [["#", "App Group", "Apps"]] - - # all rows - for idx, app_group in enumerate(app_groups): - apps_list = ", ".join(["{}:{}".format(app["scrubbed"], app["branch"]) for app in app_group["apps"]]) - row = [idx + 1, app_group["name"], apps_list] - app_groups_table.append(row) - - render_table(app_groups_table) - - @add_line_after def filter_apps(app_groups): render_group_table(app_groups) @@ -211,6 +284,68 @@ def upload_backup(local_site): return files_uploaded +def new_site(local_site): + # get new site options + site_options = get_new_site_options() + + # set preferences from site options + subdomain = get_subdomain(site_options["domain"]) + plan = choose_plan(site_options["plans"]) + + app_groups = site_options["groups"] + selected_group, filtered_apps = filter_apps(app_groups) + files_uploaded = upload_backup(local_site) + + # push to frappe_cloud + payload = json.dumps({ + "site": { + "apps": filtered_apps, + "files": files_uploaded, + "group": selected_group, + "name": subdomain, + "plan": plan + } + }) + + session.headers.update({"Content-Type": "application/json; charset=utf-8"}) + site_creation_request = session.post(upload_url, payload) + + if site_creation_request.ok: + site_url = site_creation_request.json()["message"] + print("Your site {} is being migrated ✨".format(local_site)) + print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, site_url)) + print("Your site URL: {}".format(site_url)) + else: + handle_request_failure(site_creation_request) + + +def restore_site(local_site): + # get list of existing sites they can restore + selected_site = select_site() + + # TODO: check if they can restore it + + click.confirm("This is an irreversible action. Are you sure you want to continue?", abort=True) + + # backup site + files_uploaded = upload_backup(local_site) + + # push to frappe_cloud + payload = json.dumps({ + "name": selected_site, + "files": files_uploaded + }) + headers = {"Content-Type": "application/json; charset=utf-8"} + site_restore_request = session.post(restore_site_url, payload, headers=headers) + + if site_restore_request.ok: + print("Your site {0} is being restored on {1} ✨".format(local_site, selected_site)) + print("View your site dashboard at {}/dashboard/#/sites/{}".format(remote_site, selected_site)) + print("Your site URL: {}".format(selected_site)) + else: + handle_request_failure(site_restore_request) + + @add_line_after def create_session(): print("Frappe Cloud credentials @ {}".format(remote_site)) @@ -231,3 +366,37 @@ def create_session(): return session else: handle_request_failure(message="Authorization Failed with Error Code {}".format(login_sc.status_code), traceback=False) + + +def frappecloud_migrator(local_site, frappecloud_site): + global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url + global session, migrator_actions, remote_site + + remote_site = frappecloud_site + + login_url = "https://{}/api/method/login".format(remote_site) + upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) + files_url = "https://{}/api/method/upload_file".format(remote_site) + options_url = "https://{}/api/method/press.api.site.options_for_new".format(remote_site) + site_exists_url = "https://{}/api/method/press.api.site.exists".format(remote_site) + account_details_url = "https://{}/api/method/press.api.account.get".format(remote_site) + all_site_url = "https://{}/api/method/press.api.site.all".format(remote_site) + restore_site_url = "https://{}/api/method/press.api.site.restore".format(remote_site) + + migrator_actions = [ + { "title": "Create a new site", "fn": new_site }, + { "title": "Restore to an existing site", "fn": restore_site } + ] + + # get credentials + auth user + start session + session = create_session() + + # available actions defined in migrator_actions + primary_action = select_primary_action() + + frappe.init(site=local_site) + frappe.connect() + + primary_action(local_site) + + frappe.destroy() From a1ee529cb744617b24c49cf9aa544a6d88811f8a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 18:42:06 +0530 Subject: [PATCH 051/222] fix: show only Active and Broken sites in Sites List --- frappe/integrations/frappe_providers/frappecloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 39712ecc42..7013dbf1ba 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -37,7 +37,7 @@ def render_site_table(sites_info): for n, site_data in enumerate(sites_info): name, status = site_data["name"], site_data["status"] - if status not in ("Inactive", "Suspended"): + if status in ("Active", "Broken"): sites_table.append([n + 1, name, status]) available_sites.append(name) @@ -372,7 +372,7 @@ def frappecloud_migrator(local_site, frappecloud_site): global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url global session, migrator_actions, remote_site - remote_site = frappecloud_site + remote_site = "staging.frappe.cloud" #frappecloud_site login_url = "https://{}/api/method/login".format(remote_site) upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) From a65be3e22a71fdd5d88a87655011df4cced89638 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 29 May 2020 11:07:14 +0530 Subject: [PATCH 052/222] fix: undefined variables --- frappe/integrations/frappe_providers/frappecloud.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index 7013dbf1ba..d8741e0834 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -111,7 +111,7 @@ def select_site(): if selected_site in available_sites: return selected_site else: - print("Site {} does not exist. Try again ❌".format(site)) + print("Site {} does not exist. Try again ❌".format(selected_site)) else: print("Couldn't retrive sites list...Try again later") sys.exit(1) @@ -372,7 +372,7 @@ def frappecloud_migrator(local_site, frappecloud_site): global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url global session, migrator_actions, remote_site - remote_site = "staging.frappe.cloud" #frappecloud_site + remote_site = frappecloud_site login_url = "https://{}/api/method/login".format(remote_site) upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) From 11ec3442a3d8032ac78e4149c8be8285b321a651 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 19:24:04 +0530 Subject: [PATCH 053/222] feat: remove writer introduction from bog settings --- .../website/doctype/blog_settings/blog_settings.json | 8 +------- .../doctype/blog_settings/test_blog_settings.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 7 deletions(-) create mode 100644 frappe/website/doctype/blog_settings/test_blog_settings.py diff --git a/frappe/website/doctype/blog_settings/blog_settings.json b/frappe/website/doctype/blog_settings/blog_settings.json index f0e51de170..a05c0ea5f3 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.json +++ b/frappe/website/doctype/blog_settings/blog_settings.json @@ -7,7 +7,6 @@ "field_order": [ "blog_title", "blog_introduction", - "writers_introduction", "section_break_4", "social_share_settings" ], @@ -22,11 +21,6 @@ "fieldtype": "Small Text", "label": "Blog Introduction" }, - { - "fieldname": "writers_introduction", - "fieldtype": "Small Text", - "label": "Writers Introduction" - }, { "collapsible": 1, "fieldname": "section_break_4", @@ -43,7 +37,7 @@ "idx": 1, "issingle": 1, "links": [], - "modified": "2020-05-04 09:10:41.815238", + "modified": "2020-05-28 18:49:08.356608", "modified_by": "Administrator", "module": "Website", "name": "Blog Settings", diff --git a/frappe/website/doctype/blog_settings/test_blog_settings.py b/frappe/website/doctype/blog_settings/test_blog_settings.py new file mode 100644 index 0000000000..e4ddb85c4b --- /dev/null +++ b/frappe/website/doctype/blog_settings/test_blog_settings.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class TestBlogSettings(unittest.TestCase): + pass From 892149af99ae4b81e6005b2b25dfbcf0dc4a9209 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 19:24:31 +0530 Subject: [PATCH 054/222] feat: give precedence to template --- frappe/website/render.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/frappe/website/render.py b/frappe/website/render.py index c1bca3f5c5..f137a28eaa 100644 --- a/frappe/website/render.py +++ b/frappe/website/render.py @@ -214,14 +214,13 @@ def build_page(path): context = get_context(path) - if context.source: - html = frappe.render_template(context.source, context) - - elif context.template: + if context.template: if path.endswith('min.js'): html = frappe.get_jloader().get_source(frappe.get_jenv(), context.template)[0] else: html = frappe.get_template(context.template).render(context) + elif context.source: + html = frappe.render_template(context.source, context) if '{index}' in html: html = html.replace('{index}', get_toc(context.route)) From 8b08a7d71d8418891869411cbaf7be525f35730e Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 19:25:10 +0530 Subject: [PATCH 055/222] refactor: blogger doctype * remove blog post count * Set image field --- frappe/website/doctype/blog_post/blog_post.py | 11 ++++------- frappe/website/doctype/blogger/blogger.json | 16 ++++------------ 2 files changed, 8 insertions(+), 19 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 4596c60710..5516d17780 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -39,11 +39,6 @@ class BlogPost(WebsiteGenerator): if self.published and not self.published_on: self.published_on = today() - # update posts - frappe.db.sql("""UPDATE `tabBlogger` SET `posts`=(SELECT COUNT(*) FROM `tabBlog Post` - WHERE IFNULL(`blogger`,'')=`tabBlogger`.`name`) - WHERE `name`=%s""", (self.blogger,)) - self.set_read_time() def on_update(self): @@ -133,8 +128,9 @@ class BlogPost(WebsiteGenerator): def get_list_context(context=None): list_context = frappe._dict( - template = "templates/includes/blog/blog.html", + template = "/templates/includes/blog/blog.html", get_list = get_blog_list, + no_breadcrumbs = True, hide_filters = True, children = get_children(), # show_search = True, @@ -161,7 +157,8 @@ def get_list_context(context=None): else: list_context.parents = [{"name": _("Home"), "route": "/"}] - list_context.update(frappe.get_doc("Blog Settings", "Blog Settings").as_dict(no_default_fields=True)) + list_context.update(frappe.get_doc("Blog Settings").as_dict(no_default_fields=True)) + return list_context def get_children(): diff --git a/frappe/website/doctype/blogger/blogger.json b/frappe/website/doctype/blogger/blogger.json index b8165a5908..f7494e7ec5 100644 --- a/frappe/website/doctype/blogger/blogger.json +++ b/frappe/website/doctype/blogger/blogger.json @@ -13,8 +13,7 @@ "full_name", "user", "bio", - "avatar", - "posts" + "avatar" ], "fields": [ { @@ -51,20 +50,13 @@ }, { "fieldname": "avatar", - "fieldtype": "Attach", + "fieldtype": "Attach Image", "label": "Avatar" - }, - { - "fieldname": "posts", - "fieldtype": "Int", - "in_list_view": 1, - "label": "Posts", - "no_copy": 1, - "read_only": 1 } ], "icon": "fa fa-user", "idx": 1, + "image_field": "avatar", "links": [ { "link_doctype": "Blog Post", @@ -72,7 +64,7 @@ } ], "max_attachments": 1, - "modified": "2020-04-19 08:21:09.684300", + "modified": "2020-05-28 19:22:40.959895", "modified_by": "Administrator", "module": "Website", "name": "Blogger", From 4f99b5879ec9e5aff46294b58474e76a54f65427 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 28 May 2020 20:28:55 +0530 Subject: [PATCH 056/222] fix: set frappecloud_url in conf to override specified url --- frappe/integrations/frappe_providers/__init__.py | 1 - frappe/integrations/frappe_providers/frappecloud.py | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/frappe/integrations/frappe_providers/__init__.py b/frappe/integrations/frappe_providers/__init__.py index 0b689478d2..887e191e16 100644 --- a/frappe/integrations/frappe_providers/__init__.py +++ b/frappe/integrations/frappe_providers/__init__.py @@ -7,7 +7,6 @@ from frappe.integrations.frappe_providers.frappecloud import frappecloud_migrato def migrate_to(local_site, frappe_provider): if frappe_provider in ("frappe.cloud", "frappecloud.com"): - frappe_provider = "frappecloud.com" return frappecloud_migrator(local_site, frappe_provider) else: print("{} is not supported yet".format(frappe_provider)) diff --git a/frappe/integrations/frappe_providers/frappecloud.py b/frappe/integrations/frappe_providers/frappecloud.py index d8741e0834..291b8af647 100644 --- a/frappe/integrations/frappe_providers/frappecloud.py +++ b/frappe/integrations/frappe_providers/frappecloud.py @@ -372,7 +372,7 @@ def frappecloud_migrator(local_site, frappecloud_site): global login_url, upload_url, files_url, options_url, site_exists_url, restore_site_url, account_details_url, all_site_url global session, migrator_actions, remote_site - remote_site = frappecloud_site + remote_site = frappe.conf.frappecloud_url or "frappecloud.com" login_url = "https://{}/api/method/login".format(remote_site) upload_url = "https://{}/api/method/press.api.site.new".format(remote_site) From ea728d158f630b8f8d3a1a64f9c3d2dd49b994d9 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 22:07:22 +0530 Subject: [PATCH 057/222] feat: added image extra small option --- frappe/public/scss/website-image.scss | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/frappe/public/scss/website-image.scss b/frappe/public/scss/website-image.scss index 8c32e821fe..d416c05650 100644 --- a/frappe/public/scss/website-image.scss +++ b/frappe/public/scss/website-image.scss @@ -55,6 +55,12 @@ img:after { width: 100%; } +.website-image-extra-small { + @include website-image; + width: 2.5rem; + height: 2.5rem; +} + .website-image-small { @include website-image; width: 5rem; From 5ea0688e40c27473f69743d7a0f1d210ebf7a1ba Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 22:07:37 +0530 Subject: [PATCH 058/222] feat: added blog.scss --- frappe/public/scss/blog.scss | 48 +++++++++++++++++++++++++++++++++ frappe/public/scss/website.scss | 1 + 2 files changed, 49 insertions(+) create mode 100644 frappe/public/scss/blog.scss diff --git a/frappe/public/scss/blog.scss b/frappe/public/scss/blog.scss new file mode 100644 index 0000000000..86c5b18ceb --- /dev/null +++ b/frappe/public/scss/blog.scss @@ -0,0 +1,48 @@ +.blog-list-item { + @extend .col-12; + + &:not(.featured) { + @extend .col-md-4; + + .blog-list-body { + min-height: 14rem; + } + } + + .blog-list-cover { + @extend .col-12; + } + + .blog-list-body { + @extend .col-12; + @extend .mt-3; + } + + .post-cover-container { + min-height: 12rem; + overflow: hidden; + border-radius: $border-radius; + + img { + min-height: 12rem; + width: 100%; + } + } + + &.featured { + .blog-list-cover { + @extend .col-12; + @extend .col-md-8; + } + + .blog-list-body { + @extend .col-12; + @extend .col-md-4; + @extend .mt-0; + } + + .post-cover-container { + max-height: 22rem; + } + } +} \ No newline at end of file diff --git a/frappe/public/scss/website.scss b/frappe/public/scss/website.scss index 0149ac0d0a..152551c8d1 100644 --- a/frappe/public/scss/website.scss +++ b/frappe/public/scss/website.scss @@ -5,6 +5,7 @@ @import 'multilevel-dropdown'; @import 'website-image'; @import 'page-builder'; +@import 'blog'; @import 'markdown'; @import 'sidebar'; From 78cb3a9c178964bebe0270234b4690fc9b7fb7c7 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 22:08:08 +0530 Subject: [PATCH 059/222] feat: updated blog listing layout --- frappe/templates/includes/blog/blog.html | 32 ++++++-- frappe/templates/includes/blog/blog_list.html | 25 +++++++ .../blog_post/templates/blog_post_row.html | 73 ++++++++++--------- 3 files changed, 91 insertions(+), 39 deletions(-) create mode 100644 frappe/templates/includes/blog/blog_list.html diff --git a/frappe/templates/includes/blog/blog.html b/frappe/templates/includes/blog/blog.html index 5afaeb6ab8..a841bff627 100644 --- a/frappe/templates/includes/blog/blog.html +++ b/frappe/templates/includes/blog/blog.html @@ -1,15 +1,35 @@ {% extends "templates/web.html" %} - {% block title %}{{ blog_title or _("Blog") }}{% endblock %} -{% block header %}

    {{ blog_title or _("Blog") }}

    {% endblock %} {% block hero %}{% endblock %} {% block page_content %} - - + +{{ web_blocks([ + { + 'template': "Hero", + 'values': { + 'title': blog_title, + 'subtitle': blog_introduction, + }, + 'add_container': 0, + 'add_top_padding': 0, + 'add_bottom_padding': 0, + 'css_class': "py-5" + } + ]) +}} +
    -
    - {% include "templates/includes/list/list.html" %} +
    + {% if not result -%} +
    + {{ no_result_message or _("Nothing to show") }} +
    + {% else %} + {% for item in result %} + {{ item }} + {% endfor %} + {% endif %}
    {% endblock %} diff --git a/frappe/templates/includes/blog/blog_list.html b/frappe/templates/includes/blog/blog_list.html new file mode 100644 index 0000000000..cfe25f3682 --- /dev/null +++ b/frappe/templates/includes/blog/blog_list.html @@ -0,0 +1,25 @@ +{% if sub_title %} +

    {{ sub_title }}

    +{% endif %} +{% if not result -%} +
    + {{ no_result_message or _("Nothing to show") }} +
    +{% else %} +
    + + {% if result_heading_template %}{% include result_heading_template %}{% endif %} + +
    + {% for item in result %} + {{ item }} + {% endfor %} +
    + +
    +{%- endif %} diff --git a/frappe/website/doctype/blog_post/templates/blog_post_row.html b/frappe/website/doctype/blog_post/templates/blog_post_row.html index dffe0ef81d..d197494ae5 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post_row.html +++ b/frappe/website/doctype/blog_post/templates/blog_post_row.html @@ -1,38 +1,45 @@ {%- set post = doc -%} -
    -
    -
    -
    -
    - -
    - {{ post.full_name }} - · - {{ frappe.format_date(post.published_on) }} - {% if post.comments %} - · - {% if post.comments == 1 %} - {{ _('1 comment') }} - {% else %} - {{ _('{0} comments').format(post.comments) }} - {% endif %} - {% endif %} - {% if post.read_time %} - · - {{ _('{0} min read').format(post.read_time) }} - {% endif %} -
    -
    -
    - {% if post.cover_image %} - {{post.title}} - Cover Image - {% endif %} + +{%- if post.featured -%} + {% set col_class="featured" %} +{%- else -%} + {% set col_class="" %} +{%- endif -%} + +
    +
    + {% if post.cover_image %} +
    + + {{post.title}} - Cover Image + +
    + {% endif %} +
    +
    +
    +
    + {%- if post.featured -%} + {{ _('Featured') }} · + {%- endif -%} + {{ post.category.title }} +
    + {%- if post.featured -%} +

    {{ post.title }}

    + {%- else -%} +
    {{ post.title }}
    + {%- endif -%} +

    {{ post.intro }}

    +
    +
    + +
    + {{ post.full_name }} +
    + {{ frappe.format_date(post.published_on) }} + {% if post.read_time %} · {{ post.read_time }} min read {% endif %}
    -
    +
    \ No newline at end of file From 5f4cf98ee20f5aac96fb1cbb947469414cefe44d Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 22:09:19 +0530 Subject: [PATCH 060/222] feat: added featured checkbox and validation --- frappe/website/doctype/blog_post/blog_post.json | 14 +++++++++++--- frappe/website/doctype/blog_post/blog_post.py | 10 ++++++++++ 2 files changed, 21 insertions(+), 3 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.json b/frappe/website/doctype/blog_post/blog_post.json index 3d24879c62..3fb18af2cd 100644 --- a/frappe/website/doctype/blog_post/blog_post.json +++ b/frappe/website/doctype/blog_post/blog_post.json @@ -10,12 +10,13 @@ "title", "published_on", "published", - "read_time", + "featured", "disable_comments", "column_break_3", "blog_category", "blogger", "route", + "read_time", "section_break_5", "blog_intro", "content_type", @@ -143,7 +144,8 @@ { "fieldname": "meta_image", "fieldtype": "Attach Image", - "label": "Meta Image" + "label": "Meta Image", + "mandatory_depends_on": "eval:doc.featured" }, { "fieldname": "section_break_20", @@ -167,6 +169,12 @@ "fieldtype": "Int", "label": "Read Time", "read_only": 1 + }, + { + "default": "0", + "fieldname": "featured", + "fieldtype": "Check", + "label": "Featured" } ], "has_web_view": 1, @@ -175,7 +183,7 @@ "is_published_field": "published", "links": [], "max_attachments": 5, - "modified": "2020-04-30 17:32:41.055883", + "modified": "2020-05-28 21:40:26.068480", "modified_by": "Administrator", "module": "Website", "name": "Blog Post", diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 5516d17780..0492173ad6 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -39,8 +39,18 @@ class BlogPost(WebsiteGenerator): if self.published and not self.published_on: self.published_on = today() + if self.featured: + if not self.meta_image: + frappe.throw(_("A featured post must have a cover image")) + self.reset_featured_for_other_blogs() + self.set_read_time() + def reset_featured_for_other_blogs(self): + all_posts = frappe.get_all("Blog Post", {"featured": 1}) + for post in all_posts: + frappe.db.set_value("Blog Post", post.name, "featured", 0) + def on_update(self): super(BlogPost, self).on_update() clear_cache("writers") From 54cd0191aeee4c68354da79279491b95af8bab4c Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Thu, 28 May 2020 22:10:29 +0530 Subject: [PATCH 061/222] feat: update query for new layout --- frappe/website/doctype/blog_post/blog_post.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index 0492173ad6..efa69c55bd 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -208,6 +208,9 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len select t1.title, t1.name, t1.blog_category, t1.route, t1.published_on, t1.read_time, t1.published_on as creation, + t1.read_time as read_time, + t1.featured as featured, + t1.meta_image as cover_image, t1.content as content, t1.content_type as content_type, t1.content_html as content_html, @@ -223,7 +226,7 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len where ifnull(t1.published,0)=1 and t1.blogger = t2.name %(condition)s - order by published_on desc, name asc + order by featured desc, published_on desc, name asc limit %(start)s, %(page_len)s""" % { "start": limit_start, "page_len": limit_page_length, "condition": (" and " + " and ".join(conditions)) if conditions else "" @@ -232,9 +235,9 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len posts = frappe.db.sql(query, as_dict=1) for post in posts: - post.content = get_html_content_based_on_type(post, 'content', post.content_type) - post.cover_image = find_first_image(post.content) + if not post.cover_image: + post.cover_image = find_first_image(post.content) post.published = global_date_format(post.creation) post.content = strip_html_tags(post.content) @@ -247,7 +250,7 @@ def get_blog_list(doctype, txt=None, filters=None, limit_start=0, limit_page_len post.avatar = post.avatar or "" post.category = frappe.db.get_value('Blog Category', post.blog_category, - ['route', 'title'], as_dict=True) + ['name', 'route', 'title'], as_dict=True) if post.avatar and (not "http:" in post.avatar and not "https:" in post.avatar) and not post.avatar.startswith("/"): post.avatar = "/" + post.avatar From 33857269efd594298d2f2da9186101b65c93f5a1 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 10:35:47 +0530 Subject: [PATCH 062/222] feat: updated styles for blog post --- frappe/public/scss/blog.scss | 43 +++++++++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/frappe/public/scss/blog.scss b/frappe/public/scss/blog.scss index 86c5b18ceb..b24fb631fd 100644 --- a/frappe/public/scss/blog.scss +++ b/frappe/public/scss/blog.scss @@ -7,6 +7,10 @@ .blog-list-body { min-height: 14rem; } + + .post-cover-container { + height: 12rem; + } } .blog-list-cover { @@ -19,7 +23,6 @@ } .post-cover-container { - min-height: 12rem; overflow: hidden; border-radius: $border-radius; @@ -45,4 +48,42 @@ max-height: 22rem; } } +} + +.blog-container { + max-width: 840px; + font-size: 18px; + margin: 0px auto; + font-family: Inter, sans-serif; + + .blog-intro { + font-size: 1.25rem; + font-weight: 300; + } + + .blog-content { + p { + line-height: 1.8; + margin-bottom: 1.2rem; + } + + h1, h2, h3 { + margin-top: 2rem; + } + + h4, h5, h6 { + margin-top: 1.5rem; + } + + img { + border-radius: $border-radius; + } + + blockquote{ + font-weight: 300; + padding: 1.2rem 1.8rem; + border-left: 8px solid $gray-300 ; + background: $gray-100; + } + } } \ No newline at end of file From ee5b7aa84ef5135934c1537ec0503de4093e62eb Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 10:36:15 +0530 Subject: [PATCH 063/222] refactor: remove breadcrumbs from blog post --- frappe/website/doctype/blog_post/blog_post.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index efa69c55bd..c3f78bff26 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -63,6 +63,8 @@ class BlogPost(WebsiteGenerator): if not cint(self.published): raise Exception("This blog has not been published yet!") + context.no_breadcrumbs = True + # temp fields context.full_name = get_fullname(self.owner) context.updated = global_date_format(self.published_on) From 6ffe45e5ce729e92c497666bf9c7b83618e8f787 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 10:37:42 +0530 Subject: [PATCH 064/222] refactor: simplify social links --- frappe/website/doctype/blog_post/blog_post.py | 26 ++++++-------- .../blog_post/templates/blog_post.html | 36 +++---------------- .../doctype/blog_settings/blog_settings.json | 20 +++++------ 3 files changed, 24 insertions(+), 58 deletions(-) diff --git a/frappe/website/doctype/blog_post/blog_post.py b/frappe/website/doctype/blog_post/blog_post.py index c3f78bff26..9551c26e15 100644 --- a/frappe/website/doctype/blog_post/blog_post.py +++ b/frappe/website/doctype/blog_post/blog_post.py @@ -99,25 +99,19 @@ class BlogPost(WebsiteGenerator): def fetch_social_links_info(self): + if not frappe.db.get_single_value("Blog Settings", "enable_social_sharing", cache=True): + return [] + 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_links = [ + { "icon": "twitter", "link": "https://twitter.com/intent/tweet?text=" + self.title + "&url=" + url }, + { "icon": "facebook", "link": "https://www.facebook.com/sharer.php?u=" + url }, + { "icon": "linkedin", "link": "https://www.linkedin.com/sharing/share-offsite/?url=" + url }, + { "icon": "envelope", "link": "mailto:?subject=" + self.title + "&body=" + url } + ] - 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 + return social_links 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 12e5ccf2d7..f1c99dd0fd 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -24,13 +24,12 @@ {% endif %} {% if social_links %} - {% endif %}
    @@ -54,31 +53,4 @@ -{% endblock %} - -{% block style %} - -{% endblock %} +{% endblock %} \ No newline at end of file diff --git a/frappe/website/doctype/blog_settings/blog_settings.json b/frappe/website/doctype/blog_settings/blog_settings.json index a05c0ea5f3..63dae06c68 100644 --- a/frappe/website/doctype/blog_settings/blog_settings.json +++ b/frappe/website/doctype/blog_settings/blog_settings.json @@ -7,8 +7,8 @@ "field_order": [ "blog_title", "blog_introduction", - "section_break_4", - "social_share_settings" + "column_break", + "enable_social_sharing" ], "fields": [ { @@ -22,22 +22,22 @@ "label": "Blog Introduction" }, { - "collapsible": 1, - "fieldname": "section_break_4", - "fieldtype": "Section Break" + "default": "0", + "fieldname": "enable_social_sharing", + "fieldtype": "Check", + "label": "Enable Social Sharing" }, { - "fieldname": "social_share_settings", - "fieldtype": "Table", - "label": "Social Share Settings", - "options": "Social Link Settings" + "collapsible": 1, + "fieldname": "column_break", + "fieldtype": "Column Break" } ], "icon": "fa fa-cog", "idx": 1, "issingle": 1, "links": [], - "modified": "2020-05-28 18:49:08.356608", + "modified": "2020-05-28 22:34:46.679003", "modified_by": "Administrator", "module": "Website", "name": "Blog Settings", From b60fffc60492646f2124dce7ea92629c98a9c086 Mon Sep 17 00:00:00 2001 From: Shivam Mishra Date: Fri, 29 May 2020 10:38:13 +0530 Subject: [PATCH 065/222] feat: updated layout for blog and blogger --- frappe/templates/includes/blog/blogger.html | 2 +- .../blog_post/templates/blog_post.html | 42 +++++++++++-------- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/frappe/templates/includes/blog/blogger.html b/frappe/templates/includes/blog/blogger.html index 68df22786d..136d2cdaa9 100644 --- a/frappe/templates/includes/blog/blogger.html +++ b/frappe/templates/includes/blog/blogger.html @@ -1,7 +1,7 @@ {% from "frappe/templates/includes/macros.html" import square_image_with_fallback %}
    - {{ square_image_with_fallback(src=blogger_info.avatar, size='72px', alt=blogger_info.full_name, class='align-self-start mr-3 rounded') }} + {{ square_image_with_fallback(src=blogger_info.avatar, size='90px', alt=blogger_info.full_name, class='align-self-start mr-3 rounded') }}
    {{ blogger_info.full_name }} diff --git a/frappe/website/doctype/blog_post/templates/blog_post.html b/frappe/website/doctype/blog_post/templates/blog_post.html index f1c99dd0fd..f0eb505d61 100644 --- a/frappe/website/doctype/blog_post/templates/blog_post.html +++ b/frappe/website/doctype/blog_post/templates/blog_post.html @@ -8,22 +8,30 @@
    -
    - -

    {{ title }}

    -

    - {{ blog_intro }} -

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