Browse Source

feat: configurable default views (#18409) (#18434)

Co-authored-by: hrwx <himanshuwarekar@yahoo.com>
Co-authored-by: Raffael Meyer <14891507+barredterra@users.noreply.github.com>
Co-authored-by: Ankush Menat <ankush@frappe.io>
(cherry picked from commit b0c1e400ea)

Co-authored-by: Shariq Ansari <30859809+shariquerik@users.noreply.github.com>
version-14
mergify[bot] 2 years ago
committed by GitHub
parent
commit
5c0bb7ec78
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 674 additions and 93 deletions
  1. +8
    -1
      cypress/integration/folder_navigation.js
  2. +231
    -0
      cypress/integration/view_routing.js
  3. +4
    -3
      frappe/core/doctype/communication/communication.json
  4. +19
    -0
      frappe/core/doctype/doctype/doctype.js
  5. +22
    -1
      frappe/core/doctype/doctype/doctype.json
  6. +3
    -1
      frappe/core/doctype/file/file.json
  7. +11
    -1
      frappe/custom/doctype/customize_form/customize_form.js
  8. +23
    -1
      frappe/custom/doctype/customize_form/customize_form.json
  9. +4
    -0
      frappe/custom/doctype/customize_form/customize_form.py
  10. +1
    -1
      frappe/public/js/frappe/list/base_list.js
  11. +37
    -1
      frappe/public/js/frappe/model/model.js
  12. +84
    -22
      frappe/public/js/frappe/router.js
  13. +4
    -7
      frappe/public/js/frappe/ui/toolbar/search_utils.js
  14. +5
    -14
      frappe/public/js/frappe/utils/utils.js
  15. +1
    -1
      frappe/public/js/frappe/views/breadcrumbs.js
  16. +2
    -2
      frappe/public/js/frappe/views/file/file_view.js
  17. +42
    -20
      frappe/public/js/frappe/views/kanban/kanban_view.js
  18. +5
    -0
      frappe/public/js/frappe/views/treeview.js
  19. +17
    -13
      frappe/public/js/frappe/widgets/widget_dialog.js
  20. +151
    -4
      frappe/tests/ui_test_helpers.py

+ 8
- 1
cypress/integration/folder_navigation.js View File

@@ -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", () => { 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 //Navigating inside the Attachments folder
cy.wait(500);
cy.get('[title="Attachments"] > span').click(); cy.get('[title="Attachments"] > span').click();


//To check if the URL formed after visiting the attachments folder is correct //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"); cy.click_modal_primary_button("Create");


//Navigating inside the added folder in the Attachments folder //Navigating inside the added folder in the Attachments folder
cy.wait(500);
cy.get('[title="Test Folder"] > span').click(); cy.get('[title="Test Folder"] > span').click();


//To check if the URL is correct after visiting the Test Folder //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"); cy.click_modal_primary_button("Upload");


//To check if the added file is present in the Test Folder //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-container").eq(0).should("contain.text", "72402.jpg");
cy.get(".list-row-checkbox").eq(0).click(); cy.get(".list-row-checkbox").eq(0).click();




+ 231
- 0
cypress/integration/view_routing.js View File

@@ -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");
});
});

+ 4
- 3
frappe/core/doctype/communication/communication.json View File

@@ -2,6 +2,7 @@
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"creation": "2013-01-29 10:47:14", "creation": "2013-01-29 10:47:14",
"default_view": "Inbox",
"description": "Keeps track of all communications", "description": "Keeps track of all communications",
"doctype": "DocType", "doctype": "DocType",
"document_type": "Setup", "document_type": "Setup",
@@ -198,7 +199,6 @@
"label": "More Information" "label": "More Information"
}, },
{ {
"bold": 0,
"default": "Now", "default": "Now",
"fieldname": "communication_date", "fieldname": "communication_date",
"fieldtype": "Datetime", "fieldtype": "Datetime",
@@ -395,7 +395,7 @@
"icon": "fa fa-comment", "icon": "fa fa-comment",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2022-03-30 11:24:25.728637",
"modified": "2022-05-09 00:13:45.310564",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "Communication", "name": "Communication",
@@ -454,8 +454,9 @@
"sender_field": "sender", "sender_field": "sender",
"sort_field": "modified", "sort_field": "modified",
"sort_order": "DESC", "sort_order": "DESC",
"states": [],
"subject_field": "subject", "subject_field": "subject",
"title_field": "subject", "title_field": "subject",
"track_changes": 1, "track_changes": 1,
"track_seen": 1 "track_seen": 1
}
}

