fix(routing): removed /space from routing

This commit is contained in:
Rushabh Mehta 2021-01-01 12:30:35 +05:30
parent 094e727186
commit b2686e0623
44 changed files with 176 additions and 221 deletions

View file

@ -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', () => {

View file

@ -2,7 +2,7 @@ context('Awesome Bar', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/app/space/Website');
cy.visit('/app/website');
});
beforeEach(() => {

View file

@ -1,7 +1,7 @@
context('Control Barcode', () => {
beforeEach(() => {
cy.login();
cy.visit('/app/space/Website');
cy.visit('/app/website');
});
function get_dialog_with_barcode() {

View file

@ -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) {

View file

@ -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'

View file

@ -1,7 +1,7 @@
context('Control Rating', () => {
before(() => {
cy.login();
cy.visit('/app/space/Website');
cy.visit('/app/website');
});
function get_dialog_with_rating() {

View file

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

View file

@ -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',

View file

@ -1,7 +1,7 @@
context('FileUploader', () => {
before(() => {
cy.login();
cy.visit('/app/space/Website');
cy.visit('/app/website');
});
function open_upload_dialog() {

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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', () => {

View file

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

View file

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

View file

@ -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, {

View file

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

View file

@ -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

View file

@ -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)
frappe.db.set_value('DocType', doctype.name, 'route', slug(doctype.name), update_modified = False)

View file

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

View file

@ -0,0 +1,5 @@
import frappe
def execute():
for name in ('desktop', 'space'):
frappe.delete_doc('Page', name)

View file

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

View file

@ -1,3 +0,0 @@
frappe.pages['desktop'].on_page_load = function() {
frappe.utils.set_title(__("Home"));
};

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -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():

View file

@ -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(' ', '-')

View file

@ -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

View file

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

View file

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

View file

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

View file

@ -88,8 +88,7 @@ frappe.breadcrumbs = {
if (breadcrumbs.workspace) {
if(!breadcrumbs.module_info.blocked && frappe.visible_modules.includes(breadcrumbs.module_info.module)) {
$(repl('<li><a href="/app/space/%(module)s">%(label)s</a></li>',
{ module: breadcrumbs.workspace, label: __(breadcrumbs.workspace) }))
$(`<li><a href="/app/${frappe.router.slug(breadcrumbs.workspace)}">${__(breadcrumbs.workspace)}</a></li>`)
.appendTo(this.$breadcrumbs);
}
}

View file

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

View file

@ -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. <b>Please relaod the page</b>. 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 $(`
<a
href="/app/space/${item.name}"
href="/app/${frappe.router.slug(item.name)}"
class="desk-sidebar-item standard-sidebar-item ${item.selected ? "selected" : ""}"
>
<span>${frappe.utils.icon(item.icon || "folder-normal", "md")}</span>

View file

@ -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"]

View file

@ -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 """<a href='{0}'>{1}</a>""".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)))

View file

@ -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')