diff --git a/.eslintrc b/.eslintrc index 69c731b079..a2538feab5 100644 --- a/.eslintrc +++ b/.eslintrc @@ -87,6 +87,7 @@ "open_url_post": true, "toTitle": true, "lstrip": true, + "rstrip": true, "strip": true, "strip_html": true, "replace_all": true, diff --git a/.github/helper/translation.py b/.github/helper/translation.py index 340f4f8772..9146b3b32b 100644 --- a/.github/helper/translation.py +++ b/.github/helper/translation.py @@ -2,7 +2,7 @@ import re import sys errors_encounter = 0 -pattern = re.compile(r"_\(([\"']{,3})(?P((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P((?!\11).)*)\11)*)*\)") +pattern = re.compile(r"_\(([\"']{,3})(?P((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P((?!\5).)*)\5)*(\s*,(\s*?.*?\n*?)*(,\s*([\"'])(?P((?!\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 diff --git a/.gitignore b/.gitignore index 900ae1c7b7..766288fe2e 100644 --- a/.gitignore +++ b/.gitignore @@ -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/ diff --git a/.travis.yml b/.travis.yml index 23fb525138..53ad56a948 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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: diff --git a/cypress.json b/cypress.json index 97ac41bb61..f2508ca66e 100644 --- a/cypress.json +++ b/cypress.json @@ -3,5 +3,9 @@ "projectId": "92odwv", "adminPassword": "admin", "defaultCommandTimeout": 20000, - "pageLoadTimeout": 15000 + "pageLoadTimeout": 15000, + "retries": { + "runMode": 2, + "openMode": 2 + } } diff --git a/cypress/integration/api.js b/cypress/integration/api.js index 2279dc399d..7a5b1611b0 100644 --- a/cypress/integration/api.js +++ b/cypress/integration/api.js @@ -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', () => { diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 15e85976fc..3e12101532 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -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', () => { diff --git a/cypress/integration/control_barcode.js b/cypress/integration/control_barcode.js index 4e05d864e6..1df5e64f0e 100644 --- a/cypress/integration/control_barcode.js +++ b/cypress/integration/control_barcode.js @@ -1,7 +1,7 @@ context('Control Barcode', () => { beforeEach(() => { cy.login(); - cy.visit('/desk#workspace/Website'); + cy.visit('/app/website'); }); function get_dialog_with_barcode() { diff --git a/cypress/integration/control_duration.js b/cypress/integration/control_duration.js index edad759216..266d421e70 100644 --- a/cypress/integration/control_duration.js +++ b/cypress/integration/control_duration.js @@ -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'); diff --git a/cypress/integration/control_link.js b/cypress/integration/control_link.js index 0dc7d5b88e..8f9257e9c4 100644 --- a/cypress/integration/control_link.js +++ b/cypress/integration/control_link.js @@ -1,11 +1,11 @@ context('Control Link', () => { before(() => { cy.login(); - cy.visit('/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]}`); }); }); }); diff --git a/cypress/integration/control_rating.js b/cypress/integration/control_rating.js index e89ab2d3be..31c036d240 100644 --- a/cypress/integration/control_rating.js +++ b/cypress/integration/control_rating.js @@ -1,7 +1,7 @@ context('Control Rating', () => { before(() => { cy.login(); - cy.visit('/desk#workspace/Website'); + cy.visit('/app/website'); }); function get_dialog_with_rating() { diff --git a/cypress/integration/datetime.js b/cypress/integration/datetime.js index 9bf01632bf..b310526c7c 100644 --- a/cypress/integration/datetime.js +++ b/cypress/integration/datetime.js @@ -4,7 +4,7 @@ const doctype_name = datetime_doctype.name; context('Control Date, Time and DateTime', () => { before(() => { cy.login(); - cy.visit('/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') diff --git a/cypress/integration/depends_on.js b/cypress/integration/depends_on.js index aa80afb59a..d33babb134 100644 --- a/cypress/integration/depends_on.js +++ b/cypress/integration/depends_on.js @@ -1,7 +1,7 @@ context('Depends On', () => { before(() => { cy.login(); - cy.visit('/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'); diff --git a/cypress/integration/file_uploader.js b/cypress/integration/file_uploader.js index f9f44675db..2f457983de 100644 --- a/cypress/integration/file_uploader.js +++ b/cypress/integration/file_uploader.js @@ -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'); diff --git a/cypress/integration/form.js b/cypress/integration/form.js index ef89a18e7d..9c63fe4e8b 100644 --- a/cypress/integration/form.js +++ b/cypress/integration/form.js @@ -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(); diff --git a/cypress/integration/grid_pagination.js b/cypress/integration/grid_pagination.js index b383f30bb8..8f6b79c1f4 100644 --- a/cypress/integration/grid_pagination.js +++ b/cypress/integration/grid_pagination.js @@ -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(); diff --git a/cypress/integration/list_view.js b/cypress/integration/list_view.js index 0d26ca90a2..633d1335ab 100644 --- a/cypress/integration/list_view.js +++ b/cypress/integration/list_view.js @@ -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'); }); }); diff --git a/cypress/integration/list_view_settings.js b/cypress/integration/list_view_settings.js index 47f8efe94b..52512b911e 100644 --- a/cypress/integration/list_view_settings.js +++ b/cypress/integration/list_view_settings.js @@ -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(); }); }); diff --git a/cypress/integration/login.js b/cypress/integration/login.js index 861377444c..6b109dd18d 100644 --- a/cypress/integration/login.js +++ b/cypress/integration/login.js @@ -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')); diff --git a/cypress/integration/query_report.js b/cypress/integration/query_report.js index 5581a20edc..e2a1c3fc79 100644 --- a/cypress/integration/query_report.js +++ b/cypress/integration/query_report.js @@ -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 }); }); }); }); \ No newline at end of file diff --git a/cypress/integration/recorder.js b/cypress/integration/recorder.js index a0f8cc3621..7236200741 100644 --- a/cypress/integration/recorder.js +++ b/cypress/integration/recorder.js @@ -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(); }); }); \ No newline at end of file diff --git a/cypress/integration/relative_time_filters.js b/cypress/integration/relative_time_filters.js index ac70c44345..80e6387d99 100644 --- a/cypress/integration/relative_time_filters.js +++ b/cypress/integration/relative_time_filters.js @@ -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'); }); }); diff --git a/cypress/integration/report_view.js b/cypress/integration/report_view.js index deeef6bdd5..ea76246ae2 100644 --- a/cypress/integration/report_view.js +++ b/cypress/integration/report_view.js @@ -4,7 +4,7 @@ const doctype_name = custom_submittable_doctype.name; context('Report View', () => { before(() => { cy.login(); - cy.visit('/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 => { diff --git a/cypress/integration/table_multiselect.js b/cypress/integration/table_multiselect.js index e75baf05f1..8b83a0d914 100644 --- a/cypress/integration/table_multiselect.js +++ b/cypress/integration/table_multiselect.js @@ -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'); }); }); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 3e54a9cd4c..7f0afdf035 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -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; }); }); -}); \ No newline at end of file +}); + +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(); + }); +}); diff --git a/cypress/support/index.js b/cypress/support/index.js index 8035ef117b..1bee72d2ca 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -21,5 +21,5 @@ import './commands'; // require('./commands') Cypress.Cookies.defaults({ - whitelist: 'sid' + preserve: 'sid' }); \ No newline at end of file diff --git a/frappe/.stylelintrc b/frappe/.stylelintrc new file mode 100644 index 0000000000..1e05d1fb41 --- /dev/null +++ b/frappe/.stylelintrc @@ -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 + } +} \ No newline at end of file diff --git a/frappe/__init__.py b/frappe/__init__.py index f8ae6b4ec1..95d9c782a4 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -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 = '{doctype_local} {name}' + html = '{doctype_local} {name}' return html.format( doctype=doctype, name=name, diff --git a/frappe/auth.py b/frappe/auth.py index 6d51629c58..2e0ec681d2 100644 --- a/frappe/auth.py +++ b/frappe/auth.py @@ -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 diff --git a/frappe/automation/desk_page/tools/tools.json b/frappe/automation/desk_page/tools/tools.json deleted file mode 100644 index 3fbaf62d02..0000000000 --- a/frappe/automation/desk_page/tools/tools.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/frappe/automation/doctype/auto_repeat/auto_repeat.js b/frappe/automation/doctype/auto_repeat/auto_repeat.js index d54ae8d62c..7028ac486d 100644 --- a/frappe/automation/doctype/auto_repeat/auto_repeat.js +++ b/frappe/automation/doctype/auto_repeat/auto_repeat.js @@ -30,7 +30,7 @@ frappe.ui.form.on('Auto Repeat', { refresh: function(frm) { // auto repeat message if (frm.is_new()) { - let customize_form_link = `${__('Customize Form')}`; + let customize_form_link = `${__('Customize Form')}`; 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(); }); diff --git a/frappe/automation/workspace/tools/tools.json b/frappe/automation/workspace/tools/tools.json new file mode 100644 index 0000000000..4a0835657b --- /dev/null +++ b/frappe/automation/workspace/tools/tools.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/frappe/boot.py b/frappe/boot.py index 5b1a1bf573..8cf75e02bb 100644 --- a/frappe/boot.py +++ b/frappe/boot.py @@ -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 \ No newline at end of file diff --git a/frappe/cache_manager.py b/frappe/cache_manager.py index ed5c7b64ad..bad879d2fa 100644 --- a/frappe/cache_manager.py +++ b/frappe/cache_manager.py @@ -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) diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 31b84ee98a..e9fa7217a8 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -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' diff --git a/frappe/config/__init__.py b/frappe/config/__init__.py index b62a3bc258..cc9d0e6c67 100644 --- a/frappe/config/__init__.py +++ b/frappe/config/__init__.py @@ -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" \ No newline at end of file diff --git a/frappe/config/automation.py b/frappe/config/automation.py deleted file mode 100644 index 08de969729..0000000000 --- a/frappe/config/automation.py +++ /dev/null @@ -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 diff --git a/frappe/config/core.py b/frappe/config/core.py deleted file mode 100644 index bdf39dfb3f..0000000000 --- a/frappe/config/core.py +++ /dev/null @@ -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"), - }, - ] - } - ] diff --git a/frappe/config/customization.py b/frappe/config/customization.py deleted file mode 100644 index 95fa5d355c..0000000000 --- a/frappe/config/customization.py +++ /dev/null @@ -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") - } - ] - } - ] diff --git a/frappe/config/desk.py b/frappe/config/desk.py deleted file mode 100644 index 40db97ef8c..0000000000 --- a/frappe/config/desk.py +++ /dev/null @@ -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"), - }, - ] - } - ] diff --git a/frappe/config/desktop.py b/frappe/config/desktop.py deleted file mode 100644 index 568cc76afd..0000000000 --- a/frappe/config/desktop.py +++ /dev/null @@ -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 - }, - ] diff --git a/frappe/config/docs.py b/frappe/config/docs.py deleted file mode 100644 index eafaeeb19e..0000000000 --- a/frappe/config/docs.py +++ /dev/null @@ -1,6 +0,0 @@ -# -*- coding: utf-8 -*- - -from __future__ import unicode_literals - -source_link = "https://github.com/frappe/frappe_io" -docs_base_url = "/docs" diff --git a/frappe/config/integrations.py b/frappe/config/integrations.py deleted file mode 100644 index 672c0c4acc..0000000000 --- a/frappe/config/integrations.py +++ /dev/null @@ -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."), - } - ] - } - ] diff --git a/frappe/config/settings.py b/frappe/config/settings.py deleted file mode 100644 index 0112c7ccff..0000000000 --- a/frappe/config/settings.py +++ /dev/null @@ -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 diff --git a/frappe/config/tools.py b/frappe/config/tools.py deleted file mode 100644 index 3ab2a59222..0000000000 --- a/frappe/config/tools.py +++ /dev/null @@ -1,2 +0,0 @@ -from __future__ import unicode_literals -from frappe import _ \ No newline at end of file diff --git a/frappe/config/users_and_permissions.py b/frappe/config/users_and_permissions.py deleted file mode 100644 index d50235e4a3..0000000000 --- a/frappe/config/users_and_permissions.py +++ /dev/null @@ -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") - } - ] - } - ] \ No newline at end of file diff --git a/frappe/config/website.py b/frappe/config/website.py deleted file mode 100644 index e7eb218889..0000000000 --- a/frappe/config/website.py +++ /dev/null @@ -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", - }, - ] - }, - - ] diff --git a/frappe/contacts/doctype/contact/contact.json b/frappe/contacts/doctype/contact/contact.json index 2e2fb6df67..696cd61d6c 100644 --- a/frappe/contacts/doctype/contact/contact.json +++ b/frappe/contacts/doctype/contact/contact.json @@ -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", diff --git a/frappe/core/desk_page/settings/settings.json b/frappe/core/desk_page/settings/settings.json deleted file mode 100644 index 642a4fdadd..0000000000 --- a/frappe/core/desk_page/settings/settings.json +++ /dev/null @@ -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" -} \ No newline at end of file diff --git a/frappe/core/desk_page/users/users.json b/frappe/core/desk_page/users/users.json deleted file mode 100644 index f1f43a4ef0..0000000000 --- a/frappe/core/desk_page/users/users.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/frappe/core/doctype/comment/comment.py b/frappe/core/doctype/comment/comment.py index 04ecc83b38..e4fd181733 100644 --- a/frappe/core/doctype/comment/comment.py +++ b/frappe/core/doctype/comment/comment.py @@ -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) diff --git a/frappe/core/doctype/communication/communication.js b/frappe/core/doctype/communication/communication.js index cdbdf97852..07674d16ae 100644 --- a/frappe/core/doctype/communication/communication.js +++ b/frappe/core/doctype/communication/communication.js @@ -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' + }); } ); } diff --git a/frappe/core/doctype/communication/communication.py b/frappe/core/doctype/communication/communication.py index d893e80617..5ebf714645 100644 --- a/frappe/core/doctype/communication/communication.py +++ b/frappe/core/doctype/communication/communication.py @@ -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() diff --git a/frappe/core/doctype/data_import_legacy/data_import_legacy.js b/frappe/core/doctype/data_import_legacy/data_import_legacy.js index 9a301af76e..8e4f397171 100644 --- a/frappe/core/doctype/data_import_legacy/data_import_legacy.js +++ b/frappe/core/doctype/data_import_legacy/data_import_legacy.js @@ -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 + "%"); diff --git a/frappe/core/doctype/deleted_document/deleted_document_list.js b/frappe/core/doctype/deleted_document/deleted_document_list.js index f5e1147dfb..92413bfdf4 100644 --- a/frappe/core/doctype/deleted_document/deleted_document_list.js +++ b/frappe/core/doctype/deleted_document/deleted_document_list.js @@ -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 `
  • ${docname}
  • `; + return `
  • ${docname}
  • `; }); return "
      " + html.join(""); - } - function message(title, docnames) { + }; + + let message = (title, docnames) => { return (docnames.length > 0) ? title + body(docnames) + "
    ": ""; - } + }; const { restored, invalid, failed } = r.message; const restored_summary = message(__("Documents restored successfully"), restored); diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index b3469abf29..3e2a423b06 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -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)}`); }); } } diff --git a/frappe/core/doctype/doctype/doctype.json b/frappe/core/doctype/doctype/doctype.json index 215ef8cd62..569414e98b 100644 --- a/frappe/core/doctype/doctype/doctype.json +++ b/frappe/core/doctype/doctype/doctype.json @@ -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", diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 80a576230c..8a9090bc37 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -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,6 +63,37 @@ class DocType(Document): self.validate_name() + self.set_defaults_for_single_and_table() + self.scrub_field_names() + self.set_default_in_list_view() + self.set_default_translatable() + self.validate_series() + self.validate_document_type() + validate_fields(self) + + if not self.istable: + validate_permissions(self) + + self.make_amendable() + self.make_repeatable() + self.validate_nestedset() + self.validate_website() + validate_links_table_fieldnames(self) + + if not self.is_new(): + self.before_update = frappe.get_doc('DocType', self.name) + self.setup_fields_to_fetch() + + check_email_append_to(self) + + if self.default_print_format and not self.custom: + frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) + + 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 @@ -72,44 +103,6 @@ class DocType(Document): self.allow_import = 0 self.permissions = [] - self.scrub_field_names() - self.set_default_in_list_view() - self.set_default_translatable() - self.validate_series() - self.validate_document_type() - validate_fields(self) - - if self.istable: - # no permission records for child table - self.permissions = [] - else: - validate_permissions(self) - - self.make_amendable() - self.make_repeatable() - self.validate_nestedset() - self.validate_website() - self.validate_links_table_fieldnames() - - 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) - - 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_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): diff --git a/frappe/core/doctype/doctype/patches/set_route.py b/frappe/core/doctype/doctype/patches/set_route.py new file mode 100644 index 0000000000..c052a51f38 --- /dev/null +++ b/frappe/core/doctype/doctype/patches/set_route.py @@ -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) \ No newline at end of file diff --git a/frappe/core/doctype/doctype/test_doctype.py b/frappe/core/doctype/doctype/test_doctype.py index 10169073e5..ec88a2d14c 100644 --- a/frappe/core/doctype/doctype/test_doctype.py +++ b/frappe/core/doctype/doctype/test_doctype.py @@ -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): diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 8614740d26..445ca1184d 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -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): diff --git a/frappe/core/doctype/log_settings/log_settings.py b/frappe/core/doctype/log_settings/log_settings.py index 6d59cdeb29..08e61d3289 100644 --- a/frappe/core/doctype/log_settings/log_settings.py +++ b/frappe/core/doctype/log_settings/log_settings.py @@ -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(' Error Logs ') + 'message': _("You have unseen {0}").format(' Error Logs ') } if frappe.db.sql_list("select name from `tabError Log` where seen = 0 limit 1"): diff --git a/frappe/core/doctype/module_def/module_def.json b/frappe/core/doctype/module_def/module_def.json index b841eb1f0b..7a8bfd76a7 100644 --- a/frappe/core/doctype/module_def/module_def.json +++ b/frappe/core/doctype/module_def/module_def.json @@ -51,7 +51,7 @@ "link_fieldname": "module" }, { - "link_doctype": "Desk Page", + "link_doctype": "Workspace", "link_fieldname": "module" } ], diff --git a/frappe/core/doctype/navbar_settings/navbar_settings.py b/frappe/core/doctype/navbar_settings/navbar_settings.py index f7c437bf00..db510981a4 100644 --- a/frappe/core/doctype/navbar_settings/navbar_settings.py +++ b/frappe/core/doctype/navbar_settings/navbar_settings.py @@ -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: diff --git a/frappe/core/doctype/page/page.py b/frappe/core/doctype/page/page.py index 65befcded5..bdec350efd 100644 --- a/frappe/core/doctype/page/page.py +++ b/frappe/core/doctype/page/page.py @@ -9,6 +9,7 @@ from frappe.build import html_to_js_template from frappe.model.utils import render_include from frappe import conf, _, safe_decode from frappe.desk.form.meta import get_code_files_via_hooks, get_js +from frappe.desk.utils import validate_route_conflict from frappe.core.doctype.custom_role.custom_role import get_custom_allowed_roles from six import text_type @@ -33,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")) diff --git a/frappe/core/doctype/page/patches/drop_unused_pages.py b/frappe/core/doctype/page/patches/drop_unused_pages.py new file mode 100644 index 0000000000..93b47cebcc --- /dev/null +++ b/frappe/core/doctype/page/patches/drop_unused_pages.py @@ -0,0 +1,5 @@ +import frappe + +def execute(): + for name in ('desktop', 'space'): + frappe.delete_doc('Page', name) \ No newline at end of file diff --git a/frappe/core/doctype/page/test_page.py b/frappe/core/doctype/page/test_page.py index 78659f1ffd..f7b3952a5b 100644 --- a/frappe/core/doctype/page/test_page.py +++ b/frappe/core/doctype/page/test_page.py @@ -8,4 +8,6 @@ import unittest test_records = frappe.get_test_records('Page') class TestPage(unittest.TestCase): - pass + def test_naming(self): + self.assertRaises(frappe.NameError, frappe.get_doc(dict(doctype='Page', page_name='DocType', module='Core')).insert) + self.assertRaises(frappe.NameError, frappe.get_doc(dict(doctype='Page', page_name='Settings', module='Core')).insert) diff --git a/frappe/core/doctype/role/patches/v13_set_default_desk_properties.py b/frappe/core/doctype/role/patches/v13_set_default_desk_properties.py new file mode 100644 index 0000000000..375ea02e0e --- /dev/null +++ b/frappe/core/doctype/role/patches/v13_set_default_desk_properties.py @@ -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() \ No newline at end of file diff --git a/frappe/core/doctype/role/role.json b/frappe/core/doctype/role/role.json index 0ad15ba10b..e47dc7194b 100644 --- a/frappe/core/doctype/role/role.json +++ b/frappe/core/doctype/role/role.json @@ -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", diff --git a/frappe/core/doctype/role/role.py b/frappe/core/doctype/role/role.py index 1920189f78..7adfeba8d9 100644 --- a/frappe/core/doctype/role/role.py +++ b/frappe/core/doctype/role/role.py @@ -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''' diff --git a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js index 8a121f31ae..5048d24077 100644 --- a/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js +++ b/frappe/core/doctype/role_permission_for_page_and_report/role_permission_for_page_and_report.js @@ -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 = $('
    ') - .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"); diff --git a/frappe/core/doctype/role_profile/role_profile.js b/frappe/core/doctype/role_profile/role_profile.js index d31618cc4a..e43980770a 100644 --- a/frappe/core/doctype/role_profile/role_profile.js +++ b/frappe/core/doctype/role_profile/role_profile.js @@ -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 = $('
    ') - .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(); } } diff --git a/frappe/core/doctype/server_script/test_server_script.py b/frappe/core/doctype/server_script/test_server_script.py index 8dd6d03fee..aac8b3deed 100644 --- a/frappe/core/doctype/server_script/test_server_script.py +++ b/frappe/core/doctype/server_script/test_server_script.py @@ -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') diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 565ee373f1..13dbc32620 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -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", diff --git a/frappe/core/doctype/user/test_user.py b/frappe/core/doctype/user/test_user.py index fb1fa4aff9..77b61f22bb 100644 --- a/frappe/core/doctype/user/test_user.py +++ b/frappe/core/doctype/user/test_user.py @@ -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): diff --git a/frappe/core/doctype/user/user.css b/frappe/core/doctype/user/user.css deleted file mode 100644 index ec17030060..0000000000 --- a/frappe/core/doctype/user/user.css +++ /dev/null @@ -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%; -} diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 5493baf553..3548b4c913 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -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 = $('
    ') + if (frm.can_edit_roles && !frm.is_new()) { + if (!frm.roles_editor) { + const role_area = $('
    ') .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 = $('
    ') + var module_area = $('
    ') .appendTo(frm.fields_dict.modules_html.wrapper); frm.module_editor = new frappe.ModuleEditor(frm, module_area); } else { diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 8f40959799..747ace5de6 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -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", - "login_after", - "login_before", "restrict_ip", - "bypass_restrict_ip_check_if_2fa_enabled", - "column_break1", - "last_login", "last_ip", + "column_break1", + "login_after", + "user_type", "last_active", + "section_break_63", + "login_before", + "bypass_restrict_ip_check_if_2fa_enabled", + "last_login", "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", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index dcca4f4a25..5a35907ccf 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -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='

    Your verification code is {0}

    '.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 diff --git a/frappe/core/doctype/user_permission/user_permission.py b/frappe/core/doctype/user_permission/user_permission.py index e04000c0b3..fbc788f6bf 100644 --- a/frappe/core/doctype/user_permission/user_permission.py +++ b/frappe/core/doctype/user_permission/user_permission.py @@ -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) diff --git a/frappe/core/doctype/user_permission/user_permission_list.js b/frappe/core/doctype/user_permission/user_permission_list.js index 5539a26438..0ce66fa8e3 100644 --- a/frappe/core/doctype/user_permission/user_permission_list.js +++ b/frappe/core/doctype/user_permission/user_permission_list.js @@ -163,7 +163,7 @@ frappe.listview_settings['User Permission'] = { } frappe.show_alert({ message, - indicator: 'green' + indicator: 'info' }); list_view.refresh(); }); diff --git a/frappe/core/page/background_jobs/background_jobs.css b/frappe/core/page/background_jobs/background_jobs.css new file mode 100644 index 0000000000..0c77522cb3 --- /dev/null +++ b/frappe/core/page/background_jobs/background_jobs.css @@ -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); +} diff --git a/frappe/core/page/background_jobs/background_jobs.html b/frappe/core/page/background_jobs/background_jobs.html index c5d598ccd3..1b00ec3106 100644 --- a/frappe/core/page/background_jobs/background_jobs.html +++ b/frappe/core/page/background_jobs/background_jobs.html @@ -1,6 +1,6 @@
    {% if jobs.length %} - +
    @@ -11,30 +11,41 @@ {% for j in jobs %} - + - + {% endfor %}
    {{ __("Queue / Worker") }}
    {{ j.queue.split(".").slice(-1)[0] }} + + {{ j.queue.split(".").slice(-1)[0] }} +
    - {{ frappe.utils.encode_tags(j.job_name) }} + + {{ frappe.utils.encode_tags(j.job_name) }} +
    {% if j.exc_info %} -
    +
    {{ frappe.utils.encode_tags(j.exc_info) }}
    {% endif %}
    {{ j.creation }}{{ j.creation }}
    -

    - {{ __("Started") }} - {{ __("Queued") }} - {{ __("Failed") }} - {{ __("Finished") }} -

    {% else %} -

    {{ __("No pending or current jobs for this site") }}

    +
    + Empty State +

    {{ __("No pending or current jobs for this site") }}

    +
    {% endif %} -

    {{ __("Last refreshed") }} {{ frappe.datetime.now_datetime() }}

    -
    + +
    \ No newline at end of file diff --git a/frappe/core/page/background_jobs/background_jobs.js b/frappe/core/page/background_jobs/background_jobs.js index bbc8bf049b..cabe91375f 100644 --- a/frappe/core/page/background_jobs/background_jobs.js +++ b/frappe/core/page/background_jobs/background_jobs.js @@ -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); - } - }); -} + this.called = false; + this.show_failed = false; -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.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); + } + } + }); + } +} \ No newline at end of file diff --git a/frappe/core/page/background_jobs/background_jobs_outer.html b/frappe/core/page/background_jobs/background_jobs_outer.html index 4da4498304..4ca3a32906 100644 --- a/frappe/core/page/background_jobs/background_jobs_outer.html +++ b/frappe/core/page/background_jobs/background_jobs_outer.html @@ -1,11 +1,4 @@ -
    -

    -

    - -
    -

    +
    diff --git a/frappe/core/page/dashboard/__init__.py b/frappe/core/page/dashboard_view/__init__.py similarity index 100% rename from frappe/core/page/dashboard/__init__.py rename to frappe/core/page/dashboard_view/__init__.py diff --git a/frappe/core/page/dashboard/dashboard.js b/frappe/core/page/dashboard_view/dashboard_view.js similarity index 92% rename from frappe/core/page/dashboard/dashboard.js rename to frappe/core/page/dashboard_view/dashboard_view.js index 7e45163a7e..686d11c6bf 100644 --- a/frappe/core/page/dashboard/dashboard.js +++ b/frappe/core/page/dashboard_view/dashboard_view.js @@ -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); - $(`
    + $(`
    `).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); } }); }); diff --git a/frappe/core/page/dashboard/dashboard.json b/frappe/core/page/dashboard_view/dashboard_view.json similarity index 74% rename from frappe/core/page/dashboard/dashboard.json rename to frappe/core/page/dashboard_view/dashboard_view.json index 58fda5a34c..4ece98a779 100644 --- a/frappe/core/page/dashboard/dashboard.json +++ b/frappe/core/page/dashboard_view/dashboard_view.json @@ -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", diff --git a/frappe/core/page/desktop/desktop.js b/frappe/core/page/desktop/desktop.js deleted file mode 100644 index cc36a5a4e9..0000000000 --- a/frappe/core/page/desktop/desktop.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.pages['desktop'].on_page_load = function() { - frappe.utils.set_title(__("Home")); -}; \ No newline at end of file diff --git a/frappe/core/page/desktop/desktop.json b/frappe/core/page/desktop/desktop.json deleted file mode 100644 index 66bbfbfd40..0000000000 --- a/frappe/core/page/desktop/desktop.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "content": null, - "creation": "2019-01-29 13:11:48.872579", - "docstatus": 0, - "doctype": "Page", - "icon": "icon-th", - "idx": 0, - "modified": "2019-01-29 13:11:48.872579", - "modified_by": "Administrator", - "module": "Core", - "name": "desktop", - "owner": "Administrator", - "page_name": "desktop", - "roles": [ - { - "role": "All" - } - ], - "script": null, - "standard": "Yes", - "style": null, - "system_page": 0, - "title": "Desktop" -} \ No newline at end of file diff --git a/frappe/core/page/permission_manager/permission_manager.css b/frappe/core/page/permission_manager/permission_manager.css new file mode 100644 index 0000000000..fec486aa81 --- /dev/null +++ b/frappe/core/page/permission_manager/permission_manager.css @@ -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); +} diff --git a/frappe/core/page/permission_manager/permission_manager.js b/frappe/core/page/permission_manager/permission_manager.js index 02fbf943d5..41cc900a97 100644 --- a/frappe/core/page/permission_manager/permission_manager.js +++ b/frappe/core/page/permission_manager/permission_manager.js @@ -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("

    Standard Permissions:


    "); - var $wrapper = $("

    ").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('
    \ -
    %(role)s, Level %(permlevel)s
    \ -
    %(rights)s
    \ -

    ', d)); - }); + let $d = $(d.wrapper).find(".frappe-confirm-message").append("
    Standard Permissions:

    "); + let $wrapper = $("

    ").appendTo($d); + data.message.forEach((d) => { + let rights = this.rights + .filter((r) => d[r]) + .map((r) => { + return __(toTitle(frappe.unscrub(r))); + }); - }, - 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("

    " + __("Loading") + "...

    "); - return; + d.rights = rights.join(", "); + + $wrapper.append(`
    \ +
    ${d.role}, Level ${d.permlevel || 0}
    \ +
    ${d.rights}
    \ +

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

    + ${message} +

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

    "+__("Select Document Type or Role to start.")+"

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

    " - +__("No Permissions set for this criteria.")+"

    "); + 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 = $("
    \ - \ +
    \ \ \
    \
    ").appendTo(this.body); - $.each([[__("Document Type"), 150], [__("Role"), 170], [__("Level"), 40], - [__("Permissions"), 350], ["", 40]], function(i, col) { - $("").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) => { + $("") + .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 = $("").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 = $("").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 = $("
    ").appendTo(perm_cell); + let perm_cell = this.add_cell(row, d, "permissions"); + let perm_container = $("
    ").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 $("").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 = $( `
    @@ -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(""+__(role)+"") + set_show_users(cell, role) { + cell.html("" + __(role) + "") .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('{1}', [p, p]); + callback: function (r) { + r.message = $.map(r.message, function (p) { + return $.format('{1}', [p, p]); }); frappe.msgprint(__("Users with role {0}:", [__(role)]) + "
    " + r.message.join("
    ")); @@ -295,16 +317,15 @@ frappe.PermissionEngine = Class.extend({ }); return false; }); - }, + } - add_delete_button: function(row, d) { - var me = this; - $("") - .appendTo($("").appendTo(row)) + add_delete_button(row, d) { + $(``) + .appendTo($(``).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; - $("") - .appendTo($("

    ").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(); - }); - }, + }, + "small-add" + ); + } - make_reset_button: function() { - var me = this; - $('') - .appendTo(this.body.find(".permission-toolbar")) - .on("click", function() { - me.get_standard_permissions(function(data) { - me.reset_std_permissions(data); + 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; - })[0]; - }, - - get_link_fields: function(doctype) { - return frappe.get_children("DocType", doctype, "fields", - {fieldtype:"Link", options:["not in", ["User", '[Select]']]}); } -}); + + get_perm(role) { + return $.map(this.perm_list, function (d) { + if (d.role == role) return d; + })[0]; + } + + get_link_fields(doctype) { + return frappe.get_children("DocType", doctype, "fields", + { fieldtype: "Link", options: ["not in", ["User", '[Select]']] }); + } +}; diff --git a/frappe/core/page/permission_manager/permission_manager_help.html b/frappe/core/page/permission_manager/permission_manager_help.html index f192244fc9..0613713e81 100644 --- a/frappe/core/page/permission_manager/permission_manager_help.html +++ b/frappe/core/page/permission_manager/permission_manager_help.html @@ -1,12 +1,12 @@


    -
    +

    {%= __("Quick Help for Setting Permissions") %}:

    1. {%= __("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.") %}
    2. {%= __("Permissions get applied on Users based on what Roles they are assigned.") %}
    3. {%= __("Roles can be set for users from their User page.") %} - {%= __("Setup > User") %}
    4. -
    5. {%= __("The system provides many pre-defined roles. You can add new roles to set finer permissions.") %} {%= __("Add a New Role") %}
    6. + {%= __("Setup > User") %} +
    7. {%= __("The system provides many pre-defined roles. You can add new roles to set finer permissions.") %} {%= __("Add a New Role") %}
    8. {%= __("Permissions are automatically applied to Standard Reports and searches.") %}
    9. {%= __("As a best practice, do not assign the same set of permission rule to different Roles. Instead, set multiple Roles to the same User.") %}
    @@ -24,13 +24,13 @@
  • {%= __("Permissions at level 0 are Document Level permissions, i.e. they are primary for access to the document.") %}
  • {%= __("If a Role does not have access at Level 0, then higher levels are meaningless.") %}
  • {%= __("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.") %}
  • -
  • {%= __("You can use Customize Form to set levels on fields.") %} {%= __("Setup > Customize Form") %}
  • +
  • {%= __("You can use Customize Form to set levels on fields.") %} {%= __("Setup > Customize Form") %}

  • {%= __("User Permissions") %}:

    1. {%= __("User Permissions are used to limit users to specific records.") %} - {%= __("Setup > User Permissions") %}
    2. + {%= __("Setup > User Permissions") %}
    3. {%= __("Select Document Types to set which User Permissions are used to limit access.") %}
    4. {%= __("Once you have set this, the users will only be able access documents (eg. Blog Post) where the link exists (eg. Blogger).") %}
    5. {%= __("Apart from System Manager, roles with Set User Permissions right can set permissions for other users for that Document Type.") %}
    6. diff --git a/frappe/core/page/recorder/recorder.js b/frappe/core/page/recorder/recorder.js index f38af41af0..4d6d6aa84c 100644 --- a/frappe/core/page/recorder/recorder.js +++ b/frappe/core/page/recorder/recorder.js @@ -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); diff --git a/frappe/core/page/workspace/workspace.js b/frappe/core/page/workspace/workspace.js deleted file mode 100644 index 78256e3acd..0000000000 --- a/frappe/core/page/workspace/workspace.js +++ /dev/null @@ -1,3 +0,0 @@ -frappe.pages['workspace'].on_page_load = function(wrapper) { - frappe.utils.set_title(__("Home")); -} \ No newline at end of file diff --git a/frappe/core/page/workspace/workspace.json b/frappe/core/page/workspace/workspace.json deleted file mode 100644 index 49de4a8e03..0000000000 --- a/frappe/core/page/workspace/workspace.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "content": null, - "creation": "2020-02-27 15:07:57.124916", - "docstatus": 0, - "doctype": "Page", - "icon": "icon-th", - "idx": 0, - "modified": "2020-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 -} \ No newline at end of file diff --git a/frappe/core/workspace/build/build.json b/frappe/core/workspace/build/build.json new file mode 100644 index 0000000000..c4bde55d7f --- /dev/null +++ b/frappe/core/workspace/build/build.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/frappe/core/workspace/settings/settings.json b/frappe/core/workspace/settings/settings.json new file mode 100644 index 0000000000..fb26b73cfc --- /dev/null +++ b/frappe/core/workspace/settings/settings.json @@ -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" +} \ No newline at end of file diff --git a/frappe/core/workspace/users/users.json b/frappe/core/workspace/users/users.json new file mode 100644 index 0000000000..05746a00c2 --- /dev/null +++ b/frappe/core/workspace/users/users.json @@ -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" + } + ] +} \ No newline at end of file diff --git a/frappe/custom/desk_page/customization/customization.json b/frappe/custom/desk_page/customization/customization.json deleted file mode 100644 index 29f4cb745f..0000000000 --- a/frappe/custom/desk_page/customization/customization.json +++ /dev/null @@ -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" - } - ] -} \ No newline at end of file diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 17343573ed..79978a49d7 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -4,16 +4,35 @@ frappe.provide("frappe.customize_form"); frappe.ui.form.on("Customize Form", { + setup: function(frm) { + // save the last setting if refreshing + window.addEventListener("beforeunload", () => { + if (frm.doc.doc_type && frm.doc.doc_type != "undefined") { + localStorage["customize_doctype"] = frm.doc.doc_type; + } + }); + }, + onload: function(frm) { frm.disable_save(); frm.set_query("doc_type", function() { return { translate_values: false, filters: [ - ['DocType', 'issingle', '=', 0], - ['DocType', 'custom', '=', 0], - ['DocType', 'name', 'not in', frappe.model.core_doctypes_list], - ['DocType', 'restrict_to_domain', 'in', frappe.boot.active_domains] + ["DocType", "issingle", "=", 0], + ["DocType", "custom", "=", 0], + [ + "DocType", + "name", + "not in", + frappe.model.core_doctypes_list + ], + [ + "DocType", + "restrict_to_domain", + "in", + frappe.boot.active_domains + ] ] }; }); @@ -21,15 +40,15 @@ frappe.ui.form.on("Customize Form", { frm.set_query("default_print_format", function() { return { filters: { - 'print_format_type': ['!=', 'JS'], - 'doc_type': ['=', frm.doc.doc_type] + print_format_type: ["!=", "JS"], + doc_type: ["=", frm.doc.doc_type] } - } + }; }); $(frm.wrapper).on("grid-row-render", function(e, grid_row) { - if (grid_row.doc && grid_row.doc.fieldtype=="Section Break") { - $(grid_row.row).css({"font-weight": "bold"}); + if (grid_row.doc && grid_row.doc.fieldtype == "Section Break") { + $(grid_row.row).css({ "font-weight": "bold" }); } }); @@ -40,12 +59,6 @@ frappe.ui.form.on("Customize Form", { $(frm.wrapper).on("grid-move-row", function(e, frm) { frm.trigger("setup_sortable"); }); - - if (localStorage['customize_doctype']) { - // set default value from customize form - frm.set_value('doc_type', localStorage['customize_doctype']); - } - }, doc_type: function(frm) { @@ -59,7 +72,6 @@ frappe.ui.form.on("Customize Form", { if (r._server_messages && r._server_messages.length) { frm.set_value("doc_type", ""); } else { - localStorage['customize_doctype'] = frm.doc.doc_type; frm.refresh(); frm.trigger("setup_sortable"); } @@ -72,9 +84,11 @@ frappe.ui.form.on("Customize Form", { }, setup_sortable: function(frm) { - frm.page.body.find('.highlight').removeClass('highlight'); + frm.page.body.find(".highlight").removeClass("highlight"); frm.doc.fields.forEach(function(f, i) { - var data_row = frm.page.body.find('[data-fieldname="fields"] [data-idx="'+ f.idx +'"] .data-row'); + var data_row = frm.page.body.find( + '[data-fieldname="fields"] [data-idx="' + f.idx + '"] .data-row' + ); if (f.is_custom_field) { data_row.addClass("highlight"); @@ -82,9 +96,13 @@ frappe.ui.form.on("Customize Form", { f._sortable = false; } if (f.fieldtype == "Table") { - frm.add_custom_button(f.options, function() { - frm.set_value('doc_type', f.options); - }, __('Customize Child Table')); + frm.add_custom_button( + f.options, + function() { + frm.set_value("doc_type", f.options); + }, + __("Customize Child Table") + ); } }); frm.fields_dict.fields.grid.refresh(); @@ -97,36 +115,91 @@ frappe.ui.form.on("Customize Form", { if (frm.doc.doc_type) { frappe.customize_form.set_primary_action(frm); - frm.add_custom_button(__('Go to {0} List', [frm.doc.doc_type]), function() { - frappe.set_route('List', frm.doc.doc_type); - }, __('Actions')); + frm.add_custom_button( + __("Go to {0} List", [frm.doc.doc_type]), + function() { + frappe.set_route("List", frm.doc.doc_type); + }, + __("Actions") + ); - frm.add_custom_button(__('Reload'), function() { - frm.script_manager.trigger("doc_type"); - }, __('Actions')); + frm.add_custom_button( + __("Reload"), + function() { + frm.script_manager.trigger("doc_type"); + }, + __("Actions") + ); - frm.add_custom_button(__('Reset to defaults'), function() { - frappe.customize_form.confirm(__('Remove all customizations?'), frm); - }, __('Actions')); + frm.add_custom_button( + __("Reset to defaults"), + function() { + frappe.customize_form.confirm( + __("Remove all customizations?"), + frm + ); + }, + __("Actions") + ); - frm.add_custom_button(__('Set Permissions'), function() { - frappe.set_route('permission-manager', frm.doc.doc_type); - }, __('Actions')); + frm.add_custom_button( + __("Set Permissions"), + function() { + frappe.set_route("permission-manager", frm.doc.doc_type); + }, + __("Actions") + ); + } - if (frappe.boot.developer_mode) { - frm.add_custom_button(__('Export Customizations'), function() { + frm.events.setup_export(frm); + frm.events.setup_sort_order(frm); + frm.events.set_default_doc_type(frm); + }, + + set_default_doc_type(frm) { + let doc_type; + if (frappe.route_options && frappe.route_options.doc_type) { + doc_type = frappe.route_options.doc_type; + frappe.route_options = null; + localStorage.removeItem("customize_doctype"); + } + if (!doc_type) { + doc_type = localStorage.getItem("customize_doctype"); + } + if (doc_type) { + setTimeout(() => frm.set_value("doc_type", doc_type), 1000); + } + }, + + setup_export(frm) { + if (frappe.boot.developer_mode) { + frm.add_custom_button( + __("Export Customizations"), + function() { frappe.prompt( [ - {fieldtype:'Link', fieldname:'module', options:'Module Def', - label: __('Module to Export')}, - {fieldtype:'Check', fieldname:'sync_on_migrate', - label: __('Sync on Migrate'), 'default': 1}, - {fieldtype:'Check', fieldname:'with_permissions', - label: __('Export Custom Permissions'), 'default': 1}, + { + fieldtype: "Link", + fieldname: "module", + options: "Module Def", + label: __("Module to Export") + }, + { + fieldtype: "Check", + fieldname: "sync_on_migrate", + label: __("Sync on Migrate"), + default: 1 + }, + { + fieldtype: "Check", + fieldname: "with_permissions", + label: __("Export Custom Permissions"), + default: 1 + } ], function(data) { frappe.call({ - method: 'frappe.modules.utils.export_customizations', + method: "frappe.modules.utils.export_customizations", args: { doctype: frm.doc.doc_type, module: data.module, @@ -135,27 +208,25 @@ frappe.ui.form.on("Customize Form", { } }); }, - __("Select Module")); - }, __('Actions')); - } + __("Select Module") + ); + }, + __("Actions") + ); } + }, + setup_sort_order(frm) { // sort order select if (frm.doc.doc_type) { - var fields = $.map(frm.doc.fields, - function(df) { - return frappe.model.is_value_type(df.fieldtype) ? df.fieldname : null; - }); + var fields = $.map(frm.doc.fields, function(df) { + return frappe.model.is_value_type(df.fieldtype) + ? df.fieldname + : null; + }); fields = ["", "name", "modified"].concat(fields); frm.set_df_property("sort_field", "options", fields); } - - if (frappe.route_options && frappe.route_options.doc_type) { - setTimeout(function() { - frm.set_value("doc_type", frappe.route_options.doc_type); - frappe.route_options = null; - }, 1000); - } } }); diff --git a/frappe/custom/doctype/customize_form/test_customize_form.py b/frappe/custom/doctype/customize_form/test_customize_form.py index 46a2f2f9df..f5e0371c1f 100644 --- a/frappe/custom/doctype/customize_form/test_customize_form.py +++ b/frappe/custom/doctype/customize_form/test_customize_form.py @@ -234,7 +234,7 @@ class TestCustomizeForm(unittest.TestCase): testdt1.delete() def test_custom_action(self): - test_route = '#List/DocType' + test_route = '/app/List/DocType' # create a dummy action (route) d = self.get_customize_form("Event") diff --git a/frappe/core/page/desktop/__init__.py b/frappe/custom/doctype/doctype_layout/__init__.py similarity index 100% rename from frappe/core/page/desktop/__init__.py rename to frappe/custom/doctype/doctype_layout/__init__.py diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.js b/frappe/custom/doctype/doctype_layout/doctype_layout.js new file mode 100644 index 0000000000..679330e065 --- /dev/null +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.js @@ -0,0 +1,30 @@ +// Copyright (c) 2020, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('DocType Layout', { + refresh: function(frm) { + frm.trigger('document_type'); + frm.events.set_button(frm); + }, + + document_type(frm) { + frm.set_fields_as_options('fields', frm.doc.document_type, null, [], 'fieldname').then(() => { + // child table empty? then show all fields as default + if (frm.doc.document_type) { + if (!(frm.doc.fields || []).length) { + for (let f of frappe.get_doc('DocType', frm.doc.document_type).fields) { + frm.add_child('fields', { fieldname: f.fieldname, label: f.label }); + } + } + } + }); + }, + + set_button(frm) { + if (!frm.is_new()) { + frm.add_custom_button(__('Go to {0} List', [frm.doc.name]), () => { + window.open(`/app/list/${frappe.router.slug(frm.doc.name)}/list`); + }); + } + } +}); diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.json b/frappe/custom/doctype/doctype_layout/doctype_layout.json new file mode 100644 index 0000000000..e47c9e03e0 --- /dev/null +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.json @@ -0,0 +1,72 @@ +{ + "actions": [], + "allow_rename": 1, + "autoname": "Prompt", + "creation": "2020-11-16 17:05:35.306846", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "document_type", + "route", + "fields", + "client_script" + ], + "fields": [ + { + "fieldname": "document_type", + "fieldtype": "Link", + "in_list_view": 1, + "label": "Document Type", + "options": "DocType", + "reqd": 1 + }, + { + "fieldname": "fields", + "fieldtype": "Table", + "label": "Fields", + "options": "DocType Layout Field", + "reqd": 1 + }, + { + "fieldname": "client_script", + "fieldtype": "Code", + "label": "Client Script" + }, + { + "fieldname": "route", + "fieldtype": "Data", + "label": "Route", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-12-10 15:01:04.352184", + "modified_by": "Administrator", + "module": "Custom", + "name": "DocType Layout", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + }, + { + "read": 1, + "role": "Guest" + } + ], + "route": "doctype-layout", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/custom/doctype/doctype_layout/doctype_layout.py b/frappe/custom/doctype/doctype_layout/doctype_layout.py new file mode 100644 index 0000000000..a4fe9a9bce --- /dev/null +++ b/frappe/custom/doctype/doctype_layout/doctype_layout.py @@ -0,0 +1,14 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals + +from frappe.model.document import Document + +from frappe.desk.utils import slug + +class DocTypeLayout(Document): + def validate(self): + if not self.route: + self.route = slug(self.name) diff --git a/frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py b/frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py new file mode 100644 index 0000000000..4e44743b48 --- /dev/null +++ b/frappe/custom/doctype/doctype_layout/patches/convert_web_forms_to_doctype_layout.py @@ -0,0 +1,13 @@ +import frappe + +def execute(): + for web_form_name in frappe.db.get_all('Web Form', pluck='name'): + web_form = frappe.get_doc('Web Form', web_form_name) + doctype_layout = frappe.get_doc(dict( + doctype = 'DocType Layout', + document_type = web_form.doc_type, + name = web_form.title, + route = web_form.route, + fields = [dict(fieldname = d.fieldname, label=d.label) for d in web_form.web_form_fields if d.fieldname] + )).insert() + print(doctype_layout.name) \ No newline at end of file diff --git a/frappe/desk/doctype/desk_card/test_desk_card.py b/frappe/custom/doctype/doctype_layout/test_doctype_layout.py similarity index 80% rename from frappe/desk/doctype/desk_card/test_desk_card.py rename to frappe/custom/doctype/doctype_layout/test_doctype_layout.py index de9587d17e..5765c86262 100644 --- a/frappe/desk/doctype/desk_card/test_desk_card.py +++ b/frappe/custom/doctype/doctype_layout/test_doctype_layout.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestDeskCard(unittest.TestCase): +class TestDocTypeLayout(unittest.TestCase): pass diff --git a/frappe/core/page/workspace/__init__.py b/frappe/custom/doctype/doctype_layout_field/__init__.py similarity index 100% rename from frappe/core/page/workspace/__init__.py rename to frappe/custom/doctype/doctype_layout_field/__init__.py diff --git a/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.json b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.json new file mode 100644 index 0000000000..a1a36216c3 --- /dev/null +++ b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.json @@ -0,0 +1,39 @@ +{ + "actions": [], + "creation": "2020-11-16 16:03:43.771801", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "label", + "fieldname" + ], + "fields": [ + { + "fieldname": "fieldname", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Fieldname", + "reqd": 1 + }, + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "reqd": 1 + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2020-11-16 17:13:01.892345", + "modified_by": "Administrator", + "module": "Custom", + "name": "DocType Layout Field", + "owner": "Administrator", + "permissions": [], + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/desk_shortcut/desk_shortcut.py b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py similarity index 86% rename from frappe/desk/doctype/desk_shortcut/desk_shortcut.py rename to frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py index bbf0b2e074..7f8c8edfce 100644 --- a/frappe/desk/doctype/desk_shortcut/desk_shortcut.py +++ b/frappe/custom/doctype/doctype_layout_field/doctype_layout_field.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class DeskShortcut(Document): +class DocTypeLayoutField(Document): pass diff --git a/frappe/desk/doctype/desk_card/__init__.py b/frappe/custom/doctype/test_rename_new/__init__.py similarity index 100% rename from frappe/desk/doctype/desk_card/__init__.py rename to frappe/custom/doctype/test_rename_new/__init__.py diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.js b/frappe/custom/doctype/test_rename_new/test_rename_new.js new file mode 100644 index 0000000000..f38f9486f9 --- /dev/null +++ b/frappe/custom/doctype/test_rename_new/test_rename_new.js @@ -0,0 +1,8 @@ +// Copyright (c) 2021, Frappe Technologies and contributors +// For license information, please see license.txt + +frappe.ui.form.on('Test rename new', { + // refresh: function(frm) { + + // } +}); diff --git a/frappe/custom/doctype/test_rename_new/test_rename_new.json b/frappe/custom/doctype/test_rename_new/test_rename_new.json new file mode 100644 index 0000000000..0b089091a1 --- /dev/null +++ b/frappe/custom/doctype/test_rename_new/test_rename_new.json @@ -0,0 +1,42 @@ +{ + "actions": [], + "creation": "2021-01-13 12:47:03.572640", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "random" + ], + "fields": [ + { + "fieldname": "random", + "fieldtype": "Data", + "label": "random" + } + ], + "index_web_pages_for_search": 1, + "links": [], + "modified": "2021-01-13 12:47:03.572640", + "modified_by": "Administrator", + "module": "Custom", + "name": "Test rename new", + "owner": "Administrator", + "permissions": [ + { + "create": 1, + "delete": 1, + "email": 1, + "export": 1, + "print": 1, + "read": 1, + "report": 1, + "role": "System Manager", + "share": 1, + "write": 1 + } + ], + "route": "test-rename", + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/desk_chart/desk_chart.py b/frappe/custom/doctype/test_rename_new/test_rename_new.py similarity index 66% rename from frappe/desk/doctype/desk_chart/desk_chart.py rename to frappe/custom/doctype/test_rename_new/test_rename_new.py index dbbfae6cd7..aa5984e466 100644 --- a/frappe/desk/doctype/desk_chart/desk_chart.py +++ b/frappe/custom/doctype/test_rename_new/test_rename_new.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors +# Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class DeskChart(Document): +class Testrenamenew(Document): pass diff --git a/frappe/custom/doctype/test_rename_new/test_test_rename_new.py b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py new file mode 100644 index 0000000000..554efbae45 --- /dev/null +++ b/frappe/custom/doctype/test_rename_new/test_test_rename_new.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and Contributors +# See license.txt +from __future__ import unicode_literals + +# import frappe +import unittest + +class Testrenamenew(unittest.TestCase): + pass diff --git a/frappe/custom/workspace/customization/customization.json b/frappe/custom/workspace/customization/customization.json new file mode 100644 index 0000000000..3631914249 --- /dev/null +++ b/frappe/custom/workspace/customization/customization.json @@ -0,0 +1,142 @@ +{ + "category": "Administration", + "charts": [], + "creation": "2020-03-02 15:15:03.839594", + "developer_mode_only": 0, + "disable_user_customization": 0, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "customization", + "idx": 0, + "is_standard": 1, + "label": "Customization", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Dashboards", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Dashboard", + "link_to": "Dashboard", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Dashboard Chart", + "link_to": "Dashboard Chart", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Dashboard Chart Source", + "link_to": "Dashboard Chart Source", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Form Customization", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Customize Form", + "link_to": "Customize Form", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Custom Field", + "link_to": "Custom Field", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Custom Script", + "link_to": "Custom Script", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "DocType", + "link_to": "DocType", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Other", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Custom Translations", + "link_to": "Translation", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:39.843773", + "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" + } + ] +} \ No newline at end of file diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index a476573b1a..0ded8e0717 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -31,7 +31,7 @@ def handle_not_exist(fn): class Workspace: def __init__(self, page_name, minimal=False): self.page_name = page_name - self.extended_cards = [] + self.extended_links = [] self.extended_charts = [] self.extended_shortcuts = [] @@ -57,11 +57,11 @@ class Workspace: self.restricted_pages = frappe.cache().get_value("domain_restricted_pages") or build_domain_restriced_page_cache() def is_page_allowed(self): - cards = self.doc.cards + get_custom_reports_and_doctypes(self.doc.module) + self.extended_cards + cards = self.doc.get_link_groups() + get_custom_reports_and_doctypes(self.doc.module) + self.extended_links shortcuts = self.doc.shortcuts + self.extended_shortcuts for section in cards: - links = loads(section.links) if isinstance(section.links, string_types) else section.links + links = loads(section.get('links')) if isinstance(section.get('links'), string_types) else section.get('links') for item in links: if self.is_item_allowed(item.get('name'), item.get('type')): return True @@ -108,21 +108,21 @@ class Workspace: 'extends': self.page_name, 'for_user': frappe.session.user } - user_pages = frappe.get_all("Desk Page", filters=filters, limit=1) + user_pages = frappe.get_all("Workspace", filters=filters, limit=1) if user_pages: - return frappe.get_cached_doc("Desk Page", user_pages[0]) + return frappe.get_cached_doc("Workspace", user_pages[0]) filters = { 'extends_another_page': 1, 'extends': self.page_name, 'is_default': 1 } - default_page = frappe.get_all("Desk Page", filters=filters, limit=1) + default_page = frappe.get_all("Workspace", filters=filters, limit=1) if default_page: - return frappe.get_cached_doc("Desk Page", default_page[0]) + return frappe.get_cached_doc("Workspace", default_page[0]) self.get_pages_to_extend() - return frappe.get_cached_doc("Desk Page", self.page_name) + return frappe.get_cached_doc("Workspace", self.page_name) def get_onboarding_doc(self): # Check if onboarding is enabled @@ -150,25 +150,28 @@ class Workspace: return doc def get_pages_to_extend(self): - pages = frappe.get_all("Desk Page", filters={ + pages = frappe.get_all("Workspace", filters={ "extends": self.page_name, 'restrict_to_domain': ['in', frappe.get_active_domains()], 'for_user': '', 'module': ['in', self.allowed_modules] }) - pages = [frappe.get_cached_doc("Desk Page", page['name']) for page in pages] + pages = [frappe.get_cached_doc("Workspace", page['name']) for page in pages] for page in pages: - self.extended_cards = self.extended_cards + page.cards + self.extended_links = self.extended_links + page.get_link_groups() self.extended_charts = self.extended_charts + page.charts self.extended_shortcuts = self.extended_shortcuts + page.shortcuts def is_item_allowed(self, name, item_type): + if frappe.session.user == "Administrator": + return True + item_type = item_type.lower() if item_type == "doctype": - return (name in self.can_read and name in self.restricted_doctypes) + return (name in self.can_read or [] and name in self.restricted_doctypes or []) if item_type == "page": return (name in self.allowed_pages and name in self.restricted_pages) if item_type == "report": @@ -183,7 +186,7 @@ class Workspace: def build_workspace(self): self.cards = { 'label': _(self.doc.cards_label), - 'items': self.get_cards() + 'items': self.get_links() } self.charts = { @@ -205,54 +208,61 @@ class Workspace: 'items': self.get_onboarding_steps() } + def _doctype_contains_a_record(self, name): + exists = self.table_counts.get(name, False) + + if not exists and frappe.db.exists(name): + if not frappe.db.get_value('DocType', name, 'issingle'): + exists = bool(frappe.db.get_all(name, limit=1)) + else: + exists = True + self.table_counts[name] = exists + + return exists + + def _prepare_item(self, item): + if item.dependencies: + + dependencies = [dep.strip() for dep in item.dependencies.split(",")] + + incomplete_dependencies = [d for d in dependencies if not self._doctype_contains_a_record(d)] + + if len(incomplete_dependencies): + item.incomplete_dependencies = incomplete_dependencies + else: + item.incomplete_dependencies = "" + + if item.onboard: + # Mark Spotlights for initial + if item.get("type") == "doctype": + name = item.get("name") + count = self._doctype_contains_a_record(name) + + item["count"] = count + + # Translate label + item["label"] = _(item.label) if item.label else _(item.name) + + return item + @handle_not_exist - def get_cards(self): - cards = self.doc.cards + def get_links(self): + cards = self.doc.get_link_groups() + if not self.doc.hide_custom: cards = cards + get_custom_reports_and_doctypes(self.doc.module) - if len(self.extended_cards): - cards = merge_cards_based_on_label(cards + self.extended_cards) + if len(self.extended_links): + cards = merge_cards_based_on_label(cards + self.extended_links) + default_country = frappe.db.get_default("country") - def _doctype_contains_a_record(name): - exists = self.table_counts.get(name, None) - if not exists: - if not frappe.db.get_value('DocType', name, 'issingle'): - exists = frappe.db.count(name) - else: - exists = True - self.table_counts[name] = exists - return exists - - def _prepare_item(item): - if item.dependencies: - incomplete_dependencies = [d for d in item.dependencies if not _doctype_contains_a_record(d)] - if len(incomplete_dependencies): - item.incomplete_dependencies = incomplete_dependencies - else: - item.incomplete_dependencies = "" - - if item.onboard: - # Mark Spotlights for initial - if item.get("type") == "doctype": - name = item.get("name") - count = _doctype_contains_a_record(name) - - item["count"] = count - - # Translate label - item["label"] = _(item.label) if item.label else _(item.name) - - return item - new_data = [] - for section in cards: + for card in cards: new_items = [] - if isinstance(section.links, string_types): - links = loads(section.links) - else: - links = section.links + card = _dict(card) + + links = card.get('links', []) for item in links: item = _dict(item) @@ -262,18 +272,18 @@ class Workspace: continue # Check if user is allowed to view - if self.is_item_allowed(item.name, item.type): - prepared_item = _prepare_item(item) + if self.is_item_allowed(item.link_to, item.link_type): + prepared_item = self._prepare_item(item) new_items.append(prepared_item) if new_items: - if isinstance(section, _dict): - new_section = section.copy() + if isinstance(card, _dict): + new_card = card.copy() else: - new_section = section.as_dict().copy() - new_section["links"] = new_items - new_section["label"] = _(new_section["label"]) - new_data.append(new_section) + new_card = card.as_dict().copy() + new_card["links"] = new_items + new_card["label"] = _(new_card["label"]) + new_data.append(new_card) return new_data @@ -360,56 +370,39 @@ def get_desktop_page(page): } @frappe.whitelist() -def get_desk_sidebar_items(flatten=False, cache=True): - """Get list of sidebar items for desk - """ +def get_desk_sidebar_items(): + """Get list of sidebar items for desk""" + + # don't get domain restricted pages + blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules() + + filters = { + 'restrict_to_domain': ['in', frappe.get_active_domains()], + 'extends_another_page': 0, + 'for_user': '', + 'module': ['not in', blocked_modules] + } + + if not frappe.local.conf.developer_mode: + filters['developer_mode_only'] = '0' + + # pages sorted based on pinned to top and then by name + order_by = "pin_to_top desc, pin_to_bottom asc, name asc" + all_pages = frappe.get_all("Workspace", fields=["name", "category", "icon", "module"], + filters=filters, order_by=order_by, ignore_permissions=True) pages = [] - _cache = frappe.cache() - if cache: - pages = _cache.get_value("desk_sidebar_items", user=frappe.session.user) - if not pages or not cache: - # don't get domain restricted pages - blocked_modules = frappe.get_doc('User', frappe.session.user).get_blocked_modules() + # Filter Page based on Permission + for page in all_pages: + try: + wspace = Workspace(page.get('name'), True) + if wspace.is_page_allowed(): + pages.append(page) + page['label'] = _(page.get('name')) + except frappe.PermissionError: + pass - filters = { - 'restrict_to_domain': ['in', frappe.get_active_domains()], - 'extends_another_page': 0, - 'for_user': '', - 'module': ['not in', blocked_modules] - } - - if not frappe.local.conf.developer_mode: - filters['developer_mode_only'] = '0' - - # pages sorted based on pinned to top and then by name - order_by = "pin_to_top desc, pin_to_bottom asc, name asc" - all_pages = frappe.get_all("Desk Page", fields=["name", "category", "module"], filters=filters, order_by=order_by, ignore_permissions=True) - pages = [] - - # Filter Page based on Permission - for page in all_pages: - try: - wspace = Workspace(page.get('name'), True) - if wspace.is_page_allowed(): - pages.append(page) - except frappe.PermissionError: - pass - - _cache.set_value("desk_sidebar_items", pages, frappe.session.user) - - if flatten: - return pages - - from collections import defaultdict - sidebar_items = defaultdict(list) - - # The order will be maintained while categorizing - for page in pages: - # Translate label - page['label'] = _(page.get('name')) - sidebar_items[page["category"]].append(page) - return sidebar_items + return pages def get_table_with_counts(): counts = frappe.cache().get_value("information_schema:counts") @@ -436,8 +429,9 @@ def get_custom_doctype_list(module): out = [] for d in doctypes: out.append({ - "type": "doctype", - "name": d.name, + "type": "Link", + "link_type": "doctype", + "link_to": d.name, "label": _(d.name) }) @@ -453,17 +447,18 @@ def get_custom_report_list(module): out = [] for r in reports: out.append({ - "type": "report", + "type": "Link", + "link_type": "report", "doctype": r.ref_doctype, "is_query_report": 1 if r.report_type in ("Query Report", "Script Report", "Custom Report") else 0, "label": _(r.name), - "name": r.name + "link_to": r.name, }) return out def get_custom_workspace_for_user(page): - """Get custom page from desk_page if exists or create one + """Get custom page from workspace if exists or create one Args: page (stirng): Page name @@ -475,10 +470,10 @@ def get_custom_workspace_for_user(page): 'extends': page, 'for_user': frappe.session.user } - pages = frappe.get_list("Desk Page", filters=filters) + pages = frappe.get_list("Workspace", filters=filters) if pages: - return frappe.get_doc("Desk Page", pages[0]) - doc = frappe.new_doc("Desk Page") + return frappe.get_doc("Workspace", pages[0]) + doc = frappe.new_doc("Workspace") doc.extends = page doc.for_user = frappe.session.user return doc @@ -486,7 +481,7 @@ def get_custom_workspace_for_user(page): @frappe.whitelist() def save_customization(page, config): - """Save customizations as a separate doctype in Desk page per user + """Save customizations as a separate doctype in Workspace per user Args: page (string): Name of the page to be edited @@ -495,11 +490,12 @@ def save_customization(page, config): Returns: Boolean: Customization saving status """ - original_page = frappe.get_doc("Desk Page", page) + original_page = frappe.get_doc("Workspace", page) page_doc = get_custom_workspace_for_user(page) # Update field values page_doc.update({ + "icon": original_page.icon, "charts_label": original_page.charts_label, "cards_label": original_page.cards_label, "shortcuts_label": original_page.shortcuts_label, @@ -511,11 +507,11 @@ def save_customization(page, config): config = _dict(loads(config)) if config.charts: - page_doc.charts = prepare_widget(config.charts, "Desk Chart", "charts") + page_doc.charts = prepare_widget(config.charts, "Workspace Chart", "charts") if config.shortcuts: - page_doc.shortcuts = prepare_widget(config.shortcuts, "Desk Shortcut", "shortcuts") + page_doc.shortcuts = prepare_widget(config.shortcuts, "Workspace Shortcut", "shortcuts") if config.cards: - page_doc.cards = prepare_widget(config.cards, "Desk Card", "cards") + page_doc.build_links_table_from_cards(config.cards) # Set label page_doc.label = page + '-' + frappe.session.user @@ -589,15 +585,26 @@ def update_onboarding_step(name, field, value): """ frappe.db.set_value("Onboarding Step", name, field, value) +@frappe.whitelist() +def reset_customization(page): + """Reset workspace customizations for a user + + Args: + page (string): Name of the page to be reset + """ + page_doc = get_custom_workspace_for_user(page) + page_doc.delete() + def merge_cards_based_on_label(cards): """Merge cards with common label.""" cards_dict = {} for card in cards: - if card.label in cards_dict: - links = loads(cards_dict[card.label].links) + loads(card.links) - cards_dict[card.label].update(dict(links=dumps(links))) - cards_dict[card.label] = cards_dict.pop(card.label) + label = card.get('label') + if label in cards_dict: + links = loads(cards_dict[label].links) + loads(card.links) + cards_dict[label].update(dict(links=dumps(links))) + cards_dict[label] = cards_dict.pop(label) else: - cards_dict[card.label] = card + cards_dict[label] = card - return list(cards_dict.values()) \ No newline at end of file + return list(cards_dict.values()) diff --git a/frappe/desk/doctype/dashboard/dashboard.js b/frappe/desk/doctype/dashboard/dashboard.js index 61300b920b..c640259cf2 100644 --- a/frappe/desk/doctype/dashboard/dashboard.js +++ b/frappe/desk/doctype/dashboard/dashboard.js @@ -3,7 +3,9 @@ frappe.ui.form.on('Dashboard', { refresh: function(frm) { - frm.add_custom_button(__("Show Dashboard"), () => frappe.set_route('dashboard', frm.doc.name)); + frm.add_custom_button(__("Show Dashboard"), + () => frappe.set_route('dashboard-view', frm.doc.name) + ); if (!frappe.boot.developer_mode && frm.doc.is_standard) { frm.disable_form(); diff --git a/frappe/desk/doctype/dashboard/dashboard.py b/frappe/desk/doctype/dashboard/dashboard.py index fa03bf8f80..4e66318769 100644 --- a/frappe/desk/doctype/dashboard/dashboard.py +++ b/frappe/desk/doctype/dashboard/dashboard.py @@ -99,7 +99,7 @@ def get_non_standard_warning_message(non_standard_docs_map): def get_html(docs, doctype): html = '

      {}

      '.format(frappe.bold(doctype)) for doc in docs: - html += ''.format(doctype=doctype, doc=doc) + html += ''.format(doctype=doctype, doc=doc) html += '
      ' return html diff --git a/frappe/desk/doctype/desk_card/desk_card.js b/frappe/desk/doctype/desk_card/desk_card.js deleted file mode 100644 index cc0272cb60..0000000000 --- a/frappe/desk/doctype/desk_card/desk_card.js +++ /dev/null @@ -1,8 +0,0 @@ -// Copyright (c) 2020, Frappe Technologies and contributors -// For license information, please see license.txt - -frappe.ui.form.on('Desk Card', { - // refresh: function(frm) { - - // } -}); diff --git a/frappe/desk/doctype/desk_card/desk_card.json b/frappe/desk/doctype/desk_card/desk_card.json deleted file mode 100644 index dbcb4b0d5c..0000000000 --- a/frappe/desk/doctype/desk_card/desk_card.json +++ /dev/null @@ -1,57 +0,0 @@ -{ - "actions": [], - "creation": "2020-01-29 14:45:54.383089", - "doctype": "DocType", - "editable_grid": 1, - "engine": "InnoDB", - "field_order": [ - "label", - "column_break_2", - "hidden", - "section_break_3", - "links" - ], - "fields": [ - { - "fieldname": "links", - "fieldtype": "Code", - "label": "Links", - "options": "JSON", - "reqd": 1 - }, - { - "fieldname": "section_break_3", - "fieldtype": "Section Break" - }, - { - "fieldname": "column_break_2", - "fieldtype": "Column Break" - }, - { - "default": "0", - "fieldname": "hidden", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Hidden" - }, - { - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "reqd": 1 - } - ], - "istable": 1, - "links": [], - "modified": "2020-03-31 14:38:06.303847", - "modified_by": "Administrator", - "module": "Desk", - "name": "Desk Card", - "owner": "Administrator", - "permissions": [], - "quick_entry": 1, - "sort_field": "modified", - "sort_order": "DESC", - "track_changes": 1 -} \ No newline at end of file diff --git a/frappe/desk/doctype/desk_page/desk_page.py b/frappe/desk/doctype/desk_page/desk_page.py deleted file mode 100644 index fcc3c08135..0000000000 --- a/frappe/desk/doctype/desk_page/desk_page.py +++ /dev/null @@ -1,53 +0,0 @@ -# -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors -# For license information, please see license.txt - -from __future__ import unicode_literals -import frappe -from frappe import _ -from frappe.utils.data import validate_json_string -from frappe.modules.export_file import export_to_files -from frappe.model.document import Document - -class DeskPage(Document): - def validate(self): - self.validate_cards_json() - if (self.is_standard and not frappe.conf.developer_mode and not disable_saving_as_standard()): - frappe.throw(_("You need to be in developer mode to edit this document")) - - if self.is_default and self.name and frappe.db.exists("Desk Page", { - "name": ["!=", self.name], 'is_default': 1, 'extends': self.extends - }): - frappe.throw(_("You can only have one default page that extends a particular standard page.")) - - def validate_cards_json(self): - for card in self.cards: - try: - validate_json_string(card.links) - except frappe.ValidationError: - frappe.throw(_("Invalid JSON in card links for {0}").format(frappe.bold(card.label))) - - def on_update(self): - if disable_saving_as_standard(): - return - - if frappe.conf.developer_mode and self.is_standard: - export_to_files(record_list=[['Desk Page', self.name]], record_module=self.module) - - @staticmethod - def get_module_page_map(): - filters = { - 'extends_another_page': 0, - 'for_user': '', - } - - pages = frappe.get_all("Desk Page", fields=["name", "module"], filters=filters, as_list=1) - - return { page[1]: page[0] for page in pages if page[1] } - -def disable_saving_as_standard(): - return frappe.flags.in_install or \ - frappe.flags.in_patch or \ - frappe.flags.in_test or \ - frappe.flags.in_fixtures or \ - frappe.flags.in_migrate diff --git a/frappe/desk/doctype/module_onboarding/module_onboarding.py b/frappe/desk/doctype/module_onboarding/module_onboarding.py index 89160a60f0..8315c0b304 100644 --- a/frappe/desk/doctype/module_onboarding/module_onboarding.py +++ b/frappe/desk/doctype/module_onboarding/module_onboarding.py @@ -41,3 +41,15 @@ class ModuleOnboarding(Document): def before_export(self, doc): doc.is_complete = 0 + + def reset_onboarding(self): + frappe.only_for("Administrator") + + self.is_complete = 0 + steps = self.get_steps() + for step in steps: + step.is_complete = 0 + step.is_skipped = 0 + step.save() + + self.save() diff --git a/frappe/desk/doctype/note/note.py b/frappe/desk/doctype/note/note.py index 64a68312a5..c54689418e 100644 --- a/frappe/desk/doctype/note/note.py +++ b/frappe/desk/doctype/note/note.py @@ -17,7 +17,7 @@ class Note(Document): # expire this notification in a week (default) self.expire_notification_on = frappe.utils.add_days(self.creation, 7) - def before_print(self): + def before_print(self, settings=None): self.print_heading = self.name self.sub_heading = "" diff --git a/frappe/desk/doctype/note/note_list.js b/frappe/desk/doctype/note/note_list.js index c2758ce53b..f7f8d37dcf 100644 --- a/frappe/desk/doctype/note/note_list.js +++ b/frappe/desk/doctype/note/note_list.js @@ -7,7 +7,7 @@ frappe.listview_settings['Note'] = { if(doc.public) { return [__("Public"), "green", "public,=,Yes"]; } else { - return [__("Private"), "darkgrey", "public,=,No"]; + return [__("Private"), "gray", "public,=,No"]; } } } diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.json b/frappe/desk/doctype/onboarding_step/onboarding_step.json index 79d659b1ed..f71e821f65 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.json +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.json @@ -8,14 +8,18 @@ "field_order": [ "title", "column_break_2", - "is_mandatory", "is_complete", "is_skipped", + "description_section", + "description", + "intro_video_url", "section_break_5", "action", + "action_label", "column_break_7", "reference_document", "show_full_form", + "show_form_tour", "is_single", "reference_report", "report_reference_doctype", @@ -30,13 +34,6 @@ "video_url" ], "fields": [ - { - "default": "0", - "fieldname": "is_mandatory", - "fieldtype": "Check", - "in_list_view": 1, - "label": "Is Mandatory" - }, { "default": "0", "fieldname": "is_complete", @@ -181,10 +178,38 @@ "fieldname": "show_full_form", "fieldtype": "Check", "label": "Show Full Form?" + }, + { + "description": "Description to inform the user about any action that is going to be performed", + "fieldname": "description_section", + "fieldtype": "Section Break", + "label": "Description" + }, + { + "fieldname": "description", + "fieldtype": "Markdown Editor", + "label": "Description" + }, + { + "fieldname": "intro_video_url", + "fieldtype": "Data", + "label": "Intro Video URL" + }, + { + "fieldname": "action_label", + "fieldtype": "Data", + "label": "Action Label" + }, + { + "default": "0", + "depends_on": "eval:doc.action==\"Create Entry\" && doc.show_full_form", + "fieldname": "show_form_tour", + "fieldtype": "Check", + "label": "Show Form Tour" } ], "links": [], - "modified": "2020-08-06 12:55:20.377679", + "modified": "2020-10-30 14:54:06.646513", "modified_by": "Administrator", "module": "Desk", "name": "Onboarding Step", diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.py b/frappe/desk/doctype/onboarding_step/onboarding_step.py index 8086acbb2a..e1cc5dfba4 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.py +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.py @@ -10,7 +10,3 @@ class OnboardingStep(Document): def before_export(self, doc): doc.is_complete = 0 doc.is_skipped = 0 - - def validate(self): - if self.action == "Go to Page": - self.is_mandatory = 0 diff --git a/frappe/desk/doctype/tag/tag.py b/frappe/desk/doctype/tag/tag.py index af695a861a..7e016ee91b 100644 --- a/frappe/desk/doctype/tag/tag.py +++ b/frappe/desk/doctype/tag/tag.py @@ -25,6 +25,17 @@ def add_tag(tag, dt, dn, color=None): return tag +@frappe.whitelist() +def add_tags(tags, dt, docs, color=None): + "adds a new tag to a record, and creates the Tag master" + tags = frappe.parse_json(tags) + docs = frappe.parse_json(docs) + for doc in docs: + for tag in tags: + DocTags(dt).add(doc, tag) + + # return tag + @frappe.whitelist() def remove_tag(tag, dt, dn): "removes tag from the record" diff --git a/frappe/desk/doctype/desk_chart/__init__.py b/frappe/desk/doctype/workspace/__init__.py similarity index 100% rename from frappe/desk/doctype/desk_chart/__init__.py rename to frappe/desk/doctype/workspace/__init__.py diff --git a/frappe/desk/doctype/desk_page/test_desk_page.py b/frappe/desk/doctype/workspace/test_workspace.py similarity index 81% rename from frappe/desk/doctype/desk_page/test_desk_page.py rename to frappe/desk/doctype/workspace/test_workspace.py index 7553364744..7a3f122ee2 100644 --- a/frappe/desk/doctype/desk_page/test_desk_page.py +++ b/frappe/desk/doctype/workspace/test_workspace.py @@ -6,5 +6,5 @@ from __future__ import unicode_literals # import frappe import unittest -class TestDeskPage(unittest.TestCase): +class TestWorkspace(unittest.TestCase): pass diff --git a/frappe/desk/doctype/desk_page/desk_page.js b/frappe/desk/doctype/workspace/workspace.js similarity index 84% rename from frappe/desk/doctype/desk_page/desk_page.js rename to frappe/desk/doctype/workspace/workspace.js index 503859eb61..19d429f9f6 100644 --- a/frappe/desk/doctype/desk_page/desk_page.js +++ b/frappe/desk/doctype/workspace/workspace.js @@ -1,11 +1,14 @@ // Copyright (c) 2020, Frappe Technologies and contributors // For license information, please see license.txt -frappe.ui.form.on('Desk Page', { +frappe.ui.form.on('Workspace', { + setup: function() { + frappe.meta.get_field('Workspace Link', 'only_for').no_default = true; + }, + refresh: function(frm) { frm.enable_save(); frm.get_field("is_standard").toggle(frappe.boot.developer_mode); - frm.get_field("extends_another_page").toggle(frappe.boot.developer_mode); frm.get_field("developer_mode_only").toggle(frappe.boot.developer_mode); if (frm.doc.for_user) { diff --git a/frappe/desk/doctype/desk_page/desk_page.json b/frappe/desk/doctype/workspace/workspace.json similarity index 87% rename from frappe/desk/doctype/desk_page/desk_page.json rename to frappe/desk/doctype/workspace/workspace.json index 016d6c89d4..fff766a3bf 100644 --- a/frappe/desk/doctype/desk_page/desk_page.json +++ b/frappe/desk/doctype/workspace/workspace.json @@ -12,6 +12,7 @@ "extends", "module", "category", + "icon", "restrict_to_domain", "onboarding", "column_break_3", @@ -31,7 +32,7 @@ "shortcuts", "section_break_18", "cards_label", - "cards" + "links" ], "fields": [ { @@ -42,6 +43,7 @@ }, { "collapsible": 1, + "collapsible_depends_on": "charts", "fieldname": "section_break_2", "fieldtype": "Section Break", "label": "Dashboards" @@ -50,14 +52,14 @@ "fieldname": "charts", "fieldtype": "Table", "label": "Charts", - "options": "Desk Chart" + "options": "Workspace Chart" }, { - "depends_on": "eval:!doc.extends_another_page || !doc.is_standard", + "depends_on": "eval:!doc.extends_another_page || !doc.is_standard || frappe.boot.developer_mode", "fieldname": "shortcuts", "fieldtype": "Table", "label": "Shortcuts", - "options": "Desk Shortcut" + "options": "Workspace Shortcut" }, { "fieldname": "restrict_to_domain", @@ -79,12 +81,6 @@ "fieldname": "column_break_3", "fieldtype": "Column Break" }, - { - "fieldname": "cards", - "fieldtype": "Table", - "label": "Cards", - "options": "Desk Card" - }, { "fieldname": "category", "fieldtype": "Select", @@ -145,12 +141,14 @@ }, { "collapsible": 1, + "collapsible_depends_on": "shortcuts", "fieldname": "section_break_15", "fieldtype": "Section Break", "label": "Shortcuts" }, { "collapsible": 1, + "collapsible_depends_on": "links", "fieldname": "section_break_18", "fieldtype": "Section Break", "label": "Link Cards" @@ -177,7 +175,7 @@ "fieldtype": "Link", "in_standard_filter": 1, "label": "Extends", - "options": "Desk Page", + "options": "Workspace", "search_index": 1 }, { @@ -200,19 +198,30 @@ "label": "Hide Custom DocTypes and Reports" }, { - "default": "0", - "depends_on": "extends_another_page", - "description": "Sets the current page as default for all users", - "fieldname": "is_default", - "fieldtype": "Check", - "label": "Is Default" - } + "fieldname": "icon", + "fieldtype": "Data", + "label": "Icon" + }, + { + "fieldname": "links", + "fieldtype": "Table", + "label": "Links", + "options": "Workspace Link" + }, + { + "default": "0", + "depends_on": "extends_another_page", + "description": "Sets the current page as default for all users", + "fieldname": "is_default", + "fieldtype": "Check", + "label": "Is Default" + } ], "links": [], "modified": "2021-01-21 12:09:36.156614", "modified_by": "Administrator", "module": "Desk", - "name": "Desk Page", + "name": "Workspace", "owner": "Administrator", "permissions": [ { diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py new file mode 100644 index 0000000000..0934138821 --- /dev/null +++ b/frappe/desk/doctype/workspace/workspace.py @@ -0,0 +1,127 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2020, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +import frappe +from frappe import _ +from frappe.modules.export_file import export_to_files +from frappe.model.document import Document +from frappe.desk.utils import validate_route_conflict + +from json import loads + +class Workspace(Document): + def validate(self): + if (self.is_standard and not frappe.conf.developer_mode and not disable_saving_as_standard()): + frappe.throw(_("You need to be in developer mode to edit this document")) + validate_route_conflict(self.doctype, self.name) + + duplicate_exists = frappe.db.exists("Workspace", { + "name": ["!=", self.name], 'is_default': 1, 'extends': self.extends + }) + + if self.is_default and self.name and duplicate_exists: + frappe.throw(_("You can only have one default page that extends a particular standard page.")) + + def on_update(self): + if disable_saving_as_standard(): + return + + if frappe.conf.developer_mode and self.is_standard: + export_to_files(record_list=[['Workspace', self.name]], record_module=self.module) + + @staticmethod + def get_module_page_map(): + filters = { + 'extends_another_page': 0, + 'for_user': '', + } + + pages = frappe.get_all("Workspace", fields=["name", "module"], filters=filters, as_list=1) + + return { page[1]: page[0] for page in pages if page[1] } + + def get_link_groups(self): + cards = [] + current_card = { + "label": "Link", + "type": "Card Break", + "icon": None, + "hidden": False, + } + + card_links = [] + + for link in self.links: + link = link.as_dict() + if link.type == "Card Break": + + if card_links: + current_card['links'] = card_links + cards.append(current_card) + + current_card = link + card_links = [] + else: + card_links.append(link) + + current_card['links'] = card_links + cards.append(current_card) + + return cards + + def build_links_table_from_cards(self, config): + # Empty links table + self.links = [] + order = config.get('order') + widgets = config.get('widgets') + + for idx, name in enumerate(order): + card = widgets[name].copy() + links = loads(card.get('links')) + + self.append('links', { + "label": card.get('label'), + "type": "Card Break", + "icon": card.get('icon'), + "hidden": card.get('hidden') or False + }) + + for link in links: + self.append('links', { + "label": link.get('label'), + "type": "Link", + "link_type": link.get('link_type'), + "link_to": link.get('link_to'), + "onboard": link.get('onboard'), + "only_for": link.get('only_for'), + "dependencies": link.get('dependencies'), + "is_query_report": link.get('is_query_report') + }) + + +def disable_saving_as_standard(): + return frappe.flags.in_install or \ + frappe.flags.in_patch or \ + frappe.flags.in_test or \ + frappe.flags.in_fixtures or \ + frappe.flags.in_migrate + +def get_link_type(key): + key = key.lower() + + link_type_map = { + "doctype": "DocType", + "page": "Page", + "report": "Report" + } + + if key in link_type_map: + return link_type_map[key] + + return "DocType" + +def get_report_type(report): + report_type = frappe.get_value("Report", report, "report_type") + return report_type in ["Query Report", "Script Report", "Custom Report"] diff --git a/frappe/desk/doctype/desk_page/__init__.py b/frappe/desk/doctype/workspace_chart/__init__.py similarity index 100% rename from frappe/desk/doctype/desk_page/__init__.py rename to frappe/desk/doctype/workspace_chart/__init__.py diff --git a/frappe/desk/doctype/desk_chart/desk_chart.json b/frappe/desk/doctype/workspace_chart/workspace_chart.json similarity index 90% rename from frappe/desk/doctype/desk_chart/desk_chart.json rename to frappe/desk/doctype/workspace_chart/workspace_chart.json index 09deefd59d..0d800496af 100644 --- a/frappe/desk/doctype/desk_chart/desk_chart.json +++ b/frappe/desk/doctype/workspace_chart/workspace_chart.json @@ -26,10 +26,10 @@ ], "istable": 1, "links": [], - "modified": "2020-03-31 13:33:13.128804", + "modified": "2021-01-12 13:13:25.781925", "modified_by": "Administrator", "module": "Desk", - "name": "Desk Chart", + "name": "Workspace Chart", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/frappe/desk/doctype/workspace_chart/workspace_chart.py b/frappe/desk/doctype/workspace_chart/workspace_chart.py new file mode 100644 index 0000000000..0bb6194d2e --- /dev/null +++ b/frappe/desk/doctype/workspace_chart/workspace_chart.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class WorkspaceChart(Document): + pass diff --git a/frappe/desk/doctype/desk_shortcut/__init__.py b/frappe/desk/doctype/workspace_link/__init__.py similarity index 100% rename from frappe/desk/doctype/desk_shortcut/__init__.py rename to frappe/desk/doctype/workspace_link/__init__.py diff --git a/frappe/desk/doctype/workspace_link/workspace_link.json b/frappe/desk/doctype/workspace_link/workspace_link.json new file mode 100644 index 0000000000..010fb3f316 --- /dev/null +++ b/frappe/desk/doctype/workspace_link/workspace_link.json @@ -0,0 +1,117 @@ +{ + "actions": [], + "creation": "2020-11-16 15:30:45.784417", + "doctype": "DocType", + "editable_grid": 1, + "engine": "InnoDB", + "field_order": [ + "type", + "label", + "icon", + "hidden", + "link_details_section", + "link_type", + "link_to", + "column_break_7", + "dependencies", + "only_for", + "onboard", + "is_query_report" + ], + "fields": [ + { + "default": "Link", + "fieldname": "type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Type", + "options": "Link\nCard Break", + "reqd": 1 + }, + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "reqd": 1 + }, + { + "depends_on": "eval:doc.type == \"Card Break\"", + "fieldname": "icon", + "fieldtype": "Data", + "label": "Icon" + }, + { + "default": "0", + "depends_on": "eval:doc.type == \"Card Break\"", + "fieldname": "hidden", + "fieldtype": "Check", + "label": "Hidden" + }, + { + "depends_on": "eval:doc.type == \"Link\"", + "fieldname": "link_details_section", + "fieldtype": "Section Break", + "label": "Link Details" + }, + { + "fieldname": "link_type", + "fieldtype": "Select", + "in_list_view": 1, + "label": "Link Type", + "mandatory_depends_on": "eval:doc.type==\"Link\"", + "options": "DocType\nPage\nReport", + "read_only_depends_on": "eval:doc.type!=\"Link\"" + }, + { + "fieldname": "link_to", + "fieldtype": "Dynamic Link", + "in_list_view": 1, + "label": "Link To", + "mandatory_depends_on": "eval:doc.type==\"Link\"", + "options": "link_type", + "read_only_depends_on": "eval:doc.type!=\"Link\"" + }, + { + "fieldname": "column_break_7", + "fieldtype": "Column Break" + }, + { + "fieldname": "dependencies", + "fieldtype": "Data", + "label": "Dependencies" + }, + { + "fieldname": "only_for", + "fieldtype": "Link", + "label": "Only for ", + "options": "Country" + }, + { + "default": "0", + "fieldname": "onboard", + "fieldtype": "Check", + "label": "Onboard" + }, + { + "default": "0", + "depends_on": "eval:doc.link_type == \"Report\"", + "fieldname": "is_query_report", + "fieldtype": "Check", + "label": "Is Query Report" + } + ], + "index_web_pages_for_search": 1, + "istable": 1, + "links": [], + "modified": "2021-01-12 13:13:12.379443", + "modified_by": "Administrator", + "module": "Desk", + "name": "Workspace Link", + "owner": "Administrator", + "permissions": [], + "quick_entry": 1, + "sort_field": "modified", + "sort_order": "DESC", + "track_changes": 1 +} \ No newline at end of file diff --git a/frappe/desk/doctype/desk_card/desk_card.py b/frappe/desk/doctype/workspace_link/workspace_link.py similarity index 66% rename from frappe/desk/doctype/desk_card/desk_card.py rename to frappe/desk/doctype/workspace_link/workspace_link.py index 01d835bbfb..8a139077a6 100644 --- a/frappe/desk/doctype/desk_card/desk_card.py +++ b/frappe/desk/doctype/workspace_link/workspace_link.py @@ -1,10 +1,10 @@ # -*- coding: utf-8 -*- -# Copyright (c) 2020, Frappe Technologies and contributors +# Copyright (c) 2021, Frappe Technologies and contributors # For license information, please see license.txt from __future__ import unicode_literals # import frappe from frappe.model.document import Document -class DeskCard(Document): +class WorkspaceLink(Document): pass diff --git a/frappe/desk/doctype/workspace_shortcut/__init__.py b/frappe/desk/doctype/workspace_shortcut/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json similarity index 96% rename from frappe/desk/doctype/desk_shortcut/desk_shortcut.json rename to frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json index fbcc2dfb82..8673e93cf7 100644 --- a/frappe/desk/doctype/desk_shortcut/desk_shortcut.json +++ b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json @@ -44,6 +44,36 @@ "label": "DocType View", "options": "\nList\nReport Builder\nDashboard\nTree\nNew\nCalendar" }, + { + "fieldname": "column_break_4", + "fieldtype": "Column Break" + }, + { + "fieldname": "label", + "fieldtype": "Data", + "in_list_view": 1, + "label": "Label", + "reqd": 1 + }, + { + "depends_on": "eval:frappe.boot.developer_mode", + "fieldname": "icon", + "fieldtype": "Data", + "label": "Icon" + }, + { + "depends_on": "eval:frappe.boot.developer_mode", + "fieldname": "restrict_to_domain", + "fieldtype": "Link", + "label": "Restrict to Domain", + "options": "Domain" + }, + { + "depends_on": "eval:doc.type == \"DocType\" && frappe.boot.developer_mode", + "fieldname": "section_break_5", + "fieldtype": "Section Break", + "label": "Count Filter" + }, { "fieldname": "stats_filter", "fieldtype": "Code", @@ -54,55 +84,25 @@ "fieldname": "column_break_3", "fieldtype": "Column Break" }, - { - "description": "For example: {} Open", - "fieldname": "format", - "fieldtype": "Data", - "label": "Format" - }, - { - "depends_on": "eval:doc.type == \"DocType\" && frappe.boot.developer_mode", - "fieldname": "section_break_5", - "fieldtype": "Section Break", - "label": "Count Filter" - }, { "fieldname": "color", "fieldtype": "Color", "label": "Color" }, { - "depends_on": "eval:frappe.boot.developer_mode", - "fieldname": "icon", + "description": "For example: {} Open", + "fieldname": "format", "fieldtype": "Data", - "label": "Icon" - }, - { - "fieldname": "column_break_4", - "fieldtype": "Column Break" - }, - { - "depends_on": "eval:frappe.boot.developer_mode", - "fieldname": "restrict_to_domain", - "fieldtype": "Link", - "label": "Restrict to Domain", - "options": "Domain" - }, - { - "fieldname": "label", - "fieldtype": "Data", - "in_list_view": 1, - "label": "Label", - "reqd": 1 + "label": "Format" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2020-08-12 14:11:55.080390", + "modified": "2021-01-12 13:13:17.571324", "modified_by": "Administrator", "module": "Desk", - "name": "Desk Shortcut", + "name": "Workspace Shortcut", "owner": "Administrator", "permissions": [], "quick_entry": 1, diff --git a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py new file mode 100644 index 0000000000..d676f08b73 --- /dev/null +++ b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.py @@ -0,0 +1,10 @@ +# -*- coding: utf-8 -*- +# Copyright (c) 2021, Frappe Technologies and contributors +# For license information, please see license.txt + +from __future__ import unicode_literals +# import frappe +from frappe.model.document import Document + +class WorkspaceShortcut(Document): + pass diff --git a/frappe/desk/form/document_follow.py b/frappe/desk/form/document_follow.py index 66164948f2..f5ace4d732 100644 --- a/frappe/desk/form/document_follow.py +++ b/frappe/desk/form/document_follow.py @@ -10,7 +10,15 @@ from frappe import _ from itertools import groupby @frappe.whitelist() -def follow_document(doctype, doc_name, user, force=False): +def update_follow(doctype, doc_name, following): + if following: + return follow_document(doctype, doc_name, frappe.session.user) + else: + return unfollow_document(doctype, doc_name, frappe.session.user) + + +@frappe.whitelist() +def follow_document(doctype, doc_name, user): ''' param: Doctype name @@ -76,7 +84,6 @@ def send_email_alert(receiver, docinfo, timeline): ) def send_document_follow_mails(frequency): - ''' param: frequency for sanding mails diff --git a/frappe/desk/form/load.py b/frappe/desk/form/load.py index cacbd3c633..1f5c437330 100644 --- a/frappe/desk/form/load.py +++ b/frappe/desk/form/load.py @@ -13,7 +13,7 @@ from frappe.desk.form.document_follow import is_document_followed from frappe import _ from six.moves.urllib.parse import quote -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def getdoc(doctype, name, user=None): """ Loads a doclist for a given document. This method is called directly from the client. @@ -52,7 +52,7 @@ def getdoc(doctype, name, user=None): frappe.response.docs.append(doc) -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def getdoctype(doctype, with_parent=False, cached_timestamp=None): """load doctype""" @@ -91,13 +91,17 @@ def get_docinfo(doc=None, doctype=None, name=None): raise frappe.PermissionError frappe.response["docinfo"] = { "attachments": get_attachments(doc.doctype, doc.name), + "attachment_logs": get_comments(doc.doctype, doc.name, 'attachment'), "communications": _get_communications(doc.doctype, doc.name), 'comments': get_comments(doc.doctype, doc.name), 'total_comments': len(json.loads(doc.get('_comments') or '[]')), 'versions': get_versions(doc), "assignments": get_assignments(doc.doctype, doc.name), + "assignment_logs": get_comments(doc.doctype, doc.name, 'assignment'), "permissions": get_doc_permissions(doc), "shared": frappe.share.get_users(doc.doctype, doc.name), + "share_logs": get_comments(doc.doctype, doc.name, 'share'), + "like_logs": get_comments(doc.doctype, doc.name, 'Like'), "views": get_view_logs(doc.doctype, doc.name), "energy_point_logs": get_point_logs(doc.doctype, doc.name), "additional_timeline_content": get_additional_timeline_content(doc.doctype, doc.name), @@ -128,15 +132,27 @@ def get_communications(doctype, name, start=0, limit=20): return _get_communications(doctype, name, start, limit) -def get_comments(doctype, name): - comments = frappe.get_all('Comment', fields = ['*'], filters = dict( +def get_comments(doctype, name, comment_type='Comment'): + comment_types = [comment_type] + + if comment_type == 'share': + comment_types = ['Shared', 'Unshared'] + + elif comment_type == 'assignment': + comment_types = ['Assignment Completed', 'Assigned'] + + elif comment_type == 'attachment': + comment_types = ['Attachment', 'Attachment Removed'] + + comments = frappe.get_all('Comment', fields = ['name', 'creation', 'content', 'owner', 'comment_type'], filters=dict( reference_doctype = doctype, - reference_name = name + reference_name = name, + comment_type = ['in', comment_types] )) # convert to markdown (legacy ?) - for c in comments: - if c.comment_type == 'Comment': + if comment_type == 'Comment': + for c in comments: c.content = frappe.utils.markdown(c.content) return comments @@ -222,13 +238,12 @@ def get_communication_data(doctype, name, start=0, limit=20, after=None, fields= def get_assignments(dt, dn): cl = frappe.get_all("ToDo", - fields=['name', 'owner', 'description', 'status'], - limit= 5, - filters={ - 'reference_type': dt, - 'reference_name': dn, - 'status': ('!=', 'Cancelled'), - }) + fields=['name', 'owner', 'description', 'status'], + filters={ + 'reference_type': dt, + 'reference_name': dn, + 'status': ('!=', 'Cancelled'), + }) return cl diff --git a/frappe/desk/form/meta.py b/frappe/desk/form/meta.py index c28a40657f..d5428b1da2 100644 --- a/frappe/desk/form/meta.py +++ b/frappe/desk/form/meta.py @@ -202,13 +202,17 @@ class FormMeta(Meta): self.load_kanban_column_fields() def load_kanban_column_fields(self): - values = frappe.get_list( - 'Kanban Board', fields=['field_name'], - filters={'reference_doctype': self.name}) + try: + values = frappe.get_list( + 'Kanban Board', fields=['field_name'], + filters={'reference_doctype': self.name}) - fields = [x['field_name'] for x in values] - fields = list(set(fields)) - self.set("__kanban_column_fields", fields, as_value=True) + fields = [x['field_name'] for x in values] + fields = list(set(fields)) + self.set("__kanban_column_fields", fields, as_value=True) + except frappe.PermissionError: + # no access to kanban board + pass def get_code_files_via_hooks(hook, name): code_files = [] diff --git a/frappe/desk/leaderboard.py b/frappe/desk/leaderboard.py index e5654c853f..8d00ea9bc2 100644 --- a/frappe/desk/leaderboard.py +++ b/frappe/desk/leaderboard.py @@ -8,7 +8,8 @@ def get_leaderboards(): 'User': { 'fields': ['points'], 'method': 'frappe.desk.leaderboard.get_energy_point_leaderboard', - 'company_disabled': 1 + 'company_disabled': 1, + 'icon': 'users' } } return leaderboards diff --git a/frappe/desk/listview.py b/frappe/desk/listview.py index 1d10a13930..91dc0f3ba9 100644 --- a/frappe/desk/listview.py +++ b/frappe/desk/listview.py @@ -4,7 +4,7 @@ from __future__ import unicode_literals import frappe -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) def get_list_settings(doctype): try: return frappe.get_cached_doc("List View Settings", doctype) diff --git a/frappe/desk/moduleview.py b/frappe/desk/moduleview.py index ad696520f8..df25b77e2d 100644 --- a/frappe/desk/moduleview.py +++ b/frappe/desk/moduleview.py @@ -553,4 +553,4 @@ def get_report_list(module, is_standard="No"): "name": r.name }) - return out + return out \ No newline at end of file diff --git a/frappe/desk/page/activity/activity.css b/frappe/desk/page/activity/activity.css index 87604ff7a5..b2387135c7 100644 --- a/frappe/desk/page/activity/activity.css +++ b/frappe/desk/page/activity/activity.css @@ -10,38 +10,42 @@ cursor: pointer; } +#page-activity hr { + border-top: 1px solid var(--dark-border-color); +} + .activity-label { max-width: 100px; margin-bottom: -4px; } .date-indicator { - background:none; - font-size:12px; - vertical-align:middle; - font-weight:bold; - color:#6c7680; + background: none; + font-size: 12px; + vertical-align: middle; + font-weight: bold; + color: var(--text-muted); } .date-indicator::after { - margin:0 -4px 0 12px; - content:''; - display:inline-block; - height:8px; - width:8px; - border-radius:8px; - background: #d1d8dd; + margin: 0 -4px 0 12px; + content: ""; + display: inline-block; + height: 8px; + width: 8px; + border-radius: 8px; + background: var(--dark-border-color); } .date-indicator.blue { - color: #5e64ff; + color: var(--primary); } .date-indicator.blue::after { - background: #5e64ff; + background: var(--primary); } .activity-message { - border-left: 1px solid #d1d8dd; + border-left: 1px solid var(--dark-border-color); padding: 15px; padding-right: 30px; } @@ -57,14 +61,14 @@ } #page-activity .octicon-heart { - color: #ff5858; + color: var(--red-500); margin: 0px 5px; } .heatmap { - padding-top: 30px; + padding-top: 30px; } .heatmap svg { margin: auto; -} \ No newline at end of file +} diff --git a/frappe/desk/page/activity/activity.js b/frappe/desk/page/activity/activity.js index 4ec54e1b2d..39de414122 100644 --- a/frappe/desk/page/activity/activity.js +++ b/frappe/desk/page/activity/activity.js @@ -3,7 +3,7 @@ frappe.provide("frappe.activity"); -frappe.pages['activity'].on_page_load = function(wrapper) { +frappe.pages['activity'].on_page_load = function (wrapper) { var me = this; frappe.ui.make_app_page({ @@ -14,7 +14,7 @@ frappe.pages['activity'].on_page_load = function(wrapper) { me.page = wrapper.page; me.page.set_title(__("Activity")); - frappe.model.with_doctype("Communication", function() { + frappe.model.with_doctype("Communication", function () { me.page.list = new frappe.views.Activity({ doctype: 'Communication', parent: wrapper @@ -23,7 +23,7 @@ frappe.pages['activity'].on_page_load = function(wrapper) { frappe.activity.render_heatmap(me.page); - me.page.main.on("click", ".activity-message", function() { + me.page.main.on("click", ".activity-message", function () { var link_doctype = $(this).attr("data-link-doctype"), link_name = $(this).attr("data-link-name"), doctype = $(this).attr("data-doctype"), @@ -47,13 +47,13 @@ frappe.pages['activity'].on_page_load = function(wrapper) { }); // Build Report Button - if(frappe.boot.user.can_get_report.indexOf("Feed")!=-1) { - this.page.add_menu_item(__('Build Report'), function() { + if (frappe.boot.user.can_get_report.indexOf("Feed") != -1) { + this.page.add_menu_item(__('Build Report'), function () { frappe.set_route("List", "Feed", "Report"); }, 'fa fa-th') } - this.page.add_menu_item(__('Activity Log'), function() { + this.page.add_menu_item(__('Activity Log'), function () { frappe.route_options = { "user": frappe.session.user } @@ -62,43 +62,43 @@ frappe.pages['activity'].on_page_load = function(wrapper) { }, 'fa fa-th'); }; -frappe.pages['activity'].on_page_show = function() { +frappe.pages['activity'].on_page_show = function () { frappe.breadcrumbs.add("Desk"); } frappe.activity.last_feed_date = false; frappe.activity.Feed = Class.extend({ - init: function(row, data) { + init: function (row, data) { this.scrub_data(data); this.add_date_separator(row, data); - if(!data.add_class) + if (!data.add_class) data.add_class = "label-default"; data.link = ""; if (data.link_doctype && data.link_name) { - data.link = frappe.format(data.link_name, {fieldtype: "Link", options: data.link_doctype}, - {label: __(data.link_doctype) + " " + __(data.link_name)}); + data.link = frappe.format(data.link_name, { fieldtype: "Link", options: data.link_doctype }, + { label: __(data.link_doctype) + " " + __(data.link_name) }); - } else if (data.feed_type==="Comment" && data.comment_type==="Comment") { + } else if (data.feed_type === "Comment" && data.comment_type === "Comment") { // hack for backward compatiblity data.link_doctype = data.reference_doctype; data.link_name = data.reference_name; data.reference_doctype = "Communication"; data.reference_name = data.name; - data.link = frappe.format(data.link_name, {fieldtype: "Link", options: data.link_doctype}, - {label: __(data.link_doctype) + " " + __(data.link_name)}); + data.link = frappe.format(data.link_name, { fieldtype: "Link", options: data.link_doctype }, + { label: __(data.link_doctype) + " " + __(data.link_name) }); } else if (data.reference_doctype && data.reference_name) { - data.link = frappe.format(data.reference_name, {fieldtype: "Link", options: data.reference_doctype}, - {label: __(data.reference_doctype) + " " + __(data.reference_name)}); + data.link = frappe.format(data.reference_name, { fieldtype: "Link", options: data.reference_doctype }, + { label: __(data.reference_doctype) + " " + __(data.reference_name) }); } $(row) .append(frappe.render_template("activity_row", data)) .find("a").addClass("grey"); }, - scrub_data: function(data) { + scrub_data: function (data) { data.by = frappe.user.full_name(data.owner); data.avatar = frappe.avatar(data.owner); @@ -114,22 +114,23 @@ frappe.activity.Feed = Class.extend({ data.when = comment_when(data.creation); data.feed_type = data.comment_type || data.communication_medium; }, - add_date_separator: function(row, data) { + + add_date_separator: function (row, data) { var date = frappe.datetime.str_to_obj(data.creation); var last = frappe.activity.last_feed_date; - if((last && frappe.datetime.obj_to_str(last) != frappe.datetime.obj_to_str(date)) || (!last)) { + if ((last && frappe.datetime.obj_to_str(last) != frappe.datetime.obj_to_str(date)) || (!last)) { var diff = frappe.datetime.get_day_diff(frappe.datetime.get_today(), frappe.datetime.obj_to_str(date)); var pdate; - if(diff < 1) { + if (diff < 1) { pdate = 'Today'; - } else if(diff < 2) { + } else if (diff < 2) { pdate = 'Yesterday'; } else { pdate = frappe.datetime.global_date_format(date); } data.date_sep = pdate; - data.date_class = pdate=='Today' ? "date-indicator blue" : "date-indicator"; + data.date_class = pdate == 'Today' ? "date-indicator blue" : "date-indicator"; } else { data.date_sep = null; data.date_class = ""; @@ -138,29 +139,29 @@ frappe.activity.Feed = Class.extend({ } }); -frappe.activity.render_heatmap = function(page) { - var me = this; +frappe.activity.render_heatmap = function (page) { $('
      \
      \
      ').prependTo(page.main); frappe.call({ method: "frappe.desk.page.activity.activity.get_heatmap_data", - callback: function(r) { - if(r.message) { - var heatmap = new frappe.Chart(".heatmap", { + callback: function (r) { + if (r.message) { + new frappe.Chart(".heatmap", { type: 'heatmap', start: new Date(moment().subtract(1, 'year').toDate()), countLabel: "actions", - discreteDomains: 0, + discreteDomains: 1, + radius: 3, // default 0 data: { 'dataPoints': r.message } }); } } - }) -} + }); +}; frappe.views.Activity = class Activity extends frappe.views.BaseList { constructor(opts) { @@ -181,6 +182,10 @@ frappe.views.Activity = class Activity extends frappe.views.BaseList { // } + setup_view_menu() { + // + } + setup_sort_selector() { } diff --git a/frappe/desk/page/backups/backups.css b/frappe/desk/page/backups/backups.css new file mode 100644 index 0000000000..13f093e0b1 --- /dev/null +++ b/frappe/desk/page/backups/backups.css @@ -0,0 +1,13 @@ +.download-backups { + font-size: var(--text-base); +} + +.download-backup-card { + display: block; + text-decoration: none; +} + +.download-backup-card:hover { + box-shadow: var(--shadow-md); + text-decoration: none; +} diff --git a/frappe/desk/page/backups/backups.html b/frappe/desk/page/backups/backups.html index 0056b6814b..e63481487c 100644 --- a/frappe/desk/page/backups/backups.html +++ b/frappe/desk/page/backups/backups.html @@ -1,32 +1,20 @@ - - - - - - - - - - - {% for f in files %} - - - - - - {% endfor %} - -
      - {{ _("Date") }} - - {{ _("File") }} - - {{ _("Size") }} -
      - {{ f[1] }} - - {{ f[0] }} - - {{ f[2] }} -
      +
      + {% for f in files %} + + {% endfor %} +
      \ No newline at end of file diff --git a/frappe/desk/page/backups/backups.py b/frappe/desk/page/backups/backups.py index 386af70a4e..eaa0c65143 100644 --- a/frappe/desk/page/backups/backups.py +++ b/frappe/desk/page/backups/backups.py @@ -9,7 +9,7 @@ import datetime def get_context(context): def get_time(path): dt = os.path.getmtime(path) - return convert_utc_to_user_timezone(datetime.datetime.utcfromtimestamp(dt)).strftime('%Y-%m-%d %H:%M') + return convert_utc_to_user_timezone(datetime.datetime.utcfromtimestamp(dt)).strftime('%a %b %d %H:%M %Y') def get_size(path): size = os.path.getsize(path) diff --git a/frappe/desk/page/leaderboard/leaderboard.css b/frappe/desk/page/leaderboard/leaderboard.css index a3cb4d09c4..d15b4ffcd4 100644 --- a/frappe/desk/page/leaderboard/leaderboard.css +++ b/frappe/desk/page/leaderboard/leaderboard.css @@ -52,6 +52,34 @@ } .rank { - max-width: 75px; + max-width: 100px; } +.leaderboard .result { + border-top: 1px solid var(--border-color); +} + +.leaderboard .list-item { + padding-left: 45px; +} + +.leaderboard .list-item_content { + padding-right: 60px; +} + +.leaderboard-sidebar { + padding-left: 0; + position: fixed; +} + +.leaderboard-list { + padding: var(-padding-sm) 0; + min-height: 70vh; +} + +.leaderboard-empty-state { + align-items: center; + height: 70vh; + justify-content: center; + display: flex; +} diff --git a/frappe/desk/page/leaderboard/leaderboard.js b/frappe/desk/page/leaderboard/leaderboard.js index d6dc262ef3..825e1d959b 100644 --- a/frappe/desk/page/leaderboard/leaderboard.js +++ b/frappe/desk/page/leaderboard/leaderboard.js @@ -14,11 +14,13 @@ class Leaderboard { frappe.ui.make_app_page({ parent: parent, title: __("Leaderboard"), - single_column: false + single_column: false, + card_layout: true, }); + this.parent = parent; this.page = this.parent.page; - this.page.sidebar.html(``); + this.page.sidebar.html(`
        `); this.$sidebar_list = this.page.sidebar.find('ul'); this.get_leaderboard_config(); @@ -79,7 +81,8 @@ class Leaderboard { this.$graph_area = this.$container.find(".leaderboard-graph"); this.doctypes.map(doctype => { - this.get_sidebar_item(doctype).appendTo(this.$sidebar_list); + const icon = this.leaderboard_config[doctype].icon; + this.get_sidebar_item(doctype, icon).appendTo(this.$sidebar_list); }); this.setup_leaderboard_fields(); @@ -147,7 +150,7 @@ class Leaderboard { fieldname: 'selected_date_range', placeholder: __("Date Range"), default: [frappe.datetime.month_start(), frappe.datetime.now_date()], - input_class: 'input-sm', + input_class: 'input-xs', reqd: 1, change: () => { this.selected_date_range = date_field.get_value(); @@ -163,7 +166,7 @@ class Leaderboard { this.$sidebar_list.on("click", "li", (e)=> { let $li = $(e.currentTarget); - let doctype = $li.find("span").attr("doctype-value"); + let doctype = $li.find(".doctype-text").attr("doctype-value"); this.options.selected_company = frappe.defaults.get_default("company"); this.options.selected_doctype = doctype; @@ -181,8 +184,8 @@ class Leaderboard { $(this.parent).find("[data-original-title=Company]").show(); } - this.$sidebar_list.find("li").removeClass("active"); - $li.addClass("active"); + this.$sidebar_list.find("li").removeClass("active selected"); + $li.addClass("active selected"); frappe.set_route("leaderboard", this.options.selected_doctype); this.make_request(); @@ -193,7 +196,7 @@ class Leaderboard { this.$search_box = $(``); $(this.parent).find(".page-form").append(this.$search_box); @@ -236,21 +239,16 @@ class Leaderboard { let graph_items = results.slice(0, 10); this.$graph_area.show().empty(); - let args = { + + const custom_options = { data: { - datasets: [ - { - values: graph_items.map(d => d.value) - } - ], + datasets: [{ values: graph_items.map(d => d.value) }], labels: graph_items.map(d => d.name) }, - colors: ["light-green"], format_tooltip_x: d => d[this.options.selected_filter_item], - type: "bar", height: 140 }; - new frappe.Chart(".leaderboard-graph", args); + frappe.utils.make_chart('.leaderboard-graph', custom_options); notify(this, r); }); @@ -263,7 +261,7 @@ class Leaderboard { frappe.utils.setup_search($(me.parent), ".list-item-container", ".list-id"); } else { me.$graph_area.hide(); - me.message = __("No items found."); + me.message = __("No Items Found"); me.$container.find(".leaderboard-list").html(me.render_list_view()); } } @@ -306,7 +304,7 @@ class Leaderboard { const html = `
        -
        ${filters}
        +
        ${filters}
        `; return html; } @@ -330,14 +328,16 @@ class Leaderboard { } render_message() { - - let html = - `
        -
        -

        No Item found

        -
        -
        `; - + const display_class = this.message ? '' : 'hide'; + let html = `
        +
        + Empty State +
        ${this.message}
        +
        +
        `; return html; } @@ -348,13 +348,13 @@ class Leaderboard { return fieldname === this.options.selected_filter_item; })); - const link = `#Form/${this.options.selected_doctype}/${item.name}`; + const link = `/app/${frappe.router.slug(this.options.selected_doctype)}/${item.name}`; const name_html = item.formatted_name ? `${item.formatted_name}` : ` ${item.name} `; const html = `
        -
        +
        ${index}
        @@ -368,10 +368,13 @@ class Leaderboard { return html; } - get_sidebar_item(item) { - return $(`
      • - - ${ __(item) } + get_sidebar_item(item, icon) { + let icon_html = icon ? frappe.utils.icon(icon, 'md') : ''; + return $(`
      • + ${icon_html} + + ${ __(item) } +
      • `); } diff --git a/frappe/desk/page/setup_wizard/setup_wizard.js b/frappe/desk/page/setup_wizard/setup_wizard.js index 9a950a694d..c39e7f52c0 100644 --- a/frappe/desk/page/setup_wizard/setup_wizard.js +++ b/frappe/desk/page/setup_wizard/setup_wizard.js @@ -9,36 +9,34 @@ frappe.setup = { utils: {}, domains: [], - on: function(event, fn) { - if(!frappe.setup.events[event]) { + on: function (event, fn) { + if (!frappe.setup.events[event]) { frappe.setup.events[event] = []; } frappe.setup.events[event].push(fn); }, - add_slide: function(slide) { + add_slide: function (slide) { frappe.setup.slides.push(slide); }, - remove_slide: function(slide_name){ + remove_slide: function (slide_name) { frappe.setup.slides = frappe.setup.slides.filter((slide) => slide.name !== slide_name); }, - run_event: function(event) { - $.each(frappe.setup.events[event] || [], function(i, fn) { + run_event: function (event) { + $.each(frappe.setup.events[event] || [], function (i, fn) { fn(); }); } } -frappe.pages['setup-wizard'].on_page_load = function(wrapper) { +frappe.pages['setup-wizard'].on_page_load = function (wrapper) { let requires = (frappe.boot.setup_wizard_requires || []); - - - frappe.require(requires, function() { + frappe.require(requires, function () { frappe.call({ method: "frappe.desk.page.setup_wizard.setup_wizard.load_languages", freeze: true, - callback: function(r) { + callback: function (r) { frappe.setup.data.lang = r.message; frappe.setup.run_event("before_load"); @@ -47,12 +45,13 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) { slides: frappe.setup.slides, slide_class: frappe.setup.SetupWizardSlide, unidirectional: 1, + done_state: 1, before_load: ($footer) => { $footer.find('.next-btn').removeClass('btn-default') .addClass('btn-primary'); $footer.find('.text-right').prepend( - $(` - ${__("Complete Setup")}`)); + $(``)); } } @@ -60,7 +59,7 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) { frappe.setup.run_event("after_load"); // frappe.wizard.values = test_values_edu; let route = frappe.get_route(); - if(route) { + if (route) { frappe.wizard.show_slide(route[1]); } } @@ -68,16 +67,16 @@ frappe.pages['setup-wizard'].on_page_load = function(wrapper) { }); }; -frappe.pages['setup-wizard'].on_page_show = function(wrapper) { - if(frappe.get_route()[1]) { +frappe.pages['setup-wizard'].on_page_show = function () { + if (frappe.get_route()[1]) { frappe.wizard && frappe.wizard.show_slide(frappe.get_route()[1]); } }; -frappe.setup.on("before_load", function() { +frappe.setup.on("before_load", function () { // load slides frappe.setup.slides_settings.forEach((s) => { - if(!(s.name==='user' && frappe.boot.developer_mode)) { + if (!(s.name === 'user' && frappe.boot.developer_mode)) { // if not user slide with developer mode frappe.setup.add_slide(s); } @@ -89,8 +88,8 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { super(args); $.extend(this, args); - this.welcomed = true; this.page_name = "setup-wizard"; + this.welcomed = true; frappe.set_route("setup-wizard/0"); } @@ -113,7 +112,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { handle_enter_press(e) { if (e.which === frappe.ui.keyCode.ENTER) { var $target = $(e.target); - if($target.hasClass('prev-btn')) { + if ($target.hasClass('prev-btn')) { $target.trigger('click'); } else { this.container.find('.next-btn').trigger('click'); @@ -123,7 +122,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { } before_show_slide() { - if(!this.welcomed) { + if (!this.welcomed) { frappe.set_route(this.page_name); return false; } @@ -142,10 +141,10 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { show_hide_prev_next(id) { super.show_hide_prev_next(id); - if (id + 1 === this.slides.length){ + if (id + 1 === this.slides.length) { this.$next_btn.removeClass("btn-primary").hide(); this.$complete_btn.addClass("btn-primary").show() - .on('click', this.action_on_complete.bind(this)); + .on('click', () => this.action_on_complete()); } else { this.$next_btn.addClass("btn-primary").show(); @@ -155,7 +154,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { refresh_slides() { // For Translations, etc. - if(this.in_refresh_slides || !this.current_slide.set_values()) { + if (this.in_refresh_slides || !this.current_slide.set_values()) { return; } this.in_refresh_slides = true; @@ -171,7 +170,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { // re-render all slide, only remake made slides $.each(this.slide_dict, (id, slide) => { - if(slide.made) { + if (slide.made) { this.made_slide_ids.push(id); } }); @@ -194,30 +193,30 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { return frappe.call({ method: "frappe.desk.page.setup_wizard.setup_wizard.setup_complete", - args: {args: this.values}, + args: { args: this.values }, callback: (r) => { - if(r.message.status === 'ok') { + if (r.message.status === 'ok') { this.post_setup_success(); - } else if(r.message.fail !== undefined) { + } else if (r.message.fail !== undefined) { this.abort_setup(r.message.fail); } }, - error: this.abort_setup.bind(this, "Error in setup", true) + error: this.abort_setup("Error in setup", true) }); } post_setup_success() { this.set_setup_complete_message(__("Setup Complete"), __("Refreshing...")); - if(frappe.setup.welcome_page) { + if (frappe.setup.welcome_page) { localStorage.setItem("session_last_route", frappe.setup.welcome_page); } - setTimeout(function() { + setTimeout(function () { // Reload - window.location.href = '/desk'; + window.location.href = '/app'; }, 2000); } - abort_setup(fail_msg, error=false) { + abort_setup(fail_msg) { this.$working_state.find('.state-icon-container').html(''); fail_msg = fail_msg ? fail_msg : __("Failed to complete setup"); @@ -231,12 +230,12 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { listen_for_setup_stages() { frappe.realtime.on("setup_task", (data) => { // console.log('data', data); - if(data.stage_status) { + if (data.stage_status) { // .html('Process '+ data.progress[0] + ' of ' + data.progress[1] + ': ' + data.stage_status); this.update_setup_message(data.stage_status); - this.set_setup_load_percent((data.progress[0]+1)/data.progress[1] * 100); + this.set_setup_load_percent((data.progress[0] + 1) / data.progress[1] * 100); } - if(data.fail_msg) { + if (data.fail_msg) { this.abort_setup(data.fail_msg); } }) @@ -248,8 +247,8 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { get_setup_slides_filtered_by_domain() { var filtered_slides = []; - frappe.setup.slides.forEach(function(slide) { - if(frappe.setup.domains) { + frappe.setup.slides.forEach(function (slide) { + if (frappe.setup.domains) { let active_domains = frappe.setup.domains; if (!slide.domains || slide.domains.filter(d => active_domains.includes(d)).length > 0) { @@ -277,8 +276,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { } attach_abort_button() { - this.$abort_btn = $(``); + this.$abort_btn = $(``); this.$working_state.find('.content').append(this.$abort_btn); this.$abort_btn.on('click', () => { @@ -290,18 +288,18 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { this.$abort_btn.hide(); } - get_message(title, message="") { - const loading_html = `
        -
        -
        + get_message(title, message = "") { + const loading_html = `
        +
        +
        `; - return $(`
        + return $(`
        -

        ${title}

        +

        ${title}

        ${loading_html}
        -

        ${message}

        +

        ${message}

        `); } @@ -312,7 +310,7 @@ frappe.setup.SetupWizard = class SetupWizard extends frappe.ui.Slides { } set_setup_load_percent(percent) { - this.$working_state.find('.progress-bar').css({"width": percent + "%"}); + this.$working_state.find('.progress-bar').css({ "width": percent + "%" }); } }; @@ -327,13 +325,13 @@ frappe.setup.SetupWizardSlide = class SetupWizardSlide extends frappe.ui.Slide { this.reset_action_button_state(); } - set_init_values () { + set_init_values() { var me = this; // set values from frappe.setup.values - if(frappe.wizard.values && this.fields) { - this.fields.forEach(function(f) { + if (frappe.wizard.values && this.fields) { + this.fields.forEach(function (f) { var value = frappe.wizard.values[f.fieldname]; - if(value) { + if (value) { me.get_field(f.fieldname).set_input(value); } }); @@ -354,11 +352,13 @@ frappe.setup.slides_settings = [ // help: __("Let's prepare the system for first use."), fields: [ - { fieldname: "language", label: __("Your Language"), - fieldtype: "Select", reqd: 1} + { + fieldname: "language", label: __("Your Language"), + fieldtype: "Select", reqd: 1 + } ], - onload: function(slide) { + onload: function (slide) { this.setup_fields(slide); var language_field = slide.get_field("language"); @@ -372,7 +372,7 @@ frappe.setup.slides_settings = [ moment.locale("en"); }, - setup_fields: function(slide) { + setup_fields: function (slide) { frappe.setup.utils.setup_language_field(slide); frappe.setup.utils.bind_language_events(slide); }, @@ -385,25 +385,31 @@ frappe.setup.slides_settings = [ icon: "fa fa-flag", // help: __("Select your Country, Time Zone and Currency"), fields: [ - { fieldname: "country", label: __("Your Country"), reqd:1, - fieldtype: "Select" }, + { + fieldname: "country", label: __("Your Country"), reqd: 1, + fieldtype: "Select" + }, { fieldtype: "Section Break" }, - { fieldname: "timezone", label: __("Time Zone"), reqd:1, - fieldtype: "Select" }, + { + fieldname: "timezone", label: __("Time Zone"), reqd: 1, + fieldtype: "Select" + }, { fieldtype: "Column Break" }, - { fieldname: "currency", label: __("Currency"), reqd:1, - fieldtype: "Select" } + { + fieldname: "currency", label: __("Currency"), reqd: 1, + fieldtype: "Select" + } ], - onload: function(slide) { - if(frappe.setup.data.regional_data) { + onload: function (slide) { + if (frappe.setup.data.regional_data) { this.setup_fields(slide); } else { frappe.setup.utils.load_regional_data(slide, this.setup_fields); } }, - setup_fields: function(slide) { + setup_fields: function (slide) { frappe.setup.utils.setup_region_fields(slide); frappe.setup.utils.bind_region_events(slide); } @@ -415,24 +421,30 @@ frappe.setup.slides_settings = [ title: __("The First User: You"), icon: "fa fa-user", fields: [ - { "fieldtype":"Attach Image", "fieldname":"attach_user_image", - label: __("Attach Your Picture"), is_private: 0, align: 'center'}, - { "fieldname": "full_name", "label": __("Full Name"), "fieldtype": "Data", - reqd:1}, - { "fieldname": "email", "label": __("Email Address") + ' (' + __("Will be your login ID") + ')', - "fieldtype": "Data", "options":"Email"}, + { + "fieldtype": "Attach Image", "fieldname": "attach_user_image", + label: __("Attach Your Picture"), is_private: 0, align: 'center' + }, + { + "fieldname": "full_name", "label": __("Full Name"), "fieldtype": "Data", + reqd: 1 + }, + { + "fieldname": "email", "label": __("Email Address") + ' (' + __("Will be your login ID") + ')', + "fieldtype": "Data", "options": "Email" + }, { "fieldname": "password", "label": __("Password"), "fieldtype": "Password" } ], // help: __('The first user will become the System Manager (you can change this later).'), - onload: function(slide) { - if(frappe.session.user!=="Administrator") { + onload: function (slide) { + if (frappe.session.user !== "Administrator") { slide.form.fields_dict.email.$wrapper.toggle(false); slide.form.fields_dict.password.$wrapper.toggle(false); // remove password field delete slide.form.fields_dict.password; - if(frappe.boot.user.first_name || frappe.boot.user.last_name) { + if (frappe.boot.user.first_name || frappe.boot.user.last_name) { slide.form.fields_dict.full_name.set_input( [frappe.boot.user.first_name, frappe.boot.user.last_name].join(' ').trim()); } @@ -440,7 +452,7 @@ frappe.setup.slides_settings = [ var user_image = frappe.get_cookie("user_image"); var $attach_user_image = slide.form.fields_dict.attach_user_image.$wrapper; - if(user_image) { + if (user_image) { $attach_user_image.find(".missing-image").toggle(false); $attach_user_image.find("img").attr("src", decodeURIComponent(user_image)); $attach_user_image.find(".img-container").toggle(true); @@ -457,11 +469,11 @@ frappe.setup.slides_settings = [ } }, - setup_fields: function(slide) { - if(frappe.setup.data.full_name) { + setup_fields: function (slide) { + if (frappe.setup.data.full_name) { slide.form.fields_dict.full_name.set_input(frappe.setup.data.full_name); } - if(frappe.setup.data.email) { + if (frappe.setup.data.email) { let email = frappe.setup.data.email; slide.form.fields_dict.email.set_input(email); if (frappe.get_gravatar(email, 200)) { @@ -476,21 +488,21 @@ frappe.setup.slides_settings = [ ]; frappe.setup.utils = { - load_regional_data: function(slide, callback) { + load_regional_data: function (slide, callback) { frappe.call({ - method:"frappe.geo.country_info.get_country_timezone_info", - callback: function(data) { + method: "frappe.geo.country_info.get_country_timezone_info", + callback: function (data) { frappe.setup.data.regional_data = data.message; callback(slide); } }); }, - load_user_details: function(slide, callback) { + load_user_details: function (slide, callback) { frappe.call({ method: "frappe.desk.page.setup_wizard.setup_wizard.load_user_details", freeze: true, - callback: function(r) { + callback: function (r) { frappe.setup.data.full_name = r.message.full_name; frappe.setup.data.email = r.message.email; callback(slide); @@ -498,13 +510,13 @@ frappe.setup.utils = { }) }, - setup_language_field: function(slide) { + setup_language_field: function (slide) { var language_field = slide.get_field("language"); language_field.df.options = frappe.setup.data.lang.languages; language_field.refresh(); }, - setup_region_fields: function(slide) { + setup_region_fields: function (slide) { /* Set a slide's country, timezone and currency fields */ @@ -516,33 +528,34 @@ frappe.setup.utils = { .add_options([""].concat(Object.keys(data.country_info).sort())); slide.get_input("currency").empty() - .add_options(frappe.utils.unique([""].concat($.map(data.country_info, - function(opts, country) { return opts.currency; }))).sort()); + .add_options(frappe.utils.unique([""].concat( + $.map(data.country_info, opts => opts.currency) + )).sort()); slide.get_input("timezone").empty() .add_options([""].concat(data.all_timezones)); // set values if present - if(frappe.wizard.values.country) { + if (frappe.wizard.values.country) { country_field.set_input(frappe.wizard.values.country); } else if (data.default_country) { country_field.set_input(data.default_country); } - if(frappe.wizard.values.currency) { + if (frappe.wizard.values.currency) { slide.get_field("currency").set_input(frappe.wizard.values.currency); } - if(frappe.wizard.values.timezone) { + if (frappe.wizard.values.timezone) { slide.get_field("timezone").set_input(frappe.wizard.values.timezone); } }, - bind_language_events: function(slide) { - slide.get_input("language").unbind("change").on("change", function() { - clearTimeout (slide.language_call_timeout); - slide.language_call_timeout = setTimeout (() => { + bind_language_events: function (slide) { + slide.get_input("language").unbind("change").on("change", function () { + clearTimeout(slide.language_call_timeout); + slide.language_call_timeout = setTimeout(() => { var lang = $(this).val() || "English"; frappe._messages = {}; frappe.call({ @@ -551,7 +564,7 @@ frappe.setup.utils = { args: { language: lang }, - callback: function(r) { + callback: function () { frappe.setup._from_load_messages = true; frappe.wizard.refresh_slides(); } @@ -560,11 +573,11 @@ frappe.setup.utils = { }); }, - bind_region_events: function(slide) { + bind_region_events: function (slide) { /* Bind a slide's country, timezone and currency fields */ - slide.get_input("country").on("change", function() { + slide.get_input("country").on("change", function () { var country = slide.get_input("country").val(); var $timezone = slide.get_input("timezone"); var data = frappe.setup.data.regional_data; @@ -572,7 +585,7 @@ frappe.setup.utils = { $timezone.empty(); // add country specific timezones first - if(country) { + if (country) { var timezone_list = data.country_info[country].timezones || []; $timezone.add_options(timezone_list.sort()); slide.get_field("currency").set_input(data.country_info[country].currency); @@ -589,16 +602,16 @@ frappe.setup.utils = { || "dd-mm-yyyy"); }); - slide.get_input("currency").on("change", function() { + slide.get_input("currency").on("change", function () { var currency = slide.get_input("currency").val(); if (!currency) return; - frappe.model.with_doc("Currency", currency, function() { + frappe.model.with_doc("Currency", currency, function () { frappe.provide("locals.:Currency." + currency); var currency_doc = frappe.model.get_doc("Currency", currency); var number_format = currency_doc.number_format; - if (number_format==="#.###") { + if (number_format === "#.###") { number_format = "#.###,##"; - } else if (number_format==="#,###") { + } else if (number_format === "#,###") { number_format = "#,###.##" } diff --git a/frappe/desk/page/translation_tool/translation_tool.css b/frappe/desk/page/translation_tool/translation_tool.css index 52d34777dd..9603a4ce35 100644 --- a/frappe/desk/page/translation_tool/translation_tool.css +++ b/frappe/desk/page/translation_tool/translation_tool.css @@ -5,6 +5,7 @@ cursor: pointer; overflow: hidden; } + .translation-item:hover { background-color: #fafbfc; } @@ -19,8 +20,9 @@ } .translation-tool { - border: 0px 1px 1px 1px solid #d1d8dd; + display: flex; width: 100%; + padding: 0; height: 72vh; } diff --git a/frappe/desk/page/translation_tool/translation_tool.js b/frappe/desk/page/translation_tool/translation_tool.js index 892bab32ce..b3f0c032e3 100644 --- a/frappe/desk/page/translation_tool/translation_tool.js +++ b/frappe/desk/page/translation_tool/translation_tool.js @@ -2,7 +2,8 @@ frappe.pages['translation-tool'].on_page_load = function(wrapper) { var page = frappe.ui.make_app_page({ parent: wrapper, title: 'Translation Tool', - single_column: true + single_column: true, + card_layout: true, }); frappe.translation_tool = new TranslationTool(page); @@ -250,7 +251,7 @@ class TranslationTool { set_status(translation) { this.form.get_field('header').$wrapper.find('.translation-status').html(` - + ${this.get_indicator_status_text(translation)} `); @@ -347,7 +348,7 @@ class TranslationTool { ) .then(() => { frappe.dom.unfreeze(); - frappe.show_alert(__('Successfully Submitted!')); + frappe.show_alert({ message: __('Successfully Submitted!'), indicator: 'success'}); this.edited_translations = {}; this.update_header(); this.fetch_messages_then_render(); @@ -426,10 +427,13 @@ class TranslationTool { let edited_translations_count = Object.keys(this.edited_translations) .length; if (edited_translations_count) { - this.page.set_indicator( - __('{0} translations pending', [edited_translations_count]), - 'orange' - ); + let message = ''; + if (edited_translations_count == 1) { + message = __('{0} translation pending', [edited_translations_count]); + } else { + message = __('{0} translations pending', [edited_translations_count]); + } + this.page.set_indicator(message, 'orange'); } else { this.page.set_indicator(''); } diff --git a/frappe/desk/page/user_profile/user_profile.css b/frappe/desk/page/user_profile/user_profile.css index c05a52ada2..9bcfc3394a 100644 --- a/frappe/desk/page/user_profile/user_profile.css +++ b/frappe/desk/page/user_profile/user_profile.css @@ -1,121 +1,30 @@ -.user-image-container { - margin-top: 7px; - padding-bottom: 100%; +.recent-activity .new-timeline { + padding-top: 0; } -.user-image-container .standard-image { - font-size: 72px; +.recent-activity .new-timeline:before { + top: 25px; } -.profile-details { - margin: -5px 5px; +.recent-activity-title { + font-weight: 700; + font-size: var(--text-xl); + color: var(--text-color); } -.profile-links { - margin: 30px 5px; +.recent-activity .recent-activity-footer { + margin-left: calc(var(--timeline-left-padding) + var(--timeline-item-left-margin)); + max-width: var(--timeline-content-max-width); } -.user-initial { - font-size: 72px; -} - -.chart-column-container{ - border-bottom: 1px solid #d1d8dd; - margin: 5px 0; -} - -.chart-container text.title { - text-transform: uppercase; - font-size: 11px; -} - -.heatmap-container { - height: 170px -} - -.performance-heatmap { - width: 80%; - display: inline-block; -} - -.performance-heatmap .chart-container { - margin-left: 30px; -} - -.performance-heatmap .frappe-chart { - margin-top: 5px; -} - -.performance-heatmap .frappe-chart .chart-legend { - display: none; -} - -.performance-percentage-chart .frappe-chart { - position: absolute; - top: 5px; -} - -.performance-line-chart .frappe-chart { - margin-top: -20px; -} - -.percentage-chart-container { - height: 130px; -} - -.line-chart-container .chart-filter { - z-index: 1; +.recent-activity .show-more-activity-btn { + display: block; + margin: auto; + width: max-content; + margin-top: 35px; + font-size: var(--text-md); } .recent-activity { - margin: 20px; - font-size: 12px; -} - -.show-more-activity { - text-align: center; - margin-top: 20px; -} - -.recent-activity-item { - margin: 15px 5px; -} - -.interest-icon { - margin-right: 5px; -} - -.chart-filter { - position: relative; - top: 5px; - margin-right: 10px; -} - -.filter-label { - margin-right: 4px; -} - -.performance-title { - position: relative; - left: 30px; - top: 20px; -} - -@media (max-width: 991px) { - .user-profile-sidebar { - display: flex; - } - - .percentage-chart-container { - border-top: 1px solid #d1d8dd; - } - - .user-profile-sidebar .profile-links { - margin: 0; - } - - .user-profile-sidebar .profile-details { - width: 50%; - margin: 0; - } -} + padding-bottom: 60px; +} \ No newline at end of file diff --git a/frappe/desk/page/user_profile/user_profile.html b/frappe/desk/page/user_profile/user_profile.html index ca9c8f0001..911ccc702d 100644 --- a/frappe/desk/page/user_profile/user_profile.html +++ b/frappe/desk/page/user_profile/user_profile.html @@ -1,25 +1,44 @@ - +
        +
        + +
        +
        +

        {%=__("Type Distribution") %}

        +
        +
        +
        +
        + No Data to Show +
        +
        +
        +
        +
        +

        {%=__("Energy Points") %}

        +
        +
        +
        +
        + No Data to Show +
        +
        +
        +
        +
        +
        {%=__("Recent Activity") %}
        +
        + +
        +
        +
        \ No newline at end of file diff --git a/frappe/desk/page/user_profile/user_profile.js b/frappe/desk/page/user_profile/user_profile.js index 1057cce2f3..3443a33942 100644 --- a/frappe/desk/page/user_profile/user_profile.js +++ b/frappe/desk/page/user_profile/user_profile.js @@ -1,415 +1,6 @@ -frappe.provide('frappe.energy_points'); - -frappe.pages['user-profile'].on_page_load = function(wrapper) { - - frappe.ui.make_app_page({ - parent: wrapper, - title: __('User Profile'), - }); - - let user_profile = new UserProfile(wrapper); - $(wrapper).bind('show', ()=> { +frappe.pages['user-profile'].on_page_load = function (wrapper) { + frappe.require('assets/js/user_profile_controller.min.js', () => { + let user_profile = new frappe.ui.UserProfile(wrapper); user_profile.show(); }); -}; - -class UserProfile { - - constructor(wrapper) { - this.wrapper = $(wrapper); - this.page = wrapper.page; - this.sidebar = this.wrapper.find('.layout-side-section'); - this.main_section = this.wrapper.find('.layout-main-section'); - } - - show() { - let route = frappe.get_route(); - this.user_id = route[1] || frappe.session.user; - - //validate if user - if (route.length > 1) { - frappe.db.exists('User', this.user_id).then( exists => { - if (exists) { - this.make_user_profile(); - } else { - frappe.msgprint(__('User does not exist')); - } - }); - } else { - this.user_id = frappe.session.user; - this.make_user_profile(); - } - } - - make_user_profile() { - frappe.set_route('user-profile', this.user_id, { redirect: true }); - this.user = frappe.user_info(this.user_id); - this.page.set_title(this.user.fullname); - this.setup_user_search(); - this.main_section.empty().append(frappe.render_template('user_profile')); - this.energy_points = 0; - this.review_points = 0; - this.rank = 0; - this.month_rank = 0; - this.render_user_details(); - this.render_points_and_rank(); - this.render_heatmap(); - this.render_line_chart(); - this.render_percentage_chart('type', 'Type Distribution'); - this.create_percentage_chart_filters(); - this.setup_show_more_activity(); - this.render_user_activity(); - } - - setup_user_search() { - this.$user_search_button = this.page.set_secondary_action(__('Change User'), () => { - this.show_user_search_dialog(); - }); - } - - show_user_search_dialog() { - let dialog = new frappe.ui.Dialog({ - title: __('Change User'), - fields: [ - { - fieldtype: 'Link', - fieldname: 'user', - options: 'User', - label: __('User'), - reqd: 1 - } - ], - primary_action_label: __('Go'), - primary_action: ({ user }) => { - dialog.hide(); - this.user_id = user; - this.make_user_profile(); - } - }); - dialog.show(); - } - - render_heatmap() { - this.heatmap = new frappe.Chart('.performance-heatmap', { - type: 'heatmap', - countLabel: 'Energy Points', - data: {}, - discreteDomains: 0, - }); - this.update_heatmap_data(); - this.create_heatmap_chart_filters(); - } - - update_heatmap_data(date_from) { - frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_heatmap_data', { - user: this.user_id, - date: date_from || frappe.datetime.year_start(), - }).then((r) => { - this.heatmap.update( {dataPoints: r} ); - }); - } - - - render_line_chart() { - this.line_chart_filters = [ - ['Energy Point Log', 'user', '=', this.user_id, false], - ['Energy Point Log', 'type', '!=', 'Review', false] - ]; - - this.line_chart_config = { - timespan: 'Last Month', - time_interval: 'Daily', - type: 'Line', - value_based_on: 'points', - chart_type: 'Sum', - document_type: 'Energy Point Log', - name: 'Energy Points', - width: 'half', - based_on: 'creation' - }; - - this.line_chart = new frappe.Chart( '.performance-line-chart', { - title: 'Energy Points', - type: 'line', - height: 200, - data: { - labels: [], - datasets: [{}] - }, - colors: ['purple'], - axisOptions: { - xIsSeries: 1 - } - }); - this.update_line_chart_data(); - this.create_line_chart_filters(); - } - - update_line_chart_data() { - this.line_chart_config.filters_json = JSON.stringify(this.line_chart_filters); - - frappe.xcall('frappe.desk.doctype.dashboard_chart.dashboard_chart.get', { - chart: this.line_chart_config, - no_cache: 1, - }).then(chart => { - this.line_chart.update(chart); - }); - } - - render_percentage_chart(field, title) { - frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_percentage_chart_data', { - user: this.user_id, - field: field - }).then(chart => { - if (chart.labels.length) { - this.percentage_chart = new frappe.Chart( '.performance-percentage-chart', { - title: title, - type: 'percentage', - data: { - labels: chart.labels, - datasets: chart.datasets - }, - truncateLegends: 1, - barOptions: { - height: 11, - depth: 1 - }, - height: 160, - maxSlices: 8, - colors: ['#5e64ff', '#743ee2', '#ff5858', '#ffa00a', '#feef72', '#28a745', '#98d85b', '#a9a7ac'], - }); - } else { - this.wrapper.find('.percentage-chart-container').hide(); - } - }); - } - - create_line_chart_filters() { - let filters = [ - { - label: 'All', - options: ['All', 'Auto', 'Criticism', 'Appreciation', 'Revert'], - action: (selected_item) => { - if (selected_item === 'All') { - this.line_chart_filters = [ - ['Energy Point Log', 'user', '=', this.user_id, false], - ['Energy Point Log', 'type', '!=', 'Review', false] - ]; - } else { - this.line_chart_filters[1] = ['Energy Point Log', 'type', '=', selected_item, false]; - } - this.update_line_chart_data(); - } - }, - { - label: 'Last Month', - options: ['Last Week', 'Last Month', 'Last Quarter', 'Last Year'], - action: (selected_item) => { - this.line_chart_config.timespan = selected_item; - this.update_line_chart_data(); - } - }, - { - label: 'Daily', - options: ['Daily', 'Weekly', 'Monthly'], - action: (selected_item) => { - this.line_chart_config.time_interval = selected_item; - this.update_line_chart_data(); - } - }, - ]; - frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.line-chart-container', 1); - } - - create_percentage_chart_filters() { - let filters = [ - { - label: 'Type', - options: ['Type', 'Reference Doctype', 'Rule'], - fieldnames: ['type', 'reference_doctype', 'rule'], - action: (selected_item, fieldname) => { - let title = selected_item + ' Distribution'; - this.render_percentage_chart(fieldname, title); - } - }, - ]; - frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-container'); - } - - create_heatmap_chart_filters() { - let filters = [ - { - label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()), - options: frappe.dashboard_utils.get_years_since_creation(frappe.boot.user.creation), - action: (selected_item) => { - this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item)); - } - }, - ]; - frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-container'); - } - - - edit_profile() { - let edit_profile_dialog = new frappe.ui.Dialog({ - title: __('Edit Profile'), - fields: [ - { - fieldtype: 'Attach Image', - fieldname: 'user_image', - label: 'Profile Image', - }, - { - fieldtype: 'Data', - fieldname: 'interest', - label: 'Interests', - }, - { - fieldtype: 'Column Break' - }, - { - fieldtype: 'Data', - fieldname: 'location', - label: 'Location', - }, - { - fieldtype: 'Section Break', - fieldname: 'Interest', - }, - { - fieldtype: 'Small Text', - fieldname: 'bio', - label: 'Bio', - } - ], - primary_action: values => { - edit_profile_dialog.disable_primary_action(); - frappe.xcall('frappe.desk.page.user_profile.user_profile.update_profile_info', { - profile_info: values - }).then(user => { - user.image = user.user_image; - this.user = Object.assign(values, user); - edit_profile_dialog.hide(); - this.render_user_details(); - }).finally(() => { - edit_profile_dialog.enable_primary_action(); - }); - }, - primary_action_label: __('Save') - }); - - edit_profile_dialog.set_values({ - user_image: this.user.image, - location: this.user.location, - interest: this.user.interest, - bio: this.user.bio - }); - edit_profile_dialog.show(); - } - - render_user_details() { - this.sidebar.empty().append(frappe.render_template('user_profile_sidebar', { - user_image: frappe.avatar(this.user_id, 'avatar-frame', 'user_image', this.user.image), - user_abbr: this.user.abbr, - user_location: this.user.location, - user_interest: this.user.interest, - user_bio: this.user.bio, - })); - - this.setup_user_profile_links(); - } - - setup_user_profile_links() { - if (this.user_id !== frappe.session.user) { - this.wrapper.find('.profile-links').hide(); - } else { - this.wrapper.find('.edit-profile-link').on('click', () => { - this.edit_profile(); - }); - - this.wrapper.find('.user-settings-link').on('click', () => { - this.go_to_user_settings(); - }); - } - } - - get_user_rank() { - return frappe.xcall('frappe.desk.page.user_profile.user_profile.get_user_rank', { - user: this.user_id, - }).then(r => { - if (r.monthly_rank.length) this.month_rank = r.monthly_rank[0]; - if (r.all_time_rank.length) this.rank = r.all_time_rank[0]; - }); - } - - get_user_points() { - return frappe.xcall( - 'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points', - { - user: this.user_id, - } - ).then(r => { - if (r[this.user_id]) { - this.energy_points = r[this.user_id].energy_points; - this.review_points = r[this.user_id].review_points; - } - }); - } - - render_points_and_rank() { - let $profile_details = this.wrapper.find('.profile-details'); - - this.get_user_rank().then(() => { - this.get_user_points().then(() => { - let html = $(` -

        ${__('Energy Points:')} ${this.energy_points}

        -

        ${__('Review Points:')} ${this.review_points}

        -

        ${__('Rank:')} ${this.rank}

        -

        ${__('Monthly Rank:')} ${this.month_rank}

        - `); - - $profile_details.append(html); - }); - }); - } - - go_to_user_settings() { - frappe.set_route('Form', 'User', this.user_id); - } - - render_user_activity() { - this.$recent_activity_list = this.wrapper.find('.recent-activity-list'); - - let get_recent_energy_points_html = (field) => { - let message_html = frappe.energy_points.format_history_log(field); - return `

        ${message_html}

        `; - }; - - frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_list', { - start: this.activity_start, - limit: this.activity_end, - user: this.user_id - }).then(list => { - if (list.length < 11) { - let activity_html = `${__('No More Activity')}`; - this.wrapper.find('.show-more-activity').html(activity_html); - } - let html = list.slice(0, 10).map(get_recent_energy_points_html).join(''); - this.$recent_activity_list.append(html); - }); - } - - setup_show_more_activity() { - //Show 10 items at a time - this.activity_start = 0; - this.activity_end = 11; - this.wrapper.find('.show-more-activity').on('click', () => this.show_more_activity()); - } - - show_more_activity() { - this.activity_start = this.activity_end; - this.activity_end += 11; - this.render_user_activity(); - } - -} +}; \ No newline at end of file diff --git a/frappe/desk/page/user_profile/user_profile_controller.js b/frappe/desk/page/user_profile/user_profile_controller.js new file mode 100644 index 0000000000..61f8ec3c06 --- /dev/null +++ b/frappe/desk/page/user_profile/user_profile_controller.js @@ -0,0 +1,455 @@ +import BaseTimeline from "../../../public/js/frappe/form/footer/base_timeline"; +frappe.provide('frappe.energy_points'); + +class UserProfile { + constructor(wrapper) { + this.wrapper = $(wrapper); + this.page = frappe.ui.make_app_page({ + parent: wrapper, + }); + this.sidebar = this.wrapper.find('.layout-side-section'); + this.main_section = this.wrapper.find('.layout-main-section'); + this.wrapper.bind('show', () => { + this.show(); + }); + } + + show() { + let route = frappe.get_route(); + this.user_id = route[1] || frappe.session.user; + + //validate if user + if (route.length > 1) { + frappe.dom.freeze(__('Loading user profile') + '...'); + frappe.db.exists('User', this.user_id).then(exists => { + frappe.dom.unfreeze(); + if (exists) { + this.make_user_profile(); + } else { + frappe.msgprint(__('User does not exist')); + } + }); + } else { + frappe.set_route('user-profile', frappe.session.user); + } + } + + make_user_profile() { + this.user = frappe.user_info(this.user_id); + this.page.set_title(this.user.fullname); + this.setup_user_search(); + this.main_section.empty().append(frappe.render_template('user_profile')); + this.energy_points = 0; + this.review_points = 0; + this.rank = 0; + this.month_rank = 0; + this.render_user_details(); + this.render_points_and_rank(); + this.render_heatmap(); + this.render_line_chart(); + this.render_percentage_chart('type', 'Type Distribution'); + this.create_percentage_chart_filters(); + this.setup_user_activity_timeline(); + } + + setup_user_search() { + this.$user_search_button = this.page.set_secondary_action( + __('Change User'), + () => this.show_user_search_dialog(), + { icon: 'change', size: 'sm' } + ); + } + + show_user_search_dialog() { + let dialog = new frappe.ui.Dialog({ + title: __('Change User'), + fields: [ + { + fieldtype: 'Link', + fieldname: 'user', + options: 'User', + label: __('User'), + } + ], + primary_action_label: __('Go'), + primary_action: ({ user }) => { + dialog.hide(); + this.user_id = user; + this.make_user_profile(); + } + }); + dialog.show(); + } + + render_heatmap() { + this.heatmap = new frappe.Chart('.performance-heatmap', { + type: 'heatmap', + countLabel: 'Energy Points', + data: {}, + discreteDomains: 1, + radius: 3, + height: 150 + }); + this.update_heatmap_data(); + this.create_heatmap_chart_filters(); + } + + update_heatmap_data(date_from) { + frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_heatmap_data', { + user: this.user_id, + date: date_from || frappe.datetime.year_start(), + }).then((r) => { + this.heatmap.update({ dataPoints: r }); + }); + } + + + render_line_chart() { + this.line_chart_filters = [ + ['Energy Point Log', 'user', '=', this.user_id, false], + ['Energy Point Log', 'type', '!=', 'Review', false] + ]; + + this.line_chart_config = { + timespan: 'Last Month', + time_interval: 'Daily', + type: 'Line', + value_based_on: 'points', + chart_type: 'Sum', + document_type: 'Energy Point Log', + name: 'Energy Points', + width: 'half', + based_on: 'creation' + }; + + this.line_chart = new frappe.Chart('.performance-line-chart', { + type: 'line', + height: 200, + data: { + labels: [], + datasets: [{}] + }, + colors: ['purple'], + axisOptions: { + xIsSeries: 1 + } + }); + this.update_line_chart_data(); + this.create_line_chart_filters(); + } + + update_line_chart_data() { + this.line_chart_config.filters_json = JSON.stringify(this.line_chart_filters); + + frappe.xcall('frappe.desk.doctype.dashboard_chart.dashboard_chart.get', { + chart: this.line_chart_config, + no_cache: 1, + }).then(chart => { + this.line_chart.update(chart); + }); + } + + // eslint-disable-next-line no-unused-vars + render_percentage_chart(field, title) { + // REDESIGN-TODO: chart seems to be broken. Enable this once fixed. + this.wrapper.find('.percentage-chart-container').hide(); + // frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_percentage_chart_data', { + // user: this.user_id, + // field: field + // }).then(chart => { + // if (chart.labels.length) { + // this.percentage_chart = new frappe.Chart('.performance-percentage-chart', { + // type: 'percentage', + // data: { + // labels: chart.labels, + // datasets: chart.datasets + // }, + // truncateLegends: 1, + // barOptions: { + // height: 11, + // depth: 1 + // }, + // height: 200, + // maxSlices: 8, + // colors: ['purple', 'blue', 'cyan', 'teal', 'pink', 'red', 'orange', 'yellow'], + // }); + // } else { + // this.wrapper.find('.percentage-chart-container').hide(); + // } + // }); + } + + create_line_chart_filters() { + let filters = [ + { + label: 'All', + options: ['All', 'Auto', 'Criticism', 'Appreciation', 'Revert'], + action: (selected_item) => { + if (selected_item === 'All') { + this.line_chart_filters = [ + ['Energy Point Log', 'user', '=', this.user_id, false], + ['Energy Point Log', 'type', '!=', 'Review', false] + ]; + } else { + this.line_chart_filters[1] = ['Energy Point Log', 'type', '=', selected_item, false]; + } + this.update_line_chart_data(); + } + }, + { + label: 'Last Month', + options: ['Last Week', 'Last Month', 'Last Quarter', 'Last Year'], + action: (selected_item) => { + this.line_chart_config.timespan = selected_item; + this.update_line_chart_data(); + } + }, + { + label: 'Daily', + options: ['Daily', 'Weekly', 'Monthly'], + action: (selected_item) => { + this.line_chart_config.time_interval = selected_item; + this.update_line_chart_data(); + } + }, + ]; + frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.line-chart-options', 1); + } + + create_percentage_chart_filters() { + let filters = [ + { + label: 'Type', + options: ['Type', 'Reference Doctype', 'Rule'], + fieldnames: ['type', 'reference_doctype', 'rule'], + action: (selected_item, fieldname) => { + let title = selected_item + ' Distribution'; + this.render_percentage_chart(fieldname, title); + } + }, + ]; + frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.percentage-chart-options'); + } + + create_heatmap_chart_filters() { + let filters = [ + { + label: frappe.dashboard_utils.get_year(frappe.datetime.now_date()), + options: frappe.dashboard_utils.get_years_since_creation(frappe.boot.user.creation), + action: (selected_item) => { + this.update_heatmap_data(frappe.datetime.obj_to_str(selected_item)); + } + }, + ]; + frappe.dashboard_utils.render_chart_filters(filters, 'chart-filter', '.heatmap-options'); + } + + + edit_profile() { + let edit_profile_dialog = new frappe.ui.Dialog({ + title: __('Edit Profile'), + fields: [ + { + fieldtype: 'Attach Image', + fieldname: 'user_image', + label: 'Profile Image', + }, + { + fieldtype: 'Data', + fieldname: 'interest', + label: 'Interests', + }, + { + fieldtype: 'Column Break' + }, + { + fieldtype: 'Data', + fieldname: 'location', + label: 'Location', + }, + { + fieldtype: 'Section Break', + fieldname: 'Interest', + }, + { + fieldtype: 'Small Text', + fieldname: 'bio', + label: 'Bio', + } + ], + primary_action: values => { + edit_profile_dialog.disable_primary_action(); + frappe.xcall('frappe.desk.page.user_profile.user_profile.update_profile_info', { + profile_info: values + }).then(user => { + user.image = user.user_image; + this.user = Object.assign(values, user); + edit_profile_dialog.hide(); + this.render_user_details(); + }).finally(() => { + edit_profile_dialog.enable_primary_action(); + }); + }, + primary_action_label: __('Save') + }); + + edit_profile_dialog.set_values({ + user_image: this.user.image, + location: this.user.location, + interest: this.user.interest, + bio: this.user.bio + }); + edit_profile_dialog.show(); + } + + render_user_details() { + this.sidebar.empty().append(frappe.render_template('user_profile_sidebar', { + user_image: this.user.image, + user_abbr: this.user.abbr, + user_location: this.user.location, + user_interest: this.user.interest, + user_bio: this.user.bio, + })); + + this.setup_user_profile_links(); + } + + setup_user_profile_links() { + if (this.user_id !== frappe.session.user) { + this.wrapper.find('.profile-links').hide(); + } else { + this.wrapper.find('.edit-profile-link').on('click', () => { + this.edit_profile(); + }); + + this.wrapper.find('.user-settings-link').on('click', () => { + this.go_to_user_settings(); + }); + } + } + + get_user_rank() { + return frappe.xcall('frappe.desk.page.user_profile.user_profile.get_user_rank', { + user: this.user_id, + }).then(r => { + if (r.monthly_rank.length) this.month_rank = r.monthly_rank[0]; + if (r.all_time_rank.length) this.rank = r.all_time_rank[0]; + }); + } + + get_user_points() { + return frappe.xcall( + 'frappe.social.doctype.energy_point_log.energy_point_log.get_user_energy_and_review_points', + { + user: this.user_id, + } + ).then(r => { + if (r[this.user_id]) { + this.energy_points = r[this.user_id].energy_points; + this.review_points = r[this.user_id].review_points; + } + }); + } + + render_points_and_rank() { + let $profile_details = this.wrapper.find('.user-stats'); + let $profile_details_wrapper = this.wrapper.find('.user-stats-detail'); + + const _get_stat_dom = (value, label, icon) => { + return `
        + ${frappe.utils.icon(icon, "lg", "no-stroke")} +
        +
        ${value}
        +
        ${label}
        +
        +
        `; + }; + + this.get_user_rank().then(() => { + this.get_user_points().then(() => { + let html = $(` + ${_get_stat_dom(this.energy_points, __('Energy Points'), "color-energy-points")} + ${_get_stat_dom(this.review_points, __('Review Points'), "color-review-points")} + ${_get_stat_dom(this.rank, __('Rank'), "color-rank")} + ${_get_stat_dom(this.month_rank, __('Monthly Rank'), "color-monthly-rank")} + `); + + $profile_details.append(html); + $profile_details_wrapper.removeClass("hide"); + }); + }); + } + + go_to_user_settings() { + frappe.set_route('Form', 'User', this.user_id); + } + + setup_user_activity_timeline() { + this.user_activity_timeline = new UserProfileTimeline({ + parent: this.wrapper.find('.recent-activity-list'), + footer: this.wrapper.find('.recent-activity-footer'), + user: this.user_id + }); + + this.user_activity_timeline.refresh(); + } +} + +class UserProfileTimeline extends BaseTimeline { + make() { + super.make(); + this.activity_start = 0; + this.activity_limit = 20; + this.setup_show_more_activity(); + } + prepare_timeline_contents() { + return this.get_user_activity_data().then((activities) => { + if (!activities.length) { + this.show_more_button.hide(); + this.timeline_wrapper.html(`
        ${__('No activities to show')}
        `); + return; + } + this.show_more_button.toggle(activities.length === this.activity_limit); + this.timeline_items = activities.map((activity) => this.get_activity_timeline_item(activity)); + }); + } + + get_user_activity_data() { + return frappe.xcall('frappe.desk.page.user_profile.user_profile.get_energy_points_list', { + start: this.activity_start, + limit: this.activity_limit, + user: this.user + }); + } + + get_activity_timeline_item(data) { + let icon = data.type == 'Appreciation' ? 'clap': data.type == 'Criticism' ? 'criticize': null; + return { + icon: icon, + creation: data.creation, + is_card: true, + content: frappe.energy_points.format_history_log(data), + }; + } + + setup_show_more_activity() { + this.show_more_button = $(`${__('Show More Activity')}`); + this.show_more_button.hide(); + this.footer.append(this.show_more_button); + this.show_more_button.on('click', () => this.show_more_activity()); + } + + show_more_activity() { + this.activity_start += this.activity_limit; + this.get_user_activity_data().then(activities => { + if (!activities.length || activities.length < this.activity_limit) { + this.show_more_button.hide(); + } + let timeline_items = activities.map((activity) => this.get_activity_timeline_item(activity)); + timeline_items.map((item) => this.add_timeline_item(item, true)); + }); + } +} + +frappe.provide('frappe.ui'); +frappe.ui.UserProfile = UserProfile; \ No newline at end of file diff --git a/frappe/desk/page/user_profile/user_profile_sidebar.html b/frappe/desk/page/user_profile/user_profile_sidebar.html index 77dae5edd0..4a35c6cf9c 100644 --- a/frappe/desk/page/user_profile/user_profile_sidebar.html +++ b/frappe/desk/page/user_profile/user_profile_sidebar.html @@ -1,23 +1,60 @@ \ No newline at end of file diff --git a/frappe/desk/report/todo/todo.py b/frappe/desk/report/todo/todo.py index a51d44fe08..f4fe2dc805 100644 --- a/frappe/desk/report/todo/todo.py +++ b/frappe/desk/report/todo/todo.py @@ -24,7 +24,7 @@ def execute(filters=None): for todo in todo_list: if todo.owner==frappe.session.user or todo.assigned_by==frappe.session.user: if todo.reference_type: - todo.reference = """%s: %s""" % (todo.reference_type, + todo.reference = """%s: %s""" % (todo.reference_type, todo.reference_name, todo.reference_type, todo.reference_name) else: todo.reference = None diff --git a/frappe/desk/reportview.py b/frappe/desk/reportview.py index 36870d40bb..3003385601 100644 --- a/frappe/desk/reportview.py +++ b/frappe/desk/reportview.py @@ -14,7 +14,7 @@ from frappe.core.doctype.access_log.access_log import make_access_log from frappe.utils import cstr, format_duration -@frappe.whitelist() +@frappe.whitelist(allow_guest=True) @frappe.read_only() def get(): args = get_form_params() diff --git a/frappe/desk/treeview.py b/frappe/desk/treeview.py index 811143be03..e0b6ca240a 100644 --- a/frappe/desk/treeview.py +++ b/frappe/desk/treeview.py @@ -1,5 +1,5 @@ # Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors -# License: GNU General Public License v3. See license.txt +# MIT License. See license.txt from __future__ import unicode_literals import frappe diff --git a/frappe/desk/utils.py b/frappe/desk/utils.py new file mode 100644 index 0000000000..01b47ac106 --- /dev/null +++ b/frappe/desk/utils.py @@ -0,0 +1,23 @@ +# Copyright (c) 2020, Frappe Technologies Pvt. Ltd. and Contributors +# MIT License. See license.txt + +import frappe + +def validate_route_conflict(doctype, name): + ''' + Raises exception if name clashes with routes from other documents for /app routing + ''' + + all_names = [] + for _doctype in ['Page', 'Workspace', 'DocType']: + try: + all_names.extend([slug(d) for d in frappe.get_all(_doctype, pluck='name') if (doctype != _doctype and d != name)]) + except frappe.db.TableMissingError: + pass + + if slug(name) in all_names: + frappe.msgprint(frappe._('Name already taken, please set a new name')) + raise frappe.NameError + +def slug(name): + return name.lower().replace(' ', '-') \ No newline at end of file diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.js b/frappe/email/doctype/auto_email_report/auto_email_report.js index 1b91e7a38c..3423c3ccba 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.js +++ b/frappe/email/doctype/auto_email_report/auto_email_report.js @@ -97,9 +97,18 @@ frappe.ui.form.on('Auto Email Report', { }) report_filters = report_filters_list; - report_filters.forEach(function(f) { - $('' + f.label + ''+ frappe.format(filters[f.fieldname], f) +'') - .appendTo(table.find('tbody')); + const mandatory_css = { + "background-color": "var(--error-bg)", + "font-weight": "bold" + }; + + report_filters.forEach(f => { + const css = f.reqd ? mandatory_css : {}; + const row = $("").appendTo(table.find("tbody")); + $("" + f.label + "").appendTo(row); + $("" + frappe.format(filters[f.fieldname], f) +"") + .css(css) + .appendTo(row); }); table.on('click', function() { diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.json b/frappe/email/doctype/auto_email_report/auto_email_report.json index 8067566ece..211e2e9662 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.json +++ b/frappe/email/doctype/auto_email_report/auto_email_report.json @@ -147,6 +147,7 @@ "label": "Email Settings" }, { + "description": "For multiple addresses, enter the address on different line. e.g. test@test.com \u23ce test1@test.com", "fieldname": "email_to", "fieldtype": "Small Text", "label": "Email To", @@ -200,7 +201,7 @@ "read_only": 1 } ], - "modified": "2019-05-09 22:38:27.570890", + "modified": "2021-01-28 15:59:43.151995", "modified_by": "Administrator", "module": "Email", "name": "Auto Email Report", diff --git a/frappe/email/doctype/auto_email_report/auto_email_report.py b/frappe/email/doctype/auto_email_report/auto_email_report.py index de27fafee3..d82caa7bd4 100644 --- a/frappe/email/doctype/auto_email_report/auto_email_report.py +++ b/frappe/email/doctype/auto_email_report/auto_email_report.py @@ -29,6 +29,7 @@ class AutoEmailReport(Document): self.validate_report_count() self.validate_emails() self.validate_report_format() + self.validate_mandatory_fields() def validate_emails(self): '''Cleanup list of emails''' @@ -56,6 +57,21 @@ class AutoEmailReport(Document): frappe.throw(_("{0} is not a valid report format. Report format should one of the following {1}") .format(frappe.bold(self.format), frappe.bold(", ".join(valid_report_formats)))) + def validate_mandatory_fields(self): + # Check if all Mandatory Report Filters are filled by the User + filters = frappe.parse_json(self.filters) if self.filters else {} + filter_meta = frappe.parse_json(self.filter_meta) if self.filter_meta else {} + throw_list = [] + for meta in filter_meta: + if meta.get("reqd") and not filters.get(meta["fieldname"]): + throw_list.append(meta['label']) + if throw_list: + frappe.throw( + title= _('Missing Filters Required'), + msg= _('Following Report Filters have missing values:') + + '

        • ' + '
        • '.join(throw_list) + '
        ', + ) + def get_report_content(self): '''Returns file in for the report in given format''' report = frappe.get_doc('Report', self.report) diff --git a/frappe/email/doctype/document_follow/test_document_follow.py b/frappe/email/doctype/document_follow/test_document_follow.py index 1208a6c5c1..1ac2d19305 100644 --- a/frappe/email/doctype/document_follow/test_document_follow.py +++ b/frappe/email/doctype/document_follow/test_document_follow.py @@ -8,15 +8,15 @@ import unittest import frappe.desk.form.document_follow as document_follow class TestDocumentFollow(unittest.TestCase): - - def test_add_subscription_and_send_mail(self): + def test_document_follow(self): user = get_user() event_doc = get_event() event_doc.description = "This is a test description for sending mail" event_doc.save(ignore_version=False) - doc = document_follow.follow_document("Event", event_doc.name , user.name, force=True) + document_follow.unfollow_document("Event", event_doc.name, user.name) + doc = document_follow.follow_document("Event", event_doc.name, user.name) self.assertEquals(doc.user, user.name) document_follow.send_hourly_updates() @@ -45,12 +45,15 @@ def get_event(): return doc def get_user(): - doc = frappe.new_doc("User") - doc.email = "test@docsub.com" - doc.first_name = "Test" - doc.last_name = "User" - doc.send_welcome_email = 0 - doc.document_follow_notify = 1 - doc.document_follow_frequency = "Hourly" - doc.insert() + if frappe.db.exists('User', 'test@docsub.com'): + doc = frappe.get_doc('User', 'test@docsub.com') + else: + doc = frappe.new_doc("User") + doc.email = "test@docsub.com" + doc.first_name = "Test" + doc.last_name = "User" + doc.send_welcome_email = 0 + doc.document_follow_notify = 1 + doc.document_follow_frequency = "Hourly" + doc.insert() return doc \ No newline at end of file diff --git a/frappe/email/doctype/email_account/email_account.json b/frappe/email/doctype/email_account/email_account.json index 057638697a..6d811b801f 100644 --- a/frappe/email/doctype/email_account/email_account.json +++ b/frappe/email/doctype/email_account/email_account.json @@ -56,6 +56,7 @@ "auto_reply_message", "set_footer", "footer", + "brand_logo", "uidvalidity", "uidnext", "no_remaining", @@ -65,6 +66,8 @@ { "fieldname": "email_id", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "in_global_search": 1, "in_list_view": 1, "label": "Email Address", @@ -75,46 +78,62 @@ "default": "0", "fieldname": "login_id_is_different", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Use Different Email Login ID" }, { "depends_on": "login_id_is_different", "fieldname": "login_id", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "Email Login ID" }, { "fieldname": "password", "fieldtype": "Password", + "hide_days": 1, + "hide_seconds": 1, "label": "Password" }, { "default": "0", "fieldname": "awaiting_password", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Awaiting password" }, { "default": "0", "fieldname": "ascii_encode_password", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Use ASCII encoding for password" }, { "description": "e.g. \"Support\", \"Sales\", \"Jerry Yang\"", "fieldname": "email_account_name", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "Email Account Name", "unique": 1 }, { "fieldname": "email_settings", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "depends_on": "eval:!doc.service", "fieldname": "domain", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "in_list_view": 1, "in_standard_filter": 1, "label": "Domain", @@ -124,18 +143,24 @@ "depends_on": "eval:!doc.domain", "fieldname": "service", "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, "label": "Service", "options": "\nGMail\nSendgrid\nSparkPost\nYahoo Mail\nOutlook.com\nYandex.Mail" }, { "fieldname": "mailbox_settings", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "default": "0", "description": "Check this to pull emails from your mailbox", "fieldname": "enable_incoming", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Enable Incoming" }, { @@ -144,6 +169,8 @@ "fetch_from": "domain.use_imap", "fieldname": "use_imap", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Use IMAP" }, { @@ -152,6 +179,8 @@ "fetch_from": "domain.email_server", "fieldname": "email_server", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "Email Server" }, { @@ -160,6 +189,8 @@ "fetch_from": "domain.use_ssl", "fieldname": "use_ssl", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Use SSL" }, { @@ -169,6 +200,8 @@ "fetch_from": "domain.attachment_limit", "fieldname": "attachment_limit", "fieldtype": "Int", + "hide_days": 1, + "hide_seconds": 1, "label": "Attachment Limit (MB)" }, { @@ -176,6 +209,8 @@ "description": "Append as communication against this DocType (must have fields, \"Status\", \"Subject\")", "fieldname": "append_to", "fieldtype": "Link", + "hide_days": 1, + "hide_seconds": 1, "in_standard_filter": 1, "label": "Append To", "options": "DocType" @@ -186,6 +221,8 @@ "description": "e.g. replies@yourcomany.com. All replies will come to this inbox.", "fieldname": "default_incoming", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Default Incoming" }, { @@ -193,6 +230,8 @@ "depends_on": "eval: doc.enable_incoming", "fieldname": "email_sync_option", "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, "label": "Email Sync Option", "options": "ALL\nUNSEEN" }, @@ -201,18 +240,24 @@ "description": "Total number of emails to sync in initial sync process ", "fieldname": "initial_sync_count", "fieldtype": "Select", + "hide_days": 1, + "hide_seconds": 1, "label": "Initial Sync Count", "options": "100\n250\n500" }, { "depends_on": "enable_incoming", "fieldname": "section_break_13", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "default": "0", "fieldname": "notify_if_unreplied", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Notify if unreplied" }, { @@ -220,6 +265,8 @@ "depends_on": "notify_if_unreplied", "fieldname": "unreplied_for_mins", "fieldtype": "Int", + "hide_days": 1, + "hide_seconds": 1, "label": "Notify if unreplied for (in mins)" }, { @@ -227,17 +274,23 @@ "description": "Email Addresses", "fieldname": "send_notification_to", "fieldtype": "Small Text", + "hide_days": 1, + "hide_seconds": 1, "label": "Send Notification to" }, { "fieldname": "outgoing_mail_settings", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "default": "0", "description": "SMTP Settings for outgoing emails", "fieldname": "enable_outgoing", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Enable Outgoing" }, { @@ -246,6 +299,8 @@ "fetch_from": "domain.smtp_server", "fieldname": "smtp_server", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "SMTP Server" }, { @@ -254,6 +309,8 @@ "fetch_from": "domain.use_tls", "fieldname": "use_tls", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Use TLS" }, { @@ -262,6 +319,8 @@ "fetch_from": "domain.smtp_port", "fieldname": "smtp_port", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "Port" }, { @@ -270,6 +329,8 @@ "description": "Notifications and bulk mails will be sent from this outgoing server.", "fieldname": "default_outgoing", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Default Outgoing" }, { @@ -278,6 +339,8 @@ "description": "Uses the Email Address mentioned in this Account as the Sender for all emails sent using this Account. ", "fieldname": "always_use_account_email_id_as_sender", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Always use Account's Email Address as Sender" }, { @@ -286,12 +349,16 @@ "description": "Uses the Email Address Name mentioned in this Account as the Sender's Name for all emails sent using this Account.", "fieldname": "always_use_account_name_as_sender_name", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Always use Account's Name as Sender's Name" }, { "default": "1", "fieldname": "send_unsubscribe_message", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Send unsubscribe message in email" }, { @@ -299,38 +366,52 @@ "description": "Track if your email has been opened by the recipient.\n
        \nNote: If you're sending to multiple recipients, even if 1 recipient reads the email, it'll be considered \"Opened\"", "fieldname": "track_email_status", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Track Email Status" }, { "default": "0", "fieldname": "no_smtp_authentication", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Disable SMTP server authentication" }, { "fieldname": "signature_section", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "default": "0", "fieldname": "add_signature", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Add Signature" }, { "depends_on": "add_signature", "fieldname": "signature", "fieldtype": "Text Editor", + "hide_days": 1, + "hide_seconds": 1, "label": "Signature" }, { "fieldname": "auto_reply", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "default": "0", "fieldname": "enable_auto_reply", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Enable Auto Reply" }, { @@ -338,21 +419,29 @@ "description": "ProTip: Add Reference: {{ reference_doctype }} {{ reference_name }} to send document reference", "fieldname": "auto_reply_message", "fieldtype": "Text Editor", + "hide_days": 1, + "hide_seconds": 1, "label": "Auto Reply Message" }, { "fieldname": "set_footer", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "fieldname": "footer", "fieldtype": "Text Editor", + "hide_days": 1, + "hide_seconds": 1, "label": "Footer" }, { "fieldname": "uidvalidity", "fieldtype": "Data", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "UIDVALIDITY", "no_copy": 1 }, @@ -360,6 +449,8 @@ "fieldname": "uidnext", "fieldtype": "Int", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "UIDNEXT", "no_copy": 1 }, @@ -367,6 +458,8 @@ "fieldname": "no_remaining", "fieldtype": "Data", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "No of emails remaining to be synced", "no_copy": 1 }, @@ -374,19 +467,25 @@ "fieldname": "no_failed", "fieldtype": "Int", "hidden": 1, + "hide_days": 1, + "hide_seconds": 1, "label": "no failed attempts", "no_copy": 1, "read_only": 1 }, { "fieldname": "section_break_12", - "fieldtype": "Section Break" + "fieldtype": "Section Break", + "hide_days": 1, + "hide_seconds": 1 }, { "default": "0", "description": "For more information, click here.", "fieldname": "enable_automatic_linking", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Enable Automatic Linking in Documents" }, { @@ -394,6 +493,8 @@ "description": "If non-standard port (e.g. POP3: 995/110, IMAP: 993/143)", "fieldname": "incoming_port", "fieldtype": "Data", + "hide_days": 1, + "hide_seconds": 1, "label": "Port" }, { @@ -401,6 +502,8 @@ "depends_on": "eval:!doc.domain && doc.enable_outgoing", "fieldname": "append_emails_to_sent_folder", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Append Emails to Sent Folder" }, { @@ -408,18 +511,28 @@ "depends_on": "eval:!doc.domain && doc.enable_outgoing", "fieldname": "use_ssl_for_outgoing", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Use SSL for Outgoing" }, { "default": "1", "fieldname": "create_contact", "fieldtype": "Check", + "hide_days": 1, + "hide_seconds": 1, "label": "Create Contacts from Incoming Emails" + }, + { + "fieldname": "brand_logo", + "fieldtype": "Attach Image", + "label": "Brand Logo" } ], "icon": "fa fa-inbox", + "index_web_pages_for_search": 1, "links": [], - "modified": "2020-05-11 15:18:43.931499", + "modified": "2021-01-21 10:05:24.820597", "modified_by": "Administrator", "module": "Email", "name": "Email Account", @@ -441,4 +554,4 @@ "sort_field": "modified", "sort_order": "DESC", "track_changes": 1 -} +} \ No newline at end of file diff --git a/frappe/email/doctype/email_account/email_account_list.js b/frappe/email/doctype/email_account/email_account_list.js index f3bbd99e9b..5ec56fb3db 100644 --- a/frappe/email/doctype/email_account/email_account_list.js +++ b/frappe/email/doctype/email_account/email_account_list.js @@ -2,19 +2,19 @@ frappe.listview_settings["Email Account"] = { add_fields: ["default_incoming", "default_outgoing", "enable_incoming", "enable_outgoing"], get_indicator: function(doc) { if(doc.default_incoming && doc.default_outgoing) { - var color = (doc.enable_incoming && doc.enable_outgoing) ? "blue" : "darkgrey"; + var color = (doc.enable_incoming && doc.enable_outgoing) ? "blue" : "gray"; return [__("Default Sending and Inbox"), color, "default_incoming,=,Yes|default_outgoing,=,Yes"] } else if(doc.default_incoming) { - var color = doc.enable_incoming ? "blue" : "darkgrey"; + color = doc.enable_incoming ? "blue" : "gray"; return [__("Default Inbox"), color, "default_incoming,=,Yes"]; } else if(doc.default_outgoing) { - var color = doc.enable_outgoing ? "blue" : "darkgrey"; + color = doc.enable_outgoing ? "blue" : "gray"; return [__("Default Sending"), color, "default_outgoing,=,Yes"]; } else { - var color = doc.enable_incoming ? "blue" : "darkgrey"; + color = doc.enable_incoming ? "blue" : "gray"; return [__("Inbox"), color, "is_global,=,No|is_default=No"]; } } diff --git a/frappe/email/doctype/notification/notification.js b/frappe/email/doctype/notification/notification.js index 27fcd0e453..c999f5f160 100644 --- a/frappe/email/doctype/notification/notification.js +++ b/frappe/email/doctype/notification/notification.js @@ -198,7 +198,7 @@ frappe.ui.form.on('Notification', { frappe.notification.setup_example_message(frm); if (frm.doc.channel === 'SMS' && frm.doc.__islocal) { frm.set_df_property('channel', - 'description', `To use SMS Channel, initialize SMS Settings.`); + 'description', `To use SMS Channel, initialize SMS Settings.`); } else { frm.set_df_property('channel', 'description', ` `); } diff --git a/frappe/email/email_body.py b/frappe/email/email_body.py index 8ac071fa61..7aa70830e7 100755 --- a/frappe/email/email_body.py +++ b/frappe/email/email_body.py @@ -13,7 +13,6 @@ from email.mime.multipart import MIMEMultipart from email.header import Header from email import policy - def get_email(recipients, sender='', msg='', subject='[No Subject]', text_content = None, footer=None, print_html=None, formatted=None, attachments=None, content=None, reply_to=None, cc=[], bcc=[], email_account=None, expose_recipients=None, @@ -249,11 +248,14 @@ class EMail: return self.msg_root.as_string(policy=policy.SMTPUTF8) def get_formatted_html(subject, message, footer=None, print_html=None, - email_account=None, header=None, unsubscribe_link=None, sender=None): + email_account=None, header=None, unsubscribe_link=None, sender=None, with_container=False): if not email_account: email_account = get_outgoing_email_account(False, sender=sender) rendered_email = frappe.get_template("templates/emails/standard.html").render({ + "brand_logo": get_brand_logo(email_account) if with_container or header else None, + "with_container": with_container, + "site_url": get_url(), "header": get_header(header), "content": message, "signature": get_signature(email_account), @@ -272,14 +274,14 @@ def get_formatted_html(subject, message, footer=None, print_html=None, return html @frappe.whitelist() -def get_email_html(template, args, subject, header=None): +def get_email_html(template, args, subject, header=None, with_container=False): import json - + with_container = cint(with_container) args = json.loads(args) if header and header.startswith('['): header = json.loads(header) email = frappe.utils.jinja.get_email_from_template(template, args) - return get_formatted_html(subject, email[0], header=header) + return get_formatted_html(subject, email[0], header=header, with_container=with_container) def inline_style_in_html(html): ''' Convert email.css and html to inline-styled html @@ -288,7 +290,11 @@ def inline_style_in_html(html): apps = frappe.get_installed_apps() - css_files = [] + # add frappe email css file + css_files = ['assets/css/email.css'] + if 'frappe' in apps: + apps.remove('frappe') + for app in apps: path = 'assets/{0}/css/email.css'.format(app) if os.path.exists(os.path.abspath(path)): @@ -353,7 +359,7 @@ def get_message_id(): def get_signature(email_account): if email_account and email_account.add_signature and email_account.signature: - return "

        " + email_account.signature + return "
        " + email_account.signature else: return "" @@ -366,10 +372,10 @@ def get_footer(email_account, footer=None): if email_account and email_account.footer: args.update({'email_account_footer': email_account.footer}) - company_address = frappe.db.get_default("email_footer_address") + sender_address = frappe.db.get_default("email_footer_address") - if company_address: - args.update({'company_address': company_address}) + if sender_address: + args.update({'sender_address': sender_address}) if not cint(frappe.db.get_default("disable_standard_email_footer")): args.update({'default_mail_footer': frappe.get_hooks('default_mail_footer')}) @@ -467,3 +473,6 @@ def get_header(header=None): def sanitize_email_header(str): return str.replace('\r', '').replace('\n', '') + +def get_brand_logo(email_account): + return email_account.get('brand_logo') \ No newline at end of file diff --git a/frappe/email/queue.py b/frappe/email/queue.py index f780aebdc1..2aff04edc9 100755 --- a/frappe/email/queue.py +++ b/frappe/email/queue.py @@ -24,7 +24,7 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= attachments=None, reply_to=None, cc=None, bcc=None, message_id=None, in_reply_to=None, send_after=None, expose_recipients=None, send_priority=1, communication=None, now=False, read_receipt=None, queue_separately=False, is_notification=False, add_unsubscribe_link=1, inline_images=None, - header=None, print_letterhead=False): + header=None, print_letterhead=False, with_container=False): """Add email to sending queue (Email Queue) :param recipients: List of recipients. @@ -48,6 +48,7 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= :param add_unsubscribe_link: Send unsubscribe link in the footer of the Email, default 1. :param inline_images: List of inline images as {"filename", "filecontent"}. All src properties will be replaced with random Content-Id :param header: Append header in email (boolean) + :param with_container: Wraps email inside styled container """ if not unsubscribe_method: unsubscribe_method = "/api/method/frappe.email.queue.unsubscribe" @@ -130,7 +131,7 @@ def send(recipients=None, sender=None, subject=None, message=None, text_content= email_content = get_formatted_html(subject, message, email_account=email_account, header=header, - unsubscribe_link=unsubscribe_link) + unsubscribe_link=unsubscribe_link, with_container=with_container) # add to queue add(recipients, sender, subject, diff --git a/frappe/email/test_email_body.py b/frappe/email/test_email_body.py index 705a853bc6..9b0b5e41d7 100644 --- a/frappe/email/test_email_body.py +++ b/frappe/email/test_email_body.py @@ -138,7 +138,7 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> ''' transformed_html = '''

        Hi John

        -

        This is a test email

        +

        This is a test email

        ''' self.assertTrue(transformed_html in inline_style_in_html(html)) @@ -154,10 +154,8 @@ w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> content=email_html, header=['Email Title', 'green'] ).as_string().replace("\r\n", "\n") - - self.assertTrue('''''' in email_string) + # REDESIGN-TODO: Add style for indicators in email + self.assertTrue('''''' in email_string) self.assertTrue('Email Title' in email_string) def test_get_email_header(self): diff --git a/frappe/hooks.py b/frappe/hooks.py index ea0a91a639..97a8b70953 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -10,7 +10,7 @@ app_icon = "octicon octicon-circuit-board" app_color = "orange" source_link = "https://github.com/frappe/frappe" app_license = "MIT" -app_logo_url = '/assets/frappe/images/frappe-framework-logo.png' +app_logo_url = '/assets/frappe/images/frappe-framework-logo.svg' develop_version = '13.x.x-develop' @@ -29,18 +29,17 @@ page_js = { # website app_include_js = [ - "assets/js/libs.min.js", - "assets/js/desk.min.js", - "assets/js/list.min.js", - "assets/js/form.min.js", - "assets/js/control.min.js", - "assets/js/report.min.js", + "/assets/js/libs.min.js", + "/assets/js/desk.min.js", + "/assets/js/list.min.js", + "/assets/js/form.min.js", + "/assets/js/control.min.js", + "/assets/js/report.min.js", ] app_include_css = [ - "assets/css/desk.min.css", - "assets/css/list.min.css", - "assets/css/form.min.css", - "assets/css/report.min.css", + "/assets/css/desk.min.css", + "/assets/css/list.min.css", + "/assets/css/report.min.css", ] doctype_js = { diff --git a/frappe/installer.py b/frappe/installer.py index a11c8dfbfa..0cd5b136ae 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -249,7 +249,7 @@ def remove_app(app_name, dry_run=False, yes=False, no_backup=False, force=False) linked_doctypes = frappe.get_all( "DocField", filters={"fieldtype": "Link", "options": "Module Def"}, fields=["parent"] ) - ordered_doctypes = ["Desk Page", "Report", "Page", "Web Form"] + ordered_doctypes = ["Workspace", "Report", "Page", "Web Form"] all_doctypes_with_linked_modules = ordered_doctypes + [ doctype.parent for doctype in linked_doctypes diff --git a/frappe/integrations/desk_page/integrations/integrations.json b/frappe/integrations/desk_page/integrations/integrations.json deleted file mode 100644 index 97e2b29d1a..0000000000 --- a/frappe/integrations/desk_page/integrations/integrations.json +++ /dev/null @@ -1,49 +0,0 @@ -{ - "cards": [ - { - "hidden": 0, - "label": "Backup", - "links": "[\n {\n \"description\": \"Dropbox backup settings\",\n \"label\": \"Dropbox Settings\",\n \"name\": \"Dropbox Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"S3 Backup Settings\",\n \"label\": \"S3 Backup Settings\",\n \"name\": \"S3 Backup Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Backup.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Google Services", - "links": "[\n {\n \"description\": \"Google API Settings.\",\n \"label\": \"Google Settings\",\n \"name\": \"Google Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Contacts Integration.\",\n \"label\": \"Google Contacts\",\n \"name\": \"Google Contacts\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Calendar Integration.\",\n \"label\": \"Google Calendar\",\n \"name\": \"Google Calendar\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Google Drive Integration.\",\n \"label\": \"Google Drive\",\n \"name\": \"Google Drive\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Authentication", - "links": "[\n {\n \"description\": \"Enter keys to enable login via Facebook, Google, GitHub.\",\n \"label\": \"Social Login Key\",\n \"name\": \"Social Login Key\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Ldap settings\",\n \"label\": \"LDAP Settings\",\n \"name\": \"LDAP Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Register OAuth Client App\",\n \"label\": \"OAuth Client\",\n \"name\": \"OAuth Client\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Settings for OAuth Provider\",\n \"label\": \"OAuth Provider Settings\",\n \"name\": \"OAuth Provider Settings\",\n \"type\": \"doctype\"\n }\n ,\n {\n \"description\": \"Connect to any OAuth Provider\",\n \"label\": \"Connected App\",\n \"name\": \"Connected App\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Payments", - "links": "[\n {\n \"description\": \"Braintree payment gateway settings\",\n \"label\": \"Braintree Settings\",\n \"name\": \"Braintree Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"PayPal payment gateway settings\",\n \"label\": \"PayPal Settings\",\n \"name\": \"PayPal Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Razorpay Payment gateway settings\",\n \"label\": \"Razorpay Settings\",\n \"name\": \"Razorpay Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Stripe payment gateway settings\",\n \"label\": \"Stripe Settings\",\n \"name\": \"Stripe Settings\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Paytm payment gateway settings\",\n \"label\": \"Paytm Settings\",\n \"name\": \"Paytm Settings\",\n \"type\": \"doctype\"\n }\n]" - }, - { - "hidden": 0, - "label": "Settings", - "links": "[\n {\n \"description\": \"Webhooks calling API requests into web apps\",\n \"label\": \"Webhook\",\n \"name\": \"Webhook\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"Slack Webhooks for internal integration\",\n \"label\": \"Slack Webhook URL\",\n \"name\": \"Slack Webhook URL\",\n \"type\": \"doctype\"\n },\n {\n \"description\": \"SMS Settings for sending sms\",\n \"label\": \"SMS Settings\",\n \"name\": \"SMS Settings\",\n \"type\": \"doctype\"\n }\n]" - } - ], - "category": "Administration", - "charts": [], - "creation": "2020-03-02 15:16:18.714190", - "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": "Integrations", - "modified": "2020-10-28 10:25:54.792363", - "modified_by": "Administrator", - "module": "Integrations", - "name": "Integrations", - "owner": "Administrator", - "pin_to_bottom": 0, - "pin_to_top": 0, - "shortcuts": [] -} \ No newline at end of file diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.js b/frappe/integrations/doctype/google_calendar/google_calendar.js index f941cf0575..f30c52b2f2 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.js +++ b/frappe/integrations/doctype/google_calendar/google_calendar.js @@ -4,7 +4,7 @@ frappe.ui.form.on("Google Calendar", { refresh: function(frm) { if (frm.is_new()) { - frm.dashboard.set_headline(__("To use Google Calendar, enable {0}.", [`${__('Google Settings')}`])); + frm.dashboard.set_headline(__("To use Google Calendar, enable {0}.", [`${__('Google Settings')}`])); } frappe.realtime.on("import_google_calendar", (data) => { diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 4a9acd9e84..fbedd75029 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -120,7 +120,7 @@ def authorize_access(g_calendar, reauthorize=None): frappe.db.commit() frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/desk#Form/{0}/{1}".format(quote("Google Calendar"), quote(google_calendar.name)) + frappe.local.response["location"] = "/app/Form/{0}/{1}".format(quote("Google Calendar"), quote(google_calendar.name)) frappe.msgprint(_("Google Calendar has been configured.")) except Exception as e: diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.js b/frappe/integrations/doctype/google_contacts/google_contacts.js index af194d4978..7cbef46699 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.js +++ b/frappe/integrations/doctype/google_contacts/google_contacts.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Google Contacts', { refresh: function(frm) { if (!frm.doc.enable) { - frm.dashboard.set_headline(__("To use Google Contacts, enable {0}.", [`${__('Google Settings')}`])); + frm.dashboard.set_headline(__("To use Google Contacts, enable {0}.", [`${__('Google Settings')}`])); } frappe.realtime.on('import_google_contacts', (data) => { diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index 6455623281..4c8c3b67f6 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -79,7 +79,7 @@ def authorize_access(g_contact, reauthorize=None): frappe.db.commit() frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/desk#Form/Google%20Contacts/{}".format(google_contact.name) + frappe.local.response["location"] = "/app/Form/Google%20Contacts/{}".format(google_contact.name) frappe.msgprint(_("Google Contacts has been configured.")) except Exception as e: diff --git a/frappe/integrations/doctype/google_drive/google_drive.js b/frappe/integrations/doctype/google_drive/google_drive.js index f184c6d75c..c314d02e7e 100644 --- a/frappe/integrations/doctype/google_drive/google_drive.js +++ b/frappe/integrations/doctype/google_drive/google_drive.js @@ -4,7 +4,7 @@ frappe.ui.form.on('Google Drive', { refresh: function(frm) { if (!frm.doc.enable) { - frm.dashboard.set_headline(__("To use Google Drive, enable {0}.", [`${__('Google Settings')}`])); + frm.dashboard.set_headline(__("To use Google Drive, enable {0}.", [`${__('Google Settings')}`])); } frappe.realtime.on("upload_to_google_drive", (data) => { diff --git a/frappe/integrations/doctype/google_drive/google_drive.py b/frappe/integrations/doctype/google_drive/google_drive.py index c1c73d7726..859c769018 100644 --- a/frappe/integrations/doctype/google_drive/google_drive.py +++ b/frappe/integrations/doctype/google_drive/google_drive.py @@ -88,7 +88,7 @@ def authorize_access(reauthorize=None): frappe.db.commit() frappe.local.response["type"] = "redirect" - frappe.local.response["location"] = "/desk#Form/{0}".format(quote("Google Drive")) + frappe.local.response["location"] = "/app/Form/{0}".format(quote("Google Drive")) frappe.msgprint(_("Google Drive has been configured.")) except Exception as e: diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.json b/frappe/integrations/doctype/social_login_key/social_login_key.json index 6c0fbdb26c..bb97d8f625 100644 --- a/frappe/integrations/doctype/social_login_key/social_login_key.json +++ b/frappe/integrations/doctype/social_login_key/social_login_key.json @@ -1,4 +1,5 @@ { + "actions": [], "allow_import": 1, "creation": "2017-11-18 15:36:09.676722", "doctype": "DocType", @@ -82,6 +83,7 @@ "label": "Identity Details" }, { + "depends_on": "eval:doc.social_login_provider==\"Custom\"", "fieldname": "icon", "fieldtype": "Data", "label": "Icon" @@ -157,7 +159,9 @@ "label": "User ID Property" } ], - "modified": "2019-12-03 13:13:46.989099", + "index_web_pages_for_search": 1, + "links": [], + "modified": "2020-09-30 14:37:13.616002", "modified_by": "Administrator", "module": "Integrations", "name": "Social Login Key", diff --git a/frappe/integrations/doctype/social_login_key/social_login_key.py b/frappe/integrations/doctype/social_login_key/social_login_key.py index 81df3cca97..1092c3240e 100644 --- a/frappe/integrations/doctype/social_login_key/social_login_key.py +++ b/frappe/integrations/doctype/social_login_key/social_login_key.py @@ -20,6 +20,7 @@ class SocialLoginKey(Document): self.name = frappe.scrub(self.provider_name) def validate(self): + self.set_icon() if self.custom_base_url and not self.base_url: frappe.throw(_("Please enter Base URL"), exc=BaseUrlNotSetError) if not self.authorize_url: @@ -33,6 +34,21 @@ class SocialLoginKey(Document): if self.enable_social_login and not self.client_secret: frappe.throw(_("Please enter Client Secret before social login is enabled"), exc=ClientSecretNotSetError) + def set_icon(self): + icon_map = { + "Google": "google.svg", + "Frappe": "frappe.svg", + "Facebook": "facebook.svg", + "Office 365": "office_365.svg", + "GitHub": "github.svg", + "Salesforce": "salesforce.svg", + "fairlogin": "fair.svg" + } + + if self.provider_name in icon_map: + icon_file = icon_map[self.provider_name] + self.icon = '/assets/frappe/icons/social/{0}'.format(icon_file) + def get_social_login_provider(self, provider, initialize=False): providers = {} diff --git a/frappe/integrations/workspace/integrations/integrations.json b/frappe/integrations/workspace/integrations/integrations.json new file mode 100644 index 0000000000..db96304207 --- /dev/null +++ b/frappe/integrations/workspace/integrations/integrations.json @@ -0,0 +1,260 @@ +{ + "category": "Administration", + "charts": [], + "creation": "2020-03-02 15:16:18.714190", + "developer_mode_only": 0, + "disable_user_customization": 1, + "docstatus": 0, + "doctype": "Workspace", + "extends_another_page": 0, + "hide_custom": 0, + "icon": "integration", + "idx": 0, + "is_standard": 1, + "label": "Integrations", + "links": [ + { + "hidden": 0, + "is_query_report": 0, + "label": "Backup", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Dropbox Settings", + "link_to": "Dropbox Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "S3 Backup Settings", + "link_to": "S3 Backup Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Google Drive", + "link_to": "Google Drive", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Google Services", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Google Settings", + "link_to": "Google Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Google Contacts", + "link_to": "Google Contacts", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Google Calendar", + "link_to": "Google Calendar", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Google Drive", + "link_to": "Google Drive", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Authentication", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Social Login Key", + "link_to": "Social Login Key", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "LDAP Settings", + "link_to": "LDAP Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "OAuth Client", + "link_to": "OAuth Client", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "OAuth Provider Settings", + "link_to": "OAuth Provider Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Payments", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Braintree Settings", + "link_to": "Braintree Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "PayPal Settings", + "link_to": "PayPal Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Razorpay Settings", + "link_to": "Razorpay Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Stripe Settings", + "link_to": "Stripe Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Paytm Settings", + "link_to": "Paytm Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "hidden": 0, + "is_query_report": 0, + "label": "Settings", + "onboard": 0, + "type": "Card Break" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Webhook", + "link_to": "Webhook", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Slack Webhook URL", + "link_to": "Slack Webhook URL", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "Twilio Settings", + "link_to": "Twilio Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + }, + { + "dependencies": "", + "hidden": 0, + "is_query_report": 0, + "label": "SMS Settings", + "link_to": "SMS Settings", + "link_type": "DocType", + "onboard": 0, + "type": "Link" + } + ], + "modified": "2020-12-01 13:38:39.706680", + "modified_by": "Administrator", + "module": "Integrations", + "name": "Integrations", + "owner": "Administrator", + "pin_to_bottom": 0, + "pin_to_top": 0, + "shortcuts": [] +} \ No newline at end of file diff --git a/frappe/model/db_query.py b/frappe/model/db_query.py index c799586d61..8eac75eb65 100644 --- a/frappe/model/db_query.py +++ b/frappe/model/db_query.py @@ -597,7 +597,7 @@ class DatabaseQuery(object): self.match_conditions.append("`tab{0}`.`owner` = {1}".format(self.doctype, frappe.db.escape(self.user, percent=False))) # add user permission only if role has read perm - elif role_permissions.get("read"): + elif role_permissions.get("read") or role_permissions.get("select"): # get user permissions user_permissions = frappe.permissions.get_user_permissions(self.user) self.add_user_permissions(user_permissions) @@ -698,7 +698,7 @@ class DatabaseQuery(object): if c: conditions.append(c) - permision_script_name = get_server_script_map().get("permission_query").get(self.doctype) + permision_script_name = get_server_script_map().get("permission_query", {}).get(self.doctype) if permision_script_name: script = frappe.get_doc("Server Script", permision_script_name) condition = script.get_permission_query_conditions(self.user) diff --git a/frappe/model/delete_doc.py b/frappe/model/delete_doc.py index 15de673e4b..7b29692ad1 100644 --- a/frappe/model/delete_doc.py +++ b/frappe/model/delete_doc.py @@ -292,8 +292,8 @@ def check_if_doc_is_dynamically_linked(doc, method="Delete"): raise_link_exists_exception(doc, reference_doctype, reference_docname, at_position) def raise_link_exists_exception(doc, reference_doctype, reference_docname, row=''): - doc_link = '{1}'.format(doc.doctype, doc.name) - reference_link = '{1}'.format(reference_doctype, reference_docname) + doc_link = '{1}'.format(doc.doctype, doc.name) + reference_link = '{1}'.format(reference_doctype, reference_docname) #hack to display Single doctype only once in message if reference_doctype == reference_docname: diff --git a/frappe/model/document.py b/frappe/model/document.py index 9efd8b6c94..1cd981ead8 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1190,8 +1190,8 @@ class Document(BaseDocument): doc.set(fieldname, flt(doc.get(fieldname), self.precision(fieldname, doc.parentfield))) def get_url(self): - """Returns Desk URL for this document. `/desk#Form/{doctype}/{name}`""" - return "/desk#Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name) + """Returns Desk URL for this document. `/app/Form/{doctype}/{name}`""" + return "/app/Form/{doctype}/{name}".format(doctype=self.doctype, name=self.name) def add_comment(self, comment_type='Comment', text=None, comment_email=None, link_doctype=None, link_name=None, comment_by=None): """Add a comment to this document. diff --git a/frappe/model/naming.py b/frappe/model/naming.py index c2e074990e..e954debe6f 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -67,7 +67,6 @@ def set_new_name(doc): frappe.get_meta(doc.doctype).get_field("name_case") ) - def set_name_from_naming_options(autoname, doc): """ Get a name based on the autoname field option diff --git a/frappe/model/sync.py b/frappe/model/sync.py index b7d9d4d548..e04d3d56b9 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -52,10 +52,10 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe ("desk", "onboarding_step"), ("desk", "onboarding_step_map"), ("desk", "module_onboarding"), - ("desk", "desk_card"), - ("desk", "desk_chart"), - ("desk", "desk_shortcut"), - ("desk", "desk_page")): + ("desk", "workspace_link"), + ("desk", "workspace_chart"), + ("desk", "workspace_shortcut"), + ("desk", "workspace")): files.append(os.path.join(frappe.get_app_path("frappe"), d[0], "doctype", d[1], d[1] + ".json")) @@ -68,7 +68,6 @@ def sync_for(app_name, force=0, sync_everything = False, verbose=False, reset_pe for i, doc_path in enumerate(files): import_file_by_path(doc_path, force=force, ignore_version=True, reset_permissions=reset_permissions, for_sync=True) - #print module_name + ' | ' + doctype + ' | ' + name frappe.db.commit() @@ -84,7 +83,7 @@ def get_doc_files(files, start_path, force=0, sync_everything = False, verbose=F # load in sequence - warning for devs document_types = ['doctype', 'page', 'report', 'dashboard_chart_source', 'print_format', 'website_theme', 'web_form', 'web_template', 'notification', 'print_style', - 'data_migration_mapping', 'data_migration_plan', 'desk_page', + 'data_migration_mapping', 'data_migration_plan', 'workspace', 'onboarding_step', 'module_onboarding'] for doctype in document_types: diff --git a/frappe/patches.txt b/frappe/patches.txt index 1a086303ba..f076d5bd9c 100644 --- a/frappe/patches.txt +++ b/frappe/patches.txt @@ -15,6 +15,7 @@ execute:frappe.reload_doc('core', 'doctype', 'custom_docperm') execute:frappe.reload_doc('core', 'doctype', 'docperm') #2018-05-29 execute:frappe.reload_doc('core', 'doctype', 'comment') frappe.patches.v8_0.drop_is_custom_from_docperm +execute:frappe.reload_doc('core', 'doctype', 'document_naming_rule', force=True) execute:frappe.reload_doc('core', 'doctype', 'module_def') #2020-08-28 execute:frappe.reload_doc('core', 'doctype', 'version') #2017-04-01 execute:frappe.reload_doc('email', 'doctype', 'document_follow') @@ -243,7 +244,6 @@ frappe.patches.v12_0.set_primary_key_in_series execute:frappe.delete_doc("Page", "modules", ignore_missing=True) frappe.patches.v11_0.set_default_letter_head_source frappe.patches.v12_0.setup_comments_from_communications -frappe.patches.v12_0.init_desk_settings #16-05-2019 frappe.patches.v12_0.replace_null_values_in_tables frappe.patches.v12_0.reset_home_settings frappe.patches.v12_0.update_print_format_type @@ -265,7 +265,6 @@ frappe.patches.v12_0.copy_to_parent_for_tags frappe.patches.v12_0.create_notification_settings_for_user frappe.patches.v11_0.make_all_prepared_report_attachments_private #2019-11-26 frappe.patches.v12_0.setup_email_linking -frappe.patches.v12_0.fix_home_settings_for_all_users frappe.patches.v12_0.change_existing_dashboard_chart_filters frappe.patches.v12_0.set_correct_assign_value_in_docs #2020-07-13 execute:frappe.delete_doc("Test Runner") @@ -299,13 +298,14 @@ frappe.patches.v13_0.update_duration_options frappe.patches.v13_0.replace_old_data_import # 2020-06-24 frappe.patches.v13_0.create_custom_dashboards_cards_and_charts frappe.patches.v13_0.rename_is_custom_field_in_dashboard_chart -frappe.patches.v13_0.add_standard_navbar_items +frappe.patches.v13_0.add_standard_navbar_items # 2020-12-15 frappe.patches.v13_0.generate_theme_files_in_public_folder frappe.patches.v13_0.increase_password_length frappe.patches.v12_0.fix_email_id_formatting frappe.patches.v13_0.add_toggle_width_in_navbar_settings frappe.patches.v13_0.rename_notification_fields frappe.patches.v13_0.remove_duplicate_navbar_items +frappe.patches.v13_0.set_social_icons frappe.patches.v12_0.set_default_password_reset_limit execute:frappe.reload_doc('core', 'doctype', 'doctype', force=True) frappe.patches.v13_0.set_route_for_blog_category @@ -316,5 +316,16 @@ frappe.patches.v13_0.delete_event_producer_and_consumer_keys frappe.patches.v13_0.web_template_set_module #2020-10-05 frappe.patches.v13_0.remove_custom_link execute:frappe.delete_doc("DocType", "Footer Item") +execute:frappe.reload_doctype('user') +execute:frappe.reload_doctype('docperm') frappe.patches.v13_0.replace_field_target_with_open_in_new_tab +frappe.core.doctype.role.patches.v13_set_default_desk_properties +frappe.patches.v13_0.add_switch_theme_to_navbar_settings +frappe.patches.v13_0.update_icons_in_customized_desk_pages +execute:frappe.db.set_default('desktop:home_page', 'space') +execute:frappe.delete_doc_if_exists('Page', 'workspace') +execute:frappe.delete_doc_if_exists('Page', 'dashboard', force=1) +frappe.core.doctype.page.patches.drop_unused_pages +execute:frappe.get_doc('Role', 'Guest').save() # remove desk access +frappe.patches.v13_0.rename_desk_page_to_workspace # 02.02.2021 frappe.patches.v13_0.delete_package_publish_tool diff --git a/frappe/patches/v12_0/fix_home_settings_for_all_users.py b/frappe/patches/v12_0/fix_home_settings_for_all_users.py deleted file mode 100644 index e26cbd9eef..0000000000 --- a/frappe/patches/v12_0/fix_home_settings_for_all_users.py +++ /dev/null @@ -1,41 +0,0 @@ -import frappe -from frappe.config import get_modules_from_all_apps_for_user -import json -def execute(): - users = frappe.get_all('User', fields=['name', 'home_settings']) - - for user in users: - - if not user.home_settings: - continue - - home_settings = json.loads(user.home_settings) - - modules_by_category = home_settings.get('modules_by_category') - if not modules_by_category: - continue - visible_modules = [] - category_to_check = [] - - for category, modules in modules_by_category.items(): - visible_modules += modules - category_to_check.append(category) - - all_modules = get_modules_from_all_apps_for_user(user.name) - all_modules = set([m.get('name') or m.get('module_name') or m.get('label') \ - for m in all_modules if m.get('category') in category_to_check]) - - hidden_modules = home_settings.get("hidden_modules", []) - - modules_in_home_settings = set(visible_modules + hidden_modules) - - all_modules = all_modules.union(modules_in_home_settings) - - missing_modules = all_modules - modules_in_home_settings - - if missing_modules: - home_settings['hidden_modules'] = hidden_modules + list(missing_modules) - home_settings = json.dumps(home_settings) - frappe.set_value('User', user.name, 'home_settings', home_settings) - - frappe.cache().delete_key('home_settings') diff --git a/frappe/patches/v13_0/add_switch_theme_to_navbar_settings.py b/frappe/patches/v13_0/add_switch_theme_to_navbar_settings.py new file mode 100644 index 0000000000..29b99464b5 --- /dev/null +++ b/frappe/patches/v13_0/add_switch_theme_to_navbar_settings.py @@ -0,0 +1,21 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + navbar_settings = frappe.get_single("Navbar Settings") + + if frappe.db.exists('Navbar Item', {'item_label': 'Toggle Theme'}): + return + + for navbar_item in navbar_settings.settings_dropdown[6:]: + navbar_item.idx = navbar_item.idx + 1 + + navbar_settings.append('settings_dropdown', { + 'item_label': 'Toggle Theme', + 'item_type': 'Action', + 'action': 'new frappe.ui.ThemeSwitcher().show()', + 'is_standard': 1, + 'idx': 7 + }) + + navbar_settings.save() \ No newline at end of file diff --git a/frappe/patches/v13_0/cleanup_desk_cards.py b/frappe/patches/v13_0/cleanup_desk_cards.py new file mode 100644 index 0000000000..6ac8604041 --- /dev/null +++ b/frappe/patches/v13_0/cleanup_desk_cards.py @@ -0,0 +1,68 @@ +import frappe +from six import string_types +from json import loads +from frappe.desk.doctype.workspace.workspace import get_link_type, get_report_type + +def execute(): + frappe.reload_doc('desk', 'doctype', 'workspace') + + pages = frappe.db.sql("Select `name` from `tabDesk Page`") + # pages = frappe.get_all("Workspace", filters={"is_standard": 0}, pluck="name") + + for page in pages: + rebuild_links(page[0]) + + frappe.delete_doc("DocType", "Desk Card") + +def rebuild_links(page): + # Empty links table + + try: + doc = frappe.get_doc("Workspace", page) + except frappe.DoesNotExistError: + db_doc = get_doc_from_db(page) + + doc = frappe.get_doc(db_doc) + doc.insert(ignore_permissions=True) + + doc.links = [] + + for card in get_all_cards(page): + if isinstance(card.links, string_types): + links = loads(card.links) + else: + links = card.links + + doc.append('links', { + "label": card.label, + "type": "Card Break", + "icon": card.icon, + "hidden": card.hidden or False + }) + + for link in links: + if not frappe.db.exists(get_link_type(link.get('type')), link.get('name')): + continue + + doc.append('links', { + "label": link.get('label') or link.get('name'), + "type": "Link", + "link_type": get_link_type(link.get('type')), + "link_to": link.get('name'), + "onboard": link.get('onboard'), + "dependencies": ', '.join(link.get('dependencies', [])), + "is_query_report": get_report_type(link.get('name')) if link.get('type').lower() == "report" else 0 + }) + + try: + doc.save(ignore_permissions=True) + except frappe.LinkValidationError: + print(doc.as_dict()) + +def get_doc_from_db(page): + result = frappe.db.sql("SELECT * FROM `tabDesk Page` WHERE name=%s", [page], as_dict=True) + if result: + return result[0].update({"doctype": "Workspace"}) + +def get_all_cards(page): + return frappe.db.get_all("Desk Card", filters={"parent": page}, fields=['*'], order_by="idx") \ No newline at end of file diff --git a/frappe/patches/v13_0/rename_desk_page_to_workspace.py b/frappe/patches/v13_0/rename_desk_page_to_workspace.py new file mode 100644 index 0000000000..36626815db --- /dev/null +++ b/frappe/patches/v13_0/rename_desk_page_to_workspace.py @@ -0,0 +1,20 @@ +import frappe +from frappe.model.rename_doc import rename_doc + +def execute(): + if frappe.db.exists("DocType", "Desk Page"): + if frappe.db.exists('DocType', 'Workspace'): + # this patch was not added initially, so this page might still exist + frappe.delete_doc('DocType', 'Desk Page') + else: + frappe.flags.ignore_route_conflict_validation = True + rename_doc('DocType', 'Desk Page', 'Workspace') + frappe.flags.ignore_route_conflict_validation = False + + rename_doc('DocType', 'Desk Chart', 'Workspace Chart', ignore_if_exists=True) + rename_doc('DocType', 'Desk Shortcut', 'Workspace Shortcut', ignore_if_exists=True) + if frappe.db.exists('DocType', 'Desk Link'): + rename_doc('DocType', 'Desk Link', 'Workspace Link', ignore_if_exists=True) + + frappe.reload_doc('desk', 'doctype', 'workspace') + frappe.reload_doc('desk', 'doctype', 'workspace_link') diff --git a/frappe/patches/v13_0/set_social_icons.py b/frappe/patches/v13_0/set_social_icons.py new file mode 100644 index 0000000000..b1c63b48e0 --- /dev/null +++ b/frappe/patches/v13_0/set_social_icons.py @@ -0,0 +1,9 @@ +import frappe + +def execute(): + providers = frappe.get_all("Social Login Key") + + for provider in providers: + doc = frappe.get_doc("Social Login Key", provider) + doc.set_icon() + doc.save() \ No newline at end of file diff --git a/frappe/patches/v13_0/update_icons_in_customized_desk_pages.py b/frappe/patches/v13_0/update_icons_in_customized_desk_pages.py new file mode 100644 index 0000000000..da7d054682 --- /dev/null +++ b/frappe/patches/v13_0/update_icons_in_customized_desk_pages.py @@ -0,0 +1,13 @@ +from __future__ import unicode_literals +import frappe + +def execute(): + pages = frappe.get_all("Desk Page", filters={ "is_standard": False }, fields=["name", "extends", "for_user"]) + default_icon = {} + for page in pages: + if page.extends and page.for_user: + if not default_icon.get(page.extends): + default_icon[page.extends] = frappe.db.get_value("Desk Page", page.extends, "icon") + + icon = default_icon.get(page.extends) + frappe.db.set_value("Desk Page", page.name, "icon", icon) \ No newline at end of file diff --git a/frappe/printing/doctype/print_format/print_format.json b/frappe/printing/doctype/print_format/print_format.json index 3a47fb554f..92d4a67d14 100644 --- a/frappe/printing/doctype/print_format/print_format.json +++ b/frappe/printing/doctype/print_format/print_format.json @@ -154,7 +154,7 @@ "fieldname": "font", "fieldtype": "Select", "label": "Font", - "options": "Default\nArial\nHelvetica\nVerdana\nMonospace" + "options": "Default\nHelvetica Neue\nArial\nHelvetica\nVerdana\nMonospace" }, { "depends_on": "eval:!doc.raw_printing", diff --git a/frappe/printing/doctype/print_format/test_print_format.py b/frappe/printing/doctype/print_format/test_print_format.py index e8375ae5e7..7e30bda23e 100644 --- a/frappe/printing/doctype/print_format/test_print_format.py +++ b/frappe/printing/doctype/print_format/test_print_format.py @@ -11,8 +11,8 @@ test_records = frappe.get_test_records('Print Format') class TestPrintFormat(unittest.TestCase): def test_print_user(self, style=None): print_html = frappe.get_print("User", "Administrator", style=style) - self.assertTrue("" in print_html) - self.assertTrue(re.findall('
        [\s]*administrator[\s]*
        ', print_html)) + self.assertTrue("" in print_html) + self.assertTrue(re.findall('
        [\s]*administrator[\s]*
        ', print_html)) return print_html def test_print_user_standard(self): diff --git a/frappe/printing/doctype/print_settings/print_settings.json b/frappe/printing/doctype/print_settings/print_settings.json index f93ad0ee5a..d64cb4c6d3 100644 --- a/frappe/printing/doctype/print_settings/print_settings.json +++ b/frappe/printing/doctype/print_settings/print_settings.json @@ -170,7 +170,7 @@ "fieldname": "font", "fieldtype": "Select", "label": "Font", - "options": "Default\nArial\nHelvetica\nVerdana\nMonospace" + "options": "Default\nHelvetica Neue\nArial\nHelvetica\nInter\nVerdana\nMonospace" }, { "description": "In points. Default is 9.", @@ -180,9 +180,10 @@ } ], "icon": "fa fa-cog", + "index_web_pages_for_search": 1, "issingle": 1, "links": [], - "modified": "2020-07-02 16:14:47.470668", + "modified": "2020-10-22 23:42:09.471022", "modified_by": "Administrator", "module": "Printing", "name": "Print Settings", diff --git a/frappe/printing/page/print/__init__.py b/frappe/printing/page/print/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js new file mode 100644 index 0000000000..7e1db1eddb --- /dev/null +++ b/frappe/printing/page/print/print.js @@ -0,0 +1,750 @@ +frappe.pages['print'].on_page_load = function(wrapper) { + frappe.ui.make_app_page({ + parent: wrapper, + }); + + let print_view = new frappe.ui.form.PrintView(wrapper); + + $(wrapper).bind('show', () => { + const route = frappe.get_route(); + const doctype = route[1]; + const docname = route[2]; + if (!frappe.route_options || !frappe.route_options.frm) { + frappe.model.with_doc(doctype, docname, () => { + let frm = { doctype: doctype, docname: docname }; + frm.doc = frappe.get_doc(doctype, docname); + frappe.model.with_doctype(doctype, () => { + frm.meta = frappe.get_meta(route[1]); + print_view.show(frm); + }); + }); + } else { + print_view.frm = frappe.route_options.frm; + frappe.route_options.frm = null; + print_view.show(print_view.frm); + } + }); +}; + +frappe.ui.form.PrintView = class { + constructor(wrapper) { + this.wrapper = $(wrapper); + this.page = wrapper.page; + this.make(); + } + + make() { + this.print_wrapper = this.page.main.empty().html( + `` + ); + + this.print_settings = frappe.model.get_doc( + ':Print Settings', + 'Print Settings' + ); + this.setup_toolbar(); + this.setup_menu(); + this.setup_sidebar(); + this.setup_keyboard_shortcuts(); + } + + set_title() { + this.page.set_title(this.frm.docname); + } + + setup_toolbar() { + this.page.set_primary_action( + __('Print'), + () => this.printit(), 'printer' + ); + + this.page.add_button( + __('Full Page'), + () => this.render_page('/printview?'), + { icon: 'full-page' } + ); + + this.page.add_button( + __('PDF'), + () => this.render_page('/api/method/frappe.utils.print_format.download_pdf?'), + { icon: 'small-file' } + ); + + this.page.add_button( + frappe.utils.icon('refresh'), + () => this.refresh_print_format() + ); + } + + setup_sidebar() { + this.sidebar = this.page.sidebar.addClass('print-preview-sidebar'); + + this.print_sel = this.add_sidebar_item( + { + fieldtype: 'Select', + fieldname: 'print_format', + label: 'Print Format', + options: [this.get_default_option_for_select(__('Select Print Format'))], + change: () => this.refresh_print_format(), + default: __('Select Print Format') + }, + ).$input; + + this.language_sel = this.add_sidebar_item( + { + fieldtype: 'Select', + fieldname: 'language', + placeholder: 'Language', + options: [ + this.get_default_option_for_select(__('Select Language')), + ...this.get_language_options() + ], + default: __('Select Language'), + change: () => { + this.set_user_lang(); + this.preview(); + }, + }, + ).$input; + + this.letterhead_selector = this.add_sidebar_item( + { + fieldtype: 'Select', + fieldname: 'letterhead', + label: __('Select Letterhead'), + options: [ + this.get_default_option_for_select(__('Select Letterhead')), + __('No Letterhead') + ], + change: () => this.preview(), + default: this.print_settings.with_letterhead + ? __('No Letterhead') + : __('Select Letterhead') + }, + ).$input; + + this.sidebar_dynamic_section = $( + `
        ` + ).appendTo(this.sidebar); + } + + add_sidebar_item(df, is_dynamic) { + if (df.fieldtype == 'Select') { + df.input_class = 'btn btn-default btn-sm'; + } + + let field = frappe.ui.form.make_control({ + df: df, + parent: is_dynamic ? this.sidebar_dynamic_section : this.sidebar, + render_input: 1, + }); + + if (df.default != null) { + field.set_input(df.default); + } + + return field; + } + + get_default_option_for_select(value) { + return { + label: value, + value: value, + disabled: true + }; + } + + setup_menu() { + this.page.clear_menu(); + + this.page.add_menu_item(__('Print Settings'), () => { + frappe.set_route('Form', 'Print Settings'); + }); + + if ( + frappe.model.get_doc(':Print Settings', 'Print Settings') + .enable_raw_printing == '1' + ) { + this.page.add_menu_item(__('Raw Printing Setting'), () => { + this.printer_setting_dialog(); + }); + } + + if (frappe.user.has_role('System Manager')) { + this.page.add_menu_item(__('Customize'), () => + this.edit_print_format() + ); + } + } + + show(frm) { + this.frm = frm; + this.set_title(); + this.set_breadcrumbs(); + this.setup_customize_dialog(); + + let tasks = [ + this.refresh_print_options, + this.set_default_print_language, + this.set_letterhead_options, + this.preview, + ].map((fn) => fn.bind(this)); + + this.setup_additional_settings(); + return frappe.run_serially(tasks); + } + + set_breadcrumbs() { + frappe.breadcrumbs.add(this.frm.meta.module, this.frm.doctype); + } + + setup_additional_settings() { + this.additional_settings = {}; + this.sidebar_dynamic_section.empty(); + frappe + .xcall('frappe.printing.page.print.print.get_print_settings_to_show', { + doctype: this.frm.doc.doctype, + docname: this.frm.doc.name + }) + .then((settings) => this.add_settings_to_sidebar(settings)); + } + + add_settings_to_sidebar(settings) { + for (let df of settings) { + let field = this.add_sidebar_item({ + ...df, + change: () => { + const val = field.get_value(); + this.additional_settings[field.df.fieldname] = val; + this.preview(); + }, + }, true); + } + } + + edit_print_format() { + let print_format = this.get_print_format(); + let is_custom_format = + print_format.name && + print_format.print_format_builder && + print_format.standard === 'No'; + let is_standard_but_editable = + print_format.name && print_format.custom_format; + + if (is_standard_but_editable) { + frappe.set_route('Form', 'Print Format', print_format.name); + return; + } + if (is_custom_format) { + frappe.set_route('print-format-builder', print_format.name); + return; + } + // start a new print format + frappe.prompt( + [ + { + label: __('New Print Format Name'), + fieldname: 'print_format_name', + fieldtype: 'Data', + reqd: 1, + }, + { + label: __('Based On'), + fieldname: 'based_on', + fieldtype: 'Read Only', + default: print_format.name || 'Standard', + }, + ], + (data) => { + frappe.route_options = { + make_new: true, + doctype: this.frm.doctype, + name: data.print_format_name, + based_on: data.based_on, + }; + frappe.set_route('print-format-builder'); + }, + __('New Custom Print Format'), + __('Start') + ); + } + + refresh_print_format() { + this.set_default_print_language(); + this.toggle_raw_printing(); + this.preview(); + } + + // bind_events () { + // // // hide print view on pressing escape, only if there is no focus on any input + // // $(document).on("keydown", function (e) { + // // if (e.which === 27 && me.frm && e.target === document.body) { + // // me.hide(); + // // } + // // }); + // } + + setup_customize_dialog() { + let print_format = this.get_print_format(); + $(document).on('new-print-format', (e) => { + this.refresh_print_options(); + if (e.print_format) { + this.print_sel.val(e.print_format); + } + // start a new print format + frappe.prompt( + [ + { + label: __('New Print Format Name'), + fieldname: 'print_format_name', + fieldtype: 'Data', + reqd: 1, + }, + { + label: __('Based On'), + fieldname: 'based_on', + fieldtype: 'Read Only', + default: print_format.name || 'Standard', + }, + ], + (data) => { + frappe.route_options = { + make_new: true, + doctype: this.frm.doctype, + name: data.print_format_name, + based_on: data.based_on, + }; + frappe.set_route('print-format-builder'); + }, + __('New Custom Print Format'), + __('Start') + ); + }); + } + + setup_keyboard_shortcuts() { + this.wrapper.find('.print-toolbar a.btn-default').each((i, el) => { + frappe.ui.keys.get_shortcut_group(this.frm.page).add($(el)); + }); + } + + set_letterhead_options() { + let letterhead_options = [ + this.get_default_option_for_select(__('Select Letterhead')), + __('No Letterhead') + ]; + let default_letterhead; + let doc_letterhead = this.frm.doc.letter_head; + + return frappe.db + .get_list('Letter Head', { fields: ['name', 'is_default'] }) + .then((letterheads) => { + this.letterhead_selector.empty(); + letterheads.map((letterhead) => { + if (letterhead.is_default) default_letterhead = letterhead.name; + return letterhead_options.push(letterhead.name); + }); + + this.letterhead_selector.add_options(letterhead_options); + let selected_letterhead = doc_letterhead || default_letterhead; + if (selected_letterhead) + this.letterhead_selector.val(selected_letterhead); + }); + } + + set_user_lang() { + this.lang_code = this.language_sel.val(); + } + + get_language_options() { + return frappe.get_languages(); + } + + set_default_print_language() { + let print_format = this.get_print_format(); + this.lang_code = + print_format.default_print_language || + this.frm.doc.language || + frappe.boot.lang; + this.language_sel.val(this.lang_code); + } + + toggle_raw_printing() { + const is_raw_printing = this.is_raw_printing(); + this.wrapper.find('.btn-print-preview').toggle(!is_raw_printing); + this.wrapper.find('.btn-download-pdf').toggle(!is_raw_printing); + } + + preview() { + const $print_format = this.print_wrapper.find('iframe'); + this.$print_format_body = $print_format.contents(); + this.get_print_html((out) => { + if (!out.html) { + out.html = this.get_no_preview_html(); + } + + this.setup_print_format_dom(out, $print_format); + + const print_height = $print_format.get(0).offsetHeight; + const $message = this.wrapper.find('.page-break-message'); + + const print_height_inches = frappe.dom.pixel_to_inches(print_height); + // if contents are large enough, indicate that it will get printed on multiple pages + // Maximum height for an A4 document is 11.69 inches + if (print_height_inches > 11.69) { + $message.text(__('This may get printed on multiple pages')); + } else { + $message.text(''); + } + }); + } + + setup_print_format_dom(out, $print_format) { + this.print_wrapper.find('.print-format-skeleton').remove(); + this.$print_format_body.find('head').html( + ` + ` + ); + + this.$print_format_body.find('body').html( + `` + ); + + this.show_footer(); + + this.$print_format_body.find('.print-format').css({ + display: 'flex', + flexDirection: 'column', + }); + + this.$print_format_body.find('.page-break').css({ + display: 'flex', + 'flex-direction': 'column', + flex: '1', + }); + + setTimeout(() => { + $print_format.height(this.$print_format_body.find('.print-format').outerHeight()); + }, 500); + } + + hide() { + if (this.frm.setup_done && this.frm.page.current_view_name === 'print') { + this.frm.page.set_view( + this.frm.page.previous_view_name === 'print' + ? 'main' + : this.frm.page.previous_view_name || 'main' + ); + } + } + + show_footer() { + // footer is hidden by default as reqd by pdf generation + // simple hack to show it in print preview + + this.$print_format_body.find('#footer-html').attr( + 'style', + ` + display: block !important; + order: 1; + margin-top: auto; + padding-top: var(--padding-xl) + ` + ); + } + + printit() { + let me = this; + frappe.call({ + method: + 'frappe.printing.doctype.print_settings.print_settings.is_print_server_enabled', + callback: function(data) { + if (data.message) { + frappe.call({ + method: 'frappe.utils.print_format.print_by_server', + args: { + doctype: me.frm.doc.doctype, + name: me.frm.doc.name, + print_format: me.selected_format(), + no_letterhead: me.with_letterhead(), + letterhead: this.get_letterhead(), + }, + callback: function() {}, + }); + } else if (me.get_mapped_printer().length === 1) { + // printer is already mapped in localstorage (applies for both raw and pdf ) + if (me.is_raw_printing()) { + me.get_raw_commands(function(out) { + frappe.ui.form + .qz_connect() + .then(function() { + let printer_map = me.get_mapped_printer()[0]; + let data = [out.raw_commands]; + let config = qz.configs.create(printer_map.printer); + return qz.print(config, data); + }) + .then(frappe.ui.form.qz_success) + .catch((err) => { + frappe.ui.form.qz_fail(err); + }); + }); + } else { + frappe.show_alert( + { + message: __('PDF printing via "Raw Print" is not supported.'), + subtitle: __( + 'Please remove the printer mapping in Printer Settings and try again.' + ), + indicator: 'info', + }, + 14 + ); + //Note: need to solve "Error: Cannot parse (FILE) as a PDF file" to enable qz pdf printing. + } + } else if (me.is_raw_printing()) { + // printer not mapped in localstorage and the current print format is raw printing + frappe.show_alert( + { + message: __('Printer mapping not set.'), + subtitle: __( + 'Please set a printer mapping for this print format in the Printer Settings' + ), + indicator: 'warning', + }, + 14 + ); + me.printer_setting_dialog(); + } else { + me.render_page('/printview?', true); + } + }, + }); + } + + render_page(method, printit = false) { + let w = window.open( + frappe.urllib.get_full_url( + method + + 'doctype=' + + encodeURIComponent(this.frm.doc.doctype) + + '&name=' + + encodeURIComponent(this.frm.doc.name) + + (printit ? '&trigger_print=1' : '') + + '&format=' + + encodeURIComponent(this.selected_format()) + + '&no_letterhead=' + + (this.with_letterhead() ? '0' : '1') + + '&letterhead=' + + encodeURIComponent(this.get_letterhead()) + + '&settings=' + + encodeURIComponent(JSON.stringify(this.additional_settings)) + + (this.lang_code ? '&_lang=' + this.lang_code : '') + ) + ); + if (!w) { + frappe.msgprint(__('Please enable pop-ups')); + return; + } + } + + get_print_html(callback) { + let print_format = this.get_print_format(); + if (print_format.raw_printing) { + callback({ + html: this.get_no_preview_html(), + }); + return; + } + if (this._req) { + this._req.abort(); + } + this._req = frappe.call({ + method: 'frappe.www.printview.get_html_and_style', + args: { + doc: this.frm.doc, + print_format: this.selected_format(), + no_letterhead: !this.with_letterhead() ? 1 : 0, + letterhead: this.get_letterhead(), + settings: this.additional_settings, + _lang: this.lang_code, + }, + callback: function(r) { + if (!r.exc) { + callback(r.message); + } + }, + }); + } + + get_letterhead() { + return this.letterhead_selector.val(); + } + + get_no_preview_html() { + return `
        + ${__('No Preview Available')} +
        `; + } + + get_raw_commands(callback) { + // fetches rendered raw commands from the server for the current print format. + frappe.call({ + method: 'frappe.www.printview.get_rendered_raw_commands', + args: { + doc: this.frm.doc, + print_format: this.selected_format(), + _lang: this.lang_code, + }, + callback: function(r) { + if (!r.exc) { + callback(r.message); + } + }, + }); + } + + get_mapped_printer() { + // returns a list of "print format: printer" mapping filtered by the current print format + let print_format_printer_map = this.get_print_format_printer_map(); + if (print_format_printer_map[this.frm.doctype]) { + return print_format_printer_map[this.frm.doctype].filter( + (printer_map) => printer_map.print_format == this.selected_format() + ); + } else { + return []; + } + } + + get_print_format_printer_map() { + // returns the whole object "print_format_printer_map" stored in the localStorage. + try { + let print_format_printer_map = JSON.parse( + localStorage.print_format_printer_map + ); + return print_format_printer_map; + } catch (e) { + return {}; + } + } + + refresh_print_options() { + this.print_formats = frappe.meta.get_print_formats(this.frm.doctype); + return this.print_sel.empty().add_options([ + this.get_default_option_for_select(__('Select Print Format')), + ...this.print_formats + ]); + } + + selected_format() { + return ( + this.print_sel.val() || this.frm.meta.default_print_format || 'Standard' + ); + } + + is_raw_printing(format) { + return this.get_print_format(format).raw_printing === 1; + } + + get_print_format(format) { + let print_format = {}; + if (!format) { + format = this.selected_format(); + } + + if (locals['Print Format'] && locals['Print Format'][format]) { + print_format = locals['Print Format'][format]; + } + + return print_format; + } + + with_letterhead() { + return cint(this.get_letterhead() !== __('No Letterhead')); + } + + set_style(style) { + frappe.dom.set_style(style || frappe.boot.print_css, 'print-style'); + } + + printer_setting_dialog() { + // dialog for the Printer Settings + this.print_format_printer_map = this.get_print_format_printer_map(); + this.data = this.print_format_printer_map[this.frm.doctype] || []; + this.printer_list = []; + frappe.ui.form.qz_get_printer_list().then((data) => { + this.printer_list = data; + const dialog = new frappe.ui.Dialog({ + title: __('Printer Settings'), + fields: [ + { + fieldtype: 'Section Break', + }, + { + fieldname: 'printer_mapping', + fieldtype: 'Table', + label: __('Printer Mapping'), + in_place_edit: true, + data: this.data, + get_data: () => { + return this.data; + }, + fields: [ + { + fieldtype: 'Select', + fieldname: 'print_format', + default: 0, + options: this.print_formats, + read_only: 0, + in_list_view: 1, + label: __('Print Format'), + }, + { + fieldtype: 'Select', + fieldname: 'printer', + default: 0, + options: this.printer_list, + read_only: 0, + in_list_view: 1, + label: __('Printer'), + }, + ], + }, + ], + primary_action: () => { + let printer_mapping = dialog.get_values()['printer_mapping']; + if (printer_mapping && printer_mapping.length) { + let print_format_list = printer_mapping.map((a) => a.print_format); + let has_duplicate = print_format_list.some( + (item, idx) => print_format_list.indexOf(item) != idx + ); + if (has_duplicate) + frappe.throw( + __( + 'Cannot have multiple printers mapped to a single print format.' + ) + ); + } else { + printer_mapping = []; + } + dialog.print_format_printer_map = this.get_print_format_printer_map(); + dialog.print_format_printer_map[this.frm.doctype] = printer_mapping; + localStorage.print_format_printer_map = JSON.stringify( + dialog.print_format_printer_map + ); + dialog.hide(); + }, + primary_action_label: __('Save'), + }); + dialog.show(); + if (!(this.printer_list && this.printer_list.length)) { + frappe.throw(__('No Printer is Available.')); + } + }); + } +}; diff --git a/frappe/printing/page/print/print.json b/frappe/printing/page/print/print.json new file mode 100644 index 0000000000..bea659c264 --- /dev/null +++ b/frappe/printing/page/print/print.json @@ -0,0 +1,18 @@ +{ + "content": null, + "creation": "2020-10-09 17:23:15.163030", + "docstatus": 0, + "doctype": "Page", + "idx": 0, + "modified": "2020-10-09 17:23:15.163030", + "modified_by": "Administrator", + "module": "Printing", + "name": "print", + "owner": "Administrator", + "page_name": "Print", + "roles": [], + "script": null, + "standard": "Yes", + "style": null, + "system_page": 0 +} \ No newline at end of file diff --git a/frappe/printing/page/print/print.py b/frappe/printing/page/print/print.py new file mode 100644 index 0000000000..7ddf68c30d --- /dev/null +++ b/frappe/printing/page/print/print.py @@ -0,0 +1,19 @@ +import frappe + +@frappe.whitelist() +def get_print_settings_to_show(doctype, docname): + doc = frappe.get_doc(doctype, docname) + print_settings = frappe.get_single('Print Settings') + + if hasattr(doc, 'get_print_settings'): + fields = doc.get_print_settings() or [] + else: + return [] + + print_settings_fields = [] + for fieldname in fields: + df = print_settings.meta.get_field(fieldname) + df.default = print_settings.get(fieldname) + print_settings_fields.append(df) + + return print_settings_fields diff --git a/frappe/printing/page/print/print_skeleton_loading.html b/frappe/printing/page/print/print_skeleton_loading.html new file mode 100644 index 0000000000..c1e6a0ddf4 --- /dev/null +++ b/frappe/printing/page/print/print_skeleton_loading.html @@ -0,0 +1,164 @@ + diff --git a/frappe/printing/page/print_format_builder/print_format_builder.css b/frappe/printing/page/print_format_builder/print_format_builder.css index 507cb520ed..0f7796536d 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.css +++ b/frappe/printing/page/print_format_builder/print_format_builder.css @@ -1,83 +1,160 @@ -.print-format-builder-section, .print-format-builder-add-section { - border: 1px solid #d1d8dd; +.print-format-builder-section { + border: 1px solid var(--dark-border-color); + border-radius: var(--border-radius); margin: 0px; - margin-bottom: 15px; + margin-bottom: var(--margin-md); } + .print-format-builder-add-section, .print-format-builder-header { - border: 1px dashed #d1d8dd; - padding: 15px; + border: 1px dashed var(--dark-border-color); + border-radius: var(--border-radius); + padding: var(--padding-sm); + width: 100%; + display: inline-block; + background: var(--bg-color); cursor: pointer; } +.print-format-builder-header-edit { + margin-bottom: var(--margin-sm); +} + .print-format-builder-header { - margin-bottom: 15px; + margin-bottom: var(--margin-md); +} + +.print-format-builder-add-section { + color: var(--text-light); + align-items: center; + padding: var(--padding-lg); + display: flex; + justify-content: center; +} + +.print-format-builder-add-section .icon { + margin-right: var(--margin-sm); } .print-format-builder-column { - padding: 15px; - margin: 0px -15px 0 -16px; - background-color: white; - min-height: 64px; - border-left: 1px solid #d1d8dd; - border-right: 1px solid #d1d8dd; + border-radius: var(--border-radius); } -.section-column:last-child .print-format-builder-column { - margin-right: -16px; +.print-format-builder-section .section-column { + padding: var(--padding-xs) 0 var(--padding-md) var(--padding-md); +} + +.print-format-builder-section .section-column:last-child { + padding-right: var(--padding-md); } .print-format-builder-field { - padding: 5px; - border: 1px solid #d1d8dd; - border-radius: 3px; - margin-bottom: 10px; - min-height: 34px; + padding: 8px; + width: 100%; + display: inline-block; + border-radius: var(--border-radius); + background: var(--bg-light-gray); + margin-bottom: var(--margin-sm); + font-size: var(--text-md); + color: var(--text-color); } .print-format-builder-field:last-child { - margin-bottom: 0px; + margin-bottom: 0; } -.print-format-builder-field-placeholder { - margin-bottom: 10px; -} - -.print-format-builder-field-placeholder:last-child { - margin-bottom: 0px; +.print-format-builder-field .field-label { + vertical-align: middle; } .print-format-builder-column .print-format-builder-field { cursor: move; } +.print-format-builder-section-head .section-label { + font-size: var(--text-lg); + color: var(--text-color); + font-weight: 500; + vertical-align: middle; + margin-left: var(--margin-sm); +} .print-format-builder-section-head { cursor: move; - padding: 10px 15px; - border-bottom: 1px solid #d1d8dd; + padding: var(--padding-md) calc(var(--padding-md) + 8px) + var(--padding-sm) calc(var(--padding-md) + 8px); +} + +.column-selector-row { + margin-bottom: var(--margin-xs); + padding: var(--padding-xs) 0; + cursor: grab; } .column-selector-row:hover { - background-color: #fafbfc; + background-color: var(--highlight-color); } -.column-selector-row .form-control { - margin-top: 5px; +.column-selector-row .drag-handle { + margin-right: var(--margin-sm); +} + +.print-format-builder-field .drag-handle { + margin-right: var(--margin-sm); +} + +.print-format-builder-sidebar .sidebar-field { + width: 100%; + padding: 4px 8px; + color: var(--text-on-light-gray); + /* color: var(--text-light); */ + text-align: left; + cursor: grab; +} + +.print-format-builder-sidebar .sidebar-custom-field { + background-color: var(--gray-300); } .print-format-builder-sidebar { - display: inline-block; - width: 160px; - vertical-align: top; - position: fixed; - top: 110px; - bottom: 0px; + top: calc(var(--navbar-height) + 70px); + position: sticky; +} + +.print-format-builder-sidebar-fields { + padding: var(--padding-xs); overflow-y: auto; + height: 75vh; +} + +.print-format-builder-field-placeholder { + margin-bottom: var(--margin-sm); +} + +.print-format-builder-field-placeholder:last-child { + margin-bottom: 0; +} + +.print-format-builder-field-placeholder .drag-handle { + margin-right: var(--margin-sm); +} + +.filter-searchbox { + padding: 0 var(--padding-xs); + margin-bottom: var(--margin-sm); +} + +.filter-searchbox input { + background-color: var(--control-bg-on-gray); } .print-format-builder-main { display: inline-block; vertical-align: top; border-top: 0px; - margin-left: 160px; + padding: var(--padding-lg); +} + +.print-format-help-message { + font-size: var(--text-md); + margin-bottom: var(--margin-md); } diff --git a/frappe/printing/page/print_format_builder/print_format_builder.js b/frappe/printing/page/print_format_builder/print_format_builder.js index 4e049d120a..eb87190ab5 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder.js +++ b/frappe/printing/page/print_format_builder/print_format_builder.js @@ -42,14 +42,12 @@ frappe.PrintFormatBuilder = Class.extend({ this.page = frappe.ui.make_app_page({ parent: this.parent, title: __("Print Format Builder"), - single_column: true }); this.page.main.css({"border-color": "transparent"}); - this.page.sidebar = $('').appendTo(this.page.main); - this.page.main = $('').appendTo(this.page.main); + this.page.sidebar = $('').appendTo(this.page.sidebar); + this.page.main = $('').appendTo(this.page.main); // future-bindings for buttons on sections / fields // bind only once @@ -61,7 +59,6 @@ frappe.PrintFormatBuilder = Class.extend({ }, show_start: function() { this.page.main.html(frappe.render_template("print_format_builder_start", {})); - this.page.sidebar.html(""); this.page.clear_actions(); this.page.set_title(__("Print Format Builder")); this.start_edit_print_format(); @@ -168,16 +165,11 @@ frappe.PrintFormatBuilder = Class.extend({ }); }, setup_sidebar: function() { - var me = this; - this.page.sidebar.empty(); - // prepend custom HTML field var fields = [this.get_custom_html_field()].concat(this.meta.fields); - - $(frappe.render_template("print_format_builder_sidebar", - {fields: fields})) - .appendTo(this.page.sidebar); - + this.page.sidebar.html( + $(frappe.render_template("print_format_builder_sidebar", {fields: fields})) + ); this.setup_field_filter(); }, get_custom_html_field: function() { @@ -216,8 +208,8 @@ frappe.PrintFormatBuilder = Class.extend({ if(!this.print_heading_template) { // default print heading template this.print_heading_template = ''; } @@ -319,7 +311,7 @@ frappe.PrintFormatBuilder = Class.extend({ var me = this; // drag from fields library - Sortable.create(this.page.sidebar.find(".print-format-builder-fields").get(0), + Sortable.create(this.page.sidebar.find(".print-format-builder-sidebar-fields").get(0), { group: { name:'field', put: true, pull:"clone" @@ -466,9 +458,6 @@ frappe.PrintFormatBuilder = Class.extend({ field.remove(); }, input_class: "btn-danger", - input_css: { - "margin-top": "10px" - } } ], }); diff --git a/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html b/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html index 697f27866e..15d029704b 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html +++ b/frappe/printing/page/print_format_builder/print_format_builder_column_selector.html @@ -1,33 +1,36 @@ -

        {{ __("Check columns to select, drag to set order.") }} +

        {{ __("Check columns to select, drag to set order.") }} {{ __("Widths can be set in px or %.") }}

        {{ __("Some columns might get cut off when printing to PDF. Try to keep number of columns under 10.") }}

        -

        {{ __("Column") }}

        -

        {{ __("Width") }}

        +

        {{ __("Column") }}

        +

        {{ __("Width") }}

        - {% for (i=0; i < fields.length; i++) { var f = fields[i]; %} - {% var selected = in_list(column_names, f.fieldname) %} -
        -
        -
        - -
        -
        -
        - -
        -
        - {% } %} + {% for (i=0; i < fields.length; i++) { var f = fields[i]; %} + {% var selected = in_list(column_names, f.fieldname) %} +
        +
        +
        + +
        +
        + +
        +
        +
        + +
        +
        + {% } %}
        diff --git a/frappe/printing/page/print_format_builder/print_format_builder_field.html b/frappe/printing/page/print_format_builder/print_format_builder_field.html index eb29092f28..0e207222a5 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder_field.html +++ b/frappe/printing/page/print_format_builder/print_format_builder_field.html @@ -1,35 +1,46 @@ diff --git a/frappe/printing/page/print_format_builder/print_format_builder_layout.html b/frappe/printing/page/print_format_builder/print_format_builder_layout.html index 04475d8fab..f45a760cbc 100644 --- a/frappe/printing/page/print_format_builder/print_format_builder_layout.html +++ b/frappe/printing/page/print_format_builder/print_format_builder_layout.html @@ -1,11 +1,15 @@ -
        -
        - {%= __("Drag elements from the sidebar to add. Drag them back to trash.") %}

        +
        +