+ 19
- 0
frappe/core/doctype/doctype/doctype.js View File

@@ -55,6 +55,7 @@ frappe.ui.form.on("DocType", {


if (frm.is_new()) { if (frm.is_new()) {
frm.events.set_default_permission(frm); frm.events.set_default_permission(frm);
frm.set_value("default_view", "List");
} else { } else {
frm.toggle_enable("engine", 0); frm.toggle_enable("engine", 0);
} }
@@ -66,12 +67,14 @@ frappe.ui.form.on("DocType", {


frm.cscript.autoname(frm); frm.cscript.autoname(frm);
frm.cscript.set_naming_rule_description(frm); frm.cscript.set_naming_rule_description(frm);
frm.trigger("setup_default_views");
}, },


istable: (frm) => { istable: (frm) => {
if (frm.doc.istable && frm.is_new()) { if (frm.doc.istable && frm.is_new()) {
frm.set_value("autoname", "autoincrement"); frm.set_value("autoname", "autoincrement");
frm.set_value("allow_rename", 0); frm.set_value("allow_rename", 0);
frm.set_value("default_view", null);
} else if (!frm.doc.istable && !frm.is_new()) { } else if (!frm.doc.istable && !frm.is_new()) {
frm.events.set_default_permission(frm); frm.events.set_default_permission(frm);
} }
@@ -82,6 +85,18 @@ frappe.ui.form.on("DocType", {
frm.add_child("permissions", { role: "System Manager" }); 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", { frappe.ui.form.on("DocField", {
@@ -171,6 +186,10 @@ frappe.ui.form.on("DocField", {
fieldtype: function (frm) { fieldtype: function (frm) {
frm.trigger("max_attachments"); frm.trigger("max_attachments");
}, },

fields_add: (frm) => {
frm.trigger("setup_default_views");
},
}); });


extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm })); extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({ frm: cur_frm }));

+ 22
- 1
frappe/core/doctype/doctype/doctype.json View File

@@ -14,6 +14,7 @@
"istable", "istable",
"issingle", "issingle",
"is_tree", "is_tree",
"is_calendar_and_gantt",
"editable_grid", "editable_grid",
"quick_entry", "quick_entry",
"cb01", "cb01",
@@ -53,6 +54,8 @@
"default_print_format", "default_print_format",
"sort_field", "sort_field",
"sort_order", "sort_order",
"default_view",
"force_re_route_to_default_view",
"column_break_29", "column_break_29",
"document_type", "document_type",
"icon", "icon",
@@ -606,6 +609,24 @@
"fieldname": "make_attachments_public", "fieldname": "make_attachments_public",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Make Attachments Public by Default" "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", "icon": "fa fa-bolt",
@@ -688,7 +709,7 @@
"link_fieldname": "reference_doctype" "link_fieldname": "reference_doctype"
} }
], ],
"modified": "2022-09-02 12:05:59.589751",
"modified": "2022-10-12 14:13:27.315351",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "DocType", "name": "DocType",


+ 3
- 1
frappe/core/doctype/file/file.json View File

@@ -2,6 +2,7 @@
"actions": [], "actions": [],
"allow_import": 1, "allow_import": 1,
"creation": "2012-12-12 11:19:22", "creation": "2012-12-12 11:19:22",
"default_view": "File",
"doctype": "DocType", "doctype": "DocType",
"engine": "InnoDB", "engine": "InnoDB",
"field_order": [ "field_order": [
@@ -169,10 +170,11 @@
"read_only": 1 "read_only": 1
} }
], ],
"force_re_route_to_default_view": 1,
"icon": "fa fa-file", "icon": "fa fa-file",
"idx": 1, "idx": 1,
"links": [], "links": [],
"modified": "2022-09-13 15:50:15.508250",
"modified": "2022-09-13 15:50:15.508251",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Core", "module": "Core",
"name": "File", "name": "File",


+ 11
- 1
frappe/custom/doctype/customize_form/customize_form.js View File

