Browse Source

Merge branch 'develop' into clear-session-on-password-reset

version-14
Saurabh 4 years ago
committed by GitHub
parent
commit
5087fef2a4
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
100 changed files with 2086 additions and 1986 deletions
  1. +1
    -0
      .eslintrc
  2. +5
    -5
      .github/helper/translation.py
  3. +2
    -2
      .gitignore
  4. +5
    -4
      .travis.yml
  5. +5
    -1
      cypress.json
  6. +3
    -3
      cypress/integration/api.js
  7. +6
    -6
      cypress/integration/awesome_bar.js
  8. +1
    -1
      cypress/integration/control_barcode.js
  9. +5
    -5
      cypress/integration/control_duration.js
  10. +7
    -10
      cypress/integration/control_link.js
  11. +1
    -1
      cypress/integration/control_rating.js
  12. +2
    -2
      cypress/integration/datetime.js
  13. +3
    -3
      cypress/integration/depends_on.js
  14. +15
    -23
      cypress/integration/file_uploader.js
  15. +17
    -23
      cypress/integration/form.js
  16. +8
    -8
      cypress/integration/grid_pagination.js
  17. +10
    -9
      cypress/integration/list_view.js
  18. +16
    -16
      cypress/integration/list_view_settings.js
  19. +8
    -8
      cypress/integration/login.js
  20. +15
    -15
      cypress/integration/query_report.js
  21. +21
    -25
      cypress/integration/recorder.js
  22. +15
    -17
      cypress/integration/relative_time_filters.js
  23. +4
    -5
      cypress/integration/report_view.js
  24. +5
    -6
      cypress/integration/table_multiselect.js
  25. +25
    -8
      cypress/support/commands.js
  26. +1
    -1
      cypress/support/index.js
  27. +9
    -0
      frappe/.stylelintrc
  28. +4
    -7
      frappe/__init__.py
  29. +1
    -1
      frappe/auth.py
  30. +0
    -70
      frappe/automation/desk_page/tools/tools.json
  31. +4
    -3
      frappe/automation/doctype/auto_repeat/auto_repeat.js
  32. +229
    -0
      frappe/automation/workspace/tools/tools.json
  33. +37
    -21
      frappe/boot.py
  34. +5
    -10
      frappe/cache_manager.py
  35. +3
    -2
      frappe/commands/utils.py
  36. +1
    -1
      frappe/config/__init__.py
  37. +0
    -59
      frappe/config/automation.py
  38. +0
    -66
      frappe/config/core.py
  39. +0
    -60
      frappe/config/customization.py
  40. +0
    -67
      frappe/config/desk.py
  41. +0
    -133
      frappe/config/desktop.py
  42. +0
    -6
      frappe/config/docs.py
  43. +0
    -127
      frappe/config/integrations.py
  44. +0
    -195
      frappe/config/settings.py
  45. +0
    -2
      frappe/config/tools.py
  46. +0
    -85
      frappe/config/users_and_permissions.py
  47. +0
    -117
      frappe/config/website.py
  48. +3
    -2
      frappe/contacts/doctype/contact/contact.json
  49. +0
    -74
      frappe/core/desk_page/settings/settings.json
  50. +0
    -59
      frappe/core/desk_page/users/users.json
  51. +22
    -7
      frappe/core/doctype/comment/comment.py
  52. +5
    -4
      frappe/core/doctype/communication/communication.js
  53. +10
    -11
      frappe/core/doctype/communication/communication.py
  54. +1
    -1
      frappe/core/doctype/data_import_legacy/data_import_legacy.js
  55. +6
    -5
      frappe/core/doctype/deleted_document/deleted_document_list.js
  56. +2
    -2
      frappe/core/doctype/doctype/doctype.js
  57. +4
    -3
      frappe/core/doctype/doctype/doctype.json
  58. +35
    -48
      frappe/core/doctype/doctype/doctype.py
  59. +7
    -0
      frappe/core/doctype/doctype/patches/set_route.py
  60. +11
    -6
      frappe/core/doctype/doctype/test_doctype.py
  61. +32
    -9
      frappe/core/doctype/file/file.py
  62. +1
    -1
      frappe/core/doctype/log_settings/log_settings.py
  63. +1
    -1
      frappe/core/doctype/module_def/module_def.json
  64. +1
    -1
      frappe/core/doctype/navbar_settings/navbar_settings.py
  65. +3
    -0
      frappe/core/doctype/page/page.py
  66. +5
    -0
      frappe/core/doctype/page/patches/drop_unused_pages.py
  67. +3
    -1
      frappe/core/doctype/page/test_page.py
  68. +10
    -0
      frappe/core/doctype/role/patches/v13_set_default_desk_properties.py
  69. +84
    -2
      frappe/core/doctype/role/role.json
  70. +25
    -5
      frappe/core/doctype/role/role.py
  71. +14
    -14
      frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js
  72. +6
    -8
      frappe/core/doctype/role_profile/role_profile.js
  73. +1
    -1
      frappe/core/doctype/server_script/test_server_script.py
  74. +15
    -1
      frappe/core/doctype/system_settings/system_settings.json
  75. +6
    -1
      frappe/core/doctype/user/test_user.py
  76. +0
    -28
      frappe/core/doctype/user/user.css
  77. +5
    -5
      frappe/core/doctype/user/user.js
  78. +42
    -23
      frappe/core/doctype/user/user.json
  79. +27
    -21
      frappe/core/doctype/user/user.py
  80. +2
    -2
      frappe/core/doctype/user_permission/user_permission.py
  81. +1
    -1
      frappe/core/doctype/user_permission/user_permission_list.js
  82. +60
    -0
      frappe/core/page/background_jobs/background_jobs.css
  83. +25
    -14
      frappe/core/page/background_jobs/background_jobs.html
  84. +60
    -40
      frappe/core/page/background_jobs/background_jobs.js
  85. +1
    -8
      frappe/core/page/background_jobs/background_jobs_outer.html
  86. +0
    -0
      frappe/core/page/dashboard_view/__init__.py
  87. +7
    -7
      frappe/core/page/dashboard_view/dashboard_view.js
  88. +3
    -3
      frappe/core/page/dashboard_view/dashboard_view.json
  89. +0
    -3
      frappe/core/page/desktop/desktop.js
  90. +0
    -24
      frappe/core/page/desktop/desktop.json
  91. +51
    -0
      frappe/core/page/permission_manager/permission_manager.css
  92. +238
    -215
      frappe/core/page/permission_manager/permission_manager.js
  93. +5
    -5
      frappe/core/page/permission_manager/permission_manager_help.html
  94. +2
    -1
      frappe/core/page/recorder/recorder.js
  95. +0
    -3
      frappe/core/page/workspace/workspace.js
  96. +0
    -23
      frappe/core/page/workspace/workspace.json
  97. +211
    -0
      frappe/core/workspace/build/build.json
  98. +367
    -0
      frappe/core/workspace/settings/settings.json
  99. +167
    -0
      frappe/core/workspace/users/users.json
  100. +0
    -54
      frappe/custom/desk_page/customization/customization.json

+ 1
- 0
.eslintrc View File

@@ -87,6 +87,7 @@
"open_url_post": true,
"toTitle": true,
"lstrip": true,
"rstrip": true,
"strip": true,
"strip_html": true,
"replace_all": true,


+ 5
- 5
.github/helper/translation.py View File

@@ -2,7 +2,7 @@ import re
import sys

errors_encounter = 0
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
pattern = re.compile(r"_\(([\"']{,3})(?P<message>((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P<py_context>((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P<js_context>((?!\11).)*)\11)*)*\)")
words_pattern = re.compile(r"_{1,2}\([\"'`]{1,3}.*?[a-zA-Z]")
start_pattern = re.compile(r"_{1,2}\([f\"'`]{1,3}")
f_string_pattern = re.compile(r"_\(f[\"']")
@@ -28,7 +28,7 @@ for _file in files_to_scan:
has_f_string = f_string_pattern.search(line)
if has_f_string:
errors_encounter += 1
print(f'\nF-strings are not supported for translations at line number {line_number + 1}\n{line.strip()[:100]}')
print(f'\nF-strings are not supported for translations at line number {line_number}\n{line.strip()[:100]}')
continue
else:
continue
@@ -36,7 +36,7 @@ for _file in files_to_scan:
match = pattern.search(line)
error_found = False

if not match and line.endswith(',\n'):
if not match and line.endswith((',\n', '[\n')):
# concat remaining text to validate multiline pattern
line = "".join(file_lines[line_number - 1:])
line = line[start_matches.start() + 1:]
@@ -44,11 +44,11 @@ for _file in files_to_scan:

if not match:
error_found = True
print(f'\nTranslation syntax error at line number {line_number + 1}\n{line.strip()[:100]}')
print(f'\nTranslation syntax error at line number {line_number}\n{line.strip()[:100]}')

if not error_found and not words_pattern.search(line):
error_found = True
print(f'\nTranslation is useless because it has no words at line number {line_number + 1}\n{line.strip()[:100]}')
print(f'\nTranslation is useless because it has no words at line number {line_number}\n{line.strip()[:100]}')

if error_found:
errors_encounter += 1


+ 2
- 2
.gitignore View File

@@ -7,7 +7,7 @@ locale
*.swp
*.egg-info
dist/
build/
# build/
frappe/docs/current
.vscode
node_modules
@@ -28,7 +28,7 @@ __pycache__/

# Distribution / packaging
.Python
build/
# build/
develop-eggs/
dist/
downloads/


+ 5
- 4
.travis.yml View File

@@ -43,7 +43,6 @@ matrix:
env: DB=mariadb TYPE=ui
before_script:
- bench --site test_site execute frappe.utils.install.complete_setup_wizard
- bench --site test_site_producer execute frappe.utils.install.complete_setup_wizard
script: bench --site test_site run-ui-tests frappe --headless

before_install:
@@ -75,8 +74,10 @@ install:
- mkdir ~/frappe-bench/sites/test_site
- cp $TRAVIS_BUILD_DIR/.travis/consumer_db/$DB.json ~/frappe-bench/sites/test_site/site_config.json

- mkdir ~/frappe-bench/sites/test_site_producer
- cp $TRAVIS_BUILD_DIR/.travis/producer_db/$DB.json ~/frappe-bench/sites/test_site_producer/site_config.json
- if [ $TYPE == "server" ]; then
mkdir ~/frappe-bench/sites/test_site_producer;
cp $TRAVIS_BUILD_DIR/.travis/producer_db/$DB.json ~/frappe-bench/sites/test_site_producer/site_config.json;
fi

- if [ $DB == "mariadb" ];then
mysql -u root -e "SET GLOBAL character_set_server = 'utf8mb4'";
@@ -119,7 +120,7 @@ install:

- bench start &
- bench --site test_site reinstall --yes
- bench --site test_site_producer reinstall --yes
- if [ $TYPE == "server" ]; then bench --site test_site_producer reinstall --yes; fi
- bench build --app frappe

after_script:


+ 5
- 1
cypress.json View File

@@ -3,5 +3,9 @@
"projectId": "92odwv",
"adminPassword": "admin",
"defaultCommandTimeout": 20000,
"pageLoadTimeout": 15000
"pageLoadTimeout": 15000,
"retries": {
"runMode": 2,
"openMode": 2
}
}

+ 3
- 3
cypress/integration/api.js View File

@@ -2,12 +2,12 @@ context('API Resources', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
});

it('Creates two Comments', () => {
cy.insert_doc('Comment', {comment_type: 'Comment', content: "hello"});
cy.insert_doc('Comment', {comment_type: 'Comment', content: "world"});
cy.insert_doc('Comment', { comment_type: 'Comment', content: "hello" });
cy.insert_doc('Comment', { comment_type: 'Comment', content: "world" });
});

