소스 검색

Merge pull request #12143 from rmehta/workspace-routing

fix(routing): removed /space from routing
version-14
Rushabh Mehta 4 년 전
committed by GitHub
부모
커밋
6bfd482032
No known key found for this signature in database GPG 키 ID: 4AEE18F83AFDEB23
48개의 변경된 파일253개의 추가작업 그리고 285개의 파일을 삭제
  1. +1
    -1
      cypress/integration/api.js
  2. +1
    -1
      cypress/integration/awesome_bar.js
  3. +1
    -1
      cypress/integration/control_barcode.js
  4. +1
    -1
      cypress/integration/control_duration.js
  5. +2
    -2
      cypress/integration/control_link.js
  6. +1
    -1
      cypress/integration/control_rating.js
  7. +1
    -1
      cypress/integration/datetime.js
  8. +1
    -1
      cypress/integration/depends_on.js
  9. +1
    -1
      cypress/integration/file_uploader.js
  10. +1
    -1
      cypress/integration/form.js
  11. +2
    -2
      cypress/integration/grid_pagination.js
  12. +1
    -1
      cypress/integration/list_view.js
  13. +1
    -1
      cypress/integration/list_view_settings.js
  14. +1
    -1
      cypress/integration/login.js
  15. +1
    -1
      cypress/integration/query_report.js
  16. +1
    -1
      cypress/integration/recorder.js
  17. +1
    -1
      cypress/integration/relative_time_filters.js
  18. +1
    -1
      cypress/integration/report_view.js
  19. +4
    -5
      frappe/boot.py
  20. +3
    -0
      frappe/core/doctype/doctype/doctype.py
  21. +2
    -2
      frappe/core/doctype/doctype/patches/set_route.py
  22. +2
    -4
      frappe/core/doctype/page/page.py
  23. +5
    -0
      frappe/core/doctype/page/patches/drop_unused_pages.py
  24. +3
    -1
      frappe/core/doctype/page/test_page.py
  25. +1
    -0
      frappe/core/doctype/server_script/test_server_script.py
  26. +0
    -0
      frappe/core/page/desktop/__init__.py
  27. +0
    -3
      frappe/core/page/desktop/desktop.js
  28. +0
    -24
      frappe/core/page/desktop/desktop.json
  29. +0
    -0
      frappe/core/page/space/__init__.py
  30. +0
    -12
      frappe/core/page/space/space.js
  31. +0
    -23
      frappe/core/page/space/space.json
  32. +2
    -2
      frappe/custom/doctype/doctype_layout/doctype_layout.py
  33. +28
    -46
      frappe/desk/desktop.py
  34. +2
    -0
      frappe/desk/doctype/workspace/workspace.py
  35. +13
    -1
      frappe/desk/utils.py
  36. +45
    -40
      frappe/email/doctype/newsletter/test_newsletter.py
  37. +1
    -0
      frappe/patches.txt
  38. +14
    -4
      frappe/public/js/frappe/desk.js
  39. +4
    -4
      frappe/public/js/frappe/list/views.js
  40. +35
    -29
      frappe/public/js/frappe/router.js
  41. +1
    -2
      frappe/public/js/frappe/views/breadcrumbs.js
  42. +11
    -11
      frappe/public/js/frappe/views/pageview.js
  43. +34
    -32
      frappe/public/js/frappe/views/workspace/workspace.js
  44. +2
    -2
      frappe/search/website_search.py
  45. +9
    -9
      frappe/tests/test_search.py
  46. +6
    -3
      frappe/tests/test_translate.py
  47. +5
    -5
      frappe/utils/data.py
  48. +1
    -1
      frappe/website/router.py

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