@@ -72,6 +72,7 @@ frappe.ui.form.on("Customize Form", {
} else { } else {
frm.refresh(); frm.refresh();
frm.trigger("setup_sortable"); frm.trigger("setup_sortable");
frm.trigger("setup_default_views");
} }
} }
localStorage["customize_doctype"] = frm.doc.doc_type; 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) { setup_sortable: function (frm) {
frm.doc.fields.forEach(function (f, i) {
frm.doc.fields.forEach(function (f) {
if (!f.is_custom_field) { if (!f.is_custom_field) {
f._sortable = false; f._sortable = false;
} }
@@ -222,6 +227,10 @@ frappe.ui.form.on("Customize Form", {
frm.set_df_property("sort_field", "options", fields); 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 // can't delete standard fields
@@ -237,6 +246,7 @@ frappe.ui.form.on("Customize Form Field", {
var f = frappe.model.get_doc(cdt, cdn); var f = frappe.model.get_doc(cdt, cdn);
f.is_system_generated = false; f.is_system_generated = false;
f.is_custom_field = true; f.is_custom_field = true;
frm.trigger("setup_default_views");
}, },
}); });




+ 23
- 1
frappe/custom/doctype/customize_form/customize_form.json View File

@@ -13,6 +13,7 @@
"search_fields", "search_fields",
"column_break_5", "column_break_5",
"istable", "istable",
"is_calendar_and_gantt",
"editable_grid", "editable_grid",
"quick_entry", "quick_entry",
"track_changes", "track_changes",
@@ -35,6 +36,8 @@
"show_title_field_in_link", "show_title_field_in_link",
"translated_doctype", "translated_doctype",
"default_print_format", "default_print_format",
"default_view",
"force_re_route_to_default_view",
"column_break_29", "column_break_29",
"show_preview_popup", "show_preview_popup",
"email_settings_section", "email_settings_section",
@@ -337,6 +340,25 @@
"fieldname": "make_attachments_public", "fieldname": "make_attachments_public",
"fieldtype": "Check", "fieldtype": "Check",
"label": "Make Attachments Public by Default" "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, "hide_toolbar": 1,
@@ -345,7 +367,7 @@
"index_web_pages_for_search": 1, "index_web_pages_for_search": 1,
"issingle": 1, "issingle": 1,
"links": [], "links": [],
"modified": "2022-08-24 06:57:47.966331",
"modified": "2022-08-30 11:45:16.772277",
"modified_by": "Administrator", "modified_by": "Administrator",
"module": "Custom", "module": "Custom",
"name": "Customize Form", "name": "Customize Form",


+ 4
- 0
frappe/custom/doctype/customize_form/customize_form.py View File

@@ -586,6 +586,10 @@ doctype_properties = {
"naming_rule": "Data", "naming_rule": "Data",
"autoname": "Data", "autoname": "Data",
"show_title_field_in_link": "Check", "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", "translated_doctype": "Check",
} }




+ 1
- 1
frappe/public/js/frappe/list/base_list.js View File

@@ -196,7 +196,7 @@ frappe.views.BaseList = class BaseList {
Map: "map", 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 /* @preserve
for translation, don't remove for translation, don't remove
__("List View") __("Report View") __("Dashboard View") __("Gantt View"), __("List View") __("Report View") __("Dashboard View") __("Gantt View"),


+ 37
- 1
frappe/public/js/frappe/model/model.js View File

@@ -349,7 +349,7 @@ $.extend(frappe.model, {


is_tree: function (doctype) { is_tree: function (doctype) {
if (!doctype) return false; if (!doctype) return false;
return frappe.boot.treeviews.indexOf(doctype) != -1;
return locals.DocType[doctype] && locals.DocType[doctype].is_tree;
}, },


is_fresh(doc) { is_fresh(doc) {
@@ -754,6 +754,42 @@ $.extend(frappe.model, {
} }
return frappe.model.numeric_fieldtypes.includes(fieldtype); 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 // legacy


+ 84
- 22
frappe/public/js/frappe/router.js View File

@@ -89,6 +89,18 @@ frappe.router = {
"image", "image",
"inbox", "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: {}, layout_mapped: {},


is_app_route(path) { is_app_route(path) {
@@ -115,7 +127,7 @@ frappe.router = {
} }
}, },


route() {
async route() {
// resolve the route from the URL or hash // resolve the route from the URL or hash
// translate it so the objects are well defined // translate it so the objects are well defined
// and render the page as required // and render the page as required
@@ -126,22 +138,22 @@ frappe.router = {
if (this.re_route(sub_path)) return; if (this.re_route(sub_path)) return;


this.current_sub_path = sub_path; this.current_sub_path = sub_path;
this.current_route = this.parse();
this.current_route = await this.parse();
this.set_history(sub_path); this.set_history(sub_path);
this.render(); this.render();
this.set_title(sub_path); this.set_title(sub_path);
this.trigger("change"); this.trigger("change");
}, },


parse(route) {
async parse(route) {
route = this.get_sub_path_string(route).split("/"); route = this.get_sub_path_string(route).split("/");
if (!route) return []; if (!route) return [];
route = $.map(route, this.decode_component); route = $.map(route, this.decode_component);
this.set_route_options_from_url(); 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/settings = ["Workspaces", "Settings"]
// /app/private/settings = ["Workspaces", "private", "Settings"] // /app/private/settings = ["Workspaces", "private", "Settings"]
// /app/user = ["List", "User"] // /app/user = ["List", "User"]
@@ -161,7 +173,7 @@ frappe.router = {
route = ["Workspaces", "private", frappe.workspaces[private_workspace].title]; route = ["Workspaces", "private", frappe.workspaces[private_workspace].title];
} else if (this.routes[route[0]]) { } else if (this.routes[route[0]]) {
// route // route
route = this.set_doctype_route(route);
route = await this.set_doctype_route(route);
} }


return route; return route;
@@ -174,36 +186,85 @@ frappe.router = {


set_doctype_route(route) { set_doctype_route(route) {
let doctype_route = this.routes[route[0]]; 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]; let docname = route[1];
if (route.length > 2) { if (route.length > 2) {
docname = route.slice(1).join("/"); docname = route.slice(1).join("/");
} }
route = ["Form", doctype_route.doctype, docname]; 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; let standard_route;
if (route[2].toLowerCase() === "tree") {
let _route = default_view || route[2] || "";

if (_route.toLowerCase() === "tree") {
standard_route = ["Tree", doctype_route.doctype]; standard_route = ["Tree", doctype_route.doctype];
} else { } 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 // calendar / kanban / dashboard / folder
if (route[3]) standard_route.push(...route.slice(3, route.length)); if (route[3]) standard_route.push(...route.slice(3, route.length));
} }

return standard_route; return standard_route;
}, },


@@ -345,6 +406,7 @@ frappe.router = {
} else if (view === "tree") { } else if (view === "tree") {
new_route = [this.slug(route[1]), "view", "tree"]; new_route = [this.slug(route[1]), "view", "tree"];
} }

return new_route; return new_route;
}, },




+ 4
- 7
frappe/public/js/frappe/ui/toolbar/search_utils.js View File

@@ -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));
} }
} }
} }


+ 5
- 14
frappe/public/js/frappe/utils/utils.js View File

@@ -1260,20 +1260,12 @@ Object.assign(frappe.utils, {
if (frappe.model.is_single(item.doctype)) { if (frappe.model.is_single(item.doctype)) {
route = doctype_slug; route = doctype_slug;
} else { } 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) { switch (item.doc_view) {
case "List": case "List":
if (item.filters) { if (item.filters) {
frappe.route_options = item.filters; frappe.route_options = item.filters;
} }
route = doctype_slug;
route = `${doctype_slug}/view/list`;
break; break;
case "Tree": case "Tree":
route = `${doctype_slug}/view/tree`; route = `${doctype_slug}/view/tree`;
@@ -1290,12 +1282,11 @@ Object.assign(frappe.utils, {
case "Calendar": case "Calendar":
route = `${doctype_slug}/view/calendar/default`; route = `${doctype_slug}/view/calendar/default`;
break; break;
case "Kanban":
route = `${doctype_slug}/view/kanban`;
break;
default: default:
frappe.throw({
message: __("Not a valid view:") + item.doc_view,
title: __("Unknown View"),
});
route = "";
route = doctype_slug;
} }
} }
} else if (type === "report") { } else if (type === "report") {


+ 1
- 1
frappe/public/js/frappe/views/breadcrumbs.js View File

@@ -144,7 +144,7 @@ frappe.breadcrumbs = {
} else { } else {
let route; let route;
const doctype_route = frappe.router.slug(frappe.router.doctype_layout || doctype); 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"; let view = frappe.model.user_settings[doctype].last_view || "Tree";
route = `${doctype_route}/view/${view}`; route = `${doctype_route}/view/${view}`;
} else { } else {


+ 2
- 2
frappe/public/js/frappe/views/file/file_view.js View File

@@ -74,7 +74,7 @@ frappe.views.FileView = class FileView extends frappe.views.ListView {
this.page_title = __("File Manager"); this.page_title = __("File Manager");


const route = frappe.get_route(); 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.filters = [["File", "folder", "=", this.current_folder, true]];
this.order_by = this.view_user_settings.order_by || "file_name asc"; 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() { get_breadcrumbs_html() {
const route = frappe.router.parse();
const route = frappe.get_route();
const folders = route.slice(2); const folders = route.slice(2);


return folders return folders


+ 42
- 20
frappe/public/js/frappe/views/kanban/kanban_view.js View File

@@ -9,14 +9,9 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
const doctype = route[1]; const doctype = route[1];
const user_settings = frappe.get_user_settings(doctype)["Kanban"] || {}; const user_settings = frappe.get_user_settings(doctype)["Kanban"] || {};
if (!user_settings.last_kanban_board) { 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); route.push(user_settings.last_kanban_board);
frappe.set_route(route); frappe.set_route(route);
return true; return true;
@@ -28,9 +23,35 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {
return "Kanban"; 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() { setup_defaults() {
return super.setup_defaults().then(() => { 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.page_title = __(this.board_name);
this.card_meta = this.get_card_meta(); this.card_meta = this.get_card_meta();
this.page_length = 0; this.page_length = 0;
@@ -143,21 +164,22 @@ frappe.views.KanbanView = class KanbanView extends frappe.views.ListView {


render() { render() {
const board_name = this.board_name; 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) { if (this.kanban && board_name === this.kanban.board_name) {
this.kanban.update(this.data); 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() { get_card_meta() {


+ 5
- 0
frappe/public/js/frappe/views/treeview.js View File

@@ -37,6 +37,10 @@ frappe.views.TreeFactory = class TreeFactory extends frappe.views.Factory {
let treeview = frappe.views.trees[route[1]]; let treeview = frappe.views.trees[route[1]];
treeview && treeview.make_tree(); treeview && treeview.make_tree();
} }

get view_name() {
return "Tree";
}
}; };


frappe.views.TreeView = class TreeView { frappe.views.TreeView = class TreeView {
@@ -196,6 +200,7 @@ frappe.views.TreeView = class TreeView {
}); });


cur_tree = this.tree; cur_tree = this.tree;
cur_tree.view_name = "Tree";
this.post_render(); this.post_render();
} }




+ 17
- 13
frappe/public/js/frappe/widgets/widget_dialog.js View File

@@ -384,18 +384,22 @@ class ShortcutDialog extends WidgetDialog {
onchange: () => { onchange: () => {
if (this.dialog.get_value("type") == "DocType") { if (this.dialog.get_value("type") == "DocType") {
let doctype = this.dialog.get_value("link_to"); 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 { } else {
this.hide_filters(); this.hide_filters();
} }
@@ -405,7 +409,7 @@ class ShortcutDialog extends WidgetDialog {
fieldtype: "Select", fieldtype: "Select",
fieldname: "doc_view", fieldname: "doc_view",
label: "DocType View", label: "DocType View",
options: "List\nReport Builder\nDashboard\nTree\nNew\nCalendar",
options: "List\nReport Builder\nDashboard\nTree\nNew\nCalendar\nKanban",
description: __( description: __(
"Which view of the associated DocType should this shortcut take you to?" "Which view of the associated DocType should this shortcut take you to?"
), ),


+ 151
- 4
frappe/tests/ui_test_helpers.py View File

@@ -43,16 +43,32 @@ def create_todo_records():
frappe.db.truncate("ToDo") frappe.db.truncate("ToDo")


frappe.get_doc( 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() ).insert()
frappe.get_doc( 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() ).insert()
frappe.get_doc( 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() ).insert()
frappe.get_doc( 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() ).insert()




@@ -431,3 +447,134 @@ def create_test_user():
user.append("roles", {"role": role}) user.append("roles", {"role": role})


user.save() 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()

Loading…
Cancel
Save