From 8806eae1b2c3a888f31888001cdcbc398db84224 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 18 Oct 2022 01:23:37 +0530 Subject: [PATCH 01/19] fix(frappe.client): delete child doc via parent so that parent's on_update is called no change for deletion of normal doctype (cherry picked from commit 55bc60433fe6a7b0919e074f3acaddf1667f8811) --- frappe/client.py | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/frappe/client.py b/frappe/client.py index 3c0ce8ea8a..6a4deaffa0 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -270,7 +270,7 @@ def delete(doctype, name): :param doctype: DocType of the document to be deleted :param name: name of the document to be deleted""" - frappe.delete_doc(doctype, name, ignore_missing=False) + delete_doc(doctype, name) @frappe.whitelist(methods=["POST", "PUT"]) @@ -462,3 +462,23 @@ def insert_doc(doc) -> "Document": return parent return frappe.get_doc(doc).insert() + + +def delete_doc(doctype, name): + """Deletes document + if doctype is a child table, then deletes the child record using the parent doc + so that the parent doc's `on_update` is called + """ + + if frappe.is_table(doctype): + parenttype, parent, parentfield = frappe.db.get_value( + doctype, name, ["parenttype", "parent", "parentfield"] + ) + parent = frappe.get_doc(parenttype, parent) + for row in parent.get(parentfield): + if row.name == name: + parent.remove(row) + parent.save() + break + else: + frappe.delete_doc(doctype, name, ignore_missing=False) From 0dcf5e6a54858bd6ab7309298de823f9050a109a Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 18 Oct 2022 13:46:57 +0530 Subject: [PATCH 02/19] fix: raise error if child doc not found (cherry picked from commit 8acbc3867004814ae80ae6c4de8c93277a796a8f) --- frappe/client.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/client.py b/frappe/client.py index 6a4deaffa0..bec3aefb7b 100644 --- a/frappe/client.py +++ b/frappe/client.py @@ -471,9 +471,10 @@ def delete_doc(doctype, name): """ if frappe.is_table(doctype): - parenttype, parent, parentfield = frappe.db.get_value( - doctype, name, ["parenttype", "parent", "parentfield"] - ) + values = frappe.db.get_value(doctype, name, ["parenttype", "parent", "parentfield"]) + if not values: + raise frappe.DoesNotExistError + parenttype, parent, parentfield = values parent = frappe.get_doc(parenttype, parent) for row in parent.get(parentfield): if row.name == name: From c339c0c0ff294d85624bc7340639540f34598d35 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 18 Oct 2022 14:15:37 +0530 Subject: [PATCH 03/19] test(client.delete): test for child doc delete (cherry picked from commit 2b859990d2386acc74d907bbdb879633ae80c677) --- frappe/tests/test_client.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/frappe/tests/test_client.py b/frappe/tests/test_client.py index 93a2ce93a1..bd80e7d5f3 100644 --- a/frappe/tests/test_client.py +++ b/frappe/tests/test_client.py @@ -1,5 +1,7 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +from unittest.mock import patch + import frappe from frappe.tests.utils import FrappeTestCase @@ -15,12 +17,26 @@ class TestClient(FrappeTestCase): def test_delete(self): from frappe.client import delete + from frappe.desk.doctype.note.note import Note - todo = frappe.get_doc(dict(doctype="ToDo", description="description")).insert() - delete("ToDo", todo.name) + note = frappe.get_doc( + doctype="Note", + title=frappe.generate_hash(length=8), + content="test", + seen_by=[{"user": "Administrator"}], + ).insert() + + child_row_name = note.seen_by[0].name + + with patch.object(Note, "save") as save: + delete("Note Seen By", child_row_name) + save.assert_called() + + delete("Note", note.name) - self.assertFalse(frappe.db.exists("ToDo", todo.name)) - self.assertRaises(frappe.DoesNotExistError, delete, "ToDo", todo.name) + self.assertFalse(frappe.db.exists("Note", note.name)) + self.assertRaises(frappe.DoesNotExistError, delete, "Note", note.name) + self.assertRaises(frappe.DoesNotExistError, delete, "Note Seen By", child_row_name) def test_http_valid_method_access(self): from frappe.client import delete From 798206c3b39ca35f86a181f8804f429e96f67833 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 Oct 2022 17:32:40 +0530 Subject: [PATCH 04/19] fix: dont db_set on unsaved document (cherry picked from commit 1bd61d5c255b74eb485f8a4e770041e8df3fac5b) --- frappe/model/document.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/model/document.py b/frappe/model/document.py index 3d64e68415..4812f4aa4c 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1173,6 +1173,9 @@ class Document(BaseDocument): # to trigger notification on value change self.run_method("before_change") + if self.name is None: + return + frappe.db.set_value( self.doctype, self.name, From a5ad248802124eaf1329e0028f02bc38ea3d6f2e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 18 Oct 2022 22:09:21 +0530 Subject: [PATCH 05/19] test: db_set does not query for new doc --- frappe/tests/test_document.py | 6 ++++++ frappe/tests/utils.py | 10 ++++++---- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/frappe/tests/test_document.py b/frappe/tests/test_document.py index 6080df48d3..cec7ee3bb0 100644 --- a/frappe/tests/test_document.py +++ b/frappe/tests/test_document.py @@ -163,6 +163,12 @@ class TestDocument(FrappeTestCase): self.assertRaises(frappe.ValidationError, d.run_method, "validate") self.assertRaises(frappe.ValidationError, d.save) + def test_db_set_no_query_on_new_docs(self): + user = frappe.new_doc("User") + user.db_set("user_type", "Magical Wizard") + with self.assertQueryCount(0): + user.db_set("user_type", "Magical Wizard") + def test_update_after_submit(self): d = self.test_insert() d.starts_on = "2014-09-09" diff --git a/frappe/tests/utils.py b/frappe/tests/utils.py index 7a8ebb7010..1e99966cf5 100644 --- a/frappe/tests/utils.py +++ b/frappe/tests/utils.py @@ -69,16 +69,18 @@ class FrappeTestCase(unittest.TestCase): @contextmanager def assertQueryCount(self, count): + queries = [] + def _sql_with_count(*args, **kwargs): - frappe.db.sql_query_count += 1 - return orig_sql(*args, **kwargs) + ret = orig_sql(*args, **kwargs) + queries.append(frappe.db.last_query) + return ret try: orig_sql = frappe.db.sql - frappe.db.sql_query_count = 0 frappe.db.sql = _sql_with_count yield - self.assertLessEqual(frappe.db.sql_query_count, count) + self.assertLessEqual(len(queries), count, msg="Queries executed: " + "\n\n".join(queries)) finally: frappe.db.sql = orig_sql From 871415a45ff7c74c850313f342daa7468940644e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 10:40:43 +0530 Subject: [PATCH 06/19] fix: google calendar sync times (system tz) (#18458) (#18459) * fix: google calendar sync times (system tz) * added line breaks to excessively long bit * (chore) linter appeasement * chore: linting [skip ci] (cherry picked from commit 36761880838280ee7efdf6a0670771d484fb5be2) Co-authored-by: PeterG --- .../google_calendar/google_calendar.py | 21 +++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 9f9aca1123..a4218af9e6 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -4,6 +4,7 @@ from datetime import datetime, timedelta from urllib.parse import quote +from zoneinfo import ZoneInfo import google.oauth2.credentials import requests @@ -515,12 +516,20 @@ def google_calendar_to_repeat_on(start, end, recurrence=None): Both have been mapped in a dict for easier mapping. """ repeat_on = { - "starts_on": get_datetime(start.get("date")) - if start.get("date") - else parser.parse(start.get("dateTime")).astimezone().replace(tzinfo=None), - "ends_on": get_datetime(end.get("date")) - if end.get("date") - else parser.parse(end.get("dateTime")).astimezone().replace(tzinfo=None), + "starts_on": ( + get_datetime(start.get("date")) + if start.get("date") + else parser.parse(start.get("dateTime")) + .astimezone(ZoneInfo(get_time_zone())) + .replace(tzinfo=None) + ), + "ends_on": ( + get_datetime(end.get("date")) + if end.get("date") + else parser.parse(end.get("dateTime")) + .astimezone(ZoneInfo(get_time_zone())) + .replace(tzinfo=None) + ), "all_day": 1 if start.get("date") else 0, "repeat_this_event": 1 if recurrence else 0, "repeat_on": None, From 5c0bb7ec786a4037e8d7fb0e1457c6e53f7164ba Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 11:26:07 +0530 Subject: [PATCH 07/19] feat: configurable default views (#18409) (#18434) Co-authored-by: hrwx Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Co-authored-by: Ankush Menat (cherry picked from commit b0c1e400ea165fc5b4d34ff931df0d6b265455cd) Co-authored-by: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> --- cypress/integration/folder_navigation.js | 9 +- cypress/integration/view_routing.js | 231 ++++++++++++++++++ .../doctype/communication/communication.json | 7 +- frappe/core/doctype/doctype/doctype.js | 19 ++ frappe/core/doctype/doctype/doctype.json | 23 +- frappe/core/doctype/file/file.json | 4 +- .../doctype/customize_form/customize_form.js | 12 +- .../customize_form/customize_form.json | 24 +- .../doctype/customize_form/customize_form.py | 4 + frappe/public/js/frappe/list/base_list.js | 2 +- frappe/public/js/frappe/model/model.js | 38 ++- frappe/public/js/frappe/router.js | 106 ++++++-- .../js/frappe/ui/toolbar/search_utils.js | 11 +- frappe/public/js/frappe/utils/utils.js | 19 +- frappe/public/js/frappe/views/breadcrumbs.js | 2 +- .../public/js/frappe/views/file/file_view.js | 4 +- .../js/frappe/views/kanban/kanban_view.js | 62 +++-- frappe/public/js/frappe/views/treeview.js | 5 + .../public/js/frappe/widgets/widget_dialog.js | 30 ++- frappe/tests/ui_test_helpers.py | 155 +++++++++++- 20 files changed, 674 insertions(+), 93 deletions(-) create mode 100644 cypress/integration/view_routing.js diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js index e15a354de0..60fa46bc88 100644 --- a/cypress/integration/folder_navigation.js +++ b/cypress/integration/folder_navigation.js @@ -24,6 +24,7 @@ context("Folder Navigation", () => { it("Navigating the nested folders, checking if the URL formed is correct, checking if the added content in the child folder is correct", () => { //Navigating inside the Attachments folder + cy.wait(500); cy.get('[title="Attachments"] > span').click(); //To check if the URL formed after visiting the attachments folder is correct @@ -36,6 +37,7 @@ context("Folder Navigation", () => { cy.click_modal_primary_button("Create"); //Navigating inside the added folder in the Attachments folder + cy.wait(500); cy.get('[title="Test Folder"] > span').click(); //To check if the URL is correct after visiting the Test Folder @@ -51,7 +53,12 @@ context("Folder Navigation", () => { cy.click_modal_primary_button("Upload"); //To check if the added file is present in the Test Folder - cy.get("span.level-item > span").should("contain", "Test Folder"); + cy.visit("/app/file/view/home/Attachments"); + cy.wait(500); + cy.get("span.level-item > a > span").should("contain", "Test Folder"); + cy.visit("/app/file/view/home/Attachments/Test%20Folder"); + + cy.wait(500); cy.get(".list-row-container").eq(0).should("contain.text", "72402.jpg"); cy.get(".list-row-checkbox").eq(0).click(); diff --git a/cypress/integration/view_routing.js b/cypress/integration/view_routing.js new file mode 100644 index 0000000000..1e3b841c79 --- /dev/null +++ b/cypress/integration/view_routing.js @@ -0,0 +1,231 @@ +context("View", () => { + before(() => { + cy.login(); + cy.visit("/app/website"); + }); + + it("Route to ToDo List View", () => { + cy.visit("/app/todo/view/list"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("List"); + }); + }); + + it("Route to ToDo Report View", () => { + cy.visit("/app/todo/view/report"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + + it("Route to ToDo Dashboard View", () => { + cy.visit("/app/todo/view/dashboard"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Dashboard"); + }); + }); + + it("Route to ToDo Gantt View", () => { + cy.visit("/app/todo/view/gantt"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Gantt"); + }); + }); + + it("Route to ToDo Kanban View", () => { + cy.call("frappe.tests.ui_test_helpers.create_kanban").then(() => { + cy.visit("/app/note/view/kanban/_Note _Kanban"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Kanban"); + }); + }); + }); + + it("Route to ToDo Calendar View", () => { + cy.visit("/app/todo/view/calendar"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Calendar"); + }); + }); + + it("Route to Custom Tree View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_tree_doctype").then(() => { + cy.visit("/app/custom-tree/view/tree"); + cy.wait(500); + cy.window() + .its("cur_tree") + .then((list) => { + expect(list.view_name).to.equal("Tree"); + }); + }); + }); + + it("Route to Custom Image View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_image_doctype").then(() => { + cy.visit("app/custom-image/view/image"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Image"); + }); + }); + }); + + it("Route to Communication Inbox View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_inbox").then(() => { + cy.visit("app/communication/view/inbox"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Inbox"); + }); + }); + }); + + it("Route to File View", () => { + cy.visit("app/file"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("File"); + expect(list.current_folder).to.equal("Home"); + }); + + cy.visit("app/file/view/home/Attachments"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("File"); + expect(list.current_folder).to.equal("Home/Attachments"); + }); + }); + + it("Re-route to default view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("app/event"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Route to default view from app/{doctype}", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("/app/event"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Route to default view from app/{doctype}/view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("/app/event/view"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Force Route to default view from app/{doctype}", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { + view: "Report", + force_reroute: true, + }).then(() => { + cy.visit("/app/event"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Force Route to default view from app/{doctype}/view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { + view: "Report", + force_reroute: true, + }).then(() => { + cy.visit("/app/event/view"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Force Route to default view from app/{doctype}/view", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { + view: "Report", + force_reroute: true, + }).then(() => { + cy.visit("/app/event/view/list"); + cy.wait(500); + cy.window() + .its("cur_list") + .then((list) => { + expect(list.view_name).to.equal("Report"); + }); + }); + }); + + it("Validate Route History for Default View", () => { + cy.call("frappe.tests.ui_test_helpers.setup_default_view", { view: "Report" }).then(() => { + cy.visit("/app/event"); + cy.visit("/app/event/view/list"); + cy.location("pathname").should("eq", "/app/event/view/list"); + cy.go("back"); + cy.location("pathname").should("eq", "/app/event"); + }); + }); + + it("Route to Form", () => { + cy.call("frappe.tests.ui_test_helpers.create_note").then(() => { + cy.visit("/app/note/Routing Test"); + cy.window() + .its("cur_frm") + .then((frm) => { + expect(frm.doc.title).to.equal("Routing Test"); + }); + }); + }); + + it("Route to Settings Workspace", () => { + cy.visit("/app/settings"); + cy.get(".title-text").should("contain", "Settings"); + }); +}); diff --git a/frappe/core/doctype/communication/communication.json b/frappe/core/doctype/communication/communication.json index f9d15af483..293a6b2c87 100644 --- a/frappe/core/doctype/communication/communication.json +++ b/frappe/core/doctype/communication/communication.json @@ -2,6 +2,7 @@ "actions": [], "allow_import": 1, "creation": "2013-01-29 10:47:14", + "default_view": "Inbox", "description": "Keeps track of all communications", "doctype": "DocType", "document_type": "Setup", @@ -198,7 +199,6 @@ "label": "More Information" }, { - "bold": 0, "default": "Now", "fieldname": "communication_date", "fieldtype": "Datetime", @@ -395,7 +395,7 @@ "icon": "fa fa-comment", "idx": 1, "links": [], - "modified": "2022-03-30 11:24:25.728637", + "modified": "2022-05-09 00:13:45.310564", "modified_by": "Administrator", "module": "Core", "name": "Communication", @@ -454,8 +454,9 @@ "sender_field": "sender", "sort_field": "modified", "sort_order": "DESC", + "states": [], "subject_field": "subject", "title_field": "subject", "track_changes": 1, "track_seen": 1 -} +} \ No newline at end of file diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index e91a05e17d..24c367b115 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -55,6 +55,7 @@ frappe.ui.form.on("DocType", { if (frm.is_new()) { frm.events.set_default_permission(frm); + frm.set_value("default_view", "List"); } else { frm.toggle_enable("engine", 0); } @@ -66,12 +67,14 @@ frappe.ui.form.on("DocType", { frm.cscript.autoname(frm); frm.cscript.set_naming_rule_description(frm); + frm.trigger("setup_default_views"); }, istable: (frm) => { if (frm.doc.istable && frm.is_new()) { frm.set_value("autoname", "autoincrement"); frm.set_value("allow_rename", 0); + frm.set_value("default_view", null); } else if (!frm.doc.istable && !frm.is_new()) { frm.events.set_default_permission(frm); } @@ -82,6 +85,18 @@ frappe.ui.form.on("DocType", { frm.add_child("permissions", { role: "System Manager" }); } }, + + is_tree: (frm) => { + frm.trigger("setup_default_views"); + }, + + is_calendar_and_gantt: (frm) => { + frm.trigger("setup_default_views"); + }, + + setup_default_views: (frm) => { + frappe.model.set_default_views_for_doctype(frm.doc.name, frm); + }, }); frappe.ui.form.on("DocField", { @@ -171,6 +186,10 @@ frappe.ui.form.on("DocField", { fieldtype: function (frm) { frm.trigger("max_attachments"); }, + + fields_add: (frm) => { + frm.trigger("setup_default_views"); + }, }); extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm })); diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 0b2ced43da..6258241f5d 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -14,6 +14,7 @@ "istable", "issingle", "is_tree", + "is_calendar_and_gantt", "editable_grid", "quick_entry", "cb01", @@ -53,6 +54,8 @@ "default_print_format", "sort_field", "sort_order", + "default_view", + "force_re_route_to_default_view", "column_break_29", "document_type", "icon", @@ -606,6 +609,24 @@ "fieldname": "make_attachments_public", "fieldtype": "Check", "label": "Make Attachments Public by Default" + }, + { + "fieldname": "default_view", + "fieldtype": "Select", + "label": "Default View" + }, + { + "default": "0", + "fieldname": "force_re_route_to_default_view", + "fieldtype": "Check", + "label": "Force Re-route to Default View" + }, + { + "default": "0", + "description": "Enables Calendar and Gantt views.", + "fieldname": "is_calendar_and_gantt", + "fieldtype": "Check", + "label": "Is Calendar and Gantt" } ], "icon": "fa fa-bolt", @@ -688,7 +709,7 @@ "link_fieldname": "reference_doctype" } ], - "modified": "2022-09-02 12:05:59.589751", + "modified": "2022-10-12 14:13:27.315351", "modified_by": "Administrator", "module": "Core", "name": "DocType", diff --git a/frappe/core/doctype/file/file.json b/frappe/core/doctype/file/file.json index cb727e48f0..d6c4a99bc3 100644 --- a/frappe/core/doctype/file/file.json +++ b/frappe/core/doctype/file/file.json @@ -2,6 +2,7 @@ "actions": [], "allow_import": 1, "creation": "2012-12-12 11:19:22", + "default_view": "File", "doctype": "DocType", "engine": "InnoDB", "field_order": [ @@ -169,10 +170,11 @@ "read_only": 1 } ], + "force_re_route_to_default_view": 1, "icon": "fa fa-file", "idx": 1, "links": [], - "modified": "2022-09-13 15:50:15.508250", + "modified": "2022-09-13 15:50:15.508251", "modified_by": "Administrator", "module": "Core", "name": "File", diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 8d8249b532..759a9e1b3a 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -72,6 +72,7 @@ frappe.ui.form.on("Customize Form", { } else { frm.refresh(); frm.trigger("setup_sortable"); + frm.trigger("setup_default_views"); } } localStorage["customize_doctype"] = frm.doc.doc_type; @@ -82,8 +83,12 @@ frappe.ui.form.on("Customize Form", { } }, + is_calendar_and_gantt: function (frm) { + frm.trigger("setup_default_views"); + }, + setup_sortable: function (frm) { - frm.doc.fields.forEach(function (f, i) { + frm.doc.fields.forEach(function (f) { if (!f.is_custom_field) { f._sortable = false; } @@ -222,6 +227,10 @@ frappe.ui.form.on("Customize Form", { frm.set_df_property("sort_field", "options", fields); } }, + + setup_default_views(frm) { + frappe.model.set_default_views_for_doctype(frm.doc.doc_type, frm); + }, }); // can't delete standard fields @@ -237,6 +246,7 @@ frappe.ui.form.on("Customize Form Field", { var f = frappe.model.get_doc(cdt, cdn); f.is_system_generated = false; f.is_custom_field = true; + frm.trigger("setup_default_views"); }, }); diff --git a/frappe/custom/doctype/customize_form/customize_form.json b/frappe/custom/doctype/customize_form/customize_form.json index 05989eaa00..4840184966 100644 --- a/frappe/custom/doctype/customize_form/customize_form.json +++ b/frappe/custom/doctype/customize_form/customize_form.json @@ -13,6 +13,7 @@ "search_fields", "column_break_5", "istable", + "is_calendar_and_gantt", "editable_grid", "quick_entry", "track_changes", @@ -35,6 +36,8 @@ "show_title_field_in_link", "translated_doctype", "default_print_format", + "default_view", + "force_re_route_to_default_view", "column_break_29", "show_preview_popup", "email_settings_section", @@ -337,6 +340,25 @@ "fieldname": "make_attachments_public", "fieldtype": "Check", "label": "Make Attachments Public by Default" + }, + { + "fieldname": "default_view", + "fieldtype": "Select", + "label": "Default View" + }, + { + "default": "0", + "depends_on": "default_view", + "fieldname": "force_re_route_to_default_view", + "fieldtype": "Check", + "label": "Force Re-route to Default View" + }, + { + "default": "0", + "description": "Enables Calendar and Gantt views.", + "fieldname": "is_calendar_and_gantt", + "fieldtype": "Check", + "label": "Is Calendar and Gantt" } ], "hide_toolbar": 1, @@ -345,7 +367,7 @@ "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2022-08-24 06:57:47.966331", + "modified": "2022-08-30 11:45:16.772277", "modified_by": "Administrator", "module": "Custom", "name": "Customize Form", diff --git a/frappe/custom/doctype/customize_form/customize_form.py b/frappe/custom/doctype/customize_form/customize_form.py index cacd38397a..be27ebbc0b 100644 --- a/frappe/custom/doctype/customize_form/customize_form.py +++ b/frappe/custom/doctype/customize_form/customize_form.py @@ -586,6 +586,10 @@ doctype_properties = { "naming_rule": "Data", "autoname": "Data", "show_title_field_in_link": "Check", + "translate_link_fields": "Check", + "is_calendar_and_gantt": "Check", + "default_view": "Select", + "force_re_route_to_default_view": "Check", "translated_doctype": "Check", } diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index a94f6b9b62..3079717198 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -196,7 +196,7 @@ frappe.views.BaseList = class BaseList { Map: "map", }; - if (frappe.boot.desk_settings.view_switcher) { + if (frappe.boot.desk_settings.view_switcher && !this.meta.force_re_route_to_default_view) { /* @preserve for translation, don't remove __("List View") __("Report View") __("Dashboard View") __("Gantt View"), diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 41faa0217c..99553f470c 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -349,7 +349,7 @@ $.extend(frappe.model, { is_tree: function (doctype) { if (!doctype) return false; - return frappe.boot.treeviews.indexOf(doctype) != -1; + return locals.DocType[doctype] && locals.DocType[doctype].is_tree; }, is_fresh(doc) { @@ -754,6 +754,42 @@ $.extend(frappe.model, { } return frappe.model.numeric_fieldtypes.includes(fieldtype); }, + + set_default_views_for_doctype(doctype, frm) { + frappe.model.with_doctype(doctype, () => { + let meta = frappe.get_meta(doctype); + let default_views = ["List", "Report", "Dashboard", "Kanban"]; + + if (meta.is_calendar_and_gantt && frappe.views.calendar[doctype]) { + let views = ["Calendar", "Gantt"]; + default_views.push(...views); + } + + if (meta.is_tree) { + default_views.push("Tree"); + } + + if (frm.doc.image_field) { + default_views.push("Image"); + } + + if (doctype === "Communication" && frappe.boot.email_accounts.length) { + default_views.push("Inbox"); + } + + if ( + (frm.doc.fields.find((i) => i.fieldname === "latitude") && + frm.doc.fields.find((i) => i.fieldname === "longitude")) || + frm.doc.fields.find( + (i) => i.fieldname === "location" && i.fieldtype == "Geolocation" + ) + ) { + default_views.push("Map"); + } + + frm.set_df_property("default_view", "options", default_views); + }); + }, }); // legacy diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 4524cbdce4..6c8932aa3c 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -89,6 +89,18 @@ frappe.router = { "image", "inbox", ], + list_views_route: { + list: "List", + kanban: "Kanban", + report: "Report", + calendar: "Calendar", + tree: "Tree", + gantt: "Gantt", + dashboard: "Dashboard", + image: "Image", + inbox: "Inbox", + file: "Home", + }, layout_mapped: {}, is_app_route(path) { @@ -115,7 +127,7 @@ frappe.router = { } }, - route() { + async route() { // resolve the route from the URL or hash // translate it so the objects are well defined // and render the page as required @@ -126,22 +138,22 @@ frappe.router = { if (this.re_route(sub_path)) return; this.current_sub_path = sub_path; - this.current_route = this.parse(); + this.current_route = await this.parse(); this.set_history(sub_path); this.render(); this.set_title(sub_path); this.trigger("change"); }, - parse(route) { + async parse(route) { route = this.get_sub_path_string(route).split("/"); if (!route) return []; route = $.map(route, this.decode_component); this.set_route_options_from_url(); - return this.convert_to_standard_route(route); + return await this.convert_to_standard_route(route); }, - convert_to_standard_route(route) { + async convert_to_standard_route(route) { // /app/settings = ["Workspaces", "Settings"] // /app/private/settings = ["Workspaces", "private", "Settings"] // /app/user = ["List", "User"] @@ -161,7 +173,7 @@ frappe.router = { route = ["Workspaces", "private", frappe.workspaces[private_workspace].title]; } else if (this.routes[route[0]]) { // route - route = this.set_doctype_route(route); + route = await this.set_doctype_route(route); } return route; @@ -174,36 +186,85 @@ frappe.router = { set_doctype_route(route) { let doctype_route = this.routes[route[0]]; - // doctype route - if (route[1]) { - if (route[2] && route[1] === "view") { - route = this.get_standard_route_for_list(route, doctype_route); - } else { + + return frappe.model.with_doctype(doctype_route.doctype).then(() => { + // doctype route + let meta = frappe.get_meta(doctype_route.doctype); + + if (route[1] && route[1] === "view" && route[2]) { + route = this.get_standard_route_for_list( + route, + doctype_route, + meta.force_re_route_to_default_view && meta.default_view + ? meta.default_view + : null + ); + } else if (route[1] && route[1] !== "view" && !route[2]) { let docname = route[1]; if (route.length > 2) { docname = route.slice(1).join("/"); } route = ["Form", doctype_route.doctype, docname]; + } else if (frappe.model.is_single(doctype_route.doctype)) { + route = ["Form", doctype_route.doctype, doctype_route.doctype]; + } else if (meta.default_view) { + route = [ + "List", + doctype_route.doctype, + this.list_views_route[meta.default_view.toLowerCase()], + ]; + } else { + route = ["List", doctype_route.doctype, "List"]; } - } else if (frappe.model.is_single(doctype_route.doctype)) { - route = ["Form", doctype_route.doctype, doctype_route.doctype]; - } else { - route = ["List", doctype_route.doctype, "List"]; - } - // reset the layout to avoid using incorrect views - this.doctype_layout = doctype_route.doctype_layout; - return route; + // reset the layout to avoid using incorrect views + this.doctype_layout = doctype_route.doctype_layout; + return route; + }); }, - get_standard_route_for_list(route, doctype_route) { + get_standard_route_for_list(route, doctype_route, default_view) { let standard_route; - if (route[2].toLowerCase() === "tree") { + let _route = default_view || route[2] || ""; + + if (_route.toLowerCase() === "tree") { standard_route = ["Tree", doctype_route.doctype]; } else { - standard_route = ["List", doctype_route.doctype, frappe.utils.to_title_case(route[2])]; + let new_route = this.list_views_route[_route.toLowerCase()]; + let re_route = route[2].toLowerCase() !== new_route.toLowerCase(); + + if (re_route) { + /** + * In case of force_re_route, the url of the route should change, + * if the _route and route[2] are different, it means there is a default_view + * with force_re_route enabled. + * + * To change the url, to the correct view, the route[2] is changed with default_view + * + * Eg: If default_view is set to Report with force_re_route enabled and user routes + * to List, + * route: [todo, view, list] + * default_view: report + * + * replaces the list to report and re-routes to the new route but should be replaced in + * the history since the list route should not exist in history as we are rerouting it to + * report + */ + frappe.route_flags.replace_route = true; + + route[2] = _route.toLowerCase(); + this.set_route(route); + } + + standard_route = [ + "List", + doctype_route.doctype, + this.list_views_route[_route.toLowerCase()], + ]; + // calendar / kanban / dashboard / folder if (route[3]) standard_route.push(...route.slice(3, route.length)); } + return standard_route; }, @@ -345,6 +406,7 @@ frappe.router = { } else if (view === "tree") { new_route = [this.slug(route[1]), "view", "tree"]; } + return new_route; }, diff --git a/frappe/public/js/frappe/ui/toolbar/search_utils.js b/frappe/public/js/frappe/ui/toolbar/search_utils.js index e3a34b2111..434fb888fc 100644 --- a/frappe/public/js/frappe/ui/toolbar/search_utils.js +++ b/frappe/public/js/frappe/ui/toolbar/search_utils.js @@ -208,13 +208,10 @@ frappe.search.utils = { }, }); } - if (in_list(frappe.boot.treeviews, item)) { - out.push(option("Tree", ["Tree", item], 0.05)); - } else { - out.push(option("List", ["List", item], 0.05)); - if (frappe.model.can_get_report(item)) { - out.push(option("Report", ["List", item, "Report"], 0.04)); - } + + out.push(option("List", ["List", item], 0.05)); + if (frappe.model.can_get_report(item)) { + out.push(option("Report", ["List", item, "Report"], 0.04)); } } } diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 12c6e12697..3d17e8a8f6 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1260,20 +1260,12 @@ Object.assign(frappe.utils, { if (frappe.model.is_single(item.doctype)) { route = doctype_slug; } else { - if (!item.doc_view) { - if (frappe.model.is_tree(item.doctype)) { - item.doc_view = "Tree"; - } else { - item.doc_view = "List"; - } - } - switch (item.doc_view) { case "List": if (item.filters) { frappe.route_options = item.filters; } - route = doctype_slug; + route = `${doctype_slug}/view/list`; break; case "Tree": route = `${doctype_slug}/view/tree`; @@ -1290,12 +1282,11 @@ Object.assign(frappe.utils, { case "Calendar": route = `${doctype_slug}/view/calendar/default`; break; + case "Kanban": + route = `${doctype_slug}/view/kanban`; + break; default: - frappe.throw({ - message: __("Not a valid view:") + item.doc_view, - title: __("Unknown View"), - }); - route = ""; + route = doctype_slug; } } } else if (type === "report") { diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index 93cff25df5..1a2a1198d1 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -144,7 +144,7 @@ frappe.breadcrumbs = { } else { let route; const doctype_route = frappe.router.slug(frappe.router.doctype_layout || doctype); - if (frappe.boot.treeviews.indexOf(doctype) !== -1) { + if (doctype_meta.is_tree) { let view = frappe.model.user_settings[doctype].last_view || "Tree"; route = `${doctype_route}/view/${view}`; } else { diff --git a/frappe/public/js/frappe/views/file/file_view.js b/frappe/public/js/frappe/views/file/file_view.js index 122be935d7..2c54011e8a 100644 --- a/frappe/public/js/frappe/views/file/file_view.js +++ b/frappe/public/js/frappe/views/file/file_view.js @@ -74,7 +74,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView { this.page_title = __("File Manager"); const route = frappe.get_route(); - this.current_folder = route.slice(2).join("/"); + this.current_folder = route.slice(2).join("/") || "Home"; this.filters = [["File", "folder", "=", this.current_folder, true]]; this.order_by = this.view_user_settings.order_by || "file_name asc"; @@ -286,7 +286,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView { } get_breadcrumbs_html() { - const route = frappe.router.parse(); + const route = frappe.get_route(); const folders = route.slice(2); return folders diff --git a/frappe/public/js/frappe/views/kanban/kanban_view.js b/frappe/public/js/frappe/views/kanban/kanban_view.js index 677cc5d5e6..7eccd7ebf8 100644 --- a/frappe/public/js/frappe/views/kanban/kanban_view.js +++ b/frappe/public/js/frappe/views/kanban/kanban_view.js @@ -9,14 +9,9 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { const doctype = route[1]; const user_settings = frappe.get_user_settings(doctype)["Kanban"] || {}; if (!user_settings.last_kanban_board) { - frappe.msgprint({ - title: __("Error"), - indicator: "red", - message: __("Missing parameter Kanban Board Name"), - }); - frappe.set_route("List", doctype, "List"); - return true; + return new frappe.views.KanbanView({ doctype: doctype }); } + route.push(user_settings.last_kanban_board); frappe.set_route(route); return true; @@ -28,9 +23,35 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { return "Kanban"; } + show() { + frappe.views.KanbanView.get_kanbans(this.doctype).then((kanbans) => { + if (!kanbans.length) { + return frappe.views.KanbanView.show_kanban_dialog(this.doctype, true); + } else if (kanbans.length && frappe.get_route().length !== 4) { + return frappe.views.KanbanView.show_kanban_dialog(this.doctype, true); + } else { + this.kanbans = kanbans; + + return frappe.run_serially([ + () => this.show_skeleton(), + () => this.fetch_meta(), + () => this.hide_skeleton(), + () => this.check_permissions(), + () => this.init(), + () => this.before_refresh(), + () => this.refresh(), + ]); + } + }); + } + setup_defaults() { return super.setup_defaults().then(() => { - this.board_name = frappe.get_route()[3]; + let get_board_name = () => { + return this.kanbans.length && this.kanbans[0].name; + }; + + this.board_name = frappe.get_route()[3] || get_board_name() || null; this.page_title = __(this.board_name); this.card_meta = this.get_card_meta(); this.page_length = 0; @@ -143,21 +164,22 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView { render() { const board_name = this.board_name; + if (!this.kanban) { + this.kanban = new frappe.views.KanbanBoard({ + doctype: this.doctype, + board: this.board, + board_name: board_name, + cards: this.data, + card_meta: this.card_meta, + wrapper: this.$result, + cur_list: this, + user_settings: this.view_user_settings, + }); + } + if (this.kanban && board_name === this.kanban.board_name) { this.kanban.update(this.data); - return; } - - this.kanban = new frappe.views.KanbanBoard({ - doctype: this.doctype, - board: this.board, - board_name: board_name, - cards: this.data, - card_meta: this.card_meta, - wrapper: this.$result, - cur_list: this, - user_settings: this.view_user_settings, - }); } get_card_meta() { diff --git a/frappe/public/js/frappe/views/treeview.js b/frappe/public/js/frappe/views/treeview.js index 96e3bf7227..6cbc02c649 100644 --- a/frappe/public/js/frappe/views/treeview.js +++ b/frappe/public/js/frappe/views/treeview.js @@ -37,6 +37,10 @@ frappe.views.TreeFactory = class TreeFactory extends frappe.views.Factory { let treeview = frappe.views.trees[route[1]]; treeview && treeview.make_tree(); } + + get view_name() { + return "Tree"; + } }; frappe.views.TreeView = class TreeView { @@ -196,6 +200,7 @@ frappe.views.TreeView = class TreeView { }); cur_tree = this.tree; + cur_tree.view_name = "Tree"; this.post_render(); } diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index b577700715..db7336a883 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -384,18 +384,22 @@ class ShortcutDialog extends WidgetDialog { onchange: () => { if (this.dialog.get_value("type") == "DocType") { let doctype = this.dialog.get_value("link_to"); - if (doctype && frappe.boot.single_types.includes(doctype)) { - this.hide_filters(); - } else if (doctype) { - this.setup_filter(doctype); - this.show_filters(); - } - - const views = ["List", "Report Builder", "Dashboard", "New"]; - if (frappe.boot.treeviews.includes(doctype)) views.push("Tree"); - if (frappe.boot.calendars.includes(doctype)) views.push("Calendar"); - - this.dialog.set_df_property("doc_view", "options", views.join("\n")); + frappe.model.with_doctype(doctype, () => { + let meta = frappe.get_meta(doctype); + + if (doctype && frappe.boot.single_types.includes(doctype)) { + this.hide_filters(); + } else if (doctype) { + this.setup_filter(doctype); + this.show_filters(); + } + + const views = ["List", "Report Builder", "Dashboard", "New"]; + if (meta.is_tree === "Tree") views.push("Tree"); + if (frappe.boot.calendars.includes(doctype)) views.push("Calendar"); + + this.dialog.set_df_property("doc_view", "options", views.join("\n")); + }); } else { this.hide_filters(); } @@ -405,7 +409,7 @@ class ShortcutDialog extends WidgetDialog { fieldtype: "Select", fieldname: "doc_view", label: "DocType View", - options: "List\nReport Builder\nDashboard\nTree\nNew\nCalendar", + options: "List\nReport Builder\nDashboard\nTree\nNew\nCalendar\nKanban", description: __( "Which view of the associated DocType should this shortcut take you to?" ), diff --git a/frappe/tests/ui_test_helpers.py b/frappe/tests/ui_test_helpers.py index cb8fe8e70a..297d9f5b12 100644 --- a/frappe/tests/ui_test_helpers.py +++ b/frappe/tests/ui_test_helpers.py @@ -43,16 +43,32 @@ def create_todo_records(): frappe.db.truncate("ToDo") frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), days=7), "description": "this is first todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), days=7), + "description": "this is first todo", + } ).insert() frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), days=-7), "description": "this is second todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), days=-7), + "description": "this is second todo", + } ).insert() frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), months=2), "description": "this is third todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), months=2), + "description": "this is third todo", + } ).insert() frappe.get_doc( - {"doctype": "ToDo", "date": add_to_date(now(), months=-2), "description": "this is fourth todo"} + { + "doctype": "ToDo", + "date": add_to_date(now(), months=-2), + "description": "this is fourth todo", + } ).insert() @@ -431,3 +447,134 @@ def create_test_user(): user.append("roles", {"role": role}) user.save() + + +@frappe.whitelist() +def setup_tree_doctype(): + frappe.delete_doc_if_exists("DocType", "Custom Tree") + + frappe.get_doc( + { + "doctype": "DocType", + "module": "Core", + "custom": 1, + "fields": [ + {"fieldname": "tree", "fieldtype": "Data", "label": "Tree"}, + ], + "permissions": [{"role": "System Manager", "read": 1}], + "name": "Custom Tree", + "is_tree": True, + "naming_rule": "By fieldname", + "autoname": "field:tree", + } + ).insert() + + if not frappe.db.exists("Custom Tree", "All Trees"): + frappe.get_doc({"doctype": "Custom Tree", "tree": "All Trees"}).insert() + + +@frappe.whitelist() +def setup_image_doctype(): + frappe.delete_doc_if_exists("DocType", "Custom Image") + + frappe.get_doc( + { + "doctype": "DocType", + "module": "Core", + "custom": 1, + "fields": [ + {"fieldname": "image", "fieldtype": "Attach Image", "label": "Image"}, + ], + "permissions": [{"role": "System Manager", "read": 1}], + "name": "Custom Image", + "image_field": "image", + } + ).insert() + + +@frappe.whitelist() +def setup_inbox(): + frappe.db.sql("DELETE FROM `tabUser Email`") + + user = frappe.get_doc("User", frappe.session.user) + user.append("user_emails", {"email_account": "Email Linking"}) + user.save() + + +@frappe.whitelist() +def setup_default_view(view, force_reroute=None): + frappe.delete_doc_if_exists("Property Setter", "Event-main-default_view") + frappe.delete_doc_if_exists("Property Setter", "Event-main-force_re_route_to_default_view") + + frappe.get_doc( + { + "is_system_generated": 0, + "doctype_or_field": "DocType", + "doc_type": "Event", + "property": "default_view", + "property_type": "Select", + "value": view, + "doctype": "Property Setter", + } + ).insert() + + if force_reroute: + frappe.get_doc( + { + "is_system_generated": 0, + "doctype_or_field": "DocType", + "doc_type": "Event", + "property": "force_re_route_to_default_view", + "property_type": "Check", + "value": "1", + "doctype": "Property Setter", + } + ).insert() + + +@frappe.whitelist() +def create_note(): + if not frappe.db.exists("Note", "Routing Test"): + frappe.get_doc({"doctype": "Note", "title": "Routing Test"}).insert() + + +@frappe.whitelist() +def create_kanban(): + if not frappe.db.exists("Custom Field", "Note-kanban"): + frappe.get_doc( + { + "is_system_generated": 0, + "dt": "Note", + "label": "Kanban", + "fieldname": "kanban", + "insert_after": "seen_by", + "fieldtype": "Select", + "options": "Open\nClosed", + "doctype": "Custom Field", + } + ).insert() + + if not frappe.db.exists("Kanban Board", "_Note _Kanban"): + frappe.get_doc( + { + "doctype": "Kanban Board", + "name": "_Note _Kanban", + "kanban_board_name": "_Note _Kanban", + "reference_doctype": "Note", + "field_name": "kanban", + "private": 1, + "show_labels": 0, + "columns": [ + { + "column_name": "Open", + "status": "Active", + "indicator": "Gray", + }, + { + "column_name": "Closed", + "status": "Active", + "indicator": "Gray", + }, + ], + } + ).insert() From b42013c5392eed9b3a55ec82dc3a3e9dc5f0290e Mon Sep 17 00:00:00 2001 From: phot0n Date: Mon, 17 Oct 2022 13:24:37 +0530 Subject: [PATCH 08/19] fix: consider now datetime if the default value is now for datetime field (cherry picked from commit 5bbb9b42e1c38238d94cd90e2a1976b4794fa026) --- frappe/public/js/frappe/form/controls/datetime.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index dcf23fee62..c44650d9db 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -7,6 +7,8 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co return; } else if (value === "Today") { value = this.get_now_date(); + } else if (value.toLowerCase() === "now") { + value = frappe.datetime.now_datetime(); } value = this.format_for_input(value); this.$input && this.$input.val(value); From a190830281fd255b831538819d4f164051a79c07 Mon Sep 17 00:00:00 2001 From: phot0n Date: Wed, 19 Oct 2022 12:01:25 +0530 Subject: [PATCH 09/19] fix(minor): consider lowercase value for comparision in set_formatted_input for datetime field (cherry picked from commit 71bb5e835dcf88ad314f8a0896b611a3166c1634) --- frappe/public/js/frappe/form/controls/datetime.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/controls/datetime.js b/frappe/public/js/frappe/form/controls/datetime.js index c44650d9db..5d87c209e9 100644 --- a/frappe/public/js/frappe/form/controls/datetime.js +++ b/frappe/public/js/frappe/form/controls/datetime.js @@ -5,7 +5,7 @@ frappe.ui.form.ControlDatetime = class ControlDatetime extends frappe.ui.form.Co if (!value) { this.datepicker.clear(); return; - } else if (value === "Today") { + } else if (value.toLowerCase() === "today") { value = this.get_now_date(); } else if (value.toLowerCase() === "now") { value = frappe.datetime.now_datetime(); From 7cfe2d2f59ce8e0155b4dfc8b455f62edc3b6fb3 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 15:23:57 +0530 Subject: [PATCH 10/19] feat: db storage usage report (#18464) (#18467) Basic report to check storage usage by each table. Note: This is not 100% accurate and will never be 100% accurate but gives rough indication of which table is consuming how much storage. `no-docs` [skip ci] (cherry picked from commit abaa830bbc8ec065d0ef5191740e19cba374fcac) Co-authored-by: Ankush Menat --- .../__init__.py | 0 .../database_storage_usage_by_tables.js | 7 ++++ .../database_storage_usage_by_tables.json | 28 +++++++++++++ .../database_storage_usage_by_tables.py | 40 +++++++++++++++++++ .../test_database_storage_usage_by_tables.py | 15 +++++++ 5 files changed, 90 insertions(+) create mode 100644 frappe/core/report/database_storage_usage_by_tables/__init__.py create mode 100644 frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js create mode 100644 frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.json create mode 100644 frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py create mode 100644 frappe/core/report/database_storage_usage_by_tables/test_database_storage_usage_by_tables.py diff --git a/frappe/core/report/database_storage_usage_by_tables/__init__.py b/frappe/core/report/database_storage_usage_by_tables/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js new file mode 100644 index 0000000000..b2cf268b36 --- /dev/null +++ b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.js @@ -0,0 +1,7 @@ +// Copyright (c) 2022, Frappe Technologies and contributors +// For license information, please see license.txt +/* eslint-disable */ + +frappe.query_reports["Database Storage Usage By Tables"] = { + filters: [], +}; diff --git a/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.json b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.json new file mode 100644 index 0000000000..20deb78ad6 --- /dev/null +++ b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.json @@ -0,0 +1,28 @@ +{ + "add_total_row": 1, + "columns": [], + "creation": "2022-10-19 02:25:24.326791", + "disable_prepared_report": 0, + "disabled": 0, + "docstatus": 0, + "doctype": "Report", + "filters": [], + "idx": 0, + "is_standard": "Yes", + "letter_head": "abc", + "modified": "2022-10-19 02:59:00.365307", + "modified_by": "Administrator", + "module": "Core", + "name": "Database Storage Usage By Tables", + "owner": "Administrator", + "prepared_report": 0, + "query": "", + "ref_doctype": "Error Log", + "report_name": "Database Storage Usage By Tables", + "report_type": "Script Report", + "roles": [ + { + "role": "System Manager" + } + ] +} \ No newline at end of file diff --git a/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py new file mode 100644 index 0000000000..c88262552e --- /dev/null +++ b/frappe/core/report/database_storage_usage_by_tables/database_storage_usage_by_tables.py @@ -0,0 +1,40 @@ +# Copyright (c) 2022, Frappe Technologies and contributors +# For license information, please see license.txt + +import frappe + +COLUMNS = [ + {"label": "Table", "fieldname": "table", "fieldtype": "Data", "width": 200}, + {"label": "Size (MB)", "fieldname": "size", "fieldtype": "Float"}, + {"label": "Data (MB)", "fieldname": "data_size", "fieldtype": "Float"}, + {"label": "Index (MB)", "fieldname": "index_size", "fieldtype": "Float"}, +] + + +def execute(filters=None): + frappe.only_for("System Manager") + + data = frappe.db.multisql( + { + "mariadb": """ + SELECT table_name AS `table`, + round(((data_length + index_length) / 1024 / 1024), 2) `size`, + round((data_length / 1024 / 1024), 2) as data_size, + round((index_length / 1024 / 1024), 2) as index_size + FROM information_schema.TABLES + ORDER BY (data_length + index_length) DESC; + """, + "postgres": """ + SELECT + table_name as "table", + round(pg_total_relation_size(quote_ident(table_name)) / 1024 / 1024, 2) as "size", + round(pg_relation_size(quote_ident(table_name)) / 1024 / 1024, 2) as "data_size", + round(pg_indexes_size(quote_ident(table_name)) / 1024 / 1024, 2) as "index_size" + FROM information_schema.tables + WHERE table_schema = 'public' + ORDER BY 2 DESC; + """, + }, + as_dict=1, + ) + return COLUMNS, data diff --git a/frappe/core/report/database_storage_usage_by_tables/test_database_storage_usage_by_tables.py b/frappe/core/report/database_storage_usage_by_tables/test_database_storage_usage_by_tables.py new file mode 100644 index 0000000000..e82cbe9caf --- /dev/null +++ b/frappe/core/report/database_storage_usage_by_tables/test_database_storage_usage_by_tables.py @@ -0,0 +1,15 @@ +# Copyright (c) 2022, Frappe Technologies and contributors +# For license information, please see license.txt + + +from frappe.core.report.database_storage_usage_by_tables.database_storage_usage_by_tables import ( + execute, +) +from frappe.tests.utils import FrappeTestCase + + +class TestDBUsageReport(FrappeTestCase): + def test_basic_query(self): + _, data = execute() + tables = [d.table for d in data] + self.assertFalse({"tabUser", "tabDocField"}.difference(tables)) From cb4d825c565f1f8fef340b4676b657d7e408f54f Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:05:39 +0530 Subject: [PATCH 11/19] fix: delete custom tables when doctype is deleted (#18433) (#18471) * fix: delete custom tables when doctype is deleted (cherry picked from commit 20593f155dae8a9e19deeb8879fe9c275b1fb931) Co-authored-by: Ankush Menat --- frappe/cache_manager.py | 8 +++++++- frappe/core/doctype/doctype/test_doctype.py | 12 ++++++++++++ frappe/model/delete_doc.py | 4 ++++ 3 files changed, 23 insertions(+), 1 deletion(-) diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index 2585e4c512..086e8b5a8c 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -130,12 +130,18 @@ def clear_doctype_cache(doctype=None): clear_single(doctype) # clear all parent doctypes - for dt in frappe.get_all( "DocField", "parent", dict(fieldtype=["in", frappe.model.table_fields], options=doctype) ): clear_single(dt.parent) + # clear all parent doctypes + if not frappe.flags.in_install: + for dt in frappe.get_all( + "Custom Field", "dt", dict(fieldtype=["in", frappe.model.table_fields], options=doctype) + ): + clear_single(dt.dt) + # clear all notifications delete_notification_count_for(doctype) diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 44f2877e2b..3722e5d1fa 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -670,6 +670,18 @@ class TestDocType(FrappeTestCase): self.assertEqual(test_json.test_json_field["hello"], "world") + @patch.dict(frappe.conf, {"developer_mode": 1}) + def test_custom_field_deletion(self): + """Custom child tables whose doctype doesn't exist should be auto deleted.""" + doctype = new_doctype(custom=0).insert().name + child = new_doctype(custom=0, istable=1).insert().name + + field = "abc" + create_custom_fields({doctype: [{"fieldname": field, "fieldtype": "Table", "options": child}]}) + + frappe.delete_doc("DocType", child) + self.assertFalse(frappe.get_meta(doctype).get_field(field)) + @patch.dict(frappe.conf, {"developer_mode": 1}) def test_delete_doctype_with_customization(self): from frappe.custom.doctype.property_setter.property_setter import make_property_setter diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 072b9a1d66..d1120cc22d 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -95,6 +95,10 @@ def delete_doc( update_flags(doc, flags, ignore_permissions) check_permission_and_not_submitted(doc) + # delete custom table fields using this doctype. + frappe.db.delete( + "Custom Field", {"options": name, "fieldtype": ("in", frappe.model.table_fields)} + ) frappe.db.delete("__global_search", {"doctype": name}) delete_from_table(doctype, name, ignore_doctypes, None) From 9e29a944d777c78608ab0a57e2ddf18a8a6b6639 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Wed, 19 Oct 2022 17:05:55 +0530 Subject: [PATCH 12/19] fix: validate website settings (#18446) (#18469) (cherry picked from commit c2f43c4b26c857dd9cd238ec17958b8008b503e3) Co-authored-by: Ankush Menat --- .../website_settings/test_website_settings.py | 31 +++++++++++++++++-- .../website_settings/website_settings.py | 12 +++++++ 2 files changed, 41 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/website_settings/test_website_settings.py b/frappe/website/doctype/website_settings/test_website_settings.py index 1e8410fb6e..5f75e46d5e 100644 --- a/frappe/website/doctype/website_settings/test_website_settings.py +++ b/frappe/website/doctype/website_settings/test_website_settings.py @@ -1,8 +1,35 @@ # Copyright (c) 2020, Frappe Technologies and Contributors # License: MIT. See LICENSE -# import frappe + +import frappe from frappe.tests.utils import FrappeTestCase +from frappe.website.doctype.website_settings.website_settings import get_website_settings class TestWebsiteSettings(FrappeTestCase): - pass + def test_child_items_in_top_bar(self): + ws = frappe.get_doc("Website Settings") + ws.append( + "top_bar_items", + {"label": "Parent Item"}, + ) + ws.append( + "top_bar_items", + {"parent_label": "Parent Item", "label": "Child Item"}, + ) + ws.save() + + context = get_website_settings() + + for item in context.top_bar_items: + if item.label == "Parent Item": + self.assertEqual(item.child_items[0].label, "Child Item") + break + else: + self.fail("Child items not found") + + def test_redirect_setups(self): + ws = frappe.get_doc("Website Settings") + + ws.append("route_redirects", {"source": "/engineering/(*.)", "target": "/development/(*.)"}) + self.assertRaises(frappe.ValidationError, ws.validate) diff --git a/frappe/website/doctype/website_settings/website_settings.py b/frappe/website/doctype/website_settings/website_settings.py index 0d6e8d2d1f..ef48203cda 100644 --- a/frappe/website/doctype/website_settings/website_settings.py +++ b/frappe/website/doctype/website_settings/website_settings.py @@ -1,5 +1,6 @@ # Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE +import re from urllib.parse import quote import frappe @@ -16,6 +17,7 @@ class WebsiteSettings(Document): self.validate_footer_items() self.validate_home_page() self.validate_google_settings() + self.validate_redirects() def validate_home_page(self): if frappe.flags.in_install: @@ -72,6 +74,16 @@ class WebsiteSettings(Document): if self.enable_google_indexing and not frappe.db.get_single_value("Google Settings", "enable"): frappe.throw(_("Enable Google API in Google Settings.")) + def validate_redirects(self): + for idx, row in enumerate(self.route_redirects): + try: + source = row.source.strip("/ ") + "$" + re.compile(source) + re.sub(source, row.target, "") + except Exception as e: + if not frappe.flags.in_migrate: + frappe.throw(_("Invalid redirect regex in row #{}: {}").format(idx, str(e))) + def on_update(self): self.clear_cache() From 342edb76bffced042a6a7d3985bca56cc9cb950e Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 16:49:45 +0530 Subject: [PATCH 13/19] fix: only execute generator if value is not found in redis cache (#18472) (#18479) * fix: use of generator in * fix: improve docstring * fix: improve docstring * fix: directly assign value to flags Co-authored-by: Daizy (cherry picked from commit fce9ccedaa1b292a3a1a4cfc5eb6e248931d1464) Co-authored-by: Daizy Modi <54097382+DaizyModi@users.noreply.github.com> --- frappe/model/document.py | 14 +++++++++----- frappe/utils/redis_wrapper.py | 2 +- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/frappe/model/document.py b/frappe/model/document.py index 4812f4aa4c..269e83aca6 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -953,15 +953,19 @@ class Document(BaseDocument): from frappe.email.doctype.notification.notification import evaluate_alert if self.flags.notifications is None: - alerts = frappe.cache().hget("notifications", self.doctype) - if alerts is None: - alerts = frappe.get_all( + + def _get_notifications(): + """returns enabled notifications for the current doctype""" + + return frappe.get_all( "Notification", fields=["name", "event", "method"], filters={"enabled": 1, "document_type": self.doctype}, ) - frappe.cache().hset("notifications", self.doctype, alerts) - self.flags.notifications = alerts + + self.flags.notifications = frappe.cache().hget( + "notifications", self.doctype, _get_notifications + ) if not self.flags.notifications: return diff --git a/frappe/utils/redis_wrapper.py b/frappe/utils/redis_wrapper.py index a41e47b0c6..87637c6b5d 100644 --- a/frappe/utils/redis_wrapper.py +++ b/frappe/utils/redis_wrapper.py @@ -194,7 +194,7 @@ class RedisWrapper(redis.Redis): except redis.exceptions.ConnectionError: pass - if value: + if value is not None: value = pickle.loads(value) frappe.local.cache[_name][key] = value elif generator: From f650150345f791bec42cda7e4349a9ab8a4fe883 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Thu, 20 Oct 2022 17:01:30 +0530 Subject: [PATCH 14/19] feat: meaningful report pdf name (#18422) (#18480) * feat: meaningful report pdf name * feat: limited filename length * chore: uncomment line (cherry picked from commit f383669107ba2b68d382d63642dbff94491f593c) Co-authored-by: Dany Robert --- frappe/public/js/frappe/microtemplate.js | 14 ++++++++++++-- .../public/js/frappe/views/reports/query_report.js | 8 ++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/microtemplate.js b/frappe/public/js/frappe/microtemplate.js index be1f05177b..4e0bd76601 100644 --- a/frappe/public/js/frappe/microtemplate.js +++ b/frappe/public/js/frappe/microtemplate.js @@ -175,6 +175,7 @@ frappe.render_template = function (name, data) { w.document.write(tree); w.document.close(); }); + frappe.render_pdf = function (html, opts = {}) { //Create a form to place the HTML content var formData = new FormData(); @@ -197,8 +198,17 @@ frappe.render_pdf = function (html, opts = {}) { var blob = new Blob([success.currentTarget.response], { type: "application/pdf" }); var objectUrl = URL.createObjectURL(blob); - //Open report in a new window - window.open(objectUrl); + // Create a hidden a tag to force set report name + // https://stackoverflow.com/questions/19327749/javascript-blob-filename-without-link + let hidden_a_tag = document.createElement("a"); + document.body.appendChild(hidden_a_tag); + hidden_a_tag.style = "display: none"; + hidden_a_tag.href = objectUrl; + hidden_a_tag.download = opts.report_name || "report.pdf"; + + // Open report in a new window + hidden_a_tag.click(); + window.URL.revokeObjectURL(objectUrl); } }; xhr.send(formData); diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index 8a103f7186..1febf11b6c 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1383,6 +1383,14 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { layout_direction: frappe.utils.is_rtl() ? "rtl" : "ltr", }); + let filter_values = [], + name_len = 0; + for (var key of Object.keys(applied_filters)) { + name_len = name_len + applied_filters[key].toString().length; + if (name_len > 200) break; + filter_values.push(applied_filters[key]); + } + print_settings.report_name = `${__(this.report_name)}_${filter_values.join("_")}.pdf`; frappe.render_pdf(html, print_settings); } From ddec3e0e177ae0a8b45308c1b5852a72669d5dfa Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Thu, 20 Oct 2022 22:32:10 -0600 Subject: [PATCH 15/19] fix: Improve Translation on To Do list view (#18484) (cherry picked from commit 745d956678483fe7184c4659583dc71544c30e15) --- frappe/desk/doctype/todo/todo_list.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/todo/todo_list.js b/frappe/desk/doctype/todo/todo_list.js index 2e4534e05c..bf62088f1d 100644 --- a/frappe/desk/doctype/todo/todo_list.js +++ b/frappe/desk/doctype/todo/todo_list.js @@ -17,10 +17,10 @@ frappe.listview_settings["ToDo"] = { return doc.reference_name; }, get_label: function () { - return __("Open"); + return __("Open", null, "Access"); }, get_description: function (doc) { - return __("Open {0}", [`${doc.reference_type} ${doc.reference_name}`]); + return __("Open {0}", [`${__(doc.reference_type)}: ${doc.reference_name}`]); }, action: function (doc) { frappe.set_route("Form", doc.reference_type, doc.reference_name); From ed5423517d7c0456c4168824a0749ae59d17c2f8 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 <81952590+Komal-Saraf0609@users.noreply.github.com> Date: Fri, 21 Oct 2022 15:01:06 +0530 Subject: [PATCH 16/19] fix: map view doesnt open (#18487) Co-authored-by: Komal Saraf (cherry picked from commit 8fd497a5941d856bad3429987cdcc1b7577d9f51) --- frappe/public/js/frappe/router.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 6c8932aa3c..aadd6f5da8 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -88,6 +88,7 @@ frappe.router = { "dashboard", "image", "inbox", + "map", ], list_views_route: { list: "List", @@ -100,6 +101,7 @@ frappe.router = { image: "Image", inbox: "Inbox", file: "Home", + map: "Map", }, layout_mapped: {}, From f8307cda0d2e55148c5e6683ab36ed1a17b88aee Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Sun, 23 Oct 2022 11:24:58 +0530 Subject: [PATCH 17/19] refactor: dynamically load web form module (#18486) (#18495) (cherry picked from commit 3491d0dcb049223d2b5d05e1fdd5c6ff42c85836) Co-authored-by: Ankush Menat --- frappe/website/doctype/web_form/web_form.py | 25 +++++++++------------ frappe/www/list.py | 3 ++- 2 files changed, 12 insertions(+), 16 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index e0ae91fef7..03ba7c0880 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -98,7 +98,6 @@ def get_context(context): """Build context to render the `web_form.html` template""" context.in_edit_mode = False context.in_view_mode = False - self.set_web_form_module() if frappe.form_dict.is_list: context.template = "website/doctype/web_form/templates/web_list.html" @@ -284,13 +283,14 @@ def get_context(context): def add_custom_context_and_script(self, context): """Update context from module if standard and append script""" - if self.web_form_module: - new_context = self.web_form_module.get_context(context) + if self.is_standard: + web_form_module = get_web_form_module(self) + new_context = web_form_module.get_context(context) if new_context: context.update(new_context) - js_path = os.path.join(os.path.dirname(self.web_form_module.__file__), scrub(self.name) + ".js") + js_path = os.path.join(os.path.dirname(web_form_module.__file__), scrub(self.name) + ".js") if os.path.exists(js_path): script = frappe.render_template(open(js_path).read(), context) @@ -300,9 +300,7 @@ def get_context(context): context.script = script - css_path = os.path.join( - os.path.dirname(self.web_form_module.__file__), scrub(self.name) + ".css" - ) + css_path = os.path.join(os.path.dirname(web_form_module.__file__), scrub(self.name) + ".css") if os.path.exists(css_path): style = open(css_path).read() @@ -322,14 +320,6 @@ def get_context(context): return parents - def set_web_form_module(self): - """Get custom web form module if exists""" - self.web_form_module = self.get_web_form_module() - - def get_web_form_module(self): - if self.is_standard: - return get_doc_module(self.module, self.doctype, self.name) - def validate_mandatory(self, doc): """Validate mandatory web form fields""" missing = [] @@ -368,6 +358,11 @@ def get_context(context): return False +def get_web_form_module(doc): + if doc.is_standard: + return get_doc_module(doc.module, doc.doctype, doc.name) + + @frappe.whitelist(allow_guest=True) @rate_limit(key="web_form", limit=5, seconds=60, methods=["POST"]) def accept(web_form, data, docname=None): diff --git a/frappe/www/list.py b/frappe/www/list.py index 06a2ea48aa..9b704d5d44 100644 --- a/frappe/www/list.py +++ b/frappe/www/list.py @@ -163,6 +163,7 @@ def prepare_filters(doctype, controller, kwargs): def get_list_context(context, doctype, web_form_name=None): from frappe.modules import load_doctype_module + from frappe.website.doctype.web_form.web_form import get_web_form_module list_context = context or frappe._dict() meta = frappe.get_meta(doctype) @@ -193,7 +194,7 @@ def get_list_context(context, doctype, web_form_name=None): # get context from web form module if web_form_name: web_form = frappe.get_doc("Web Form", web_form_name) - list_context = update_context_from_module(web_form.get_web_form_module(), list_context) + list_context = update_context_from_module(get_web_form_module(web_form), list_context) # get path from '/templates/' folder of the doctype if not meta.custom and not list_context.row_template: From b661c1a669b627ecd592e7e6f31bc0e50229b9ff Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 11:32:16 +0530 Subject: [PATCH 18/19] build(deps): bump bruceadams/get-release from 1.2.3 to 1.3.1 (#18535) (#18549) Bumps [bruceadams/get-release](https://github.com/bruceadams/get-release) from 1.2.3 to 1.3.1. - [Release notes](https://github.com/bruceadams/get-release/releases) - [Commits](https://github.com/bruceadams/get-release/compare/v1.2.3...v1.3.1) --- updated-dependencies: - dependency-name: bruceadams/get-release dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> [skip ci] (cherry picked from commit ce9570de569c0625ab8164e5c59f3c78d3193062) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- .github/workflows/on_release.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/on_release.yml b/.github/workflows/on_release.yml index 59e14a8c4d..851b5b1d6a 100644 --- a/.github/workflows/on_release.yml +++ b/.github/workflows/on_release.yml @@ -41,7 +41,7 @@ jobs: - name: Get release id: get_release - uses: bruceadams/get-release@v1.2.3 + uses: bruceadams/get-release@v1.3.1 - name: Upload built Assets to Release uses: actions/upload-release-asset@v1.0.2 From 2991e2a51476c609766a8e452745409f5a5e5940 Mon Sep 17 00:00:00 2001 From: "mergify[bot]" <37929162+mergify[bot]@users.noreply.github.com> Date: Tue, 25 Oct 2022 12:08:38 +0530 Subject: [PATCH 19/19] chore: fix DE translations (#18552) added missing translation (cherry picked from commit 66fbd14c3a330117749878f4c270197ac5557b66) Co-authored-by: Wolfram Schmidt --- frappe/translations/de.csv | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index 2df384bc43..505084d3e7 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -2592,6 +2592,7 @@ Tree,Baum, Trigger Method,Trigger-Methode, Trigger Name,Name des Auslösers, "Trigger on valid methods like ""before_insert"", ""after_update"", etc (will depend on the DocType selected)","Trigger auf gültige Methoden wie "before_insert", "after_update" usw. (hängt von der DocType ausgewählt)", +Try a naming Series, Nummernkreis testen, Try to avoid repeated words and characters,"Versuchen Sie, wiederholte Wörter und Zeichen zu vermeiden", Try to use a longer keyboard pattern with more turns,"Versuchen Sie, eine längere Tastaturmuster mit mehr Windungen zu verwenden", Two Factor Authentication,Zwei-Faktor-Authentifizierung,