+ 1
- 1
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(() => {


+ 1
- 1
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() {


+ 1
- 1
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) {


+ 2
- 2
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'


+ 1
- 1
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() {


+ 1
- 1
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);
});



+ 1
- 1
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',


+ 1
- 1
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() {


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


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


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


+ 1
- 1
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');


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



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


+ 1
- 1
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');


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


+ 1
- 1
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, {


+ 4
- 5
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")

@@ -250,13 +250,12 @@ def add_home_page(bootinfo, docs):

try:
page = frappe.desk.desk_page.get(home_page)
docs.append(page)
bootinfo['home_page'] = page.name
except (frappe.DoesNotExistError, frappe.PermissionError):
if frappe.message_log:
frappe.message_log.pop()
page = frappe.desk.desk_page.get('space')

bootinfo['home_page'] = page.name
docs.append(page)
bootinfo['home_page'] = 'Workspaces'

def add_timezone_info(bootinfo):
system = bootinfo.sysdefaults.get("time_zone")


+ 3
- 0
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


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

+ 2
- 4
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"))


+ 5
- 0
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)

+ 3
- 1
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)

+ 1
- 0
frappe/core/doctype/server_script/test_server_script.py 파일 보기

@@ -81,6 +81,7 @@ class TestServerScript(unittest.TestCase):
def tearDownClass(cls):
frappe.db.commit()
frappe.db.sql('truncate `tabServer Script`')
frappe.cache().delete_value('server_script_map')

def setUp(self):
frappe.cache().delete_value('server_script_map')


+ 0
- 0
frappe/core/page/desktop/__init__.py 파일 보기


+ 0
- 3
frappe/core/page/desktop/desktop.js 파일 보기

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

+ 0
- 24
frappe/core/page/desktop/desktop.json 파일 보기

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

+ 0
- 0
frappe/core/page/space/__init__.py 파일 보기


+ 0
- 12
frappe/core/page/space/space.js 파일 보기

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

+ 0
- 23
frappe/core/page/space/space.json 파일 보기

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

+ 2
- 2
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)

