diff --git a/cypress/integration/api.js b/cypress/integration/api.js index 767cfbb55e..7a5b1611b0 100644 --- a/cypress/integration/api.js +++ b/cypress/integration/api.js @@ -2,7 +2,7 @@ context('API Resources', () => { before(() => { cy.visit('/login'); cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); it('Creates two Comments', () => { diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 55c8015bae..3e12101532 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -2,7 +2,7 @@ context('Awesome Bar', () => { before(() => { cy.visit('/login'); cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); beforeEach(() => { diff --git a/cypress/integration/control_barcode.js b/cypress/integration/control_barcode.js index 674d825504..1df5e64f0e 100644 --- a/cypress/integration/control_barcode.js +++ b/cypress/integration/control_barcode.js @@ -1,7 +1,7 @@ context('Control Barcode', () => { beforeEach(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); function get_dialog_with_barcode() { diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index 4d974018d3..266d421e70 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -1,7 +1,7 @@ context('Control Duration', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); function get_dialog_with_duration(hide_days = 0, hide_seconds = 0) { diff --git a/cypress/integration/control_link.js b/cypress/integration/control_link.js index 702c8430d6..918155b99b 100644 --- a/cypress/integration/control_link.js +++ b/cypress/integration/control_link.js @@ -1,11 +1,11 @@ context('Control Link', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); beforeEach(() => { - cy.visit('/app/space/Website'); + cy.visit('/app/website'); cy.create_records({ doctype: 'ToDo', description: 'this is a test todo for link' diff --git a/cypress/integration/control_rating.js b/cypress/integration/control_rating.js index 02d8e0639e..31c036d240 100644 --- a/cypress/integration/control_rating.js +++ b/cypress/integration/control_rating.js @@ -1,7 +1,7 @@ context('Control Rating', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); function get_dialog_with_rating() { diff --git a/cypress/integration/datetime.js b/cypress/integration/datetime.js index 74d823a81f..d66680b96b 100644 --- a/cypress/integration/datetime.js +++ b/cypress/integration/datetime.js @@ -4,7 +4,7 @@ const doctype_name = datetime_doctype.name; context('Control Date, Time and DateTime', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); return cy.insert_doc('DocType', datetime_doctype, true); }); diff --git a/cypress/integration/depends_on.js b/cypress/integration/depends_on.js index 7031634d98..1a6e1082aa 100644 --- a/cypress/integration/depends_on.js +++ b/cypress/integration/depends_on.js @@ -1,7 +1,7 @@ context('Depends On', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); return cy.window().its('frappe').then(frappe => { return frappe.call('frappe.tests.ui_test_helpers.create_doctype', { name: 'Test Depends On', diff --git a/cypress/integration/file_uploader.js b/cypress/integration/file_uploader.js index 5ef041b797..6bfe30ff55 100644 --- a/cypress/integration/file_uploader.js +++ b/cypress/integration/file_uploader.js @@ -1,7 +1,7 @@ context('FileUploader', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); function open_upload_dialog() { diff --git a/cypress/integration/form.js b/cypress/integration/form.js index f574770520..bea987b36d 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -1,7 +1,7 @@ context('Form', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); return cy.window().its('frappe').then(frappe => { return frappe.call("frappe.tests.ui_test_helpers.create_contact_records"); }); diff --git a/cypress/integration/grid_pagination.js b/cypress/integration/grid_pagination.js index 87c0fb0af4..8f6b79c1f4 100644 --- a/cypress/integration/grid_pagination.js +++ b/cypress/integration/grid_pagination.js @@ -1,11 +1,11 @@ context('Grid Pagination', () => { beforeEach(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); return cy.window().its('frappe').then(frappe => { return frappe.call("frappe.tests.ui_test_helpers.create_contact_phone_nos_records"); }); diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index a1ba1cf25e..ac51f42ccb 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -1,7 +1,7 @@ context('List View', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); return cy.window().its('frappe').then(frappe => { return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow"); }); diff --git a/cypress/integration/list_view_settings.js b/cypress/integration/list_view_settings.js index cdb1c5d778..123d7e70f2 100644 --- a/cypress/integration/list_view_settings.js +++ b/cypress/integration/list_view_settings.js @@ -1,7 +1,7 @@ context('List View Settings', () => { beforeEach(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); it('Default settings', () => { cy.visit('/app/List/DocType/List'); diff --git a/cypress/integration/login.js b/cypress/integration/login.js index 4eb8933771..d1ee99b36d 100644 --- a/cypress/integration/login.js +++ b/cypress/integration/login.js @@ -35,7 +35,7 @@ context('Login', () => { cy.get('#login_password').type(Cypress.config('adminPassword')); cy.get('.btn-login:visible').click(); - cy.location('pathname').should('eq', '/app/space/Home'); + cy.location('pathname').should('eq', '/app/home'); cy.window().its('frappe.session.user').should('eq', 'Administrator'); }); diff --git a/cypress/integration/query_report.js b/cypress/integration/query_report.js index d0ca844362..e2a1c3fc79 100644 --- a/cypress/integration/query_report.js +++ b/cypress/integration/query_report.js @@ -1,7 +1,7 @@ context('Query Report', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); }); it('add custom column in report', () => { diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js index 2c71dbe64d..4b86844d2a 100644 --- a/cypress/integration/recorder.js +++ b/cypress/integration/recorder.js @@ -4,7 +4,7 @@ context('Recorder', () => { }); it('Navigate to Recorder', () => { - cy.visit('/app/space/Website'); + cy.visit('/app/website'); cy.awesomebar('recorder'); cy.get('h1').should('contain', 'Recorder'); cy.location('pathname').should('eq', '#recorder'); diff --git a/cypress/integration/relative_time_filters.js b/cypress/integration/relative_time_filters.js index 33ea49f2d2..380d488547 100644 --- a/cypress/integration/relative_time_filters.js +++ b/cypress/integration/relative_time_filters.js @@ -4,7 +4,7 @@ context('Relative Timeframe', () => { }); before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); cy.window().its('frappe').then(frappe => { frappe.call("frappe.tests.ui_test_helpers.create_todo_records"); }); diff --git a/cypress/integration/report_view.js b/cypress/integration/report_view.js index 00816a4fa6..d1a3e4dfd8 100644 --- a/cypress/integration/report_view.js +++ b/cypress/integration/report_view.js @@ -4,7 +4,7 @@ const doctype_name = custom_submittable_doctype.name; context('Report View', () => { before(() => { cy.login(); - cy.visit('/app/space/Website'); + cy.visit('/app/website'); cy.insert_doc('DocType', custom_submittable_doctype, true); cy.clear_cache(); cy.insert_doc(doctype_name, { diff --git a/frappe/boot.py b/frappe/boot.py index 2f4569dee9..f15b62614e 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -109,7 +109,7 @@ def load_conf_settings(bootinfo): def load_desktop_data(bootinfo): from frappe.desk.desktop import get_desk_sidebar_items - bootinfo.allowed_workspaces = get_desk_sidebar_items(flatten=True, cache=False) + bootinfo.allowed_workspaces = get_desk_sidebar_items() bootinfo.module_page_map = get_controller("Workspace").get_module_page_map() bootinfo.dashboards = frappe.get_all("Dashboard") diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index a59bef9fc7..09187b5467 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -26,6 +26,7 @@ from frappe.database.schema import validate_column_name, validate_column_length from frappe.model.docfield import supports_translation from frappe.modules.import_file import get_file_path from frappe.model.meta import Meta +from frappe.desk.utils import validate_route_conflict class InvalidFieldNameError(frappe.ValidationError): pass class UniqueFieldnameError(frappe.ValidationError): pass @@ -648,6 +649,8 @@ class DocType(Document): if not re.match("^(?![\W])[^\d_\s][\w ]+$", name, **flags): frappe.throw(_("DocType's name should start with a letter and it can only consist of letters, numbers, spaces and underscores"), frappe.NameError) + validate_route_conflict(self.doctype, self.name) + def validate_links_table_fieldnames(meta): """Validate fieldnames in Links table""" if frappe.flags.in_patch: return diff --git a/frappe/core/doctype/doctype/patches/set_route.py b/frappe/core/doctype/doctype/patches/set_route.py index 655935f861..c052a51f38 100644 --- a/frappe/core/doctype/doctype/patches/set_route.py +++ b/frappe/core/doctype/doctype/patches/set_route.py @@ -1,7 +1,7 @@ import frappe -from frappe.desk.utils import get_doctype_route +from frappe.desk.utils import slug def execute(): for doctype in frappe.get_all('DocType', ['name', 'route'], dict(istable=0)): if not doctype.route: - frappe.db.set_value('DocType', doctype.name, 'route', get_doctype_route(doctype.name), update_modified = False) \ No newline at end of file + frappe.db.set_value('DocType', doctype.name, 'route', slug(doctype.name), update_modified = False) \ No newline at end of file diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index 2d616542f3..bdec350efd 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -9,6 +9,7 @@ from frappe.build import html_to_js_template from frappe.model.utils import render_include from frappe import conf, _, safe_decode from frappe.desk.form.meta import get_code_files_via_hooks, get_js +from frappe.desk.utils import validate_route_conflict from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles from six import text_type @@ -33,10 +34,7 @@ class Page(Document): self.name += '-' + str(cnt) def validate(self): - if frappe.db.get_value('DocType', self.name): - frappe.throw( - _("{} is the name of a DocType. DocType names cannot be the same as a Page name, please choose another name.").format(self.page_name) - ) + validate_route_conflict(self.doctype, self.name) if self.is_new() and not getattr(conf,'developer_mode', 0): frappe.throw(_("Not in Developer Mode")) diff --git a/frappe/core/doctype/page/patches/drop_unused_pages.py b/frappe/core/doctype/page/patches/drop_unused_pages.py new file mode 100644 index 0000000000..93b47cebcc --- /dev/null +++ b/frappe/core/doctype/page/patches/drop_unused_pages.py @@ -0,0 +1,5 @@ +import frappe + +def execute(): + for name in ('desktop', 'space'): + frappe.delete_doc('Page', name) \ No newline at end of file diff --git a/frappe/core/doctype/page/test_page.py b/frappe/core/doctype/page/test_page.py index 78659f1ffd..f7b3952a5b 100644 --- a/frappe/core/doctype/page/test_page.py +++ b/frappe/core/doctype/page/test_page.py @@ -8,4 +8,6 @@ import unittest test_records = frappe.get_test_records('Page') class TestPage(unittest.TestCase): - pass + def test_naming(self): + self.assertRaises(frappe.NameError, frappe.get_doc(dict(doctype='Page', page_name='DocType', module='Core')).insert) + self.assertRaises(frappe.NameError, frappe.get_doc(dict(doctype='Page', page_name='Settings', module='Core')).insert) diff --git a/frappe/core/page/desktop/__init__.py b/frappe/core/page/desktop/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js deleted file mode 100644 index cc36a5a4e9..0000000000 --- a/frappe/core/page/desktop/desktop.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.pages['desktop'].on_page_load = function() { - frappe.utils.set_title(__("Home")); -}; \ No newline at end of file diff --git a/frappe/core/page/desktop/desktop.json b/frappe/core/page/desktop/desktop.json deleted file mode 100644 index 66bbfbfd40..0000000000 --- a/frappe/core/page/desktop/desktop.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "content": null, - "creation": "2019-01-29 13:11:48.872579", - "docstatus": 0, - "doctype": "Page", - "icon": "icon-th", - "idx": 0, - "modified": "2019-01-29 13:11:48.872579", - "modified_by": "Administrator", - "module": "Core", - "name": "desktop", - "owner": "Administrator", - "page_name": "desktop", - "roles": [ - { - "role": "All" - } - ], - "script": null, - "standard": "Yes", - "style": null, - "system_page": 0, - "title": "Desktop" -} \ No newline at end of file diff --git a/frappe/core/page/space/__init__.py b/frappe/core/page/space/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/frappe/core/page/space/space.js b/frappe/core/page/space/space.js deleted file mode 100644 index e781c56d8b..0000000000 --- a/frappe/core/page/space/space.js +++ /dev/null @@ -1,12 +0,0 @@ -frappe.pages['space'].on_page_load = function (wrapper) { - frappe.ui.make_app_page({ - parent: wrapper, - name: 'space', - title: __("Workspace"), - }); - - frappe.workspace = new frappe.views.Workspace(wrapper); - $(wrapper).bind('show', function () { - frappe.workspace.show(); - }); -} \ No newline at end of file diff --git a/frappe/core/page/space/space.json b/frappe/core/page/space/space.json deleted file mode 100644 index d5f0b4c9db..0000000000 --- a/frappe/core/page/space/space.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "content": null, - "creation": "2020-02-27 15:07:57.124916", - "docstatus": 0, - "doctype": "Page", - "icon": "icon-th", - "idx": 0, - "modified": "2020-12-16 14:22:05.591912", - "modified_by": "Administrator", - "module": "Core", - "name": "space", - "owner": "Administrator", - "page_name": "space", - "roles": [ - { - "role": "All" - } - ], - "script": null, - "standard": "Yes", - "style": null, - "system_page": 0 -} \ No newline at end of file diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.py b/frappe/custom/doctype/doctype_layout/doctype_layout.py index b580ac8f56..6a080b8fa2 100644 --- a/frappe/custom/doctype/doctype_layout/doctype_layout.py +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.py @@ -7,9 +7,9 @@ from __future__ import unicode_literals import frappe from frappe.model.document import Document -from frappe.desk.utils import get_doctype_route +from frappe.desk.utils import slug class DocTypeLayout(Document): def validate(self): if not self.route: - self.route = get_doctype_route(self.name) + self.route = slug(self.name) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index 1fa3f61752..f1bf7832ae 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -361,57 +361,39 @@ def get_desktop_page(page): } @frappe.whitelist() -def get_desk_sidebar_items(flatten=False, cache=True): - """Get list of sidebar items for desk - """ +def get_desk_sidebar_items(): + """Get list of sidebar items for desk""" + + # don't get domain restricted pages + blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules() + + filters = { + 'restrict_to_domain': ['in', frappe.get_active_domains()], + 'extends_another_page': 0, + 'for_user': '', + 'module': ['not in', blocked_modules] + } + + if not frappe.local.conf.developer_mode: + filters['developer_mode_only'] = '0' + + # pages sorted based on pinned to top and then by name + order_by = "pin_to_top desc, pin_to_bottom asc, name asc" + all_pages = frappe.get_all("Workspace", fields=["name", "category", "icon", "module"], + filters=filters, order_by=order_by, ignore_permissions=True) pages = [] - _cache = frappe.cache() - if cache: - pages = _cache.get_value("desk_sidebar_items", user=frappe.session.user) - if not pages or not cache: - # don't get domain restricted pages - blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules() + # Filter Page based on Permission + for page in all_pages: + try: + wspace = Workspace(page.get('name'), True) + if wspace.is_page_allowed(): + pages.append(page) + page['label'] = _(page.get('name')) + except frappe.PermissionError: + pass - filters = { - 'restrict_to_domain': ['in', frappe.get_active_domains()], - 'extends_another_page': 0, - 'for_user': '', - 'module': ['not in', blocked_modules] - } - - if not frappe.local.conf.developer_mode: - filters['developer_mode_only'] = '0' - - # pages sorted based on pinned to top and then by name - order_by = "pin_to_top desc, pin_to_bottom asc, name asc" - all_pages = frappe.get_all("Workspace", fields=["name", "category", "icon", "module"], - filters=filters, order_by=order_by, ignore_permissions=True) - pages = [] - - # Filter Page based on Permission - for page in all_pages: - try: - wspace = Workspace(page.get('name'), True) - if wspace.is_page_allowed(): - pages.append(page) - except frappe.PermissionError: - pass - - _cache.set_value("desk_sidebar_items", pages, frappe.session.user) - - if flatten: - return pages - - from collections import defaultdict - sidebar_items = defaultdict(list) - - # The order will be maintained while categorizing - for page in pages: - # Translate label - page['label'] = _(page.get('name')) - sidebar_items[page["category"]].append(page) - return sidebar_items + return pages def get_table_with_counts(): counts = frappe.cache().get_value("information_schema:counts") diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 3c7ba10bcb..dcecfc4b5a 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -8,6 +8,7 @@ from frappe import _, _dict from frappe.utils.data import validate_json_string from frappe.modules.export_file import export_to_files from frappe.model.document import Document +from frappe.desk.utils import validate_route_conflict from json import loads, dumps @@ -15,6 +16,7 @@ class Workspace(Document): def validate(self): if (self.is_standard and not frappe.conf.developer_mode and not disable_saving_as_standard()): frappe.throw(_("You need to be in developer mode to edit this document")) + validate_route_conflict(self.doctype, self.name) def on_update(self): if disable_saving_as_standard(): diff --git a/frappe/desk/utils.py b/frappe/desk/utils.py index a3de00cd54..7281f456b0 100644 --- a/frappe/desk/utils.py +++ b/frappe/desk/utils.py @@ -3,5 +3,17 @@ import frappe -def get_doctype_route(name): +def validate_route_conflict(doctype, name): + ''' + Raises exception if name clashes with routes from other documents for /app routing + ''' + all_names = [] + for _doctype in ['Page', 'Workspace', 'DocType']: + all_names.extend([slug(d) for d in frappe.get_all(_doctype, pluck='name') if (doctype != _doctype and d != name)]) + + if slug(name) in all_names: + frappe.msgprint(frappe._('Name already taken, please set a new name')) + raise frappe.NameError + +def slug(name): return name.lower().replace(' ', '-') \ No newline at end of file diff --git a/frappe/patches.txt b/frappe/patches.txt index 08b30be90a..8617eac40d 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -323,3 +323,4 @@ frappe.patches.v13_0.update_icons_in_customized_desk_pages execute:frappe.db.set_default('desktop:home_page', 'space') execute:frappe.delete_doc_if_exists('Page', 'workspace') execute:frappe.delete_doc_if_exists('Page', 'dashboard', force=1) +frappe.core.doctype.page.patches.drop_unused_pages diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 16f54c1b13..1ca4424355 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -253,10 +253,7 @@ frappe.Application = Class.extend({ }, load_bootinfo: function() { if(frappe.boot) { - frappe.modules = {}; - (frappe.boot.allowed_workspaces || []).forEach(function(m) { - frappe.modules[m.module]=m; - }); + this.setup_workspaces(); frappe.model.sync(frappe.boot.docs); $.extend(frappe._messages, frappe.boot.__messages); this.check_metadata_cache_status(); @@ -278,6 +275,15 @@ frappe.Application = Class.extend({ } }, + setup_workspaces() { + frappe.modules = {}; + frappe.workspaces = {}; + for (let page of frappe.boot.allowed_workspaces || []) { + frappe.modules[page.module]=page; + frappe.workspaces[frappe.router.slug(page.name)] = page; + } + }, + load_user_permissions: function() { frappe.defaults.update_user_permissions(); diff --git a/frappe/public/js/frappe/list/views.js b/frappe/public/js/frappe/list/views.js index 7eada35d42..060f43488a 100644 --- a/frappe/public/js/frappe/list/views.js +++ b/frappe/public/js/frappe/list/views.js @@ -35,7 +35,7 @@ frappe.views.Views = class Views { } set_route(view, calendar_name) { - const route = [this.get_doctype_route(), 'view', view]; + const route = [this.slug(), 'view', view]; if (calendar_name) route.push(calendar_name); frappe.set_route(route); } @@ -233,11 +233,11 @@ frappe.views.Views = class Views { // has standard calendar view calendars.push({ name: 'Default', - route: `/app/${this.get_doctype_route()}/view/calendar/default` + route: `/app/${this.slug()}/view/calendar/default` }); } result.map(calendar => { - calendars.push({name: calendar.name, route: `/app/${this.get_doctype_route()}/view/calendar/${calendar.name}`}); + calendars.push({name: calendar.name, route: `/app/${this.slug()}/view/calendar/${calendar.name}`}); }); return calendars; @@ -263,7 +263,7 @@ frappe.views.Views = class Views { return accounts_to_add; } - get_doctype_route() { + slug() { return frappe.router.slug(frappe.router.doctype_layout || this.doctype); } } \ No newline at end of file diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index 4ececbb7ca..bd53396ceb 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -117,40 +117,50 @@ frappe.router = { }, convert_to_standard_route(route) { + // /app/settings = ["Workspaces", "Settings"] // /app/user = ["List", "User"] // /app/user/view/report = ["List", "User", "Report"] // /app/user/view/tree = ["Tree", "User"] // /app/user/user-001 = ["Form", "User", "user-001"] // /app/user/user-001 = ["Form", "User", "user-001"] // /app/event/view/calendar/default = ["List", "Event", "Calendar", "Default"] - let standard_route = route; - let doctype_route = this.routes[route[0]]; - if (doctype_route) { - // doctype route - if (route[1]) { - if (route[2] && route[1]==='view') { - standard_route = this.get_standard_route_for_list(route, doctype_route); - } else { - let docname = route[1]; - if (route.length > 2) { - docname = route.slice(1).join('/'); - } - standard_route = ['Form', doctype_route.doctype, docname]; - } - } else if (frappe.model.is_single(doctype_route.doctype)) { - standard_route = ['Form', doctype_route.doctype, doctype_route.doctype]; - } else { - standard_route = ['List', doctype_route.doctype, 'List']; - } - - if (doctype_route.doctype_layout) { - // set the layout - this.doctype_layout = doctype_route.doctype_layout; - } + if (frappe.workspaces[route[0]]) { + // workspace + route = ['Workspaces', frappe.workspaces[route[0]].name]; + } else if (this.routes[route[0]]) { + // route + route = this.set_doctype_route(route); } - return standard_route; + return route; + }, + + 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 { + 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 { + route = ['List', doctype_route.doctype, 'List']; + } + + if (doctype_route.doctype_layout) { + // set the layout + this.doctype_layout = doctype_route.doctype_layout; + } + + return route; }, get_standard_route_for_list(route, doctype_route) { @@ -186,14 +196,8 @@ frappe.router = { // create the page generator (factory) object and call `show` // if there is no generator, render the `Page` object - // first the router needs to know if its a "page", "doctype", "space" - const route = this.current_route; const factory = frappe.utils.to_title_case(route[0]); - if (factory === 'Workspace') { - frappe.views.pageview.show(''); - return; - } if (route[1] && frappe.views[factory + "Factory"]) { route[0] = factory; diff --git a/frappe/public/js/frappe/views/breadcrumbs.js b/frappe/public/js/frappe/views/breadcrumbs.js index b845fb3e8b..4f2a9d2163 100644 --- a/frappe/public/js/frappe/views/breadcrumbs.js +++ b/frappe/public/js/frappe/views/breadcrumbs.js @@ -88,8 +88,7 @@ frappe.breadcrumbs = { if (breadcrumbs.workspace) { if(!breadcrumbs.module_info.blocked && frappe.visible_modules.includes(breadcrumbs.module_info.module)) { - $(repl('
  • %(label)s
  • ', - { module: breadcrumbs.workspace, label: __(breadcrumbs.workspace) })) + $(`
  • ${__(breadcrumbs.workspace)}
  • `) .appendTo(this.$breadcrumbs); } } diff --git a/frappe/public/js/frappe/views/pageview.js b/frappe/public/js/frappe/views/pageview.js index 48a2780fac..7d344c3832 100644 --- a/frappe/public/js/frappe/views/pageview.js +++ b/frappe/public/js/frappe/views/pageview.js @@ -6,7 +6,7 @@ frappe.provide("frappe.standard_pages"); frappe.views.pageview = { with_page: function(name, callback) { - if(in_list(Object.keys(frappe.standard_pages), name)) { + if(frappe.standard_pages[name]) { if(!frappe.pages[name]) { frappe.standard_pages[name](); } diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js index ce6a0a73fc..f36ed23542 100644 --- a/frappe/public/js/frappe/views/workspace/workspace.js +++ b/frappe/public/js/frappe/views/workspace/workspace.js @@ -1,3 +1,18 @@ +frappe.standard_pages['Workspaces'] = function() { + var wrapper = frappe.container.add_page('Workspaces'); + + frappe.ui.make_app_page({ + parent: wrapper, + name: 'Workspaces', + title: __("Workspace"), + }); + + frappe.workspace = new frappe.views.Workspace(wrapper); + $(wrapper).bind('show', function () { + frappe.workspace.show(); + }); +} + frappe.views.Workspace = class Workspace { constructor(wrapper) { this.wrapper = $(wrapper); @@ -14,15 +29,24 @@ frappe.views.Workspace = class Workspace { "Administration" ]; - this.fetch_desktop_settings().then(() => { - this.make_sidebar(); - }) + this.setup_workspaces(); + this.make_sidebar(); + } + + setup_workspaces() { + // workspaces grouped by categories + this.workspaces = {}; + for (let page of frappe.boot.allowed_workspaces) { + if (!this.workspaces[page.category]) { + this.workspaces[page.category] = []; + } + this.workspaces[page.category].push(page); + } } show() { let page = this.get_page_to_show(); this.page.set_title(`${__(page)}`); - frappe.set_route('space', page); this.show_page(page); } @@ -40,8 +64,8 @@ frappe.views.Workspace = class Workspace { if (localStorage.current_workspace) { default_page = localStorage.current_workspace; - } else if (this.desktop_settings) { - default_page = this.desktop_settings["Modules"][0].name; + } else if (this.workspaces) { + default_page = this.workspaces["Modules"][0].name; } else if (frappe.boot.allowed_workspaces) { default_page = frappe.boot.allowed_workspaces[0].name; } else { @@ -53,31 +77,10 @@ frappe.views.Workspace = class Workspace { return page; } - fetch_desktop_settings() { - return frappe - .call("frappe.desk.desktop.get_desk_sidebar_items") - .then(response => { - if (response.message) { - this.desktop_settings = response.message; - } else { - frappe.throw({ - title: __("Couldn't Load Desk"), - message: - __("Something went wrong while loading Desk. Please relaod the page. If the problem persists, contact the Administrator"), - indicator: "red", - primary_action: { - label: __("Reload"), - action: () => location.reload() - } - }); - } - }); - } - make_sidebar() { this.sidebar_categories.forEach(category => { - if (this.desktop_settings[category]) { - this.build_sidebar_section(category, this.desktop_settings[category]) + if (this.workspaces[category]) { + this.build_sidebar_section(category, this.workspaces[category]) } }); } @@ -94,7 +97,7 @@ frappe.views.Workspace = class Workspace { const get_sidebar_item = function (item) { return $(` ${frappe.utils.icon(item.icon || "folder-normal", "md")} diff --git a/frappe/search/website_search.py b/frappe/search/website_search.py index de93fea3f5..87ef4b08ad 100644 --- a/frappe/search/website_search.py +++ b/frappe/search/website_search.py @@ -31,7 +31,7 @@ class WebsiteSearch(FullTextSearch): self (object): FullTextSearch Instance """ routes = get_static_pages_from_all_apps() - routes += get_doctype_routes_with_web_view() + routes += slugs_with_web_view() documents = [self.get_document_to_index(route) for route in routes] return documents @@ -74,7 +74,7 @@ class WebsiteSearch(FullTextSearch): ) -def get_doctype_routes_with_web_view(): +def slugs_with_web_view(): all_routes = [] filters = { "has_web_view": 1, "allow_guest_to_view": 1, "index_web_pages_for_search": 1} fields = ["name", "is_published_field"] diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 678fa956c2..9d2d98674d 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -17,7 +17,7 @@ from six.moves.urllib.parse import quote, urljoin from html2text import html2text from markdown2 import markdown, MarkdownError from six import iteritems, text_type, string_types, integer_types -from frappe.desk.utils import get_doctype_route +from frappe.desk.utils import slug DATE_FORMAT = "%Y-%m-%d" TIME_FORMAT = "%H:%M:%S.%f" @@ -1059,17 +1059,17 @@ def get_link_to_report(name, label=None, report_type=None, doctype=None, filters return """{1}""".format(get_url_to_report(name, report_type, doctype), label) def get_absolute_url(doctype, name): - return "/app/{0}/{1}".format(quoted(get_doctype_route(doctype)), quoted(name)) + return "/app/{0}/{1}".format(quoted(slug(doctype)), quoted(name)) def get_url_to_form(doctype, name): - return get_url(uri = "/app/{0}/{1}".format(quoted(get_doctype_route(doctype)), quoted(name))) + return get_url(uri = "/app/{0}/{1}".format(quoted(slug(doctype)), quoted(name))) def get_url_to_list(doctype): - return get_url(uri = "/app/{0}".format(quoted(get_doctype_route(doctype)))) + return get_url(uri = "/app/{0}".format(quoted(slug(doctype)))) def get_url_to_report(name, report_type = None, doctype = None): if report_type == "Report Builder": - return get_url(uri = "/app/{0}/view/report/{1}".format(quoted(get_doctype_route(doctype)), quoted(name))) + return get_url(uri = "/app/{0}/view/report/{1}".format(quoted(slug(doctype)), quoted(name))) else: return get_url(uri = "/app/query-report/{0}".format(quoted(name))) diff --git a/frappe/website/router.py b/frappe/website/router.py index 5244c57ba8..946c83811a 100644 --- a/frappe/website/router.py +++ b/frappe/website/router.py @@ -441,4 +441,4 @@ def get_doctypes_with_web_view(): return frappe.cache().get_value('doctypes_with_web_view', _get) def get_start_folders(): - return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') + return frappe.local.flags.web_pages_folders or ('www', 'templates/pages') \ No newline at end of file