it('Lists the Comments', () => {


+ 6
- 6
cypress/integration/awesome_bar.js View File

@@ -2,11 +2,11 @@ context('Awesome Bar', () => {
before(() => {
cy.visit('/login');
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
});

beforeEach(() => {
cy.get('.navbar-header .navbar-home').click();
cy.get('.navbar .navbar-home').click();
});

it('navigates to doctype list', () => {
@@ -14,16 +14,16 @@ context('Awesome Bar', () => {
cy.get('#navbar-search + ul').should('be.visible');
cy.get('#navbar-search').type('{downarrow}{enter}', { delay: 100 });

cy.get('h1').should('contain', 'To Do');
cy.get('.title-text').should('contain', 'To Do');

cy.location('hash').should('eq', '#List/ToDo/List');
cy.location('pathname').should('eq', '/app/todo');
});

it('find text in doctype list', () => {
cy.get('#navbar-search')
.type('test in todo{downarrow}{enter}', { delay: 200 });

cy.get('h1').should('contain', 'To Do');
cy.get('.title-text').should('contain', 'To Do');

cy.get('[data-original-title="Name"] > .input-with-feedback')
.should('have.value', '%test%');
@@ -33,7 +33,7 @@ context('Awesome Bar', () => {
cy.get('#navbar-search')
.type('new blog post{downarrow}{enter}', { delay: 200 });

cy.get('.title-text:visible').should('have.text', 'New Blog Post 1');
cy.get('.title-text:visible').should('have.text', 'New Blog Post');
});

it('calculates math expressions', () => {


+ 1
- 1
cypress/integration/control_barcode.js View File

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

function get_dialog_with_barcode() {


+ 5
- 5
cypress/integration/control_duration.js View File

@@ -1,10 +1,10 @@
context('Control Duration', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
});

function get_dialog_with_duration(hide_days=0, hide_seconds=0) {
function get_dialog_with_duration(hide_days = 0, hide_seconds = 0) {
return cy.dialog({
title: 'Duration',
fields: [{
@@ -22,11 +22,11 @@ context('Control Duration', () => {
.first()
.click();
cy.get('.duration-input[data-duration=days]')
.type(45, {force: true})
.blur({force: true});
.type(45, { force: true })
.blur({ force: true });
cy.get('.duration-input[data-duration=minutes]')
.type(30)
.blur({force: true});
.blur({ force: true });
cy.get('.frappe-control[data-fieldname=duration] input').first().should('have.value', '45d 30m');
cy.get('.frappe-control[data-fieldname=duration] input').first().blur();
cy.get('.duration-picker').should('not.be.visible');


+ 7
- 10
cypress/integration/control_link.js View File

@@ -1,11 +1,11 @@
context('Control Link', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
});

beforeEach(() => {
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
cy.create_records({
doctype: 'ToDo',
description: 'this is a test todo for link'
@@ -29,8 +29,7 @@ context('Control Link', () => {
it('should set the valid value', () => {
get_dialog_with_link().as('dialog');

cy.server();
cy.route('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');

cy.get('.frappe-control[data-fieldname=link] input').focus().as('input');
cy.wait('@search_link');
@@ -50,8 +49,7 @@ context('Control Link', () => {
it('should unset invalid value', () => {
get_dialog_with_link().as('dialog');

cy.server();
cy.route('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
cy.intercept('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');

cy.get('.frappe-control[data-fieldname=link] input')
.type('invalid value', { delay: 100 })
@@ -63,9 +61,8 @@ context('Control Link', () => {
it('should route to form on arrow click', () => {
get_dialog_with_link().as('dialog');

cy.server();
cy.route('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
cy.route('POST', '/api/method/frappe.desk.search.search_link').as('search_link');
cy.intercept('GET', '/api/method/frappe.desk.form.utils.validate_link*').as('validate_link');
cy.intercept('POST', '/api/method/frappe.desk.search.search_link').as('search_link');

cy.get('@todos').then(todos => {
cy.get('.frappe-control[data-fieldname=link] input').as('input');
@@ -77,7 +74,7 @@ context('Control Link', () => {
cy.get('.frappe-control[data-fieldname=link] .link-btn')
.should('be.visible')
.click();
cy.location('hash').should('eq', `#Form/ToDo/${todos[0]}`);
cy.location('pathname').should('eq', `/app/todo/${todos[0]}`);
});
});
});

+ 1
- 1
cypress/integration/control_rating.js View File

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

function get_dialog_with_rating() {


+ 2
- 2
cypress/integration/datetime.js View File

@@ -4,7 +4,7 @@ const doctype_name = datetime_doctype.name;
context('Control Date, Time and DateTime', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
return cy.insert_doc('DocType', datetime_doctype, true);
});

@@ -42,7 +42,7 @@ context('Control Date, Time and DateTime', () => {
.should('be.visible');
cy.get(
'.datepickers-container .datepicker.active .datepicker--cell-day.-current-'
).click();
).click({ force: true });

cy.window()
.its('cur_frm')


+ 3
- 3
cypress/integration/depends_on.js View File

@@ -1,7 +1,7 @@
context('Depends On', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.xcall('frappe.tests.ui_test_helpers.create_child_doctype', {
name: 'Child Test Depends On',
@@ -64,7 +64,7 @@ context('Depends On', () => {
cy.fill_field('test_field', 'Some Value');
cy.get('button.primary-action').contains('Save').click();
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('be.visible');
cy.get('body').click();
cy.hide_dialog();
cy.fill_field('test_field', 'Random value');
cy.get('button.primary-action').contains('Save').click();
cy.get('.msgprint-dialog .modal-title').contains('Missing Fields').should('not.be.visible');
@@ -92,7 +92,7 @@ context('Depends On', () => {
cy.fill_table_field('child_test_depends_on_field', '1', 'child_test_field', 'Some Value');
cy.fill_table_field('child_test_depends_on_field', '1', 'child_dependant_field', 'Some Other Value');

cy.get('@row1-form_in_grid').find('.octicon-triangle-up').click();
cy.get('@row1-form_in_grid').find('.grid-collapse-row').click();

// set the table to read-only
cy.fill_field('test_field', 'Some Other Value');


+ 15
- 23
cypress/integration/file_uploader.js View File

@@ -1,7 +1,7 @@
context('FileUploader', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app');
});

function open_upload_dialog() {
@@ -19,44 +19,36 @@ context('FileUploader', () => {
it('should accept dropped files', () => {
open_upload_dialog();

cy.fixture('example.json').then(fileContent => {
cy.get_open_dialog().find('.file-upload-area').upload({
fileContent,
fileName: 'example.json',
mimeType: 'application/json'
}, {
subjectType: 'drag-n-drop',
force: true
});
cy.get_open_dialog().find('.file-info').should('contain', 'example.json');
cy.server();
cy.route('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().find('.btn-primary').click();
cy.wait('@upload_file').its('status').should('be', 200);
cy.get('.modal:visible').should('not.exist');
cy.get_open_dialog().find('.file-upload-area').attachFile('example.json', {
subjectType: 'drag-n-drop',
});

cy.get_open_dialog().find('.file-name').should('contain', 'example.json');
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().find('.btn-modal-primary').click();
cy.wait('@upload_file').its('response.statusCode').should('eq', 200);
cy.get('.modal:visible').should('not.exist');
});

it('should accept uploaded files', () => {
open_upload_dialog();

cy.get_open_dialog().find('a:contains("uploaded file")').click();
cy.get_open_dialog().find('.btn-file-upload div:contains("Library")').click();
cy.get('.file-filter').type('example.json');
cy.get_open_dialog().find('.tree-label:contains("example.json")').first().click();
cy.server();
cy.route('POST', '/api/method/upload_file').as('upload_file');
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().find('.btn-primary').click();
cy.wait('@upload_file').its('response.body.message')
.should('have.property', 'file_url', '/private/files/example.json');
.should('have.property', 'file_name', 'example.json');
cy.get('.modal:visible').should('not.exist');
});

it('should accept web links', () => {
open_upload_dialog();

cy.get_open_dialog().find('a:contains("web link")').click();
cy.get_open_dialog().find('.btn-file-upload div:contains("Link")').click();
cy.get_open_dialog().find('.file-web-link input').type('https://github.com', { delay: 100, force: true });
cy.server();
cy.route('POST', '/api/method/upload_file').as('upload_file');
cy.intercept('POST', '/api/method/upload_file').as('upload_file');
cy.get_open_dialog().find('.btn-primary').click();
cy.wait('@upload_file').its('response.body.message')
.should('have.property', 'file_url', 'https://github.com');


+ 17
- 23
cypress/integration/form.js View File

@@ -1,56 +1,50 @@
context('Form', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.call("frappe.tests.ui_test_helpers.create_contact_records");
});
});
it('create a new form', () => {
cy.visit('/desk#Form/ToDo/New ToDo 1');
cy.visit('/app/todo/new');
cy.fill_field('description', 'this is a test todo', 'Text Editor').blur();
cy.wait(300);
cy.get('.page-title').should('contain', 'Not Saved');
cy.server();
cy.route({
cy.intercept({
method: 'POST',
url: 'api/method/frappe.desk.form.save.savedocs'
}).as('form_save');
cy.get('.primary-action').click();
cy.wait('@form_save').its('status').should('eq', 200);
cy.visit('/desk#List/ToDo');
cy.location('hash').should('eq', '#List/ToDo/List');
cy.get('h1').should('be.visible').and('contain', 'To Do');
cy.wait('@form_save').its('response.statusCode').should('eq', 200);
cy.visit('/app/todo');
cy.get('.title-text').should('be.visible').and('contain', 'To Do');
cy.get('.list-row').should('contain', 'this is a test todo');
});
it('navigates between documents with child table list filters applied', () => {
cy.visit('/desk#List/Contact');
cy.location('hash').should('eq', '#List/Contact/List');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.get('.fieldname-select-area').should('exist');
cy.get('.fieldname-select-area input').type('Number{enter}', { force: true });
cy.visit('/app/contact');
cy.add_filter();
cy.get('.filter-field .input-with-feedback.form-control').type('123', { force: true });
cy.get('.filter-box .btn:contains("Apply")').click({ force: true });
cy.visit('/desk#Form/Contact/Test Form Contact 3');
cy.get('.filter-popover .apply-filters').click({ force: true });
cy.visit('/app/contact/Test Form Contact 3');
cy.get('.prev-doc').should('be.visible').click();
cy.get('.msgprint-dialog .modal-body').contains('No further records').should('be.visible');
cy.get('.btn-modal-close:visible').click();
cy.hide_dialog();
cy.get('.next-doc').click();
cy.wait(200);
cy.hide_dialog();
cy.contains('Test Form Contact 2').should('not.exist');
cy.get('.page-title .title-text').should('contain', 'Test Form Contact 1');
cy.get('.title-text').should('contain', 'Test Form Contact 3');
// clear filters
cy.window().its('frappe').then((frappe) => {
let list_view = frappe.get_list_view('Contact');
list_view.filter_area.filter_list.clear_filters();
});
cy.visit('/app/contact');
cy.clear_filters();
});
it('validates behaviour of Data options validations in child table', () => {
// test email validations for set_invalid controller
let website_input = 'website.in';
let expectBackgroundColor = 'rgb(255, 220, 220)';
let expectBackgroundColor = 'rgb(255, 245, 245)';

cy.visit('/desk#Form/Contact/New Contact 1');
cy.visit('/app/contact/new');
cy.get('.frappe-control[data-fieldname="email_ids"]').as('table');
cy.get('@table').find('button.grid-add-row').click();
cy.get('.grid-body .rows [data-fieldname="email_id"]').click();


+ 8
- 8
cypress/integration/grid_pagination.js View File

@@ -1,24 +1,24 @@
context('Grid Pagination', () => {
beforeEach(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
});
before(() => {
cy.login();
cy.visit('/desk#workspace/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");
});
});
it('creates pages for child table', () => {
cy.visit('/desk#Form/Contact/Test Contact');
cy.visit('/app/contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('.current-page-number').should('contain', '1');
cy.get('@table').find('.total-page-number').should('contain', '20');
cy.get('@table').find('.grid-body .grid-row').should('have.length', 50);
});
it('goes to the next and previous page', () => {
cy.visit('/desk#Form/Contact/Test Contact');
cy.visit('/app/contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('.next-page').click();
cy.get('@table').find('.current-page-number').should('contain', '2');
@@ -27,21 +27,21 @@ context('Grid Pagination', () => {
cy.get('@table').find('.current-page-number').should('contain', '1');
cy.get('@table').find('.grid-body .grid-row').first().should('have.attr', 'data-idx', '1');
});
it('adds and deletes rows and changes page', ()=> {
cy.visit('/desk#Form/Contact/Test Contact');
it('adds and deletes rows and changes page', () => {
cy.visit('/app/contact/Test Contact');
cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
cy.get('@table').find('button.grid-add-row').click();
cy.get('@table').find('.grid-body .row-index').should('contain', 1001);
cy.get('@table').find('.current-page-number').should('contain', '21');
cy.get('@table').find('.total-page-number').should('contain', '21');
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({force: true});
cy.get('@table').find('.grid-body .grid-row .grid-row-check').click({ force: true });
cy.get('@table').find('button.grid-remove-rows').click();
cy.get('@table').find('.grid-body .row-index').last().should('contain', 1000);
cy.get('@table').find('.current-page-number').should('contain', '20');
cy.get('@table').find('.total-page-number').should('contain', '20');
});
// it('deletes all rows', ()=> {
// cy.visit('/desk#Form/Contact/Test Contact');
// cy.visit('/app/contact/Test Contact');
// cy.get('.frappe-control[data-fieldname="phone_nos"]').as('table');
// cy.get('@table').find('.grid-heading-row .grid-row-check').click({force: true});
// cy.get('@table').find('button.grid-remove-all-rows').click();


+ 10
- 9
cypress/integration/list_view.js View File

@@ -1,30 +1,31 @@
context('List View', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
return cy.window().its('frappe').then(frappe => {
return frappe.xcall("frappe.tests.ui_test_helpers.setup_workflow");
});
});
it('enables "Actions" button', () => {
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Print', 'Delete'];
const actions = ['Approve', 'Reject', 'Edit', 'Assign To', 'Apply Assignment Rule', 'Add Tags', 'Print', 'Delete'];
cy.go_to_list('ToDo');
cy.get('.list-row-container:contains("Pending") .list-row-checkbox').click({ multiple: true, force: true });
cy.get('.btn.btn-primary.btn-sm.dropdown-toggle').contains('Actions').should('be.visible').click();
cy.get('.dropdown-menu li:visible').should('have.length', 7).each((el, index) => {
cy.get('.actions-btn-group button').contains('Actions').should('be.visible').click();
cy.get('.dropdown-menu li:visible .dropdown-item').should('have.length', 8).each((el, index) => {
cy.wrap(el).contains(actions[index]);
}).then((elements) => {
cy.server();
cy.route({
cy.intercept({
method: 'POST',
url:'api/method/frappe.model.workflow.bulk_workflow_approval'
url: 'api/method/frappe.model.workflow.bulk_workflow_approval'
}).as('bulk-approval');
cy.route({
cy.intercept({
method: 'POST',
url:'api/method/frappe.desk.reportview.get'
url: 'api/method/frappe.desk.reportview.get'
}).as('real-time-update');
cy.wrap(elements).contains('Approve').click();
cy.wait(['@bulk-approval', '@real-time-update']);
cy.hide_dialog();
cy.clear_filters();
cy.get('.list-row-container:visible').should('contain', 'Approved');
});
});


+ 16
- 16
cypress/integration/list_view_settings.js View File

@@ -1,36 +1,36 @@
context('List View Settings', () => {
beforeEach(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
});
it('Default settings', () => {
cy.visit('/desk#List/DocType/List');
cy.visit('/app/List/DocType/List');
cy.get('.list-count').should('contain', "20 of");
cy.get('.sidebar-stat').should('contain', "Tags");
cy.get('.list-stats').should('contain', "Tags");
});
it('disable count and sidebar stats then verify', () => {
cy.wait(300);
cy.visit('/desk#List/DocType/List');
cy.visit('/app/List/DocType/List');
cy.wait(300);
cy.get('.list-count').should('contain', "20 of");
cy.get('button').contains('Menu').click();
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
cy.get('.modal-dialog').should('contain', 'Settings');
cy.get('.menu-btn-group button').click();
cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click();
cy.get('.modal-dialog').should('contain', 'DocType Settings');

cy.get('input[data-fieldname="disable_count"]').check({force: true});
cy.get('input[data-fieldname="disable_sidebar_stats"]').check({force: true});
cy.get('input[data-fieldname="disable_count"]').check({ force: true });
cy.get('input[data-fieldname="disable_sidebar_stats"]').check({ force: true });
cy.get('button').filter(':visible').contains('Save').click();

cy.reload();
cy.reload({ force: true });

cy.get('.list-count').should('be.empty');
cy.get('.list-sidebar .sidebar-stat').should('not.exist');
cy.get('.list-sidebar .list-tags').should('not.exist');

cy.get('button').contains('Menu').click({force: true});
cy.get('.dropdown-menu li').filter(':visible').contains('Settings').click();
cy.get('.modal-dialog').should('contain', 'Settings');
cy.get('input[data-fieldname="disable_count"]').uncheck({force: true});
cy.get('input[data-fieldname="disable_sidebar_stats"]').uncheck({force: true});
cy.get('.menu-btn-group button').click({ force: true });
cy.get('.dropdown-menu li').filter(':visible').contains('List Settings').click();
cy.get('.modal-dialog').should('contain', 'DocType Settings');
cy.get('input[data-fieldname="disable_count"]').uncheck({ force: true });
cy.get('input[data-fieldname="disable_sidebar_stats"]').uncheck({ force: true });
cy.get('button').filter(':visible').contains('Save').click();
});
});

+ 8
- 8
cypress/integration/login.js View File

@@ -2,7 +2,7 @@ context('Login', () => {
beforeEach(() => {
cy.request('/api/method/logout');
cy.visit('/login');
cy.location().should('be', '/login');
cy.location('pathname').should('eq', '/login');
});

it('greets with login screen', () => {
@@ -11,13 +11,13 @@ context('Login', () => {

it('validates password', () => {
cy.get('#login_email').type('Administrator');
cy.get('.btn-login').click();
cy.get('.btn-login:visible').click();
cy.location('pathname').should('eq', '/login');
});

it('validates email', () => {
cy.get('#login_password').type('qwe');
cy.get('.btn-login').click();
cy.get('.btn-login:visible').click();
cy.location('pathname').should('eq', '/login');
});

@@ -25,8 +25,8 @@ context('Login', () => {
cy.get('#login_email').type('Administrator');
cy.get('#login_password').type('qwer');

cy.get('.btn-login').click();
cy.get('.page-card-head').contains('Invalid Login. Try again.');
cy.get('.btn-login:visible').click();
cy.get('.btn-login:visible').contains('Invalid Login. Try again.');
cy.location('pathname').should('eq', '/login');
});

@@ -34,8 +34,8 @@ context('Login', () => {
cy.get('#login_email').type('Administrator');
cy.get('#login_password').type(Cypress.config('adminPassword'));

cy.get('.btn-login').click();
cy.location('pathname').should('eq', '/desk');
cy.get('.btn-login:visible').click();
cy.location('pathname').should('eq', '/app');
cy.window().its('frappe.session.user').should('eq', 'Administrator');
});

@@ -60,7 +60,7 @@ context('Login', () => {
cy.get('#login_email').type('Administrator');
cy.get('#login_password').type(Cypress.config('adminPassword'));

cy.get('.btn-login').click();
cy.get('.btn-login:visible').click();

// verify redirected location and url params after login
cy.url().should('include', '/me?' + payload.toString().replace('+', '%20'));


+ 15
- 15
cypress/integration/query_report.js View File

@@ -1,33 +1,33 @@
context('Query Report', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
});

it('add custom column in report', () => {
cy.visit('/desk#query-report/Permitted Documents For User');
cy.visit('/app/query-report/Permitted Documents For User');

cy.get('div[class="page-form flex"]', {timeout: 60000}).should('have.length', 1).then(()=>{
cy.get('.page-form.flex', { timeout: 60000 }).should('have.length', 1).then(() => {
cy.get('#page-query-report input[data-fieldname="user"]').as('input');
cy.get('@input').focus().type('test@erpnext.com', { delay: 100 });
cy.get('@input').focus().type('test@erpnext.com', { delay: 100 }).blur();
cy.wait(300);
cy.get('#page-query-report input[data-fieldname="doctype"]').as('input-test');
cy.get('@input-test').focus().type('Role', { delay: 100 }).blur();

cy.get('.datatable').should('exist');
cy.get('button').contains('Menu').click({force: true});
cy.get('.dropdown-menu li').contains('Add Column').click({force: true});
cy.get('.menu-btn-group button').click({ force: true });
cy.get('.dropdown-menu li').contains('Add Column').click({ force: true });
cy.get('.modal-dialog').should('contain', 'Add Column');
cy.get('select[data-fieldname="doctype"]').select("Role", {force: true});
cy.get('select[data-fieldname="field"]').select("Role Name", {force: true});
cy.get('select[data-fieldname="insert_after"]').select("Name", {force: true});
cy.get('button').contains('Submit').click({force: true});
cy.get('button').contains('Menu').click({force: true});
cy.get('.dropdown-menu li').contains('Save').click({force: true});
cy.get('select[data-fieldname="doctype"]').select("Role", { force: true });
cy.get('select[data-fieldname="field"]').select("Role Name", { force: true });
cy.get('select[data-fieldname="insert_after"]').select("Name", { force: true });
cy.get('button').contains('Submit').click({ force: true });
cy.get('.menu-btn-group button').click({ force: true });
cy.get('.dropdown-menu li').contains('Save').click({ force: true });
cy.get('.modal-dialog').should('contain', 'Save Report');

cy.get('input[data-fieldname="report_name"]').type("Test Report", {delay:100, force: true});
cy.get('button').contains('Submit').click({timeout:1000, force: true});
cy.get('input[data-fieldname="report_name"]').type("Test Report", { delay: 100, force: true });
cy.get('button').contains('Submit').click({ timeout: 1000, force: true });
});
});
});

+ 21
- 25
cypress/integration/recorder.js View File

@@ -4,17 +4,17 @@ context('Recorder', () => {
});

it('Navigate to Recorder', () => {
cy.visit('/desk#workspace/Website');
cy.visit('/app');
cy.awesomebar('recorder');
cy.get('h1').should('contain', 'Recorder');
cy.location('hash').should('eq', '#recorder');
cy.get('h3').should('contain', 'Recorder');
cy.url().should('include', '/recorder/detail');
});

it('Recorder Empty State', () => {
cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.title-text').should('contain', 'Recorder');

cy.get('.indicator').should('contain', 'Inactive').should('have.class', 'red');
cy.get('.indicator-pill').should('contain', 'Inactive').should('have.class', 'red');

cy.get('.primary-action').should('contain', 'Start');
cy.get('.btn-secondary').should('contain', 'Clear');
@@ -24,53 +24,49 @@ context('Recorder', () => {
});

it('Recorder Start', () => {
cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.primary-action').should('contain', 'Start').click();
cy.get('.indicator').should('contain', 'Active').should('have.class', 'green');
cy.get('.indicator-pill').should('contain', 'Active').should('have.class', 'green');

cy.get('.msg-box').should('contain', 'No Requests');

cy.server();
cy.visit('/desk#List/DocType/List');
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.visit('/app/List/DocType/List');
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.wait('@list_refresh');

cy.get('.title-text').should('contain', 'DocType');
cy.get('.list-count').should('contain', '20 of ');

cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.title-text').should('contain', 'Recorder');
cy.get('.result-list').should('contain', '/api/method/frappe.desk.reportview.get');

cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
cy.wait(500);
cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
cy.get('.msg-box').should('contain', 'Inactive');
});

it('Recorder View Request', () => {
cy.visit('/desk#recorder');
cy.visit('/app/recorder');
cy.get('.primary-action').should('contain', 'Start').click();

cy.server();
cy.visit('/desk#List/DocType/List');
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.visit('/app/List/DocType/List');
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.wait('@list_refresh');

cy.get('.title-text').should('contain', 'DocType');
cy.get('.list-count').should('contain', '20 of ');

// temporarily commenting out theses tests as they seem to be
// randomly failing maybe due a backround event
cy.visit('/app/recorder');

// cy.visit('/desk#recorder');
cy.get('.list-row-container span').contains('/api/method/frappe').click();

// cy.get('.list-row-container span').contains('/api/method/frappe').click();
cy.url().should('include', '/recorder/request');
cy.get('form').should('contain', '/api/method/frappe');

// cy.location('hash').should('contain', '#recorder/request/');
// cy.get('form').should('contain', '/api/method/frappe');

// cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
// cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
// cy.location('hash').should('eq', '#recorder');
cy.get('#page-recorder .primary-action').should('contain', 'Stop').click();
cy.wait(200);
cy.get('#page-recorder .btn-secondary').should('contain', 'Clear').click();
});
});

+ 15
- 17
cypress/integration/relative_time_filters.js View File

@@ -4,46 +4,44 @@ context('Relative Timeframe', () => {
});
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
cy.window().its('frappe').then(frappe => {
frappe.call("frappe.tests.ui_test_helpers.create_todo_records");
});
});
it('sets relative timespan filter for last week and filters list', () => {
cy.visit('/desk#List/ToDo/List');
cy.visit('/app/List/ToDo/List');
cy.clear_filters();
cy.get('.list-row:contains("this is fourth todo")').should('exist');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.add_filter();
cy.get('.fieldname-select-area').should('exist');
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
cy.get('select.condition.form-control').select("Timespan");
cy.get('.filter-field select.input-with-feedback.form-control').select("last week");
cy.server();
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.get('.filter-box .btn:contains("Apply")').click();
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.get('.filter-popover .apply-filters').click({ force: true });
cy.wait('@list_refresh');
cy.get('.list-row-container').its('length').should('eq', 1);
cy.get('.list-row-container').should('contain', 'this is second todo');
cy.route('POST', '/api/method/frappe.model.utils.user_settings.save')
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
.as('save_user_settings');
cy.get('.remove-filter.btn').click();
cy.clear_filters();
cy.wait('@save_user_settings');
});
it('sets relative timespan filter for next week and filters list', () => {
cy.visit('/desk#List/ToDo/List');
cy.visit('/app/List/ToDo/List');
cy.clear_filters();
cy.get('.list-row:contains("this is fourth todo")').should('exist');
cy.get('.tag-filters-area .btn:contains("Add Filter")').click();
cy.add_filter();
cy.get('.fieldname-select-area input').type("Due Date{enter}", { delay: 100 });
cy.get('select.condition.form-control').select("Timespan");
cy.get('.filter-field select.input-with-feedback.form-control').select("next week");
cy.server();
cy.route('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.get('.filter-box .btn:contains("Apply")').click();
cy.intercept('POST', '/api/method/frappe.desk.reportview.get').as('list_refresh');
cy.get('.filter-popover .apply-filters').click({ force: true });
cy.wait('@list_refresh');
cy.get('.list-row-container').its('length').should('eq', 1);
cy.get('.list-row').should('contain', 'this is first todo');
cy.route('POST', '/api/method/frappe.model.utils.user_settings.save')
cy.intercept('POST', '/api/method/frappe.model.utils.user_settings.save')
.as('save_user_settings');
cy.get('.remove-filter.btn').click();
cy.clear_filters();
cy.wait('@save_user_settings');
});
});

+ 4
- 5
cypress/integration/report_view.js View File

@@ -4,7 +4,7 @@ const doctype_name = custom_submittable_doctype.name;
context('Report View', () => {
before(() => {
cy.login();
cy.visit('/desk#workspace/Website');
cy.visit('/app/website');
cy.insert_doc('DocType', custom_submittable_doctype, true);
cy.clear_cache();
cy.insert_doc(doctype_name, {
@@ -16,15 +16,14 @@ context('Report View', () => {
}, true).as('doc');
});
it('Field with enabled allow_on_submit should be editable.', () => {
cy.server();
cy.route('POST', 'api/method/frappe.client.set_value').as('value-update');
cy.visit(`/desk#List/${doctype_name}/Report`);
cy.intercept('POST', 'api/method/frappe.client.set_value').as('value-update');
cy.visit(`/app/List/${doctype_name}/Report`);
// check status column added from docstatus
cy.get('.dt-row-0 > .dt-cell--col-3').should('contain', 'Submitted');
let cell = cy.get('.dt-row-0 > .dt-cell--col-4');
// select the cell
cell.dblclick();
cell.find('input[data-fieldname="enabled"]').check({force: true});
cell.find('input[data-fieldname="enabled"]').check({ force: true });
cy.get('.dt-row-0 > .dt-cell--col-5').click();
cy.wait('@value-update');
cy.get('@doc').then(doc => {


+ 5
- 6
cypress/integration/table_multiselect.js View File

@@ -13,15 +13,14 @@ context('Table MultiSelect', () => {
cy.get('input[data-fieldname="users"]').focus().as('input');
cy.get('input[data-fieldname="users"] + ul').should('be.visible');
cy.get('@input').type('test{enter}', { delay: 100 });
cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value')
.first().as('selected-value');
cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value .btn-link-to-form')
.as('selected-value');
cy.get('@selected-value').should('contain', 'test@erpnext.com');

cy.server();
cy.route('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
cy.intercept('POST', '/api/method/frappe.desk.form.save.savedocs').as('save_form');
// trigger save
cy.get('.primary-action').click();
cy.wait('@save_form').its('status').should('eq', 200);
cy.wait('@save_form').its('response.statusCode').should('eq', 200);
cy.get('@selected-value').should('contain', 'test@erpnext.com');
});

@@ -46,6 +45,6 @@ context('Table MultiSelect', () => {
cy.get(`.list-subject:contains("table multiselect")`).last().find('a').click();
cy.get('.frappe-control[data-fieldname="users"] .form-control .tb-selected-value').as('existing_value');
cy.get('@existing_value').find('.btn-link-to-form').click();
cy.location('hash').should('contain', 'Form/User/test@erpnext.com');
cy.location('pathname').should('contain', '/user/test@erpnext.com');
});
});

+ 25
- 8
cypress/support/commands.js View File

@@ -244,14 +244,14 @@ Cypress.Commands.add('awesomebar', text => {
});

Cypress.Commands.add('new_form', doctype => {
let route = `Form/${doctype}/New ${doctype} 1`;
cy.visit(`/desk#${route}`);
cy.get('body').should('have.attr', 'data-route', route);
let dt_in_route = doctype.toLowerCase().replace(/ /g, '-');
cy.visit(`/app/${dt_in_route}/new`);
cy.get('body').should('have.attr', 'data-route', `Form/${doctype}/new-${dt_in_route}-1`);
cy.get('body').should('have.attr', 'data-ajax-state', 'complete');
});

Cypress.Commands.add('go_to_list', doctype => {
cy.visit(`/desk#List/${doctype}/List`);
cy.visit(`/app/list/${doctype}/list`);
});

Cypress.Commands.add('clear_cache', () => {
@@ -275,9 +275,8 @@ Cypress.Commands.add('get_open_dialog', () => {
});

Cypress.Commands.add('hide_dialog', () => {
cy.get_open_dialog()
.find('.btn-modal-close')
.click();
cy.wait(200);
cy.get_open_dialog().find('.btn-modal-close').click();
cy.get('.modal:visible').should('not.exist');
});

@@ -307,4 +306,22 @@ Cypress.Commands.add('insert_doc', (doctype, args, ignore_duplicate) => {
return res.body.data;
});
});
});
});

Cypress.Commands.add('add_filter', () => {
cy.get('.filter-section .filter-button').click();
cy.wait(300);
cy.get('.filter-popover').should('exist');
cy.get('.filter-popover').find('.add-filter').click();
});

Cypress.Commands.add('clear_filters', () => {
cy.get('.filter-section .filter-button').click();
cy.wait(300);
cy.get('.filter-popover').should('exist');
cy.get('.filter-popover').find('.clear-filters').click();
cy.get('.filter-section .filter-button').click();
cy.window().its('cur_list').then(cur_list => {
cur_list && cur_list.filter_area && cur_list.filter_area.clear();
});
});

+ 1
- 1
cypress/support/index.js View File

@@ -21,5 +21,5 @@ import './commands';
// require('./commands')

Cypress.Cookies.defaults({
whitelist: 'sid'
preserve: 'sid'
});

+ 9
- 0
frappe/.stylelintrc View File

@@ -0,0 +1,9 @@
{
"extends": ["stylelint-config-recommended"],
"plugins": ["stylelint-scss"],
"rules": {
"at-rule-no-unknown": null,
"scss/at-rule-no-unknown": true,
"no-descending-specificity": null
}
}

+ 4
- 7
frappe/__init__.py View File

@@ -466,7 +466,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
attachments=None, content=None, doctype=None, name=None, reply_to=None,
cc=[], bcc=[], message_id=None, in_reply_to=None, send_after=None, expose_recipients=None,
send_priority=1, communication=None, retry=1, now=None, read_receipt=None, is_notification=False,
inline_images=None, template=None, args=None, header=None, print_letterhead=False):
inline_images=None, template=None, args=None, header=None, print_letterhead=False, with_container=False):
"""Send email using user's default **Email Account** or global default **Email Account**.


@@ -492,6 +492,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
:param template: Name of html template from templates/emails folder
:param args: Arguments for rendering the template
:param header: Append header in email
:param with_container: Wraps email inside a styled container
"""
text_content = None
if template:
@@ -514,7 +515,7 @@ def sendmail(recipients=[], sender="", subject="No Subject", message="No Message
attachments=attachments, reply_to=reply_to, cc=cc, bcc=bcc, message_id=message_id, in_reply_to=in_reply_to,
send_after=send_after, expose_recipients=expose_recipients, send_priority=send_priority,
communication=communication, now=now, read_receipt=read_receipt, is_notification=is_notification,
inline_images=inline_images, header=header, print_letterhead=print_letterhead)
inline_images=inline_images, header=header, print_letterhead=print_letterhead, with_container=with_container)

whitelisted = []
guest_methods = []
@@ -964,10 +965,6 @@ def get_installed_apps(sort=False, frappe_last=False):
if not local.all_apps:
local.all_apps = cache().get_value('all_apps', get_all_apps)

#cache bench apps
if not cache().get_value('all_apps'):
cache().set_value('all_apps', local.all_apps)

installed = json.loads(db.get_global("installed_apps") or "[]")

if sort:
@@ -1632,7 +1629,7 @@ def log_error(message=None, title=_("Error")):
method=title)).insert(ignore_permissions=True)

def get_desk_link(doctype, name):
html = '<a href="#Form/{doctype}/{name}" style="font-weight: bold;">{doctype_local} {name}</a>'
html = '<a href="/app/Form/{doctype}/{name}" style="font-weight: bold;">{doctype_local} {name}</a>'
return html.format(
doctype=doctype,
name=name,


+ 1
- 1
frappe/auth.py View File

@@ -173,7 +173,7 @@ class LoginManager:
frappe.local.cookie_manager.set_cookie("system_user", "yes")
if not resume:
frappe.local.response['message'] = 'Logged In'
frappe.local.response["home_page"] = "/desk"
frappe.local.response["home_page"] = "/app"

if not resume:
frappe.response["full_name"] = self.full_name


+ 0
- 70
frappe/automation/desk_page/tools/tools.json View File

@@ -1,70 +0,0 @@
{
"cards": [
{
"hidden": 0,
"label": "Tools",
"links": "[\n {\n \"description\": \"Documents assigned to you and by you.\",\n \"label\": \"To Do\",\n \"name\": \"ToDo\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Event and other calendars.\",\n \"label\": \"Calendar\",\n \"link\": \"List/Event/Calendar\",\n \"name\": \"Event\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Private and public Notes.\",\n \"label\": \"Note\",\n \"name\": \"Note\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Files\",\n \"name\": \"File\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Activity log of all users.\",\n \"label\": \"Activity\",\n \"name\": \"activity\",\n \"type\": \"page\"\n }\n]"
},
{
"hidden": 0,
"label": "Email",
"links": "[\n {\n \"description\": \"Newsletters to contacts, leads.\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Group List\",\n \"label\": \"Email Group\",\n \"name\": \"Email Group\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Automation",
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Assignment Rule\",\n \"description\": \"Set up rules for user assignments.\",\n \"label\": \"Assignment Rule\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Milestone\",\n \"description\": \"Tracks milestones on the lifecycle of a document if it undergoes multiple stages.\",\n \"label\": \"Milestone\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Auto Repeat\",\n \"description\": \"Automatically generates recurring documents.\",\n \"label\": \"Auto Repeat\"\n }\n]"
},
{
"hidden": 0,
"label": "Event Streaming",
"links": "[\n {\n \"type\": \"doctype\",\n \"name\": \"Event Producer\",\n \"description\": \"The site you want to subscribe to for consuming events.\",\n \"label\": \"Event Producer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Consumer\",\n \"description\": \"The site which is consuming your events.\",\n \"label\": \"Event Consumer\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Update Log\",\n \"description\": \"Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.\",\n \"label\": \"Event Update Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Event Sync Log\",\n \"description\": \"Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.\",\n \"label\": \"Event Sync Log\"\n },\n {\n \"type\": \"doctype\",\n \"name\": \"Document Type Mapping\",\n \"description\": \"The mapping configuration between two doctypes.\",\n \"label\": \"Document Type Mapping\"\n }\n]"
}
],
"category": "Administration",
"charts": [],
"creation": "2020-03-02 14:53:24.980279",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Tools",
"modified": "2020-07-21 19:32:18.480700",
"modified_by": "Administrator",
"module": "Automation",
"name": "Tools",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"label": "ToDo",
"link_to": "ToDo",
"type": "DocType"
},
{
"label": "Note",
"link_to": "Note",
"type": "DocType"
},
{
"label": "File",
"link_to": "File",
"type": "DocType"
},
{
"label": "Assignment Rule",
"link_to": "Assignment Rule",
"type": "DocType"
},
{
"label": "Auto Repeat",
"link_to": "Auto Repeat",
"type": "DocType"
}
]
}

+ 4
- 3
frappe/automation/doctype/auto_repeat/auto_repeat.js View File

@@ -30,7 +30,7 @@ frappe.ui.form.on('Auto Repeat', {
refresh: function(frm) {
// auto repeat message
if (frm.is_new()) {
let customize_form_link = `<a href="#Form/Customize Form">${__('Customize Form')}</a>`;
let customize_form_link = `<a href="/app/customize form">${__('Customize Form')}</a>`;
frm.dashboard.set_headline(__('To configure Auto Repeat, enable "Allow Auto Repeat" from {0}.', [customize_form_link]));
}

@@ -106,8 +106,9 @@ frappe.auto_repeat.render_schedule = function(frm) {
frm.dashboard.wrapper.empty();
frm.dashboard.add_section(
frappe.render_template("auto_repeat_schedule", {
schedule_details : r.message || []
})
schedule_details: r.message || []
}),
__('Auto Repeat Schedule')
);
frm.dashboard.show();
});


+ 229
- 0
frappe/automation/workspace/tools/tools.json View File

@@ -0,0 +1,229 @@
{
"category": "Administration",
"charts": [],
"creation": "2020-03-02 14:53:24.980279",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Workspace",
"extends_another_page": 0,
"hide_custom": 0,
"icon": "tool",
"idx": 0,
"is_standard": 1,
"label": "Tools",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Tools",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "To Do",
"link_to": "ToDo",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Calendar",
"link_to": "Event",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Note",
"link_to": "Note",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Files",
"link_to": "File",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Activity",
"link_to": "activity",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Group",
"link_to": "Email Group",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Automation",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Assignment Rule",
"link_to": "Assignment Rule",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Milestone",
"link_to": "Milestone",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Auto Repeat",
"link_to": "Auto Repeat",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Event Streaming",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Event Producer",
"link_to": "Event Producer",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Event Consumer",
"link_to": "Event Consumer",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Event Update Log",
"link_to": "Event Update Log",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Event Sync Log",
"link_to": "Event Sync Log",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Document Type Mapping",
"link_to": "Document Type Mapping",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2020-12-01 13:38:39.950350",
"modified_by": "Administrator",
"module": "Automation",
"name": "Tools",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"label": "ToDo",
"link_to": "ToDo",
"type": "DocType"
},
{
"label": "Note",
"link_to": "Note",
"type": "DocType"
},
{
"label": "File",
"link_to": "File",
"type": "DocType"
},
{
"label": "Assignment Rule",
"link_to": "Assignment Rule",
"type": "DocType"
},
{
"label": "Auto Repeat",
"link_to": "Auto Repeat",
"type": "DocType"
}
]
}

+ 37
- 21
frappe/boot.py View File

@@ -39,7 +39,7 @@ def get_bootinfo():
bootinfo.server_date = frappe.utils.nowdate()

if frappe.session['user'] != 'Guest':
bootinfo.user_info = get_fullnames()
bootinfo.user_info = get_user_info()
bootinfo.sid = frappe.session['sid']

bootinfo.modules = {}
@@ -48,6 +48,7 @@ def get_bootinfo():
bootinfo.letter_heads = get_letter_heads()
bootinfo.active_domains = frappe.get_active_domains()
bootinfo.all_domains = [d.get("name") for d in frappe.get_all("Domain")]
add_layouts(bootinfo)

bootinfo.module_app = frappe.local.module_app
bootinfo.single_types = [d.name for d in frappe.get_all('DocType', {'issingle': 1})]
@@ -88,6 +89,7 @@ def get_bootinfo():
bootinfo.frequently_visited_links = frequently_visited_links()
bootinfo.link_preview_doctypes = get_link_preview_doctypes()
bootinfo.additional_filters_config = get_additional_filters_from_hooks()
bootinfo.desk_settings = get_desk_settings()

return bootinfo

@@ -106,11 +108,9 @@ def load_conf_settings(bootinfo):
if key in conf: bootinfo[key] = conf.get(key)

def load_desktop_data(bootinfo):
from frappe.config import get_modules_from_all_apps_for_user
from frappe.desk.desktop import get_desk_sidebar_items
bootinfo.allowed_modules = get_modules_from_all_apps_for_user()
bootinfo.allowed_workspaces = get_desk_sidebar_items(flatten=True, cache=False)
bootinfo.module_page_map = get_controller("Desk Page").get_module_page_map()
bootinfo.allowed_workspaces = get_desk_sidebar_items()
bootinfo.module_page_map = get_controller("Workspace").get_module_page_map()
bootinfo.dashboards = frappe.get_all("Dashboard")

def get_allowed_pages(cache=False):
@@ -222,19 +222,18 @@ def load_translations(bootinfo):

bootinfo["__messages"] = messages

def get_fullnames():
"""map of user fullnames"""
ret = frappe.db.sql("""select `name`, full_name as fullname,
user_image as image, gender, email, username, bio, location, interest, banner_image, allowed_in_mentions
from tabUser where enabled=1 and user_type!='Website User'""", as_dict=1)
def get_user_info():
user_info = frappe.db.get_all('User', fields=['`name`', 'full_name as fullname', 'user_image as image',
'gender', 'email', 'username', 'bio', 'location', 'interest', 'banner_image', 'allowed_in_mentions', 'user_type'],
filters=dict(enabled=1))

d = {}
for r in ret:
# if not r.image:
# r.image = get_gravatar(r.name)
d[r.name] = r
user_info_map = {d.name: d for d in user_info}

return d
admin_data = user_info_map.get('Administrator')
if admin_data:
user_info_map[admin_data.email] = admin_data

return user_info_map

def get_user(bootinfo):
"""get user info"""
@@ -251,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('workspace')

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

def add_timezone_info(bootinfo):
system = bootinfo.sysdefaults.get("time_zone")
@@ -273,7 +271,7 @@ def load_print(bootinfo, doclist):

def load_print_css(bootinfo, print_settings):
import frappe.www.printview
bootinfo.print_css = frappe.www.printview.get_print_style(print_settings.print_style or "Modern", for_legacy=True)
bootinfo.print_css = frappe.www.printview.get_print_style(print_settings.print_style or "Redesign", for_legacy=True)

def get_unseen_notes():
return frappe.db.sql('''select `name`, title, content, notify_on_every_login from `tabNote` where notify_on_login=1
@@ -308,3 +306,21 @@ def get_additional_filters_from_hooks():
filter_config.update(frappe.get_attr(hook)())

return filter_config

def add_layouts(bootinfo):
# add routes for readable doctypes
bootinfo.doctype_layouts = frappe.get_all('DocType Layout', ['name', 'route', 'document_type'])

def get_desk_settings():
role_list = frappe.get_all('Role', fields=['*'], filters=dict(
name=['in', frappe.get_roles()]
))
desk_settings = {}

from frappe.core.doctype.role.role import desk_properties

for role in role_list:
for key in desk_properties:
desk_settings[key] = desk_settings.get(key) or role.get(key)

return desk_settings

+ 5
- 10
frappe/cache_manager.py View File

@@ -13,7 +13,7 @@ common_default_keys = ["__default", "__global"]
doctype_map_keys = ('energy_point_rule_map', 'assignment_rule_map',
'milestone_tracker_map', 'event_consumer_document_type_map')

global_cache_keys = ("app_hooks", "installed_apps",
global_cache_keys = ("app_hooks", "installed_apps", 'all_apps',
"app_modules", "module_app", "system_settings",
'scheduler_events', 'time_zone', 'webhooks', 'active_domains',
'active_modules', 'assignment_rule', 'server_script_map', 'wkhtmltopdf_version',
@@ -67,10 +67,6 @@ def clear_defaults_cache(user=None):
elif frappe.flags.in_install!="frappe":
frappe.cache().delete_key("defaults")

def clear_document_cache():
frappe.local.document_cache = {}
frappe.cache().delete_key("document_cache")

def clear_doctype_cache(doctype=None):
clear_controller_cache(doctype)
cache = frappe.cache()
@@ -78,9 +74,11 @@ def clear_doctype_cache(doctype=None):
if getattr(frappe.local, 'meta_cache') and (doctype in frappe.local.meta_cache):
del frappe.local.meta_cache[doctype]

for key in ('is_table', 'doctype_modules'):
for key in ('is_table', 'doctype_modules', 'document_cache'):
cache.delete_value(key)

frappe.local.document_cache = {}

def clear_single(dt):
for name in doctype_cache_keys:
cache.hdel(name, dt)
@@ -102,15 +100,12 @@ def clear_doctype_cache(doctype=None):
for name in doctype_cache_keys:
cache.delete_value(name)

# Clear all document's cache. To clear documents of a specific DocType document_cache should be restructured
clear_document_cache()

def clear_controller_cache(doctype=None):
if not doctype:
del frappe.controllers
frappe.controllers = {}
return
for site_controllers in frappe.controllers.values():
site_controllers.pop(doctype, None)



+ 3
- 2
frappe/commands/utils.py View File

@@ -571,10 +571,11 @@ def run_ui_tests(context, app, headless=False):
plugin_path = "{0}/cypress-file-upload".format(node_bin)

# check if cypress in path...if not, install it.
if not (os.path.exists(cypress_path) or os.path.exists(plugin_path)):
if not (os.path.exists(cypress_path) or os.path.exists(plugin_path)) \
or not subprocess.getoutput("npm view cypress version").startswith("6."):
# install cypress
click.secho("Installing Cypress...", fg="yellow")
frappe.commands.popen("yarn add cypress@3 cypress-file-upload@^3.1 --no-lockfile")
frappe.commands.popen("yarn add cypress@^6 cypress-file-upload@^5 --no-lockfile")

# run for headless mode
run_or_open = 'run --browser chrome --record --key 4a48f41c-11b3-425b-aa88-c58048fa69eb' if headless else 'open'


+ 1
- 1
frappe/config/__init__.py View File

@@ -108,4 +108,4 @@ def is_domain(module):
return module.get("category") == "Domains"

def is_module(module):
return module.get("type") == "module"
return module.get("type") == "module"

+ 0
- 59
frappe/config/automation.py View File

@@ -1,59 +0,0 @@
from __future__ import unicode_literals
from frappe import _

def get_data():
data = [
{
"label": _("Automation"),
"icon": "fa fa-random",
"items": [
{
"type": "doctype",
"name": "Assignment Rule",
"description": _("Set up rules for user assignments.")
},
{
"type": "doctype",
"name": "Milestone",
"description": _("Tracks milestones on the lifecycle of a document if it undergoes multiple stages.")
},
{
"type": "doctype",
"name": "Auto Repeat",
"description": _("Automatically generates recurring documents.")
},
]
},
{
"label": _("Event Streaming"),
"icon": "fa fa-random",
"items": [
{
"type": "doctype",
"name": "Event Producer",
"description": _("The site you want to subscribe to for consuming events.")
},
{
"type": "doctype",
"name": "Event Consumer",
"description": _("The site which is consuming your events.")
},
{
"type": "doctype",
"name": "Event Update Log",
"description": _("Maintains a Log of all inserts, updates and deletions on Event Producer site for documents that have consumers.")
},
{
"type": "doctype",
"name": "Event Sync Log",
"description": _("Maintains a log of every event consumed along with the status of the sync and a Resync button in case sync fails.")
},
{
"type": "doctype",
"name": "Document Type Mapping",
"description": _("The mapping configuration between two doctypes.")
}
]
}
]
return data

+ 0
- 66
frappe/config/core.py View File

@@ -1,66 +0,0 @@
from __future__ import unicode_literals
from frappe import _

def get_data():
return [
{
"label": _("Documents"),
"items": [
{
"type": "doctype",
"name": "DocType",
"description": _("Models (building blocks) of the Application"),
},
{
"type": "doctype",
"name": "Module Def",
"description": _("Groups of DocTypes"),
},
{
"type": "doctype",
"name": "Page",
"description": _("Pages in Desk (place holders)"),
},
{
"type": "doctype",
"name": "Report",
"description": _("Script or Query reports"),
},
{
"type": "doctype",
"name": "Print Format",
"description": _("Customized Formats for Printing, Email"),
},
{
"type": "doctype",
"name": "Custom Script",
"description": _("Client side script extensions in Javascript"),
}
]
},
{
"label": _("Logs"),
"items": [
{
"type": "doctype",
"name": "Error Log",
"description": _("Errors in Background Events"),
},
{
"type": "doctype",
"name": "Email Queue",
"description": _("Background Email Queue"),
},
{
"type": "page",
"label": _("Background Jobs"),
"name": "background_jobs",
},
{
"type": "doctype",
"name": "Error Snapshot",
"description": _("A log of request errors"),
},
]
}
]

+ 0
- 60
frappe/config/customization.py View File

@@ -1,60 +0,0 @@
from __future__ import unicode_literals
from frappe import _

def get_data():
return [
{
"label": _("Form Customization"),
"icon": "fa fa-glass",
"items": [
{
"type": "doctype",
"name": "Customize Form",
"description": _("Change field properties (hide, readonly, permission etc.)")
},
{
"type": "doctype",
"name": "Custom Field",
"description": _("Add fields to forms.")
},
{
"type": "doctype",
"name": "Custom Script",
"description": _("Add custom javascript to forms.")
},
{
"type": "doctype",
"name": "DocType",
"description": _("Add custom forms.")
},
]
},
{
"label": _("Dashboards"),
"items": [
{
"type": "doctype",
"name": "Dashboard",
},
{
"type": "doctype",
"name": "Dashboard Chart",
},
{
"type": "doctype",
"name": "Dashboard Chart Source",
},
]
},
{
"label": _("Other"),
"items": [
{
"type": "doctype",
"label": _("Custom Translations"),
"name": "Translation",
"description": _("Add your own translations")
}
]
}
]

+ 0
- 67
frappe/config/desk.py View File

@@ -1,67 +0,0 @@
from __future__ import unicode_literals
from frappe import _

def get_data():
return [
{
"label": _("Tools"),
"icon": "octicon octicon-briefcase",
"items": [
{
"type": "doctype",
"name": "ToDo",
"label": _("To Do"),
"description": _("Documents assigned to you and by you."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Event",
"label": _("Calendar"),
"link": "List/Event/Calendar",
"description": _("Event and other calendars."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Note",
"description": _("Private and public Notes."),
"onboard": 1,
},
{
"type": "doctype",
"name": "File",
"label": _("Files"),
},
{
"type": "page",
"label": _("Chat"),
"name": "chat",
"description": _("Chat messages and other notifications."),
"data_doctype": "Communication"
},
{
"type": "page",
"label": _("Activity"),
"name": "activity",
"description": _("Activity log of all users."),
},
]
},
{
'label': _('Email'),
'items': [
{
"type": "doctype",
"name": "Newsletter",
"description": _("Newsletters to contacts, leads."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Email Group",
"description": _("Email Group List"),
},
]
}
]

+ 0
- 133
frappe/config/desktop.py View File

@@ -1,133 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe import _

def get_data():
return [
# Administration
{
"module_name": "Desk",
"category": "Administration",
"label": _("Tools"),
"color": "#FFF5A7",
"reverse": 1,
"icon": "octicon octicon-calendar",
"type": "module",
"description": "Todos, notes, calendar and newsletter."
},
{
"module_name": "Settings",
"category": "Administration",
"label": _("Settings"),
"color": "#bdc3c7",
"reverse": 1,
"icon": "octicon octicon-settings",
"type": "module",
"description": "Data import, printing, email and workflows."
},
{
"module_name": "Automation",
"category": "Administration",
"label": _("Automation"),
"color": "#bdc3c7",
"reverse": 1,
"icon": "octicon octicon-gist",
"type": "module",
"description": "Auto Repeat, Assignment Rule, Milestone Tracking and Event Streaming."
},
{
"module_name": "Users and Permissions",
"category": "Administration",
"label": _("Users and Permissions"),
"color": "#bdc3c7",
"reverse": 1,
"icon": "octicon octicon-settings",
"type": "module",
"description": "Setup roles and permissions for users on documents."
},
{
"module_name": "Customization",
"category": "Administration",
"label": _("Customization"),
"color": "#bdc3c7",
"reverse": 1,
"icon": "octicon octicon-settings",
"type": "module",
"description": "Customize forms, custom fields, scripts and translations."
},
{
"module_name": "Integrations",
"category": "Administration",
"label": _("Integrations"),
"color": "#16a085",
"icon": "octicon octicon-globe",
"type": "module",
"description": "DropBox, Woocomerce, AWS, Shopify and GoCardless."
},
{
"module_name": 'Contacts',
"category": "Administration",
"label": _("Contacts"),
"type": 'module',
"icon": "octicon octicon-book",
"color": '#ffaedb',
"description": "People Contacts and Address Book."
},
{
"module_name": "Core",
"category": "Administration",
"_label": _("Developer"),
"label": "Developer",
"color": "#589494",
"icon": "octicon octicon-circuit-board",
"type": "module",
"system_manager": 1,
"condition": getattr(frappe.local.conf, 'developer_mode', 0),
"description": "Doctypes, dev tools and logs."
},

# Places
{
"module_name": "Website",
"category": "Places",
"label": _("Website"),
"_label": _("Website"),
"color": "#16a085",
"icon": "octicon octicon-globe",
"type": "module",
"description": "Webpages, webforms, blogs and website theme."
},
{
"module_name": 'Social',
"category": "Places",
"label": _('Social'),
"icon": "octicon octicon-heart",
"type": 'link',
"link": '#social/home',
"color": '#FF4136',
'standard': 1,
'idx': 15,
"description": "Build your profile and share posts with other users."
},
{
"module_name": 'Leaderboard',
"category": "Places",
"label": _('Leaderboard'),
"icon": "fa fa-trophy",
"type": 'link',
"link": '#leaderboard/User',
"color": '#FF4136',
'standard': 1,
},
{
"module_name": 'dashboard',
"category": "Places",
"label": _('Dashboard'),
"icon": "octicon octicon-graph",
"type": "link",
"link": "#dashboard",
"color": '#FF4136',
'standard': 1,
'idx': 10
},
]

+ 0
- 6
frappe/config/docs.py View File

@@ -1,6 +0,0 @@
# -*- coding: utf-8 -*-

from __future__ import unicode_literals

source_link = "https://github.com/frappe/frappe_io"
docs_base_url = "/docs"

+ 0
- 127
frappe/config/integrations.py View File

@@ -1,127 +0,0 @@
from __future__ import unicode_literals
from frappe import _

def get_data():
return [
{
"label": _("Payments"),
"icon": "fa fa-star",
"items": [
{
"type": "doctype",
"name": "Braintree Settings",
"description": _("Braintree payment gateway settings"),
},
{
"type": "doctype",
"name": "PayPal Settings",
"description": _("PayPal payment gateway settings"),
},
{
"type": "doctype",
"name": "Razorpay Settings",
"description": _("Razorpay Payment gateway settings"),
},
{
"type": "doctype",
"name": "Stripe Settings",
"description": _("Stripe payment gateway settings"),
},
{
"type": "doctype",
"name": "Paytm Settings",
"description": _("Paytm payment gateway settings"),
},
]
},
{
"label": _("Backup"),
"items": [
{
"type": "doctype",
"name": "Dropbox Settings",
"description": _("Dropbox backup settings"),
},
{
"type": "doctype",
"name": "S3 Backup Settings",
"description": _("S3 Backup Settings"),
},
{
"type": "doctype",
"name": "Google Drive",
"description": _("Google Drive Backup."),
}
]
},
{
"label": _("Authentication"),
"items": [
{
"type": "doctype",
"name": "Social Login Key",
"description": _("Enter keys to enable login via Facebook, Google, GitHub."),
},
{
"type": "doctype",
"name": "LDAP Settings",
"description": _("Ldap settings"),
},
{
"type": "doctype",
"name": "OAuth Client",
"description": _("Register OAuth Client App"),
},
{
"type": "doctype",
"name": "OAuth Provider Settings",
"description": _("Settings for OAuth Provider"),
},
{
"type": "doctype",
"name": "Connected App",
"description": _("Connect to any OAuth Provider"),
},
]
},
{
"label": _("Webhook"),
"items": [
{
"type": "doctype",
"name": "Webhook",
"description": _("Webhooks calling API requests into web apps"),
},
{
"type": "doctype",
"name": "Slack Webhook URL",
"description": _("Slack Webhooks for internal integration"),
},
]
},
{
"label": _("Google Services"),
"items": [
{
"type": "doctype",
"name": "Google Settings",
"description": _("Google API Settings."),
},
{
"type": "doctype",
"name": "Google Contacts",
"description": _("Google Contacts Integration."),
},
{
"type": "doctype",
"name": "Google Calendar",
"description": _("Google Calendar Integration."),
},
{
"type": "doctype",
"name": "Google Drive",
"description": _("Google Drive Integration."),
}
]
}
]

+ 0
- 195
frappe/config/settings.py View File

@@ -1,195 +0,0 @@
from __future__ import unicode_literals
import frappe
from frappe import _
from frappe.desk.moduleview import add_setup_section

def get_data():
data = [
{
"label": _("Core"),
"icon": "fa fa-wrench",
"items": [
{
"type": "doctype",
"name": "System Settings",
"label": _("System Settings"),
"description": _("Language, Date and Time settings"),
"hide_count": True
},
{
"type": "doctype",
"name": "Global Defaults",
"label": _("Global Defaults"),
"description": _("Company, Fiscal Year and Currency defaults"),
"hide_count": True
},
{
"type": "doctype",
"name": "Log Settings",
"description": _("Log cleanup and notification configuration")
},
{
"type": "doctype",
"name": "Error Log",
"description": _("Log of error on automated events (scheduler).")
},
{
"type": "doctype",
"name": "Error Snapshot",
"description": _("Log of error during requests.")
},
{
"type": "doctype",
"name": "Domain Settings",
"label": _("Domain Settings"),
"description": _("Enable / Disable Domains"),
"hide_count": True
},
]
},
{
"label": _("Data"),
"icon": "fa fa-th",
"items": [
{
"type": "doctype",
"name": "Data Import",
"label": _("Import Data"),
"icon": "octicon octicon-cloud-upload",
"description": _("Import Data from CSV / Excel files.")
},
{
"type": "doctype",
"name": "Data Export",
"label": _("Export Data"),
"icon": "octicon octicon-cloud-upload",
"description": _("Export Data in CSV / Excel format.")
},
{
"type": "doctype",
"name": "Naming Series",
"description": _("Set numbering series for transactions."),
"hide_count": True
},
{
"type": "doctype",
"name": "Rename Tool",
"label": _("Bulk Rename"),
"description": _("Rename many items by uploading a .csv file."),
"hide_count": True
},
{
"type": "doctype",
"name": "Bulk Update",
"label": _("Bulk Update"),
"description": _("Update many values at one time."),
"hide_count": True
},
{
"type": "page",
"name": "backups",
"label": _("Download Backups"),
"description": _("List of backups available for download"),
"icon": "fa fa-download"
},
{
"type": "doctype",
"name": "Deleted Document",
"label": _("Deleted Documents"),
"description": _("Restore or permanently delete a document.")
},
]
},
{
"label": _("Email / Notifications"),
"icon": "fa fa-envelope",
"items": [
{
"type": "doctype",
"name": "Email Account",
"description": _("Add / Manage Email Accounts.")
},
{
"type": "doctype",
"name": "Email Domain",
"description": _("Add / Manage Email Domains.")
},
{
"type": "doctype",
"name": "Notification",
"description": _("Setup Notifications based on various criteria.")
},
{
"type": "doctype",
"name": "Email Template",
"description": _("Email Templates for common queries.")
},
{
"type": "doctype",
"name": "Auto Email Report",
"description": _("Setup Reports to be emailed at regular intervals"),
},
{
"type": "doctype",
"name": "Newsletter",
"description": _("Create and manage newsletter")
},
{
"type": "doctype",
"route": "Form/Notification Settings/{}".format(frappe.session.user),
"name": "Notification Settings",
"description": _("Configure notifications for mentions, assignments, energy points and more.")
}
]
},
{
"label": _("Printing"),
"icon": "fa fa-print",
"items": [
{
"type": "page",
"label": _("Print Format Builder"),
"name": "print-format-builder",
"description": _("Drag and Drop tool to build and customize Print Formats.")
},
{
"type": "doctype",
"name": "Print Settings",
"description": _("Set default format, page size, print style etc.")
},
{
"type": "doctype",
"name": "Print Format",
"description": _("Customized HTML Templates for printing transactions.")
},
{
"type": "doctype",
"name": "Print Style",
"description": _("Stylesheets for Print Formats")
},
]
},
{
"label": _("Workflow"),
"icon": "fa fa-random",
"items": [
{
"type": "doctype",
"name": "Workflow",
"description": _("Define workflows for forms.")
},
{
"type": "doctype",
"name": "Workflow State",
"description": _("States for workflow (e.g. Draft, Approved, Cancelled).")
},
{
"type": "doctype",
"name": "Workflow Action",
"description": _("Actions for workflow (e.g. Approve, Cancel).")
},
]
}
]
add_setup_section(data, "frappe", "website", _("Website"), "fa fa-globe")
return data

+ 0
- 2
frappe/config/tools.py View File

@@ -1,2 +0,0 @@
from __future__ import unicode_literals
from frappe import _

+ 0
- 85
frappe/config/users_and_permissions.py View File

@@ -1,85 +0,0 @@
from __future__ import unicode_literals
from frappe import _

def get_data():
return [
{
"label": _("Users"),
"icon": "fa fa-group",
"items": [
{
"type": "doctype",
"name": "User",
"description": _("System and Website Users")
},
{
"type": "doctype",
"name": "Role",
"description": _("User Roles")
},
{
"type": "doctype",
"name": "Role Profile",
"description": _("Role Profile")
}
]
},
{
"label": _("Permissions"),
"icon": "fa fa-lock",
"items": [
{
"type": "page",
"name": "permission-manager",
"label": _("Role Permissions Manager"),
"icon": "fa fa-lock",
"description": _("Set Permissions on Document Types and Roles")
},
{
"type": "doctype",
"name": "User Permission",
"label": _("User Permissions"),
"icon": "fa fa-lock",
"description": _("Restrict user for specific document")
},
{
"type": "doctype",
"name": "Role Permission for Page and Report",
"description": _("Set custom roles for page and report")
},
{
"type": "report",
"is_query_report": True,
"doctype": "User",
"icon": "fa fa-eye-open",
"name": "Permitted Documents For User",
"description": _("Check which Documents are readable by a User")
},
{
"type": "report",
"doctype": "DocShare",
"icon": "fa fa-share",
"name": "Document Share Report",
"description": _("Report of all document shares")
}
]
},
{
"label": _("Logs"),
"icon": "fa fa-group",
"items": [
{
"type": "doctype",
"name": "Activity Log",
"label": _("Activity Log"),
"description": _("Activity Log by ")
},
{
"type": "doctype",
"name": "Access Log",
"label": _("Access Log"),
"description": _("View Log of all print, download and export events")
}
]
}
]

+ 0
- 117
frappe/config/website.py View File

@@ -1,117 +0,0 @@
from __future__ import unicode_literals
from frappe import _

def get_data():
return [
{
"label": _("Web Site"),
"icon": "fa fa-star",
"items": [
{
"type": "doctype",
"name": "Web Page",
"description": _("Content web page."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Web Form",
"description": _("User editable form on Website."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Website Sidebar",
},
{
"type": "doctype",
"name": "Website Slideshow",
"description": _("Embed image slideshows in website pages."),
},
{
"type": "doctype",
"name": "Website Route Meta",
"description": _("Add meta tags to your web pages"),
},
]
},
{
"label": _("Blog"),
"items": [
{
"type": "doctype",
"name": "Blog Post",
"description": _("Single Post (article)."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Blogger",
"description": _("A user who posts blogs."),
},
{
"type": "doctype",
"name": "Blog Category",
"description": _("Categorize blog posts."),
},
]
},
{
"label": _("Setup"),
"icon": "fa fa-cog",
"items": [
{
"type": "doctype",
"name": "Website Settings",
"description": _("Setup of top navigation bar, footer and logo."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Website Theme",
"description": _("List of themes for Website."),
"onboard": 1,
},
{
"type": "doctype",
"name": "Website Script",
"description": _("Javascript to append to the head section of the page."),
},
{
"type": "doctype",
"name": "About Us Settings",
"description": _("Settings for About Us Page."),
},
{
"type": "doctype",
"name": "Contact Us Settings",
"description": _("Settings for Contact Us Page."),
},
]
},
{
"label": _("Portal"),
"items": [
{
"type": "doctype",
"name": "Portal Settings",
"label": _("Portal Settings"),
"onboard": 1,
}
]
},
{
"label": _("Knowledge Base"),
"items": [
{
"type": "doctype",
"name": "Help Category",
},
{
"type": "doctype",
"name": "Help Article",
},
]
},

]

+ 3
- 2
frappe/contacts/doctype/contact/contact.json View File

@@ -34,8 +34,8 @@
"email_ids",
"phone_nos",
"contact_details",
"is_primary_contact",
"links",
"is_primary_contact",
"more_info",
"department",
"unsubscribed"
@@ -248,8 +248,9 @@
"icon": "fa fa-user",
"idx": 1,
"image_field": "image",
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-04-06 18:25:28.223693",
"modified": "2020-08-27 14:12:09.906719",
"modified_by": "Administrator",
"module": "Contacts",
"name": "Contact",


+ 0
- 74
frappe/core/desk_page/settings/settings.json View File

@@ -1,74 +0,0 @@
{
"cards": [
{
"hidden": 0,
"label": "Data",
"links": "[\n {\n \"description\": \"Import Data from CSV / Excel files.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Import Data\",\n \"name\": \"Data Import\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Export Data in CSV / Excel format.\",\n \"icon\": \"octicon octicon-cloud-upload\",\n \"label\": \"Export Data\",\n \"name\": \"Data Export\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Update many values at one time.\",\n \"hide_count\": true,\n \"label\": \"Bulk Update\",\n \"name\": \"Bulk Update\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of backups available for download\",\n \"icon\": \"fa fa-download\",\n \"label\": \"Download Backups\",\n \"name\": \"backups\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restore or permanently delete a document.\",\n \"label\": \"Deleted Documents\",\n \"name\": \"Deleted Document\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Email / Notifications",
"links": "[\n {\n \"description\": \"Add / Manage Email Accounts.\",\n \"label\": \"Email Account\",\n \"name\": \"Email Account\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add / Manage Email Domains.\",\n \"label\": \"Email Domain\",\n \"name\": \"Email Domain\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Notifications based on various criteria.\",\n \"label\": \"Notification\",\n \"name\": \"Notification\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Email Templates for common queries.\",\n \"label\": \"Email Template\",\n \"name\": \"Email Template\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Setup Reports to be emailed at regular intervals\",\n \"label\": \"Auto Email Report\",\n \"name\": \"Auto Email Report\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Create and manage newsletter\",\n \"label\": \"Newsletter\",\n \"name\": \"Newsletter\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Configure notifications for mentions, assignments, energy points and more.\",\n \"label\": \"Notification Settings\",\n \"name\": \"Notification Settings\",\n \"route\": \"Form/Notification Settings/Administrator\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Website",
"links": "[\n {\n \"description\": \"Setup of top navigation bar, footer and logo.\",\n \"label\": \"Website Settings\",\n \"name\": \"Website Settings\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"List of themes for Website.\",\n \"label\": \"Website Theme\",\n \"name\": \"Website Theme\",\n \"onboard\": 1,\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Javascript to append to the head section of the page.\",\n \"label\": \"Website Script\",\n \"name\": \"Website Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for About Us Page.\",\n \"label\": \"About Us Settings\",\n \"name\": \"About Us Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for Contact Us Page.\",\n \"label\": \"Contact Us Settings\",\n \"name\": \"Contact Us Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Core",
"links": "[\n {\n \"description\": \"Language, Date and Time settings\",\n \"hide_count\": true,\n \"label\": \"System Settings\",\n \"name\": \"System Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Company, Fiscal Year and Currency defaults\",\n \"hide_count\": true,\n \"label\": \"Global Defaults\",\n \"name\": \"Global Defaults\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error on automated events (scheduler).\",\n \"label\": \"Error Log\",\n \"name\": \"Error Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Log of error during requests.\",\n \"label\": \"Error Snapshot\",\n \"name\": \"Error Snapshot\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Enable / Disable Domains\",\n \"hide_count\": true,\n \"label\": \"Domain Settings\",\n \"name\": \"Domain Settings\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Printing",
"links": "[\n {\n \"description\": \"Drag and Drop tool to build and customize Print Formats.\",\n \"label\": \"Print Format Builder\",\n \"name\": \"print-format-builder\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Set default format, page size, print style etc.\",\n \"label\": \"Print Settings\",\n \"name\": \"Print Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Customized HTML Templates for printing transactions.\",\n \"label\": \"Print Format\",\n \"name\": \"Print Format\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stylesheets for Print Formats\",\n \"label\": \"Print Style\",\n \"name\": \"Print Style\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Workflow",
"links": "[\n {\n \"description\": \"Define workflows for forms.\",\n \"label\": \"Workflow\",\n \"name\": \"Workflow\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"States for workflow (e.g. Draft, Approved, Cancelled).\",\n \"label\": \"Workflow State\",\n \"name\": \"Workflow State\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Actions for workflow (e.g. Approve, Cancel).\",\n \"label\": \"Workflow Action\",\n \"name\": \"Workflow Action\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Modules",
"charts": [],
"creation": "2020-03-02 15:09:40.527211",
"developer_mode_only": 0,
"disable_user_customization": 1,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"hide_custom": 0,
"idx": 0,
"is_standard": 1,
"label": "Settings",
"modified": "2020-07-14 10:09:09.520557",
"modified_by": "Administrator",
"module": "Core",
"name": "Settings",
"owner": "Administrator",
"pin_to_bottom": 1,
"pin_to_top": 0,
"shortcuts": [
{
"icon": "octicon octicon-settings",
"label": "System Settings",
"link_to": "System Settings",
"type": "DocType"
},
{
"icon": "fa fa-print",
"label": "Print Settings",
"link_to": "Print Settings",
"type": "DocType"
},
{
"icon": "fa fa-globe",
"label": "Website Settings",
"link_to": "Website Settings",
"type": "DocType"
}
],
"shortcuts_label": "Settings"
}

+ 0
- 59
frappe/core/desk_page/users/users.json View File

@@ -1,59 +0,0 @@
{
"cards": [
{
"hidden": 0,
"label": "Users",
"links": "[\n {\n \"description\": \"System and Website Users\",\n \"label\": \"User\",\n \"name\": \"User\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"User Roles\",\n \"label\": \"Role\",\n \"name\": \"Role\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Role Profile\",\n \"label\": \"Role Profile\",\n \"name\": \"Role Profile\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Logs",
"links": "[\n {\n \"description\": \"Activity Log by \",\n \"label\": \"Activity Log\",\n \"name\": \"Activity Log\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"View Log of all print, download and export events\",\n \"label\": \"Access Log\",\n \"name\": \"Access Log\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Permissions",
"links": "[\n {\n \"description\": \"Set Permissions on Document Types and Roles\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"Role Permissions Manager\",\n \"name\": \"permission-manager\",\n \"type\": \"page\"\n },\n {\n \"description\": \"Restrict user for specific document\",\n \"icon\": \"fa fa-lock\",\n \"label\": \"User Permissions\",\n \"name\": \"User Permission\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Set custom roles for page and report\",\n \"label\": \"Role Permission for Page and Report\",\n \"name\": \"Role Permission for Page and Report\",\n \"type\": \"doctype\"\n },\n {\n \"dependencies\": [\n \"User\"\n ],\n \"description\": \"Check which Documents are readable by a User\",\n \"doctype\": \"User\",\n \"icon\": \"fa fa-eye-open\",\n \"is_query_report\": true,\n \"label\": \"Permitted Documents For User\",\n \"name\": \"Permitted Documents For User\",\n \"type\": \"report\"\n },\n {\n \"dependencies\": [\n \"DocShare\"\n ],\n \"description\": \"Report of all document shares\",\n \"doctype\": \"DocShare\",\n \"icon\": \"fa fa-share\",\n \"label\": \"Document Share Report\",\n \"name\": \"Document Share Report\",\n \"type\": \"report\"\n }\n]"
}
],
"category": "Administration",
"charts": [],
"creation": "2020-03-02 15:12:16.754449",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"idx": 0,
"is_standard": 1,
"label": "Users",
"modified": "2020-04-26 22:36:14.311554",
"modified_by": "Administrator",
"module": "Core",
"name": "Users",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"label": "User",
"link_to": "User",
"type": "DocType"
},
{
"label": "Role",
"link_to": "Role",
"type": "DocType"
},
{
"label": "Permission Manager",
"link_to": "permission-manager",
"type": "Page"
},
{
"label": "User Profile",
"link_to": "user-profile",
"type": "Page"
}
]
}

+ 22
- 7
frappe/core/doctype/comment/comment.py View File

@@ -18,10 +18,7 @@ from frappe.exceptions import ImplicitCommitError
class Comment(Document):
def after_insert(self):
self.notify_mentions()

frappe.publish_realtime('new_communication', self.as_dict(),
doctype=self.reference_doctype, docname=self.reference_name,
after_commit=True)
self.notify_change('add')

def validate(self):
if not self.comment_email:
@@ -30,12 +27,30 @@ class Comment(Document):

def on_update(self):
update_comment_in_doc(self)
if self.is_new():
self.notify_change('update')

def on_trash(self):
self.remove_comment_from_cache()
frappe.publish_realtime('delete_communication', self.as_dict(),
doctype= self.reference_doctype, docname = self.reference_name,
after_commit=True)
self.notify_change('delete')

def notify_change(self, action):
key_map = {
'Like': 'like_logs',
'Assigned': 'assignment_logs',
'Assignment Completed': 'assignment_logs',
'Comment': 'comments',
'Attachment': 'attachment_logs',
'Attachment Removed': 'attachment_logs',
}
key = key_map.get(self.comment_type)
if not key: return

frappe.publish_realtime('update_docinfo_for_{}_{}'.format(self.reference_doctype, self.reference_name), {
'doc': self.as_dict(),
'key': key,
'action': action
}, after_commit=True)

def remove_comment_from_cache(self):
_comments = get_comments_from_parent(self)


+ 5
- 4
frappe/core/doctype/communication/communication.js View File

@@ -99,8 +99,7 @@ frappe.ui.form.on("Communication", {
}
},

show_relink_dialog: function(frm){
var lib = "frappe.email";
show_relink_dialog: function(frm) {
var d = new frappe.ui.Dialog ({
title: __("Relink Communication"),
fields: [{
@@ -138,8 +137,10 @@ frappe.ui.form.on("Communication", {
}
});
},
function () {
frappe.show_alert('Document not Relinked')
function() {
frappe.show_alert({
message: __('Document not Relinked'), 'indicator': 'info'
});
}
);
}


+ 10
- 11
frappe/core/doctype/communication/communication.py View File

@@ -99,10 +99,7 @@ class Communication(Document):
frappe.db.set_value("Communication", self.reference_name, "status", "Replied")

if self.communication_type == "Communication":
# send new comment to listening clients
frappe.publish_realtime('new_communication', self.as_dict(),
doctype=self.reference_doctype, docname=self.reference_name,
after_commit=True)
self.notify_change('add')

elif self.communication_type in ("Chat", "Notification", "Bot"):
if self.reference_name == frappe.session.user:
@@ -125,10 +122,14 @@ class Communication(Document):

def on_trash(self):
if self.communication_type == "Communication":
# send delete comment to listening clients
frappe.publish_realtime('delete_communication', self.as_dict(),
doctype= self.reference_doctype, docname = self.reference_name,
after_commit=True)
self.notify_change('delete')

def notify_change(self, action):
frappe.publish_realtime('update_docinfo_for_{}_{}'.format(self.reference_doctype, self.reference_name), {
'doc': self.as_dict(),
'key': 'communications',
'action': action
}, after_commit=True)

def set_status(self):
if not self.is_new():
@@ -244,9 +245,7 @@ class Communication(Document):

if delivery_status:
self.db_set('delivery_status', delivery_status)

frappe.publish_realtime('update_communication', self.as_dict(),
doctype=self.reference_doctype, docname=self.reference_name, after_commit=True)
self.notify_change('update')

# for list views and forms
self.notify_update()


+ 1
- 1
frappe/core/doctype/data_import_legacy/data_import_legacy.js View File

@@ -32,7 +32,7 @@ frappe.ui.form.on('Data Import Legacy', {
frm.reload_doc();
}
if (data.progress) {
let progress_bar = $(frm.dashboard.progress_area).find(".progress-bar");
let progress_bar = $(frm.dashboard.progress_area.body).find(".progress-bar");
if (progress_bar) {
$(progress_bar).removeClass("progress-bar-danger").addClass("progress-bar-success progress-bar-striped");
$(progress_bar).css("width", data.progress + "%");


+ 6
- 5
frappe/core/doctype/deleted_document/deleted_document_list.js View File

@@ -9,15 +9,16 @@ frappe.listview_settings["Deleted Document"] = {
args: { docnames },
callback: function (r) {
if (r.message) {
function body(docnames) {
let body = (docnames) => {
const html = docnames.map(docname => {
return `<li><a href='/desk#Form/Deleted Document/${docname}'>${docname}</a></li>`;
return `<li><a href='/app/deleted-document/${docname}'>${docname}</a></li>`;
});
return "<br><ul>" + html.join("");
}
function message(title, docnames) {
};

let message = (title, docnames) => {
return (docnames.length > 0) ? title + body(docnames) + "</ul>": "";
}
};

const { restored, invalid, failed } = r.message;
const restored_summary = message(__("Documents restored successfully"), restored);


+ 2
- 2
frappe/core/doctype/doctype/doctype.js View File

@@ -24,11 +24,11 @@ frappe.ui.form.on('DocType', {
if (!frm.is_new() && !frm.doc.istable) {
if (frm.doc.issingle) {
frm.add_custom_button(__('Go to {0}', [frm.doc.name]), () => {
frappe.set_route('Form', frm.doc.name);
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
});
} else {
frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => {
frappe.set_route('List', frm.doc.name, 'List');
window.open(`/app/${frappe.router.slug(frm.doc.name)}`);
});
}
}


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

@@ -132,7 +132,7 @@
"label": "Editable Grid"
},
{
"default": "1",
"default": "0",
"depends_on": "eval:!doc.istable && !doc.issingle",
"description": "Open a dialog with mandatory fields to create a new record quickly",
"fieldname": "quick_entry",
@@ -427,7 +427,7 @@
"label": "Allow Guest to View"
},
{
"depends_on": "has_web_view",
"depends_on": "eval:!doc.istable",
"fieldname": "route",
"fieldtype": "Data",
"label": "Route"
@@ -609,7 +609,7 @@
"link_fieldname": "reference_doctype"
}
],
"modified": "2020-09-24 13:13:58.227153",
"modified": "2020-12-10 15:10:09.227205",
"modified_by": "Administrator",
"module": "Core",
"name": "DocType",
@@ -637,6 +637,7 @@
"write": 1
}
],
"route": "doctype",
"search_fields": "module",
"show_name_in_global_search": 1,
"sort_field": "modified",


+ 35
- 48
frappe/core/doctype/doctype/doctype.py View File

@@ -26,7 +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
@@ -63,15 +63,7 @@ class DocType(Document):

self.validate_name()

if self.issingle:
self.allow_import = 0
self.is_submittable = 0
self.istable = 0

elif self.istable:
self.allow_import = 0
self.permissions = []

self.set_defaults_for_single_and_table()
self.scrub_field_names()
self.set_default_in_list_view()
self.set_default_translatable()
@@ -79,22 +71,17 @@ class DocType(Document):
self.validate_document_type()
validate_fields(self)

if self.istable:
# no permission records for child table
self.permissions = []
else:
if not self.istable:
validate_permissions(self)

self.make_amendable()
self.make_repeatable()
self.validate_nestedset()
self.validate_website()
self.validate_links_table_fieldnames()
validate_links_table_fieldnames(self)

if not self.is_new():
self.before_update = frappe.get_doc('DocType', self.name)

if not self.is_new():
self.setup_fields_to_fetch()

check_email_append_to(self)
@@ -102,14 +89,20 @@ class DocType(Document):
if self.default_print_format and not self.custom:
frappe.throw(_('Standard DocType cannot have default print format, use Customize Form'))

if frappe.conf.get('developer_mode'):
self.owner = 'Administrator'
self.modified_by = 'Administrator'

def after_insert(self):
# clear user cache so that on the next reload this doctype is included in boot
clear_user_cache(frappe.session.user)

def set_defaults_for_single_and_table(self):
if self.issingle:
self.allow_import = 0
self.is_submittable = 0
self.istable = 0

elif self.istable:
self.allow_import = 0
self.permissions = []

def set_default_in_list_view(self):
'''Set default in-list-view for first 4 mandatory fields'''
if not [d.fieldname for d in self.fields if d.in_list_view]:
@@ -134,6 +127,10 @@ class DocType(Document):
if not frappe.conf.get("developer_mode") and not self.custom:
frappe.throw(_("Not in Developer Mode! Set in site_config.json or make 'Custom' DocType."), CannotCreateStandardDoctypeError)

if frappe.conf.get('developer_mode'):
self.owner = 'Administrator'
self.modified_by = 'Administrator'

def setup_fields_to_fetch(self):
'''Setup query to update values for newly set fetch values'''
try:
@@ -192,6 +189,9 @@ class DocType(Document):

def validate_website(self):
"""Ensure that website generator has field 'route'"""
if self.route:
self.route = self.route.strip('/')

if self.has_web_view:
# route field must be present
if not 'route' in [d.fieldname for d in self.fields]:
@@ -278,7 +278,6 @@ class DocType(Document):

def on_update(self):
"""Update database schema, make controller templates if `custom` is not set and clear cache."""
self.delete_duplicate_custom_fields()
try:
frappe.db.updatedb(self.name, Meta(self))
except Exception as e:
@@ -323,18 +322,6 @@ class DocType(Document):

clear_linked_doctype_cache()

def delete_duplicate_custom_fields(self):
if not (frappe.db.table_exists(self.name) and frappe.db.table_exists("Custom Field")):
return

fields = [d.fieldname for d in self.fields if d.fieldtype in data_fieldtypes]
if fields:
frappe.db.sql('''delete from
`tabCustom Field`
where
dt = {0} and fieldname in ({1})
'''.format('%s', ', '.join(['%s'] * len(fields))), tuple([self.name] + fields), as_dict=True)

def sync_global_search(self):
'''If global search settings are changed, rebuild search properties for this table'''
global_search_fields_before_update = [d.fieldname for d in
@@ -677,24 +664,24 @@ 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)

def validate_links_table_fieldnames(self):
"""Validate fieldnames in Links table"""
if frappe.flags.in_patch: return
if frappe.flags.in_fixtures: return
if not self.links: return

for index, link in enumerate(self.links):
meta = frappe.get_meta(link.link_doctype)
if not meta.get_field(link.link_fieldname):
message = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname"))
validate_route_conflict(self.doctype, self.name)

def validate_links_table_fieldnames(meta):
"""Validate fieldnames in Links table"""
if frappe.flags.in_patch: return
if frappe.flags.in_fixtures: return
if not meta.links: return

for index, link in enumerate(meta.links):
link_meta = frappe.get_meta(link.link_doctype)
if not link_meta.get_field(link.link_fieldname):
message = _("Row #{0}: Could not find field {1} in {2} DocType").format(index+1, frappe.bold(link.link_fieldname), frappe.bold(link.link_doctype))
frappe.throw(message, InvalidFieldNameError, _("Invalid Fieldname"))

def validate_fields_for_doctype(doctype):
doc = frappe.get_doc("DocType", doctype)
doc.delete_duplicate_custom_fields()
validate_fields(frappe.get_meta(doctype, cached=False))
meta = frappe.get_meta(doctype, cached=False)
validate_links_table_fieldnames(meta)
validate_fields(meta)

# this is separate because it is also called via custom field
def validate_fields(meta):


+ 7
- 0
frappe/core/doctype/doctype/patches/set_route.py View File

@@ -0,0 +1,7 @@
import frappe
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', slug(doctype.name), update_modified = False)

+ 11
- 6
frappe/core/doctype/doctype/test_doctype.py View File

@@ -5,12 +5,17 @@ from __future__ import unicode_literals

import frappe
import unittest
from frappe.core.doctype.doctype.doctype import UniqueFieldnameError, IllegalMandatoryError, DoctypeLinkError, WrongOptionsDoctypeLinkError,\
HiddenAndMandatoryWithoutDefaultError, CannotIndexedError, InvalidFieldNameError, CannotCreateStandardDoctypeError
from frappe.core.doctype.doctype.doctype import (UniqueFieldnameError,
IllegalMandatoryError,
DoctypeLinkError,
WrongOptionsDoctypeLinkError,
HiddenAndMandatoryWithoutDefaultError,
CannotIndexedError,
InvalidFieldNameError,
validate_links_table_fieldnames)

# test_records = frappe.get_test_records('DocType')


class TestDocType(unittest.TestCase):
def test_validate_name(self):
self.assertRaises(frappe.NameError, new_doctype("_Some DocType").insert)
@@ -459,7 +464,7 @@ class TestDocType(unittest.TestCase):
'link_doctype': "User",
'link_fieldname': "first_name"
})
doc.validate_links_table_fieldnames() # no error
validate_links_table_fieldnames(doc) # no error
doc.links = [] # reset links table

# check invalid doctype
@@ -467,7 +472,7 @@ class TestDocType(unittest.TestCase):
'link_doctype': "User2",
'link_fieldname': "first_name"
})
self.assertRaises(frappe.DoesNotExistError, doc.validate_links_table_fieldnames)
self.assertRaises(frappe.DoesNotExistError, validate_links_table_fieldnames, doc)
doc.links = [] # reset links table

# check invalid fieldname
@@ -475,7 +480,7 @@ class TestDocType(unittest.TestCase):
'link_doctype': "User",
'link_fieldname': "a_field_that_does_not_exists"
})
self.assertRaises(InvalidFieldNameError, doc.validate_links_table_fieldnames)
self.assertRaises(InvalidFieldNameError, validate_links_table_fieldnames, doc)


def new_doctype(name, unique=0, depends_on='', fields=None):


+ 32
- 9
frappe/core/doctype/file/file.py View File

@@ -456,7 +456,7 @@ class File(Document):
def save_file(self, content=None, decode=False, ignore_existing_file_check=False):
file_exists = False
self.content = content
if decode:
if isinstance(content, text_type):
self.content = content.encode("utf-8")
@@ -467,19 +467,19 @@ class File(Document):

if not self.is_private:
self.is_private = 0
self.content_type = mimetypes.guess_type(self.file_name)[0]
self.file_size = self.check_max_file_size()
if (
self.content_type and "image" in self.content_type
and frappe.get_system_settings("strip_exif_metadata_from_uploaded_images")
):
self.content = strip_exif_data(self.content, self.content_type)
self.content = strip_exif_data(self.content, self.content_type)

self.content_hash = get_content_hash(self.content)
duplicate_file = None

# check if a file exists with the same content hash and is also in the same folder (public or private)
@@ -940,10 +940,33 @@ def validate_filename(filename):
return fname

@frappe.whitelist()
def get_files_in_folder(folder):
return frappe.db.get_all('File',
def get_files_in_folder(folder, start=0, page_length=20):
start = cint(start)
page_length = cint(page_length)

files = frappe.db.get_all('File',
{ 'folder': folder },
['name', 'file_name', 'file_url', 'is_folder', 'modified']
['name', 'file_name', 'file_url', 'is_folder', 'modified'],
start=start,
page_length=page_length + 1
)
return {
'files': files[:page_length],
'has_more': len(files) > page_length
}

@frappe.whitelist()
def get_files_by_search_text(text):
if not text:
return []

text = '%' + cstr(text).lower() + '%'
return frappe.db.get_all('File',
fields=['name', 'file_name', 'file_url', 'is_folder', 'modified'],
filters={'is_folder': False},
or_filters={'file_name': ('like', text), 'file_url': text, 'name': ('like', text)},
order_by='modified desc',
limit=20
)

def update_existing_file_docs(doc):


+ 1
- 1
frappe/core/doctype/log_settings/log_settings.py View File

@@ -36,7 +36,7 @@ def has_unseen_error_log(user):
def _get_response(show_alert=True):
return {
'show_alert': True,
'message': _("You have unseen {0}").format('<a href="/desk#List/Error%20Log/List"> Error Logs </a>')
'message': _("You have unseen {0}").format('<a href="/app/List/Error%20Log/List"> Error Logs </a>')
}

if frappe.db.sql_list("select name from `tabError Log` where seen = 0 limit 1"):


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

@@ -51,7 +51,7 @@
"link_fieldname": "module"
},
{
"link_doctype": "Desk Page",
"link_doctype": "Workspace",
"link_fieldname": "module"
}
],


+ 1
- 1
frappe/core/doctype/navbar_settings/navbar_settings.py View File

@@ -23,7 +23,7 @@ class NavbarSettings(Document):
if not frappe.flags.in_patch and (len(before_save_items) > len(after_save_items)):
frappe.throw(_("Please hide the standard navbar items instead of deleting them"))

@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def get_app_logo():
app_logo = frappe.db.get_single_value('Navbar Settings', 'app_logo')
if not app_logo:


+ 3
- 0
frappe/core/doctype/page/page.py 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,6 +34,8 @@ class Page(Document):
self.name += '-' + str(cnt)

def validate(self):
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 View File

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

+ 10
- 0
frappe/core/doctype/role/patches/v13_set_default_desk_properties.py View File

@@ -0,0 +1,10 @@
import frappe
from ..role import desk_properties

def execute():
frappe.reload_doctype('role')
for role in frappe.get_all('Role', ['name', 'desk_access']):
role_doc = frappe.get_doc('Role', role.name)
for key in desk_properties:
role_doc.set(key, role_doc.desk_access)
role_doc.save()

+ 84
- 2
frappe/core/doctype/role/role.json View File

@@ -13,7 +13,19 @@
"column_break_4",
"disabled",
"desk_access",
"two_factor_auth"
"two_factor_auth",
"navigation_settings_section",
"search_bar",
"notifications",
"chat",
"list_settings_section",
"list_sidebar",
"bulk_actions",
"view_switcher",
"form_settings_section",
"form_sidebar",
"timeline",
"dashboard"
],
"fields": [
{
@@ -60,12 +72,82 @@
{
"fieldname": "column_break_4",
"fieldtype": "Column Break"
},
{
"fieldname": "navigation_settings_section",
"fieldtype": "Section Break",
"label": "Navigation Settings"
},
{
"default": "1",
"fieldname": "search_bar",
"fieldtype": "Check",
"label": "Search Bar"
},
{
"default": "1",
"fieldname": "chat",
"fieldtype": "Check",
"label": "Chat"
},
{
"fieldname": "list_settings_section",
"fieldtype": "Section Break",
"label": "List Settings"
},
{
"default": "1",
"fieldname": "list_sidebar",
"fieldtype": "Check",
"label": "Sidebar"
},
{
"default": "1",
"fieldname": "bulk_actions",
"fieldtype": "Check",
"label": "Bulk Actions"
},
{
"fieldname": "form_settings_section",
"fieldtype": "Section Break",
"label": "Form Settings"
},
{
"default": "1",
"fieldname": "form_sidebar",
"fieldtype": "Check",
"label": "Sidebar"
},
{
"default": "1",
"fieldname": "timeline",
"fieldtype": "Check",
"label": "Timeline"
},
{
"default": "1",
"fieldname": "dashboard",
"fieldtype": "Check",
"label": "Dashboard"
},
{
"default": "1",
"fieldname": "view_switcher",
"fieldtype": "Check",
"label": "View Switcher"
},
{
"default": "1",
"fieldname": "notifications",
"fieldtype": "Check",
"label": "Notifications"
}
],
"icon": "fa fa-bookmark",
"idx": 1,
"index_web_pages_for_search": 1,
"links": [],
"modified": "2020-08-06 15:42:59.036960",
"modified": "2020-12-03 14:08:38.181035",
"modified_by": "Administrator",
"module": "Core",
"name": "Role",


+ 25
- 5
frappe/core/doctype/role/role.py View File

@@ -6,6 +6,9 @@ import frappe

from frappe.model.document import Document

desk_properties = ("search_bar", "notifications", "chat", "list_sidebar",
"bulk_actions", "view_switcher", "form_sidebar", "timeline", "dashboard")

class Role(Document):
def before_rename(self, old, new, merge=False):
if old in ("Guest", "Administrator", "System Manager", "All"):
@@ -16,11 +19,28 @@ class Role(Document):

def validate(self):
if self.disabled:
if self.name in ("Guest", "Administrator", "System Manager", "All"):
frappe.throw(frappe._("Standard roles cannot be disabled"))
else:
frappe.db.sql("delete from `tabHas Role` where role = %s", self.name)
frappe.clear_cache()
self.disable_role()
else:
self.set_desk_properties()

def disable_role(self):
if self.name in ("Guest", "Administrator", "System Manager", "All"):
frappe.throw(frappe._("Standard roles cannot be disabled"))
else:
self.remove_roles()

def set_desk_properties(self):
# set if desk_access is not allowed, unset all desk properties
if self.name == 'Guest':
self.desk_access = 0
if not self.desk_access:
for key in desk_properties:
self.set(key, 0)

def remove_roles(self):
frappe.db.sql("delete from `tabHas Role` where role = %s", self.name)
frappe.clear_cache()

def on_update(self):
'''update system user desk access if this has changed in this update'''


+ 14
- 14
frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js View File

@@ -3,32 +3,32 @@

frappe.ui.form.on('Role Permission for Page and Report', {
setup: function(frm) {
frm.trigger("set_queries")
frm.trigger("set_queries");
},

refresh: function(frm) {
frm.disable_save();
frm.role_area.hide();
frm.events.add_custom_buttons(frm);
frm.events.setup_buttons(frm);
},

add_custom_buttons: function(frm) {
setup_buttons: function(frm) {
frm.clear_custom_buttons();
if(frm.doc.set_role_for && frm.doc[frappe.model.scrub(frm.doc.set_role_for)]) {
frm.page.clear_actions();
if (frm.doc.set_role_for && frm.doc[frappe.model.scrub(frm.doc.set_role_for)]) {
frm.add_custom_button(__("Reset to defaults"), function() {
frm.trigger("reset_roles");
});

frm.add_custom_button(__("Update"), function() {
frm.page.set_primary_action(__("Update"), () => {
frm.trigger("update_report_page_data");
}).addClass('btn-primary');
});
}
},

onload: function(frm) {
if(!frm.roles_editor) {
frm.role_area = $('<div style="min-height: 300px">')
.appendTo(frm.fields_dict.roles_html.wrapper);
if (!frm.roles_editor) {
frm.role_area = $(frm.fields_dict.roles_html.wrapper);
frm.roles_editor = new frappe.RoleEditor(frm.role_area, frm);
}
},
@@ -54,17 +54,17 @@ frappe.ui.form.on('Role Permission for Page and Report', {
},

page: function(frm) {
frm.events.add_custom_buttons(frm);
if(frm.doc.page) {
frm.events.setup_buttons(frm);
if (frm.doc.page) {
frm.trigger("set_report_page_data");
} else {
frm.trigger("set_role_for");
}
},

report: function(frm){
frm.events.add_custom_buttons(frm);
if(frm.doc.report) {
report: function(frm) {
frm.events.setup_buttons(frm);
if (frm.doc.report) {
frm.trigger("set_report_page_data");
} else {
frm.trigger("set_role_for");


+ 6
- 8
frappe/core/doctype/role_profile/role_profile.js View File

@@ -3,20 +3,18 @@

frappe.ui.form.on('Role Profile', {
refresh: function(frm) {
if(has_common(frappe.user_roles, ["Administrator", "System Manager"])) {
if(!frm.roles_editor) {
var role_area = $('<div style="min-height: 300px">')
.appendTo(frm.fields_dict.roles_html.wrapper);
if (has_common(frappe.user_roles, ["Administrator", "System Manager"])) {
if (!frm.roles_editor) {
const role_area = $(frm.fields_dict.roles_html.wrapper);
frm.roles_editor = new frappe.RoleEditor(role_area, frm);
frm.roles_editor.show();
} else {
frm.roles_editor.show();
}
frm.roles_editor.show();

}
},

validate: function(frm) {
if(frm.roles_editor) {
if (frm.roles_editor) {
frm.roles_editor.set_roles_in_table();
}
}


+ 1
- 1
frappe/core/doctype/server_script/test_server_script.py View File

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

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


+ 15
- 1
frappe/core/doctype/system_settings/system_settings.json View File

@@ -6,6 +6,7 @@
"engine": "InnoDB",
"field_order": [
"localization",
"app_name",
"country",
"language",
"column_break_3",
@@ -462,6 +463,19 @@
"fieldtype": "Section Break",
"label": "Prepared Report"
},
{
"default": "Frappe",
"fieldname": "app_name",
"fieldtype": "Data",
"label": "App Name"
},
{
"default": "3",
"description": "Hourly rate limit for generating password reset links",
"fieldname": "password_reset_limit",
"fieldtype": "Int",
"label": "Password Reset Link Generation Limit"
},
{
"default": "1",
"fieldname": "strip_exif_metadata_from_uploaded_images",
@@ -472,7 +486,7 @@
"icon": "fa fa-cog",
"issingle": 1,
"links": [],
"modified": "2020-11-30 18:52:22.161391",
"modified": "2020-12-30 18:52:22.161391",
"modified_by": "Administrator",
"module": "Core",
"name": "System Settings",


+ 6
- 1
frappe/core/doctype/user/test_user.py View File

@@ -20,6 +20,7 @@ class TestUser(unittest.TestCase):
frappe.db.set_value("System Settings", "System Settings", "enable_password_policy", 0)
frappe.db.set_value("System Settings", "System Settings", "minimum_password_score", "")
frappe.db.set_value("System Settings", "System Settings", "password_reset_limit", 3)
frappe.set_user('Administrator')

def test_user_type(self):
new_user = frappe.get_doc(dict(doctype='User', email='test-for-type@example.com',
@@ -106,13 +107,17 @@ class TestUser(unittest.TestCase):
frappe.set_user("testperm@example.com")

me = frappe.get_doc("User", "testperm@example.com")
self.assertRaises(frappe.PermissionError, me.add_roles, "System Manager")
me.add_roles("System Manager")

# system manager is not added (it is reset)
self.assertFalse('System Manager' in [d.role for d in me.roles])

frappe.set_user("Administrator")

me = frappe.get_doc("User", "testperm@example.com")
me.add_roles("System Manager")

# system manager now added by Administrator
self.assertTrue("System Manager" in [d.role for d in me.get("roles")])

# def test_deny_multiple_sessions(self):


+ 0
- 28
frappe/core/doctype/user/user.css View File

@@ -1,28 +0,0 @@
.user-role {
padding: 5px;
width: 50%;
float: left;
}

table.user-perm {
border-collapse: collapse;
width: 100%;
overflow-x: scroll;
-webkit-overflow-scrolling: touch;
-ms-overflow-style: -ms-autohiding-scrollbar;
}

table.user-perm td, table.user-perm th {
padding: 5px;
text-align: center;
border-bottom: 1px solid #aaa;
min-width: 30px;
}

.module-block-list .checkbox {
margin-bottom: 0px;
}

.module-block-list .checkbox label {
width: 100%;
}

+ 5
- 5
frappe/core/doctype/user/user.js View File

@@ -27,7 +27,7 @@ frappe.ui.form.on('User', {
},
callback: function(data) {
frm.set_value("roles", []);
$.each(data.message || [], function(i, v){
$.each(data.message || [], function(i, v) {
var d = frm.add_child("roles");
d.role = v.role;
});
@@ -59,13 +59,13 @@ frappe.ui.form.on('User', {
onload: function(frm) {
frm.can_edit_roles = has_access_to_edit_user();

if(frm.can_edit_roles && !frm.is_new()) {
if(!frm.roles_editor) {
var role_area = $('<div style="min-height: 300px">')
if (frm.can_edit_roles && !frm.is_new()) {
if (!frm.roles_editor) {
const role_area = $('<div class="role-editor">')
.appendTo(frm.fields_dict.roles_html.wrapper);
frm.roles_editor = new frappe.RoleEditor(role_area, frm, frm.doc.role_profile_name ? 1 : 0);

var module_area = $('<div style="min-height: 300px">')
var module_area = $('<div>')
.appendTo(frm.fields_dict.modules_html.wrapper);
frm.module_editor = new frappe.ModuleEditor(frm, module_area);
} else {


+ 42
- 23
frappe/core/doctype/user/user.json View File

@@ -7,20 +7,20 @@
"doctype": "DocType",
"engine": "InnoDB",
"field_order": [
"sb0_5",
"enabled",
"section_break_3",
"email",
"first_name",
"middle_name",
"last_name",
"language",
"column_break0",
"first_name",
"full_name",
"time_zone",
"column_break_11",
"middle_name",
"username",
"send_welcome_email",
"unsubscribed",
"column_break0",
"username",
"language",
"time_zone",
"user_image",
"sb1",
"role_profile_name",
@@ -28,15 +28,17 @@
"roles",
"short_bio",
"gender",
"phone",
"mobile_no",
"birth_date",
"location",
"banner_image",
"column_break_22",
"interest",
"banner_image",
"desk_theme",
"column_break_26",
"phone",
"location",
"bio",
"mute_sounds",
"column_break_22",
"mobile_no",
"change_password",
"new_password",
"logout_all_sessions",
@@ -47,10 +49,10 @@
"document_follow_notify",
"document_follow_frequency",
"email_settings",
"email_signature",
"thread_notify",
"send_me_a_copy",
"allowed_in_mentions",
"email_signature",
"user_emails",
"sb_allow_modules",
"module_profile",
@@ -61,15 +63,16 @@
"defaults",
"sb3",
"simultaneous_sessions",
"user_type",
"restrict_ip",
"last_ip",
"column_break1",
"login_after",
"user_type",
"last_active",
"section_break_63",
"login_before",
"restrict_ip",
"bypass_restrict_ip_check_if_2fa_enabled",
"column_break1",
"last_login",
"last_ip",
"last_active",
"last_known_versions",
"third_party_authentication",
"social_logins",
@@ -80,10 +83,6 @@
"api_secret"
],
"fields": [
{
"fieldname": "sb0_5",
"fieldtype": "Section Break"
},
{
"default": "1",
"fieldname": "enabled",
@@ -96,7 +95,8 @@
{
"depends_on": "enabled",
"fieldname": "section_break_3",
"fieldtype": "Section Break"
"fieldtype": "Section Break",
"label": "Basic Info"
},
{
"fieldname": "email",
@@ -578,6 +578,24 @@
"label": "API Secret",
"read_only": 1
},
{
"fieldname": "column_break_11",
"fieldtype": "Column Break"
},
{
"fieldname": "column_break_26",
"fieldtype": "Column Break"
},
{
"fieldname": "section_break_63",
"fieldtype": "Column Break"
},
{
"fieldname": "desk_theme",
"fieldtype": "Select",
"label": "Desk Theme",
"options": "Light\nDark"
},
{
"fieldname": "module_profile",
"fieldtype": "Link",
@@ -679,6 +697,7 @@
}
],
"quick_entry": 1,
"route": "user",
"search_fields": "full_name",
"show_name_in_global_search": 1,
"sort_field": "modified",


+ 27
- 21
frappe/core/doctype/user/user.py View File

@@ -197,20 +197,17 @@ class User(Document):


def share_with_self(self):
if self.user_type=="System User":
frappe.share.add(self.doctype, self.name, self.name, write=1, share=1,
flags={"ignore_share_permission": True})
else:
frappe.share.remove(self.doctype, self.name, self.name,
flags={"ignore_share_permission": True, "ignore_permissions": True})
frappe.share.add(self.doctype, self.name, self.name, write=1, share=1,
flags={"ignore_share_permission": True})

def validate_share(self, docshare):
if docshare.user == self.name:
if self.user_type=="System User":
if docshare.share != 1:
frappe.throw(_("Sorry! User should have complete access to their own record."))
else:
frappe.throw(_("Sorry! Sharing with Website User is prohibited."))
pass
# if docshare.user == self.name:
# if self.user_type=="System User":
# if docshare.share != 1:
# frappe.throw(_("Sorry! User should have complete access to their own record."))
# else:
# frappe.throw(_("Sorry! Sharing with Website User is prohibited."))

def send_password_notification(self, new_password):
try:
@@ -302,16 +299,16 @@ class User(Document):
from frappe.utils.user import get_user_fullname
from frappe.utils import get_url

full_name = get_user_fullname(frappe.session['user'])
if full_name == "Guest":
full_name = "Administrator"
created_by = get_user_fullname(frappe.session['user'])
if created_by == "Guest":
created_by = "Administrator"

args = {
'first_name': self.first_name or self.last_name or "user",
'user': self.name,
'title': subject,
'login_url': get_url(),
'user_fullname': full_name
'created_by': created_by
}

args.update(add_args)
@@ -595,7 +592,7 @@ def update_password(new_password, logout_all_sessions=0, key=None, old_password=
frappe.db.set_value("User", user, "reset_password_key", "")

if user_doc.user_type == "System User":
return "/desk"
return "/app"
else:
return redirect_url if redirect_url else "/"

@@ -1016,9 +1013,14 @@ def send_token_via_email(tmp_id,token=None):
hotp = pyotp.HOTP(otpsecret)

frappe.sendmail(
recipients=user_email, sender=None, subject='Verification Code',
message='<p>Your verification code is {0}</p>'.format(hotp.at(int(count))),
delayed=False, retry=3)
recipients=user_email,
sender=None,
subject="Verification Code",
template="verification_code",
args=dict(code=hotp.at(int(count))),
delayed=False,
retry=3
)

return True

@@ -1117,7 +1119,6 @@ def create_contact(user, ignore_links=False, ignore_mandatory=False):

contact.save(ignore_permissions=True)


@frappe.whitelist()
def generate_keys(user):
"""
@@ -1138,6 +1139,11 @@ def generate_keys(user):
return {"api_secret": api_secret}
frappe.throw(frappe._("Not Permitted"), frappe.PermissionError)

@frappe.whitelist()
def switch_theme(theme):
if theme in ["Dark", "Light"]:
frappe.db.set_value("User", frappe.session.user, "desk_theme", theme)

def update_password_reset_limit(user):
generated_link_count = get_generated_link_count(user)
generated_link_count += 1


+ 2
- 2
frappe/core/doctype/user_permission/user_permission.py View File

@@ -56,7 +56,7 @@ class UserPermission(Document):
ref_link = frappe.get_desk_link(self.doctype, overlap_exists[0].name)
frappe.throw(_("{0} has already assigned default value for {1}.").format(ref_link, self.allow))

@frappe.whitelist()
@frappe.whitelist(allow_guest=True)
def get_user_permissions(user=None):
'''Get all users permissions for the user as a dict of doctype'''
# if this is called from client-side,
@@ -67,7 +67,7 @@ def get_user_permissions(user=None):
if not user:
user = frappe.session.user

if not user or user == "Administrator":
if not user or user in ("Administrator", "Guest"):
return {}

cached_user_permissions = frappe.cache().hget("user_permissions", user)


+ 1
- 1
frappe/core/doctype/user_permission/user_permission_list.js View File

@@ -163,7 +163,7 @@ frappe.listview_settings['User Permission'] = {
}
frappe.show_alert({
message,
indicator: 'green'
indicator: 'info'
});
list_view.refresh();
});


+ 60
- 0
frappe/core/page/background_jobs/background_jobs.css View File

@@ -0,0 +1,60 @@
.list-jobs {
font-size: var(--text-base);
}

.table {
margin-bottom: 0px;
margin-top: 0px;
}

thead {
background-color: var(--control-bg);
border-radius: var(--border-radius-sm);
}

thead > tr {
border-radius: var(--border-radius-sm);
}

thead > tr > th:first-child {
border-radius: var(--border-radius-sm) 0 0 var(--border-radius-sm);
}
thead > tr > th:last-child {
border-radius: 0 var(--border-radius-sm) var(--border-radius-sm) 0;
}

.worker-name {
display: flex;
align-items: center;
}

.job-name {
font-size: var(--text-md);
font-family: "Courier New", Courier, monospace;
/* background-color: var(--control-bg); */
/* padding: var(--padding-xs) var(--padding-sm); */
/* border-radius: var(--border-radius-md); */
}

.background-job-row:hover {
background-color: var(--bg-color);
}

.no-background-jobs {
min-height: 320px;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
}

.no-background-jobs > img {
margin-bottom: var(--margin-md);
max-height: 100px;
}

.footer {
align-items: flex-end;
margin-top: var(--margin-md);
font-size: var(--text-base);
}

+ 25
- 14
frappe/core/page/background_jobs/background_jobs.html View File

@@ -1,6 +1,6 @@
<div class="list-jobs">
{% if jobs.length %}
<table class="table table-bordered" style="table-layout: fixed;">
<table class="table table-borderless" style="table-layout: fixed;">
<thead>
<tr>
<th style="width: 20%">{{ __("Queue / Worker") }}</th>
@@ -11,30 +11,41 @@
<tbody>
{% for j in jobs %}
<tr>
<td><span class="indicator {{ j.color }}" title="{{ j.status }}">{{ j.queue.split(".").slice(-1)[0] }}</span></td>
<td class="worker-name">
<span class="indicator-pill no-margin {{ j.color }}"></span>
<span class="ml-2">{{ j.queue.split(".").slice(-1)[0] }}</span>
</td>
<td style="overflow: auto;">
<div>
{{ frappe.utils.encode_tags(j.job_name) }}
<span class="job-name">
{{ frappe.utils.encode_tags(j.job_name) }}
</span>
</div>
{% if j.exc_info %}
<div>
<div class="exc_info">
<pre>{{ frappe.utils.encode_tags(j.exc_info) }}</pre>
</div>
{% endif %}
</td>
<td class="small">{{ j.creation }}</td>
<td class="creation">{{ j.creation }}</td>
</tr>
{% endfor %}
</tbody>
</table>
<p>
<span class="indicator blue" style="margin-right: 20px;">{{ __("Started") }}</span>
<span class="indicator orange" style="margin-right: 20px;">{{ __("Queued") }}</span>
<span class="indicator red" style="margin-right: 20px;">{{ __("Failed") }}</span>
<span class="indicator green">{{ __("Finished") }}</span>
</p>
{% else %}
<p class="text-muted">{{ __("No pending or current jobs for this site") }}</p>
<div class="no-background-jobs">
<img src="/assets/frappe/images/ui-states/list-empty-state.svg" alt="Empty State">
<p class="text-muted">{{ __("No pending or current jobs for this site") }}</p>
</div>
{% endif %}
<p class="text-muted" style="margin-top: 30px;">{{ __("Last refreshed") }} {{ frappe.datetime.now_datetime() }}</p>
</div>
<div class="footer row">
<div class="col-md-6 text-muted text-center text-md-left">{{ __("Last refreshed") }}
{{ frappe.datetime.now_datetime(true).toLocaleString() }}</div>
<div class="col-md-6 text-center text-md-right">
<span class="indicator-pill blue" class="mr-2">{{ __("Started") }}</span>
<span class="indicator-pill orange" class="mr-2">{{ __("Queued") }}</span>
<span class="indicator-pill red" class="mr-2">{{ __("Failed") }}</span>
<span class="indicator-pill green">{{ __("Finished") }}</span>
</div>
</div>
</div>

+ 60
- 40
frappe/core/page/background_jobs/background_jobs.js View File

@@ -1,45 +1,65 @@
frappe.pages['background_jobs'].on_page_load = function(wrapper) {
var page = frappe.ui.make_app_page({
parent: wrapper,
title: __('Background Jobs'),
single_column: true
frappe.pages["background_jobs"].on_page_load = (wrapper) => {
const background_job = new BackgroundJobs(wrapper);
$(wrapper).bind('show', () => {
background_job.show();
});

$(frappe.render_template('background_jobs_outer')).appendTo(page.body);
page.content = $(page.body).find('.table-area');
window.background_jobs = background_job;
};

frappe.pages.background_jobs.page = page;
}
class BackgroundJobs {
constructor(wrapper) {
this.page = frappe.ui.make_app_page({
parent: wrapper,
title: __('Background Jobs'),
single_column: true
});

frappe.pages['background_jobs'].on_page_show = function(wrapper) {
frappe.pages.background_jobs.refresh_jobs();
frappe.call({
method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status',
callback: function(r) {
frappe.pages.background_jobs.page.set_indicator(...r.message);
}
});
}

frappe.pages.background_jobs.refresh_jobs = function() {
var page = frappe.pages.background_jobs.page;

// don't call if already waiting for a response
if(page.called) return;
page.called = true;
frappe.call({
method: 'frappe.core.page.background_jobs.background_jobs.get_info',
args: {
show_failed: page.body.find('.show-failed').prop('checked') ? 1 : 0
},
callback: function(r) {
page.called = false;
page.body.find('.list-jobs').remove();
$(frappe.render_template('background_jobs', {jobs:r.message || []})).appendTo(page.content);

if(frappe.get_route()[0]==='background_jobs') {
frappe.background_jobs_timeout = setTimeout(frappe.pages.background_jobs.refresh_jobs, 2000);
this.called = false;
this.show_failed = false;

this.show_failed_button = this.page.add_inner_button(__("Show Failed Jobs"), () => {
this.show_failed = !this.show_failed;
if (this.show_failed_button) {
this.show_failed_button.text(
this.show_failed ? __("Hide Failed Jobs") : __("Show Failed Jobs")
);
}
}
});
}
});

$(frappe.render_template('background_jobs_outer')).appendTo(this.page.body);
this.content = $(this.page.body).find('.table-area');
}

show() {
this.refresh_jobs();
frappe.call({
method: 'frappe.core.page.background_jobs.background_jobs.get_scheduler_status',
callback: res => {
this.page.set_indicator(...res.message);
}
});
}

refresh_jobs() {
if (this.called) return;
this.called = true;

frappe.call({
method: 'frappe.core.page.background_jobs.background_jobs.get_info',
args: {
show_failed: this.show_failed
},
callback: (res) => {
this.called = false;
this.page.body.find('.list-jobs').remove();
$(frappe.render_template('background_jobs', { jobs: res.message || [] })).appendTo(this.content);

if (frappe.get_route()[0] === 'background_jobs') {
setTimeout(() => this.refresh_jobs(), 2000);
}
}
});
}
}

+ 1
- 8
frappe/core/page/background_jobs/background_jobs_outer.html View File

@@ -1,11 +1,4 @@
<div style="padding: 20px;">
<p>
<div class="checkbox">
<label>
<input type="checkbox" class="show-failed"> {{ __("Show failed jobs") }}
</label>
</div>
</p>
<div class="frappe-card">
<div class="table-area">

</div>

frappe/core/page/dashboard/__init__.py → frappe/core/page/dashboard_view/__init__.py View File


frappe/core/page/dashboard/dashboard.js → frappe/core/page/dashboard_view/dashboard_view.js View File

@@ -5,7 +5,7 @@ frappe.provide('frappe.dashboards');
frappe.provide('frappe.dashboards.chart_sources');


frappe.pages['dashboard'].on_page_load = function(wrapper) {
frappe.pages['dashboard-view'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: __("Dashboard"),
@@ -21,7 +21,7 @@ frappe.pages['dashboard'].on_page_load = function(wrapper) {
class Dashboard {
constructor(wrapper) {
this.wrapper = $(wrapper);
$(`<div class="dashboard" style="overflow-y: hidden">
$(`<div class="dashboard" style="overflow: visible">
<div class="dashboard-graph"></div>
</div>`).appendTo(this.wrapper.find(".page-content").empty());
this.container = this.wrapper.find(".dashboard-graph");
@@ -36,17 +36,17 @@ class Dashboard {
} else {
// last opened
if (frappe.last_dashboard) {
frappe.set_route('dashboard', frappe.last_dashboard);
frappe.set_route('dashboard-view', frappe.last_dashboard);
} else {
// default dashboard
frappe.db.get_list('Dashboard', {filters: {is_default: 1}}).then(data => {
if (data && data.length) {
frappe.set_route('dashboard', data[0].name);
frappe.set_route('dashboard-view', data[0].name);
} else {
// no default, get the latest one
frappe.db.get_list('Dashboard', {limit: 1}).then(data => {
if (data && data.length) {
frappe.set_route('dashboard', data[0].name);
frappe.set_route('dashboard-view', data[0].name);
} else {
// create a new dashboard!
frappe.new_doc('Dashboard');
@@ -183,8 +183,8 @@ class Dashboard {
frappe.db.get_list('Dashboard').then(dashboards => {
dashboards.map(dashboard => {
let name = dashboard.name;
if(name != this.dashboard_name){
this.page.add_menu_item(name, () => frappe.set_route("dashboard", name), 1);
if (name != this.dashboard_name) {
this.page.add_menu_item(name, () => frappe.set_route("dashboard-view", name), 1);
}
});
});

frappe/core/page/dashboard/dashboard.json → frappe/core/page/dashboard_view/dashboard_view.json View File

@@ -4,12 +4,12 @@
"docstatus": 0,
"doctype": "Page",
"idx": 0,
"modified": "2020-03-26 13:30:44.603948",
"modified": "2020-12-16 12:29:08.610352",
"modified_by": "Administrator",
"module": "Core",
"name": "dashboard",
"name": "dashboard-view",
"owner": "Administrator",
"page_name": "Dashboard",
"page_name": "dashboard-view",
"roles": [],
"script": null,
"standard": "Yes",

+ 0
- 3
frappe/core/page/desktop/desktop.js View File

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

+ 0
- 24
frappe/core/page/desktop/desktop.json 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"
}

+ 51
- 0
frappe/core/page/permission_manager/permission_manager.css View File

@@ -0,0 +1,51 @@
.table {
margin-bottom: 0px;
margin-top: 0px;
border-radius: var(--border-radius-md);
}

thead {
border: none;
background-color: var(--control-bg);
border-radius: var(--border-radius-md);
}

thead > tr {
border-radius: var(--border-radius-md);
}

thead > tr > th:first-child {
border-radius: var(--border-radius-md) 0 0 var(--border-radius-md);
}
thead > tr > th:last-child {
border-radius: 0 var(--border-radius-md) var(--border-radius-md) 0;
}

/* Space between thead and tbody */
/* tbody:before {
content: "@";
display: block;
line-height: var(--margin-md);
text-indent: -99999px;
} */

td[data-fieldname="permissions"] > .row > .col-md-4 {
margin-bottom: var(--margin-sm);
}

tbody > tr {
border-top: 1px solid var(--border-color);
}

tbody > tr:first-child {
border-top: none;
}

button.btn-remove-perm {
box-shadow: none;
padding: var(--padding-xs) var(--padding-xs);
}

button.btn-remove-perm > svg > use {
stroke: var(--white);
}

+ 238
- 215
frappe/core/page/permission_manager/permission_manager.js View File

@@ -1,8 +1,8 @@
frappe.pages['permission-manager'].on_page_load = (wrapper) => {
var page = frappe.ui.make_app_page({
let page = frappe.ui.make_app_page({
parent: wrapper,
title: __('Role Permissions Manager'),
icon: "fa fa-lock",
card_layout: true,
single_column: true
});

@@ -14,233 +14,253 @@ frappe.pages['permission-manager'].on_page_load = (wrapper) => {

};

frappe.pages['permission-manager'].refresh = function(wrapper) {
frappe.pages['permission-manager'].refresh = function (wrapper) {
wrapper.permission_engine.set_from_route();
};

frappe.PermissionEngine = Class.extend({
init: function(wrapper) {
frappe.PermissionEngine = class PermissionEngine {
constructor(wrapper) {
this.wrapper = wrapper;
this.page = wrapper.page;
this.body = $(this.wrapper).find(".perm-engine");
this.make();
this.refresh();
this.add_check_events();
},
make: function() {
var me = this;

me.make_reset_button();
return frappe.call({
module:"frappe.core",
page:"permission_manager",
method: "get_roles_and_doctypes",
callback: function(r) {
me.options = r.message;
me.setup_page();
}
}

make() {
this.make_reset_button();
frappe.call({
module: "frappe.core",
page: "permission_manager",
method: "get_roles_and_doctypes"
}).then((res) => {
this.options = res.message;
this.setup_page();
});
}

},
setup_page: function() {
var me = this;
setup_page() {
this.doctype_select
= this.wrapper.page.add_select(__("Document Type"),
[{value: "", label: __("Select Document Type")+"..."}].concat(this.options.doctypes))
.change(function() {
[{ value: "", label: __("Select Document Type") + "..." }].concat(this.options.doctypes))
.change(function () {
frappe.set_route("permission-manager", $(this).val());
});

this.role_select
= this.wrapper.page.add_select(__("Roles"),
[__("Select Role")+"..."].concat(this.options.roles))
.change(function() {
me.refresh();
[__("Select Role") + "..."].concat(this.options.roles))
.change(() => {
this.refresh();
});

this.page.add_inner_button(__('Set User Permissions'), () => {
return frappe.set_route('List', 'User Permission');
});
this.set_from_route();
},
set_from_route: function() {
var me = this;
if(!this.doctype_select) {
}
set_from_route() {
if (!this.doctype_select) {
// selects not yet loaded, call again after a bit
setTimeout(() => {
me.set_from_route();
this.set_from_route();
}, 500);
return;
}
if(frappe.get_route()[1]) {
if (frappe.get_route()[1]) {
this.doctype_select.val(frappe.get_route()[1]);
} else if(frappe.route_options) {
if(frappe.route_options.doctype) {
} else if (frappe.route_options) {
if (frappe.route_options.doctype) {
this.doctype_select.val(frappe.route_options.doctype);
}
if(frappe.route_options.role) {
if (frappe.route_options.role) {
this.role_select.val(frappe.route_options.role);
}
frappe.route_options = null;
}
this.refresh();
},
get_standard_permissions: function(callback) {
var doctype = this.get_doctype();
if(doctype) {
}

get_standard_permissions(callback) {
let doctype = this.get_doctype();
if (doctype) {
return frappe.call({
module:"frappe.core",
page:"permission_manager",
module: "frappe.core",
page: "permission_manager",
method: "get_standard_permissions",
args: {doctype: doctype},
args: { doctype: doctype },
callback: callback
});
}
return false;
},
reset_std_permissions: function(data) {
var me = this;
var d = frappe.confirm(__("Reset Permissions for {0}?", [me.get_doctype()]), function() {
}

reset_std_permissions(data) {
let doctype = this.get_doctype();
let d = frappe.confirm(__("Reset Permissions for {0}?", [doctype]), () => {
return frappe.call({
module:"frappe.core",
page:"permission_manager",
method:"reset",
args: {
doctype: me.get_doctype(),
},
callback: function() {
me.refresh();
}
module: "frappe.core",
page: "permission_manager",
method: "reset",
args: { doctype }
}).then(() => {
this.refresh();
});
});

// show standard permissions
var $d = $(d.wrapper).find(".frappe-confirm-message").append("<hr><h4>Standard Permissions:</h4><br>");
var $wrapper = $("<p></p>").appendTo($d);
$.each(data.message, function(i, d) {
d.rights = [];
$.each(me.rights, function(i, r) {
if(d[r]===1) {
d.rights.push(__(toTitle(r.replace("_", " "))));
}
});
d.rights = d.rights.join(", ");
$wrapper.append(repl('<div class="row">\
<div class="col-xs-5"><b>%(role)s</b>, Level %(permlevel)s</div>\
<div class="col-xs-7">%(rights)s</div>\
</div><br>', d));
let $d = $(d.wrapper).find(".frappe-confirm-message").append("<hr><h5>Standard Permissions:</h5><br>");
let $wrapper = $("<p></p>").appendTo($d);
data.message.forEach((d) => {
let rights = this.rights
.filter((r) => d[r])
.map((r) => {
return __(toTitle(frappe.unscrub(r)));
});

d.rights = rights.join(", ");

$wrapper.append(`<div class="row">\
<div class="col-xs-5"><b>${d.role}</b>, Level ${d.permlevel || 0}</div>\
<div class="col-xs-7">${d.rights}</div>\
</div><br>`);
});
}

},
get_doctype: function() {
var doctype = this.doctype_select.val();
return this.doctype_select.get(0).selectedIndex==0 ? null : doctype;
},
get_role: function() {
var role = this.role_select.val();
return this.role_select.get(0).selectedIndex==0 ? null : role;
},
refresh: function() {
var me = this;
if(!me.doctype_select) {
this.body.html("<p class='text-muted'>" + __("Loading") + "...</p>");
return;
get_doctype() {
let doctype = this.doctype_select.val();
return this.doctype_select.get(0).selectedIndex == 0 ? null : doctype;
}

get_role() {
let role = this.role_select.val();
return this.role_select.get(0).selectedIndex == 0 ? null : role;
}

set_empty_message(message) {
this.body.html(`
<div class="text-muted flex justify-center align-center" style="min-height: 300px;">
<p class='text-muted'>
${message}
</p>
</div>`);
}

refresh() {
this.page.clear_secondary_action();
this.page.clear_primary_action();

if (!this.doctype_select) {
return this.set_empty_message(__("Loading"));
}
if(!me.get_doctype() && !me.get_role()) {
this.body.html("<p class='text-muted'>"+__("Select Document Type or Role to start.")+"</p>");
return;

let doctype = this.get_doctype();
let role = this.get_role();

if (!doctype && !role) {
return this.set_empty_message(__("Select Document Type or Role to start."));
}

// get permissions
frappe.call({
module: "frappe.core",
page: "permission_manager",
method: "get_permissions",
args: {
doctype: me.get_doctype(),
role: me.get_role()
},
callback: function(r) {
me.render(r.message);
}
args: { doctype, role }
}).then((r) => {
this.render(r.message);
});
},
render: function(perm_list) {
}

render(perm_list) {
this.body.empty();
this.perm_list = perm_list || [];
if(!this.perm_list.length) {
this.body.html("<p class='text-muted'>"
+__("No Permissions set for this criteria.")+"</p>");
if (!this.perm_list.length) {
this.set_empty_message(__("No Permissions set for this criteria."));
} else {
this.show_permission_table(this.perm_list);
}
this.show_add_rule();
this.make_reset_button();
},
show_permission_table: function(perm_list) {
this.get_doctype() && this.make_reset_button();
}

var me = this;
show_permission_table(perm_list) {
this.table = $("<div class='table-responsive'>\
<table class='table table-bordered'>\
<table class='table table-borderless'>\
<thead><tr></tr></thead>\
<tbody></tbody>\
</table>\
</div>").appendTo(this.body);

$.each([[__("Document Type"), 150], [__("Role"), 170], [__("Level"), 40],
[__("Permissions"), 350], ["", 40]], function(i, col) {
$("<th>").html(col[0]).css("width", col[1]+"px")
.appendTo(me.table.find("thead tr"));
const table_columns = [
[__("Document Type"), 150],
[__("Role"), 170],
[__("Level"), 40],
[__("Permissions"), 350],
["", 40]
];

table_columns.forEach((col) => {
$("<th>")
.html(col[0])
.css("width", col[1] + "px")
.appendTo(this.table.find("thead tr"));
});

$.each(perm_list, function(i, d) {
if(d.parent==="DocType") {
perm_list.forEach((d) => {
if (d.parent === "DocType") {
return;
}
if(!d.permlevel) d.permlevel = 0;
var row = $("<tr>").appendTo(me.table.find("tbody"));
me.add_cell(row, d, "parent");
var role_cell = me.add_cell(row, d, "role");
me.set_show_users(role_cell, d.role);

if (d.permlevel===0) {
// me.setup_user_permissions(d, role_cell);
me.setup_if_owner(d, role_cell);

if (!d.permlevel) d.permlevel = 0;

let row = $("<tr>").appendTo(this.table.find("tbody"));
this.add_cell(row, d, "parent");
let role_cell = this.add_cell(row, d, "role");

this.set_show_users(role_cell, d.role);

if (d.permlevel === 0) {
// this.setup_user_permissions(d, role_cell);
this.setup_if_owner(d, role_cell);
}

var cell = me.add_cell(row, d, "permlevel");
if(d.permlevel==0) {
let cell = this.add_cell(row, d, "permlevel");

if (d.permlevel == 0) {
cell.css("font-weight", "bold");
row.addClass("warning");
}

var perm_cell = me.add_cell(row, d, "permissions").css("padding-top", 0);
var perm_container = $("<div class='row'></div>").appendTo(perm_cell);
let perm_cell = this.add_cell(row, d, "permissions");
let perm_container = $("<div class='row'></div>").appendTo(perm_cell);

me.rights.forEach(r => {
this.rights.forEach(r => {
if (!d.is_submittable && ['submit', 'cancel', 'amend'].includes(r)) return;
if (d.in_create && ['create', 'write', 'delete'].includes(r)) return;
me.add_check(perm_container, d, r);
this.add_check(perm_container, d, r);
});

// buttons
me.add_delete_button(row, d);
this.add_delete_button(row, d);
});
},
}

add_cell: function(row, d, fieldname) {
add_cell(row, d, fieldname) {
return $("<td>").appendTo(row)
.attr("data-fieldname", fieldname)
.addClass("pt-4")
.html(__(d[fieldname]));
},

add_check: (cell, d, fieldname, label, description="") => {
var me = this;
}

if(!label) label = toTitle(fieldname.replace(/_/g, " "));
if(d.permlevel > 0 && ["read", "write"].indexOf(fieldname)==-1) {
add_check(cell, d, fieldname, label, description = "") {
if (!label) label = toTitle(fieldname.replace(/_/g, " "));
if (d.permlevel > 0 && ["read", "write"].indexOf(fieldname) == -1) {
return;
}

var checkbox = $(
let checkbox = $(
`<div class='col-md-4'>
<div class='checkbox'>
<label><input type='checkbox'>${__(label)}</input></label>
@@ -251,7 +271,7 @@ frappe.PermissionEngine = Class.extend({
.attr("data-fieldname", fieldname);

checkbox.find("input")
.prop("checked", d[fieldname] ? true: false)
.prop("checked", d[fieldname] ? true : false)
.attr("data-ptype", fieldname)
.attr("data-role", d.role)
.attr("data-permlevel", d.permlevel)
@@ -261,23 +281,25 @@ frappe.PermissionEngine = Class.extend({
.css("text-transform", "capitalize");

return checkbox;
},
}

setup_if_owner: function(d, role_cell) {
setup_if_owner(d, role_cell) {
this.add_check(role_cell, d, "if_owner", "Only If Creator")
.removeClass("col-md-4")
.css({"margin-top": "15px"});
},
.css({ "margin-top": "15px" });
}

rights: ["select", "read", "write", "create", "delete", "submit", "cancel", "amend",
"print", "email", "report", "import", "export", "set_user_permissions", "share"],
get rights() {
return ["select", "read", "write", "create", "delete", "submit", "cancel", "amend",
"print", "email", "report", "import", "export", "set_user_permissions", "share"];
}

set_show_users: function(cell, role) {
cell.html("<a class='grey' href='#'>"+__(role)+"</a>")
set_show_users(cell, role) {
cell.html("<a class='grey' href='#'>" + __(role) + "</a>")
.find("a")
.attr("data-role", role)
.click(function() {
var role = $(this).attr("data-role");
.click(function () {
let role = $(this).attr("data-role");
frappe.call({
module: "frappe.core",
page: "permission_manager",
@@ -285,9 +307,9 @@ frappe.PermissionEngine = Class.extend({
args: {
role: role
},
callback: function(r) {
r.message = $.map(r.message, function(p) {
return $.format('<a href="#Form/User/{0}">{1}</a>', [p, p]);
callback: function (r) {
r.message = $.map(r.message, function (p) {
return $.format('<a href="/app/user/{0}">{1}</a>', [p, p]);
});
frappe.msgprint(__("Users with role {0}:", [__(role)])
+ "<br>" + r.message.join("<br>"));
@@ -295,16 +317,15 @@ frappe.PermissionEngine = Class.extend({
});
return false;
});
},
}

add_delete_button: function(row, d) {
var me = this;
$("<button class='btn btn-default btn-sm'><i class='fa fa-remove'></i></button>")
.appendTo($("<td>").appendTo(row))
add_delete_button(row, d) {
$(`<button class='btn btn-danger btn-remove-perm btn-xs'>${frappe.utils.icon('delete')}</button>`)
.appendTo($(`<td class="pt-4">`).appendTo(row))
.attr("data-doctype", d.parent)
.attr("data-role", d.role)
.attr("data-permlevel", d.permlevel)
.click(function() {
.click(function () {
return frappe.call({
module: "frappe.core",
page: "permission_manager",
@@ -314,29 +335,27 @@ frappe.PermissionEngine = Class.extend({
role: $(this).attr("data-role"),
permlevel: $(this).attr("data-permlevel")
},
callback: function(r) {
if(r.exc) {
callback: (r) => {
if (r.exc) {
frappe.msgprint(__("Did not remove"));
} else {
me.refresh();
this.refresh();
}
}
});
});
},

add_check_events: function() {
var me = this;
}

this.body.on("click", ".show-user-permissions", function() {
frappe.route_options = { allow: me.get_doctype() || "" };
add_check_events() {
this.body.on("click", ".show-user-permissions", () => {
frappe.route_options = { allow: this.get_doctype() || "" };
frappe.set_route('List', 'User Permission');
});

this.body.on("click", "input[type='checkbox']", function() {
this.body.on("click", "input[type='checkbox']", function () {
frappe.dom.freeze();
var chk = $(this);
var args = {
let chk = $(this);
let args = {
role: chk.attr("data-role"),
permlevel: chk.attr("data-permlevel"),
doctype: chk.attr("data-doctype"),
@@ -348,49 +367,53 @@ frappe.PermissionEngine = Class.extend({
page: "permission_manager",
method: "update",
args: args,
callback: function(r) {
callback: (r) => {
frappe.dom.unfreeze();
if(r.exc) {
if (r.exc) {
// exception: reverse
chk.prop("checked", !chk.prop("checked"));
} else {
me.get_perm(args.role)[args.ptype]=args.value;
this.get_perm(args.role)[args.ptype] = args.value;
}
}
});
});
},

show_add_rule: function() {
var me = this;
$("<button class='btn btn-default btn-primary btn-sm'><i class='fa fa-plus'></i> "
+__("Add A New Rule")+"</button>")
.appendTo($("<p class='permission-toolbar'>").appendTo(this.body))
.click(function() {
var d = new frappe.ui.Dialog({
}

show_add_rule() {
this.page.set_primary_action(
__("Add A New Rule"),
() => {
let d = new frappe.ui.Dialog({
title: __("Add New Permission Rule"),
fields: [
{fieldtype:"Select", label:__("Document Type"),
options:me.options.doctypes, reqd:1, fieldname:"parent"},
{fieldtype:"Select", label:__("Role"),
options:me.options.roles, reqd:1,fieldname:"role"},
{fieldtype:"Select", label:__("Permission Level"),
options:[0,1,2,3,4,5,6,7,8,9], reqd:1, fieldname: "permlevel",
description: __("Level 0 is for document level permissions, higher levels for field level permissions.")}
{
fieldtype: "Select", label: __("Document Type"),
options: this.options.doctypes, reqd: 1, fieldname: "parent"
},
{
fieldtype: "Select", label: __("Role"),
options: this.options.roles, reqd: 1, fieldname: "role"
},
{
fieldtype: "Select", label: __("Permission Level"),
options: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9], reqd: 1, fieldname: "permlevel",
description: __("Level 0 is for document level permissions, higher levels for field level permissions.")
}
]
});
if(me.get_doctype()) {
d.set_value("parent", me.get_doctype());
if (this.get_doctype()) {
d.set_value("parent", this.get_doctype());
d.get_input("parent").prop("disabled", true);
}
if(me.get_role()) {
d.set_value("role", me.get_role());
if (this.get_role()) {
d.set_value("role", this.get_role());
d.get_input("role").prop("disabled", true);
}
d.set_value("permlevel", "0");
d.set_primary_action(__('Add'), function() {
var args = d.get_values();
if(!args) {
d.set_primary_action(__('Add'), () => {
let args = d.get_values();
if (!args) {
return;
}
frappe.call({
@@ -398,40 +421,40 @@ frappe.PermissionEngine = Class.extend({
page: "permission_manager",
method: "add",
args: args,
callback: function(r) {
if(r.exc) {
callback: (r) => {
if (r.exc) {
frappe.msgprint(__("Did not add"));
} else {
me.refresh();
this.refresh();
}
}
});
d.hide();
});
d.show();
});
},
make_reset_button: function() {
var me = this;
$('<button class="btn btn-default btn-sm" style="margin-left: 10px;">\
<i class="fa fa-refresh"></i> ' + __("Restore Original Permissions") + '</button>')
.appendTo(this.body.find(".permission-toolbar"))
.on("click", function() {
me.get_standard_permissions(function(data) {
me.reset_std_permissions(data);
},
"small-add"
);
}
make_reset_button() {
this.page.set_secondary_action(
__("Restore Original Permissions"),
() => {
this.get_standard_permissions((data) => {
this.reset_std_permissions(data);
});
});
},
}

get_perm: function(role) {
return $.map(this.perm_list, function(d) {
if(d.role==role) return d;
get_perm(role) {
return $.map(this.perm_list, function (d) {
if (d.role == role) return d;
})[0];
},
}

get_link_fields: function(doctype) {
get_link_fields(doctype) {
return frappe.get_children("DocType", doctype, "fields",
{fieldtype:"Link", options:["not in", ["User", '[Select]']]});
{ fieldtype: "Link", options: ["not in", ["User", '[Select]']] });
}
});
};

+ 5
- 5
frappe/core/page/permission_manager/permission_manager_help.html View File

@@ -1,12 +1,12 @@
<hr>
<div style="padding: 0px 15px;">
<div class="p-3">
<h4>{%= __("Quick Help for Setting Permissions") %}:</h4>
<ol>
<li>{%= __("Permissions are set on Roles and Document Types (called DocTypes) by setting rights like Read, Write, Create, Delete, Submit, Cancel, Amend, Report, Import, Export, Print, Email and Set User Permissions.") %}</li>
<li>{%= __("Permissions get applied on Users based on what Roles they are assigned.") %}</li>
<li>{%= __("Roles can be set for users from their User page.") %}
<a href="#List/User">{%= __("Setup > User") %}</a></li>
<li>{%= __("The system provides many pre-defined roles. You can add new roles to set finer permissions.") %}<a href="#List/Role"> {%= __("Add a New Role") %}</a></li>
<a href="/app/List/User">{%= __("Setup > User") %}</a></li>
<li>{%= __("The system provides many pre-defined roles. You can add new roles to set finer permissions.") %}<a href="/app/List/Role"> {%= __("Add a New Role") %}</a></li>
<li>{%= __("Permissions are automatically applied to Standard Reports and searches.") %}</li>
<li>{%= __("As a best practice, do not assign the same set of permission rule to different Roles. Instead, set multiple Roles to the same User.") %}</li>
</ol>
@@ -24,13 +24,13 @@
<li>{%= __("Permissions at level 0 are Document Level permissions, i.e. they are primary for access to the document.") %}</li>
<li>{%= __("If a Role does not have access at Level 0, then higher levels are meaningless.") %}</li>
<li>{%= __("Permissions at higher levels are Field Level permissions. All Fields have a Permission Level set against them and the rules defined at that permissions apply to the field. This is useful in case you want to hide or make certain field read-only for certain Roles.") %}</li>
<li>{%= __("You can use Customize Form to set levels on fields.") %} <a href="#Form/Customize Form">{%= __("Setup > Customize Form") %}</a></li>
<li>{%= __("You can use Customize Form to set levels on fields.") %} <a href="/app/Form/Customize Form">{%= __("Setup > Customize Form") %}</a></li>
</ol>
<hr>
<h4>{%= __("User Permissions") %}:</h4>
<ol>
<li>{%= __("User Permissions are used to limit users to specific records.") %}
<a href="#List/User Permission">{%= __("Setup > User Permissions") %}</a></li>
<a href="/app/List/User Permission">{%= __("Setup > User Permissions") %}</a></li>
<li>{%= __("Select Document Types to set which User Permissions are used to limit access.") %}</li>
<li>{%= __("Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger).") %}</li>
<li>{%= __("Apart from System Manager, roles with Set User Permissions right can set permissions for other users for that Document Type.") %}</li>


+ 2
- 1
frappe/core/page/recorder/recorder.js View File

@@ -2,7 +2,8 @@ frappe.pages['recorder'].on_page_load = function(wrapper) {
frappe.ui.make_app_page({
parent: wrapper,
title: 'Recorder',
single_column: true
single_column: true,
card_layout: true
});

frappe.recorder = new Recorder(wrapper);


+ 0
- 3
frappe/core/page/workspace/workspace.js View File

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

+ 0
- 23
frappe/core/page/workspace/workspace.json 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-02-27 15:07:57.124916",
"modified_by": "Administrator",
"module": "Core",
"name": "workspace",
"owner": "Administrator",
"page_name": "workspace",
"roles": [
{
"role": "All"
}
],
"script": null,
"standard": "Yes",
"style": null,
"system_page": 0
}

+ 211
- 0
frappe/core/workspace/build/build.json View File

@@ -0,0 +1,211 @@
{
"cards_label": "Elements",
"category": "Modules",
"charts": [],
"creation": "2021-01-02 10:51:16.579957",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Workspace",
"extends_another_page": 0,
"hide_custom": 0,
"icon": "tool",
"idx": 0,
"is_standard": 1,
"label": "Build",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Modules",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Def",
"link_to": "Module Def",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Workspace",
"link_to": "Workspace",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Module Onboarding",
"link_to": "Module Onboarding",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Block Module",
"link_to": "Block Module",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Models",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "DocType",
"link_to": "DocType",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Workflow",
"link_to": "Workflow",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Views",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Report",
"link_to": "Report",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Print Format",
"link_to": "Print Format",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Workspace",
"link_to": "Workspace",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Dashboard",
"link_to": "Dashboard",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Scripting",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Card Break"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Server Script",
"link_to": "Server Script",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Custom Script",
"link_to": "Custom Script",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Scheduled Job Type",
"link_to": "Scheduled Job Type",
"link_type": "DocType",
"onboard": 0,
"only_for": "",
"type": "Link"
}
],
"modified": "2021-01-02 14:03:15.029699",
"modified_by": "Administrator",
"module": "Core",
"name": "Build",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"doc_view": "",
"label": "DocType",
"link_to": "DocType",
"type": "DocType"
},
{
"doc_view": "",
"label": "Workspace",
"link_to": "Workspace",
"type": "DocType"
},
{
"doc_view": "",
"label": "Report",
"link_to": "Report",
"type": "DocType"
}
]
}

+ 367
- 0
frappe/core/workspace/settings/settings.json View File

@@ -0,0 +1,367 @@
{
"category": "Modules",
"charts": [],
"creation": "2020-03-02 15:09:40.527211",
"developer_mode_only": 0,
"disable_user_customization": 1,
"docstatus": 0,
"doctype": "Workspace",
"extends_another_page": 0,
"hide_custom": 0,
"icon": "setting",
"idx": 0,
"is_standard": 1,
"label": "Settings",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Data",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Import Data",
"link_to": "Data Import",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Export Data",
"link_to": "Data Export",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Bulk Update",
"link_to": "Bulk Update",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Download Backups",
"link_to": "backups",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Deleted Documents",
"link_to": "Deleted Document",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Email / Notifications",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Account",
"link_to": "Email Account",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Domain",
"link_to": "Email Domain",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Notification",
"link_to": "Notification",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Email Template",
"link_to": "Email Template",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Auto Email Report",
"link_to": "Auto Email Report",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Newsletter",
"link_to": "Newsletter",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Notification Settings",
"link_to": "Notification Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Website",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Settings",
"link_to": "Website Settings",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Theme",
"link_to": "Website Theme",
"link_type": "DocType",
"onboard": 1,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Website Script",
"link_to": "Website Script",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "About Us Settings",
"link_to": "About Us Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Contact Us Settings",
"link_to": "Contact Us Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Core",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "System Settings",
"link_to": "System Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Error Log",
"link_to": "Error Log",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Error Snapshot",
"link_to": "Error Snapshot",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Domain Settings",
"link_to": "Domain Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Printing",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Format Builder",
"link_to": "print-format-builder",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Settings",
"link_to": "Print Settings",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Format",
"link_to": "Print Format",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Print Style",
"link_to": "Print Style",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Workflow",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow",
"link_to": "Workflow",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow State",
"link_to": "Workflow State",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Workflow Action",
"link_to": "Workflow Action",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
}
],
"modified": "2020-12-01 13:38:40.235323",
"modified_by": "Administrator",
"module": "Core",
"name": "Settings",
"owner": "Administrator",
"pin_to_bottom": 1,
"pin_to_top": 0,
"shortcuts": [
{
"icon": "setting",
"label": "System Settings",
"link_to": "System Settings",
"type": "DocType"
},
{
"icon": "printer",
"label": "Print Settings",
"link_to": "Print Settings",
"type": "DocType"
},
{
"icon": "website",
"label": "Website Settings",
"link_to": "Website Settings",
"type": "DocType"
}
],
"shortcuts_label": "Settings"
}

+ 167
- 0
frappe/core/workspace/users/users.json View File

@@ -0,0 +1,167 @@
{
"category": "Administration",
"charts": [],
"creation": "2020-03-02 15:12:16.754449",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Workspace",
"extends_another_page": 0,
"hide_custom": 0,
"icon": "users",
"idx": 0,
"is_standard": 1,
"label": "Users",
"links": [
{
"hidden": 0,
"is_query_report": 0,
"label": "Users",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "User",
"link_to": "User",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role",
"link_to": "Role",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role Profile",
"link_to": "Role Profile",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Logs",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Activity Log",
"link_to": "Activity Log",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Access Log",
"link_to": "Access Log",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"hidden": 0,
"is_query_report": 0,
"label": "Permissions",
"onboard": 0,
"type": "Card Break"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role Permissions Manager",
"link_to": "permission-manager",
"link_type": "Page",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "User Permissions",
"link_to": "User Permission",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "",
"hidden": 0,
"is_query_report": 0,
"label": "Role Permission for Page and Report",
"link_to": "Role Permission for Page and Report",
"link_type": "DocType",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "User",
"hidden": 0,
"is_query_report": 1,
"label": "Permitted Documents For User",
"link_to": "Permitted Documents For User",
"link_type": "Report",
"onboard": 0,
"type": "Link"
},
{
"dependencies": "DocShare",
"hidden": 0,
"is_query_report": 0,
"label": "Document Share Report",
"link_to": "Document Share Report",
"link_type": "Report",
"onboard": 0,
"type": "Link"
}
],
"modified": "2020-12-01 13:38:40.085519",
"modified_by": "Administrator",
"module": "Core",
"name": "Users",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"label": "User",
"link_to": "User",
"type": "DocType"
},
{
"label": "Role",
"link_to": "Role",
"type": "DocType"
},
{
"label": "Permission Manager",
"link_to": "permission-manager",
"type": "Page"
},
{
"label": "User Profile",
"link_to": "user-profile",
"type": "Page"
}
]
}

+ 0
- 54
frappe/custom/desk_page/customization/customization.json View File

@@ -1,54 +0,0 @@
{
"cards": [
{
"hidden": 0,
"label": "Dashboards",
"links": "[\n {\n \"label\": \"Dashboard\",\n \"name\": \"Dashboard\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart\",\n \"name\": \"Dashboard Chart\",\n \"type\": \"doctype\"\n },\n {\n \"label\": \"Dashboard Chart Source\",\n \"name\": \"Dashboard Chart Source\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Form Customization",
"links": "[\n {\n \"description\": \"Change field properties (hide, readonly, permission etc.)\",\n \"label\": \"Customize Form\",\n \"name\": \"Customize Form\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add fields to forms.\",\n \"label\": \"Custom Field\",\n \"name\": \"Custom Field\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom javascript to forms.\",\n \"label\": \"Custom Script\",\n \"name\": \"Custom Script\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Add custom forms.\",\n \"label\": \"DocType\",\n \"name\": \"DocType\",\n \"type\": \"doctype\"\n }\n]"
},
{
"hidden": 0,
"label": "Other",
"links": "[\n {\n \"description\": \"Add your own translations\",\n \"label\": \"Custom Translations\",\n \"name\": \"Translation\",\n \"type\": \"doctype\"\n }\n]"
}
],
"category": "Administration",
"charts": [],
"creation": "2020-03-02 15:15:03.839594",
"developer_mode_only": 0,
"disable_user_customization": 0,
"docstatus": 0,
"doctype": "Desk Page",
"extends_another_page": 0,
"idx": 0,
"is_standard": 1,
"label": "Customization",
"modified": "2020-04-01 11:24:40.787109",
"modified_by": "Administrator",
"module": "Custom",
"name": "Customization",
"owner": "Administrator",
"pin_to_bottom": 0,
"pin_to_top": 0,
"shortcuts": [
{
"label": "Customize Form",
"link_to": "Customize Form",
"type": "DocType"
},
{
"label": "Custom Role",
"link_to": "Custom Role",
"type": "DocType"
},
{
"label": "Custom Script",
"link_to": "Custom Script",
"type": "DocType"
}
]
}

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save