+ 28
- 46
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
"""
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()

filters = {
'restrict_to_domain': ['in', frappe.get_active_domains()],
'extends_another_page': 0,
'for_user': '',
'module': ['not in', blocked_modules]
}
def get_desk_sidebar_items():
"""Get list of sidebar items for desk"""

if not frappe.local.conf.developer_mode:
filters['developer_mode_only'] = '0'
# don't get domain restricted pages
blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules()

# 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)
filters = {
'restrict_to_domain': ['in', frappe.get_active_domains()],
'extends_another_page': 0,
'for_user': '',
'module': ['not in', blocked_modules]
}

if flatten:
return pages
if not frappe.local.conf.developer_mode:
filters['developer_mode_only'] = '0'

from collections import defaultdict
sidebar_items = defaultdict(list)
# 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 = []

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

return pages

def get_table_with_counts():
counts = frappe.cache().get_value("information_schema:counts")


+ 2
- 0
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():


+ 13
- 1
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(' ', '-')

+ 45
- 40
frappe/email/doctype/newsletter/test_newsletter.py 파일 보기

@@ -15,21 +15,7 @@ emails = ["test_subscriber1@example.com", "test_subscriber2@example.com",

class TestNewsletter(unittest.TestCase):
def setUp(self):
frappe.set_user("Administrator")
frappe.db.sql('delete from `tabEmail Group Member`')

group_exist=frappe.db.exists("Email Group", "_Test Email Group")
if len(group_exist) == 0:
frappe.get_doc({
"doctype": "Email Group",
"title": "_Test Email Group"
}).insert()
for email in emails:
frappe.get_doc({
"doctype": "Email Group Member",
"email": email,
"email_group": "_Test Email Group"
}).insert()
self.make_email_group()

def test_send(self):
name = self.send_newsletter()
@@ -46,8 +32,9 @@ class TestNewsletter(unittest.TestCase):
from frappe.email.queue import flush
flush(from_test=True)
to_unsubscribe = unquote(frappe.local.flags.signed_query_string.split("email=")[1].split("&")[0])
group = frappe.get_all("Newsletter Email Group", filters={"parent" : name}, fields=["email_group"])
confirmed_unsubscribe(to_unsubscribe, group[0].email_group)

email_group = frappe.db.get_value('Newsletter Email Group', dict(parent=name), 'email_group')
confirmed_unsubscribe(to_unsubscribe, email_group)

name = self.send_newsletter()

@@ -58,6 +45,36 @@ class TestNewsletter(unittest.TestCase):
if email != to_unsubscribe:
self.assertTrue(email in recipients)

frappe.db.set_value('Email Group Member', dict(email=to_unsubscribe), 'unsubscribed', 0)

def test_portal(self):
self.send_newsletter(1)
frappe.set_user("test1@example.com")
from frappe.email.doctype.newsletter.newsletter import get_newsletter_list
newsletters = get_newsletter_list("Newsletter", None, None, 0)
self.assertEqual(len(newsletters), 1)
frappe.set_user("Administrator")


def test_newsletter_context(self):
context = frappe._dict()
newsletter_name = self.send_newsletter(1)
frappe.set_user("test2@example.com")
doc = frappe.get_doc("Newsletter", newsletter_name)
doc.get_context(context)
self.assertEqual(context.no_cache, 1)
self.assertTrue("attachments" not in list(context))
frappe.set_user("Administrator")

def test_schedule_send(self):
self.send_newsletter(schedule_send=add_days(getdate(), -1))

email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")]
self.assertEqual(len(email_queue_list), 4)
recipients = [e.recipients[0].recipient for e in email_queue_list]
for email in emails:
self.assertTrue(email in recipients)

@staticmethod
def send_newsletter(published=0, schedule_send=None):
frappe.db.sql("delete from `tabEmail Queue`")
@@ -83,27 +100,15 @@ class TestNewsletter(unittest.TestCase):
newsletter.send_emails()
return newsletter.name

def test_portal(self):
self.send_newsletter(1)
frappe.set_user("test1@example.com")
from frappe.email.doctype.newsletter.newsletter import get_newsletter_list
newsletters = get_newsletter_list("Newsletter", None, None, 0)
self.assertEqual(len(newsletters), 1)

def test_newsletter_context(self):
context = frappe._dict()
newsletter_name = self.send_newsletter(1)
frappe.set_user("test2@example.com")
doc = frappe.get_doc("Newsletter", newsletter_name)
doc.get_context(context)
self.assertEqual(context.no_cache, 1)
self.assertTrue("attachments" not in list(context))

def test_schedule_send(self):
self.send_newsletter(schedule_send=add_days(getdate(), -1))

email_queue_list = [frappe.get_doc('Email Queue', e.name) for e in frappe.get_all("Email Queue")]
self.assertEqual(len(email_queue_list), 4)
recipients = [e.recipients[0].recipient for e in email_queue_list]
def make_email_group(self):
email_group = "_Test Email Group"
if not frappe.db.exists("Email Group", email_group):
frappe.get_doc("Email Group", email_group).insert()
for email in emails:
self.assertTrue(email in recipients)
if not frappe.db.exists('Email Group Member', dict(email=email, email_group = email_group)):
frappe.get_doc({
"doctype": "Email Group Member",
"email": email,
"email_group": email_group
}).insert()

+ 1
- 0
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

+ 14
- 4
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,19 @@ 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;
}
if (!frappe.workspaces['home']) {
// default workspace is settings for Frappe
frappe.workspaces['home'] = frappe.workspaces['settings'];
}
},

load_user_permissions: function() {
frappe.defaults.update_user_permissions();



+ 4
- 4
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);
}
}

+ 35
- 29
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];
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 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 {
standard_route = ['List', doctype_route.doctype, 'List'];
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;
}
if (doctype_route.doctype_layout) {
// set the layout
this.doctype_layout = doctype_route.doctype_layout;
}

return standard_route;
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;
@@ -329,7 +333,7 @@ frappe.router = {
},

make_url(params) {
return '/app/' + $.map(params, function(a) {
let path_string = $.map(params, function(a) {
if ($.isPlainObject(a)) {
frappe.route_options = a;
return null;
@@ -342,6 +346,8 @@ frappe.router = {
return a;
}
}).join('/');

return '/app/' + (path_string || 'home');
},

push_state(url) {


+ 1
- 2
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('<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);
}
}


+ 11
- 11
frappe/public/js/frappe/views/pageview.js 파일 보기

@@ -6,18 +6,18 @@ frappe.provide("frappe.standard_pages");

frappe.views.pageview = {
with_page: function(name, callback) {
if(in_list(Object.keys(frappe.standard_pages), name)) {
if(!frappe.pages[name]) {
if (frappe.standard_pages[name]) {
if (!frappe.pages[name]) {
frappe.standard_pages[name]();
}
callback();
return;
}

if((locals.Page && locals.Page[name] && locals.Page[name].script) || name==window.page_name) {
if ((locals.Page && locals.Page[name] && locals.Page[name].script) || name==window.page_name) {
// already loaded
callback();
} else if(localStorage["_page:" + name] && frappe.boot.developer_mode!=1) {
} else if (localStorage["_page:" + name] && frappe.boot.developer_mode!=1) {
// cached in local storage
frappe.model.sync(JSON.parse(localStorage["_page:" + name]));
callback();
@@ -27,7 +27,7 @@ frappe.views.pageview = {
method: 'frappe.desk.desk_page.getpage',
args: {'name':name },
callback: function(r) {
if(!r.docs._dynamic_page) {
if (!r.docs._dynamic_page) {
localStorage["_page:" + name] = JSON.stringify(r.docs);
}
callback();
@@ -61,14 +61,14 @@ frappe.views.Page = class Page {
var me = this;

// web home page
if(name==window.page_name) {
if (name==window.page_name) {
this.wrapper = document.getElementById('page-' + name);
this.wrapper.label = document.title || window.page_name;
this.wrapper.page_name = window.page_name;
frappe.pages[window.page_name] = this.wrapper;
} else {
this.pagedoc = locals.Page[this.name];
if(!this.pagedoc) {
if (!this.pagedoc) {
frappe.show_not_found(name);
return;
}
@@ -77,7 +77,7 @@ frappe.views.Page = class Page {
this.wrapper.page_name = this.pagedoc.name;

// set content, script and style
if(this.pagedoc.content)
if (this.pagedoc.content)
this.wrapper.innerHTML = this.pagedoc.content;
frappe.dom.eval(this.pagedoc.__script || this.pagedoc.script || '');
frappe.dom.set_style(this.pagedoc.style || '');
@@ -98,7 +98,7 @@ frappe.views.Page = class Page {

trigger_page_event(eventname) {
var me = this;
if(me.wrapper[eventname]) {
if (me.wrapper[eventname]) {
me.wrapper[eventname](me.wrapper);
}
}
@@ -123,11 +123,11 @@ frappe.show_not_permitted = function(page_name) {

frappe.show_message_page = function(opts) {
// opts can include `page_name`, `message`, `icon` or `img`
if(!opts.page_name) {
if (!opts.page_name) {
opts.page_name = frappe.get_route_str();
}

if(opts.icon) {
if (opts.icon) {
opts.img = repl('<span class="%(icon)s message-page-icon"></span> ', opts);
} else if (opts.img) {
opts.img = repl('<img src="%(img)s" class="message-page-image">', opts);


+ 34
- 32
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,44 +64,22 @@ 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 {
default_page = "Website";
default_page = "Settings";
}

let page = frappe.get_route()[1] || default_page;

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 +96,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>


+ 2
- 2
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"]


+ 9
- 9
frappe/tests/test_search.py 파일 보기

@@ -41,15 +41,15 @@ class TestSearch(unittest.TestCase):

#Search for the word "pay", part of the word "pays" (country) in french.
def test_link_search_in_foreign_language(self):
frappe.local.lang = 'fr'
search_widget(doctype="DocType", txt="pay", page_length=20)
output = frappe.response["values"]
result = [['found' for x in y if x=="Country"] for y in output]
self.assertTrue(['found'] in result)
def tearDown(self):
frappe.local.lang = 'en'
try:
frappe.local.lang = 'fr'
search_widget(doctype="DocType", txt="pay", page_length=20)
output = frappe.response["values"]
result = [['found' for x in y if x=="Country"] for y in output]
self.assertTrue(['found'] in result)
finally:
frappe.local.lang = 'en'

def test_validate_and_sanitize_search_inputs(self):



+ 6
- 3
frappe/tests/test_translate.py 파일 보기

@@ -15,9 +15,12 @@ class TestTranslate(unittest.TestCase):
self.assertListEqual(data, expected_output)

def test_translation_with_context(self):
frappe.local.lang = 'fr'
self.assertEqual(_('Change'), 'Changement')
self.assertEqual(_('Change', context='Coins'), 'la monnaie')
try:
frappe.local.lang = 'fr'
self.assertEqual(_('Change'), 'Changement')
self.assertEqual(_('Change', context='Coins'), 'la monnaie')
finally:
frappe.local.lang = 'en'

expected_output = [
('apps/frappe/frappe/tests/translation_test_file.txt', 'Warning: Unable to find {0} in any table related to {1}', 'This is some context', 2),


+ 5
- 5
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 """<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)))



+ 1
- 1
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')

불러오는 중...
취소
저장