From dd14ac5f93dad9f931f61f0914718b34730692d9 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 8 Jun 2022 11:37:11 +0530 Subject: [PATCH 01/38] feat(minor): add custom formatters for text type controls --- frappe/public/js/frappe/doctype/index.js | 20 +++++++++++++ frappe/public/js/frappe/form/dashboard.js | 2 +- frappe/public/js/frappe/form/formatters.js | 28 +++++++++++++++---- .../js/frappe/widgets/onboarding_widget.js | 18 ++++++------ frappe/public/scss/common/grid.scss | 2 +- frappe/public/scss/desk/desktop.scss | 4 +-- frappe/public/scss/login.bundle.scss | 3 +- 7 files changed, 58 insertions(+), 19 deletions(-) diff --git a/frappe/public/js/frappe/doctype/index.js b/frappe/public/js/frappe/doctype/index.js index 09f020f370..e31e2dab7a 100644 --- a/frappe/public/js/frappe/doctype/index.js +++ b/frappe/public/js/frappe/doctype/index.js @@ -5,6 +5,26 @@ frappe.provide("frappe.model"); apply to both DocType form and customize form. */ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.Controller { + setup() { + // setup formatters for fieldtype + frappe.meta.docfield_map['DocField'].fieldtype.formatter = (value) => { + const prefix = { + 'Tab Break': '🔴', + 'Section Break': '🔵', + 'Column Break': '🟡', + 'Check': '☑', + 'Link': '🔗', + 'Currency': '💲', + 'Date': '📆', + 'Table': '🗂' + } + if (prefix[value]) { + value = prefix[value] + ' ' + value; + } + return value; + } + } + max_attachments() { if (!this.frm.doc.max_attachments) { return; diff --git a/frappe/public/js/frappe/form/dashboard.js b/frappe/public/js/frappe/form/dashboard.js index 0731bdf8fb..c057903a63 100644 --- a/frappe/public/js/frappe/form/dashboard.js +++ b/frappe/public/js/frappe/form/dashboard.js @@ -19,7 +19,7 @@ frappe.ui.form.Dashboard = class FormDashboard { }); this.heatmap_area = this.make_section({ - label: __("Overview"), + label: __("Activity"), css_class: 'form-heatmap', hidden: 1, collapsible: 1, diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 2b0f996661..a5425eeb9f 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -15,17 +15,35 @@ frappe.form.formatters = { return "
" + value + "
"; } }, + _apply_custom_formatter: function(value, df) { + /* you can add an arbitrary formatter in df.formatter + example: + frappe.meta.docfield_map[df.parent][df.fieldname].formatter = (value) => { + if (value==='Test') return '😜'; + } + */ + + if (df) { + const std_df = frappe.meta.docfield_map[df.parent] && frappe.meta.docfield_map[df.parent][df.fieldname]; + if (std_df && std_df.formatter && typeof std_df.formatter==='function') { + value = std_df.formatter(value); + } + } + return value; + }, Data: function(value, df) { if (df && df.options == "URL") { return `${value}`; } - return value==null ? "" : value; + value = value==null ? "" : value + + return frappe.form.formatters._apply_custom_formatter(value, df); }, - Autocomplete: function(value) { - return __(frappe.form.formatters["Data"](value)); + Autocomplete: function(value, df) { + return __(frappe.form.formatters["Data"](value, df)); }, - Select: function(value) { - return __(frappe.form.formatters["Data"](value)); + Select: function(value, df) { + return __(frappe.form.formatters["Data"](value, df)); }, Float: function(value, docfield, options, doc) { // don't allow 0 precision for Floats, hence or'ing with null diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index 7d379d4531..128d7e691a 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -222,7 +222,7 @@ export default class OnboardingWidget extends Widget { const on_finish = () => { let msg_dialog = frappe.msgprint({ message: __("Let's take you back to onboarding"), - title: __("Great Job"), + title: __("Onboarding complete"), primary_action: { action: () => { frappe.set_route(current_route).then(() => { @@ -265,7 +265,7 @@ export default class OnboardingWidget extends Widget { if (success) { args.message = __("Let's take you back to onboarding"); - args.title = __("Looks Great"); + args.title = __("Action Complete"); args.primary_action = { action: () => { frappe.set_route(current_route).then(() => { @@ -278,7 +278,7 @@ export default class OnboardingWidget extends Widget { custom_onhide = () => args.primary_action.action(); } else { args.message = __("Looks like you didn't change the value"); - args.title = __("Oops"); + args.title = __("Try Again"); args.secondary_action = { action: () => frappe.set_route(current_route), label: __("Go Back"), @@ -314,7 +314,7 @@ export default class OnboardingWidget extends Widget { const on_finish = () => { frappe.msgprint({ message: __("Awesome, now try making an entry yourself"), - title: __("Great"), + title: __("Document Saved"), primary_action: { action: () => { frappe.set_route(current_route).then(() => { @@ -337,8 +337,8 @@ export default class OnboardingWidget extends Widget { let callback = () => { frappe.msgprint({ - message: __("You're doing great, let's take you back to the onboarding page."), - title: __("Good Work 🎉"), + message: __("Let's take you back to onboarding"), + title: __("Action Complete"), primary_action: { action: () => { frappe.set_route(current_route).then(() => { @@ -358,7 +358,7 @@ export default class OnboardingWidget extends Widget { frappe.route_hooks.after_save = () => { frappe.msgprint({ message: __("Submit this document to complete this step."), - title: __("Great") + title: __("Document Saved") }); }; frappe.route_hooks.after_submit = callback; @@ -377,7 +377,7 @@ export default class OnboardingWidget extends Widget { if (frappe.get_route_str() != current_route) { let success_dialog = frappe.msgprint({ message: __("Let's take you back to onboarding"), - title: __("Looks Great"), + title: __("Document Saved"), primary_action: { action: () => { success_dialog.hide(); @@ -397,7 +397,7 @@ export default class OnboardingWidget extends Widget { } else { frappe.msgprint({ message: __("Let us continue with the onboarding"), - title: __("Looks Great") + title: __("Document Saved") }); this.mark_complete(step); } diff --git a/frappe/public/scss/common/grid.scss b/frappe/public/scss/common/grid.scss index d1f89abbcd..07ab6d75a9 100644 --- a/frappe/public/scss/common/grid.scss +++ b/frappe/public/scss/common/grid.scss @@ -201,7 +201,7 @@ } .link-btn { - top: 8px; + top: 2px; } .form-control:focus { diff --git a/frappe/public/scss/desk/desktop.scss b/frappe/public/scss/desk/desktop.scss index 8be8abed35..c0fef60162 100644 --- a/frappe/public/scss/desk/desktop.scss +++ b/frappe/public/scss/desk/desktop.scss @@ -421,8 +421,8 @@ body { display: none; } - i { - color: var(--green-600); + .icon { + stroke: var(--white); } span { diff --git a/frappe/public/scss/login.bundle.scss b/frappe/public/scss/login.bundle.scss index 8d0a32846f..488dd4106e 100644 --- a/frappe/public/scss/login.bundle.scss +++ b/frappe/public/scss/login.bundle.scss @@ -16,7 +16,7 @@ body { .for-forgot, .for-signup, .for-email-login { - padding: max(15vh, 70px) 0; + padding: max(10vh, 60px) 0; @include media-breakpoint-up(sm) { .page-card { @@ -177,6 +177,7 @@ body { } h4 { + margin-top: 1rem; font-size: var(--text-xl); color: var(--text-color); } From 9bbc592ed4d723a455909dd0161bb058bffc628a Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 8 Jun 2022 12:00:43 +0530 Subject: [PATCH 02/38] chore: add semis --- frappe/public/js/frappe/doctype/index.js | 2 +- frappe/public/js/frappe/form/formatters.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/doctype/index.js b/frappe/public/js/frappe/doctype/index.js index e31e2dab7a..c3618e6fd8 100644 --- a/frappe/public/js/frappe/doctype/index.js +++ b/frappe/public/js/frappe/doctype/index.js @@ -17,7 +17,7 @@ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form. 'Currency': '💲', 'Date': '📆', 'Table': '🗂' - } + }; if (prefix[value]) { value = prefix[value] + ' ' + value; } diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index a5425eeb9f..70b637671a 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -35,7 +35,7 @@ frappe.form.formatters = { if (df && df.options == "URL") { return `${value}`; } - value = value==null ? "" : value + value = value==null ? "" : value; return frappe.form.formatters._apply_custom_formatter(value, df); }, From cc10479e1123dbff0e42ce9bd99789edd3dfd410 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 8 Jun 2022 12:54:17 +0530 Subject: [PATCH 03/38] fix(minor): fix for customize form --- frappe/public/js/frappe/doctype/index.js | 5 +++-- frappe/public/js/frappe/form/formatters.js | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/doctype/index.js b/frappe/public/js/frappe/doctype/index.js index c3618e6fd8..f8b342217f 100644 --- a/frappe/public/js/frappe/doctype/index.js +++ b/frappe/public/js/frappe/doctype/index.js @@ -6,8 +6,9 @@ frappe.provide("frappe.model"); */ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.Controller { setup() { + console.log(this.frm.doctype); // setup formatters for fieldtype - frappe.meta.docfield_map['DocField'].fieldtype.formatter = (value) => { + frappe.meta.docfield_map[this.frm.doctype==='DocType' ? 'DocField' : 'Customize Form Field'].fieldtype.formatter = (value) => { const prefix = { 'Tab Break': '🔴', 'Section Break': '🔵', @@ -22,7 +23,7 @@ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form. value = prefix[value] + ' ' + value; } return value; - } + }; } max_attachments() { diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 70b637671a..15bbd53a1b 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -16,7 +16,7 @@ frappe.form.formatters = { } }, _apply_custom_formatter: function(value, df) { - /* you can add an arbitrary formatter in df.formatter + /* you can add a custom formatter in df.formatter example: frappe.meta.docfield_map[df.parent][df.fieldname].formatter = (value) => { if (value==='Test') return '😜'; From 17d537517b00baaa3fb8faf8a040aae00eb744d0 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 8 Jun 2022 13:04:58 +0530 Subject: [PATCH 04/38] chore: remove console.log --- frappe/public/js/frappe/doctype/index.js | 1 - 1 file changed, 1 deletion(-) diff --git a/frappe/public/js/frappe/doctype/index.js b/frappe/public/js/frappe/doctype/index.js index f8b342217f..a8b84d3ff0 100644 --- a/frappe/public/js/frappe/doctype/index.js +++ b/frappe/public/js/frappe/doctype/index.js @@ -6,7 +6,6 @@ frappe.provide("frappe.model"); */ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.Controller { setup() { - console.log(this.frm.doctype); // setup formatters for fieldtype frappe.meta.docfield_map[this.frm.doctype==='DocType' ? 'DocField' : 'Customize Form Field'].fieldtype.formatter = (value) => { const prefix = { From 378b44a382741b4dc9b58e7867431a562ecb35f3 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 8 Jun 2022 16:36:19 +0530 Subject: [PATCH 05/38] fix(minor): fix flaky test --- cypress/integration/customize_form.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/cypress/integration/customize_form.js b/cypress/integration/customize_form.js index 70615085c3..3857d7ccd8 100644 --- a/cypress/integration/customize_form.js +++ b/cypress/integration/customize_form.js @@ -1,5 +1,6 @@ context('Customize Form', () => { before(() => { + cy.login(); cy.visit('/app/customize-form'); }); it('Changing to naming rule should update autoname', () => { @@ -19,4 +20,4 @@ context('Customize Form', () => { cy.get_field("autoname", "Data").should("have.value", value); }); }); -}); \ No newline at end of file +}); From 3ceafc42fd7dbc42e2f79563a7af24cb5894c506 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Jun 2022 09:14:47 +0530 Subject: [PATCH 06/38] fix(minor): fewer emojis --- frappe/public/js/frappe/doctype/index.js | 5 ----- frappe/public/js/frappe/form/formatters.js | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/doctype/index.js b/frappe/public/js/frappe/doctype/index.js index a8b84d3ff0..d8ffa1097a 100644 --- a/frappe/public/js/frappe/doctype/index.js +++ b/frappe/public/js/frappe/doctype/index.js @@ -12,11 +12,6 @@ frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form. 'Tab Break': '🔴', 'Section Break': '🔵', 'Column Break': '🟡', - 'Check': '☑', - 'Link': '🔗', - 'Currency': '💲', - 'Date': '📆', - 'Table': '🗂' }; if (prefix[value]) { value = prefix[value] + ' ' + value; diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 15bbd53a1b..955a956b90 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -218,7 +218,7 @@ frappe.form.formatters = { } } - return frappe.form.formatters.Data(value); + return frappe.form.formatters.Data(value, df); }, Time: function(value) { if (value) { From 65f5910dc39706cd9a86424b9f0aca1889874da3 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Jun 2022 10:31:08 +0530 Subject: [PATCH 07/38] fix(minor): missing parameter --- frappe/public/js/frappe/form/formatters.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 955a956b90..3bf36c86af 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -201,7 +201,7 @@ frappe.form.formatters = { return ""; } }, - Text: function(value) { + Text: function(value, df) { if(value) { var tags = [" Date: Thu, 9 Jun 2022 12:34:09 +0530 Subject: [PATCH 08/38] fix(minor): added UI test --- cypress/integration/control_data.js | 16 +++++++++++----- cypress/support/commands.js | 15 +++++++++++++-- 2 files changed, 24 insertions(+), 7 deletions(-) diff --git a/cypress/integration/control_data.js b/cypress/integration/control_data.js index 01f9168667..78cece627b 100644 --- a/cypress/integration/control_data.js +++ b/cypress/integration/control_data.js @@ -34,6 +34,12 @@ context('Data Control', () => { }); }); }); + + it('check custom formatters', () => { + cy.visit(`/app/doctype/User`); + cy.get('[data-fieldname="fields"] .grid-row[data-idx="2"] [data-fieldname="fieldtype"] .static-area').should('have.text', '🔵 Section Break'); + }); + it('Verifying data control by inputting different patterns for "Name" field', () => { cy.new_form('Test Data Control'); @@ -54,7 +60,7 @@ context('Data Control', () => { //Checking if the border color of the field changes to red cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error'); - cy.findByRole('button', {name: 'Save'}).click(); + cy.save(); //Checking for the error message cy.get('.modal-title').should('have.text', 'Message'); @@ -64,7 +70,7 @@ context('Data Control', () => { cy.get_field('name1', 'Data').clear({force: true}); cy.fill_field('name1', 'Komal{}/!', 'Data'); cy.get('.frappe-control[data-fieldname="name1"]').should('have.class', 'has-error'); - cy.findByRole('button', {name: 'Save'}).click(); + cy.save(); cy.get('.modal-title').should('have.text', 'Message'); cy.get('.msgprint').should('have.text', 'Komal{}/! is not a valid Name'); cy.hide_dialog(); @@ -76,14 +82,14 @@ context('Data Control', () => { cy.get_field('email', 'Data').clear({force: true}); cy.fill_field('email', 'komal', 'Data'); cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error'); - cy.findByRole('button', {name: 'Save'}).click(); + cy.save(); cy.get('.modal-title').should('have.text', 'Message'); cy.get('.msgprint').should('have.text', 'komal is not a valid Email Address'); cy.hide_dialog(); cy.get_field('email', 'Data').clear({force: true}); cy.fill_field('email', 'komal@test', 'Data'); cy.get('.frappe-control[data-fieldname="email"]').should('have.class', 'has-error'); - cy.findByRole('button', {name: 'Save'}).click(); + cy.save(); cy.get('.modal-title').should('have.text', 'Message'); cy.get('.msgprint').should('have.text', 'komal@test is not a valid Email Address'); cy.hide_dialog(); @@ -125,4 +131,4 @@ context('Data Control', () => { cy.get('.actions-btn-group > .dropdown-menu [data-label="Delete"]').click(); cy.click_modal_primary_button('Yes'); }); -}); \ No newline at end of file +}); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 6398018e10..d3b13127cb 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -27,6 +27,7 @@ import "cypress-real-events/support"; // // -- This is will overwrite an existing command -- // Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... }); + Cypress.Commands.add('login', (email, password) => { if (!email) { email = 'Administrator'; @@ -139,6 +140,10 @@ Cypress.Commands.add('create_records', doc => { .then(r => r.message); }); +Cypress.Commands.add('open_doc', (doctype, name) => { + cy.visit(`/app/${doctype}/${name}`) +}); + Cypress.Commands.add('set_value', (doctype, name, obj) => { return cy.call('frappe.client.set_value', { doctype, @@ -265,9 +270,15 @@ Cypress.Commands.add('get_open_dialog', () => { return cy.get('.modal:visible').last(); }); +Cypress.Commands.add('save', () => { + cy.intercept('/api').as('api'); + cy.get(`button[data-label="Save"]:visible`).click({scrollBehavior: false, force:true}); + cy.wait('@api'); +}); + Cypress.Commands.add('hide_dialog', () => { - cy.wait(300); - cy.get_open_dialog().find('.btn-modal-close').click(); + cy.wait(400); + cy.get_open_dialog().find('.btn-modal-close').click({force:true}); cy.get('.modal:visible').should('not.exist'); }); From da9ac52ffb3a32612da4e640310239192c0b7c2d Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Jun 2022 12:44:47 +0530 Subject: [PATCH 09/38] fix(minor): lint --- cypress/support/commands.js | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index d3b13127cb..6b4348df7c 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -140,10 +140,6 @@ Cypress.Commands.add('create_records', doc => { .then(r => r.message); }); -Cypress.Commands.add('open_doc', (doctype, name) => { - cy.visit(`/app/${doctype}/${name}`) -}); - Cypress.Commands.add('set_value', (doctype, name, obj) => { return cy.call('frappe.client.set_value', { doctype, @@ -272,13 +268,13 @@ Cypress.Commands.add('get_open_dialog', () => { Cypress.Commands.add('save', () => { cy.intercept('/api').as('api'); - cy.get(`button[data-label="Save"]:visible`).click({scrollBehavior: false, force:true}); + cy.get(`button[data-label="Save"]:visible`).click({scrollBehavior: false, force: true}); cy.wait('@api'); }); Cypress.Commands.add('hide_dialog', () => { cy.wait(400); - cy.get_open_dialog().find('.btn-modal-close').click({force:true}); + cy.get_open_dialog().find('.btn-modal-close').click({force: true}); cy.get('.modal:visible').should('not.exist'); }); From e66cd830fb1db25e78cfc061fb2ed9001336a9c1 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Thu, 9 Jun 2022 14:17:43 +0530 Subject: [PATCH 10/38] fix(minor): test + tabs --- cypress/support/commands.js | 2 +- frappe/public/js/frappe/form/tab.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index 6b4348df7c..c64f0bf469 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -274,7 +274,7 @@ Cypress.Commands.add('save', () => { Cypress.Commands.add('hide_dialog', () => { cy.wait(400); - cy.get_open_dialog().find('.btn-modal-close').click({force: true}); + cy.get('.btn-modal-close:visible').click({force: true}); cy.get('.modal:visible').should('not.exist'); }); diff --git a/frappe/public/js/frappe/form/tab.js b/frappe/public/js/frappe/form/tab.js index 0e740ce49c..3fad807f06 100644 --- a/frappe/public/js/frappe/form/tab.js +++ b/frappe/public/js/frappe/form/tab.js @@ -3,7 +3,7 @@ export default class Tab { this.parent = parent; this.df = df || {}; this.frm = frm; - this.doctype = 'User'; + this.doctype = this.frm.doctype; this.label = this.df && this.df.label; this.tabs_list = tabs_list; this.tabs_content = tabs_content; From fc9308f8ba9cf22f8432fad78634d24601d45c91 Mon Sep 17 00:00:00 2001 From: gavin Date: Thu, 9 Jun 2022 15:34:57 +0530 Subject: [PATCH 11/38] fix: Use same labels for standard fields throughout interfaces (#17031) * fix(model)!: Match labels in meta & model Labels for fields have been inconsistent. In standard filters, doc.name would be labelled as "Name" while it's "ID" in the edit filters option. Similarly for other fields, the same view will address the same fields differently. This PR aims to get rid of such inconsistencies. Changes: * [model] Change label ID for name to 'Name' * [meta] Match labels of owner, modified, modified_by * [meta] Add labels for more fields - from model.js * fix!: get_label to fetch labels as Desk does * fix!(label): Refer to name field as 'ID' over 'Name' * test: Use 'ID' label instead of 'Name' --- cypress/integration/awesome_bar.js | 2 +- frappe/model/meta.py | 11 ++++++++--- frappe/public/js/frappe/list/base_list.js | 2 +- frappe/public/js/frappe/list/list_settings.js | 2 +- frappe/public/js/frappe/list/list_view.js | 4 ++-- frappe/public/js/frappe/model/meta.js | 17 +++++++++++------ 6 files changed, 24 insertions(+), 14 deletions(-) diff --git a/cypress/integration/awesome_bar.js b/cypress/integration/awesome_bar.js index 053d015366..e62ba6bec5 100644 --- a/cypress/integration/awesome_bar.js +++ b/cypress/integration/awesome_bar.js @@ -26,7 +26,7 @@ context('Awesome Bar', () => { cy.get('.title-text').should('contain', 'To Do'); - cy.findByPlaceholderText('Name') + cy.findByPlaceholderText('ID') .should('have.value', '%test%'); }); diff --git a/frappe/model/meta.py b/frappe/model/meta.py index 65ab7d39c2..9f5c2e7611 100644 --- a/frappe/model/meta.py +++ b/frappe/model/meta.py @@ -252,10 +252,15 @@ class Meta(Document): else: label = { "name": _("ID"), - "owner": _("Created By"), - "modified_by": _("Modified By"), "creation": _("Created On"), - "modified": _("Last Modified On"), + "docstatus": _("Document Status"), + "idx": _("Index"), + "modified": _("Last Updated On"), + "modified_by": _("Last Updated By"), + "owner": _("Created By"), + "_user_tags": _("Tags"), + "_liked_by": _("Liked By"), + "_comments": _("Comments"), "_assign": _("Assigned To"), }.get(fieldname) or _("No Label") return label diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 76fb782045..b91693257a 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -740,7 +740,7 @@ class FilterArea { let fields = [ { fieldtype: "Data", - label: "Name", + label: "ID", condition: "like", fieldname: "name", onchange: () => this.refresh_list_view(), diff --git a/frappe/public/js/frappe/list/list_settings.js b/frappe/public/js/frappe/list/list_settings.js index b7318ea780..6c48bd013a 100644 --- a/frappe/public/js/frappe/list/list_settings.js +++ b/frappe/public/js/frappe/list/list_settings.js @@ -310,7 +310,7 @@ export default class ListSettings { let me = this; me.subject_field = { - label: "Name", + label: "ID", fieldname: "name" }; diff --git a/frappe/public/js/frappe/list/list_view.js b/frappe/public/js/frappe/list/list_view.js index f755d6902e..d57c57a0b6 100644 --- a/frappe/public/js/frappe/list/list_view.js +++ b/frappe/public/js/frappe/list/list_view.js @@ -331,7 +331,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.columns.push({ type: "Subject", df: { - label: __("Name"), + label: __("ID"), fieldname: "name", }, }); @@ -398,7 +398,7 @@ frappe.views.ListView = class ListView extends frappe.views.BaseList { this.columns.push({ type: "Field", df: { - label: __("Name"), + label: __("ID"), fieldname: "name", }, }); diff --git a/frappe/public/js/frappe/model/meta.js b/frappe/public/js/frappe/model/meta.js index 4a7295ed4e..b89374ab23 100644 --- a/frappe/public/js/frappe/model/meta.js +++ b/frappe/public/js/frappe/model/meta.js @@ -109,7 +109,7 @@ $.extend(frappe.meta, { var fields = $.map(frappe.meta.get_docfields(doctype, name), function(df) { return (df.fieldtype==="Link" && df.ignore_user_permissions!==1) ? df : null; }); - fields = fields.concat({label: "Name", fieldname: name, options: doctype}); + fields = fields.concat({label: "ID", fieldname: name, options: doctype}); return fields; }, @@ -177,12 +177,17 @@ $.extend(frappe.meta, { get_label: function(dt, fn, dn) { var standard = { - 'owner': __('Owner'), + 'name': __('ID'), 'creation': __('Created On'), - 'modified': __('Last Modified On'), - 'idx': __('Idx'), - 'name': __('Name'), - 'modified_by': __('Last Modified By') + 'docstatus': __('Document Status'), + 'idx': __('Index'), + 'modified': __('Last Updated On'), + 'modified_by': __('Last Updated By'), + 'owner': __('Created By'), + '_user_tags': __('Tags'), + '_liked_by': __('Liked By'), + '_comments': __('Comments'), + '_assign': __('Assigned To'), } if(standard[fn]) { return standard[fn]; From c67ec6c87aeb3b829eecd96c30126bd0d420d326 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 9 Jun 2022 16:31:29 +0530 Subject: [PATCH 12/38] chore(deps): Bump cryptography from 3.4.8 to 37.0.2 changelog: https://cryptography.io/en/latest/changelog/ diff: https://github.com/pyca/cryptography/compare/3.4.8...37.0.x --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index bf798fe747..616f1a2c42 100644 --- a/requirements.txt +++ b/requirements.txt @@ -7,7 +7,7 @@ braintree~=4.8.0 chardet~=4.0.0 Click~=7.1.2 croniter~=1.0.11 -cryptography~=3.4.7 +cryptography~=37.0.2 dropbox~=11.7.0 email-reply-parser~=0.5.12 git-url-parse~=1.2.2 From 7e346933c73235e3a0f68e4c323083ab5c6f84d9 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 9 Jun 2022 16:43:58 +0530 Subject: [PATCH 13/38] fix: Cleaner error message on invalid encryption_key --- frappe/utils/password.py | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/frappe/utils/password.py b/frappe/utils/password.py index f2c4b9685a..c539891ac7 100644 --- a/frappe/utils/password.py +++ b/frappe/utils/password.py @@ -213,21 +213,16 @@ def decrypt(txt, encryption_key=None): try: cipher_suite = Fernet(encode(encryption_key or get_encryption_key())) - plain_text = cstr(cipher_suite.decrypt(encode(txt))) - return plain_text + return cstr(cipher_suite.decrypt(encode(txt))) except InvalidToken: # encryption_key in site_config is changed and not valid - frappe.throw( - _("Encryption key is invalid") + "!" - if encryption_key - else _(", please check site_config.json.") - ) + frappe.throw(_("Encryption key is invalid! Please check site_config.json")) def get_encryption_key(): - from frappe.installer import update_site_config - if "encryption_key" not in frappe.local.conf: + from frappe.installer import update_site_config + encryption_key = Fernet.generate_key().decode() update_site_config("encryption_key", encryption_key) frappe.local.conf.encryption_key = encryption_key From eb86a933e0ea22528a560c653d6f6418f6fa7ab5 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 9 Jun 2022 16:49:27 +0530 Subject: [PATCH 14/38] chore(deps): Bump IPython from 7.31.0 to 8.4.0 Major version includes multiple bug, security & performance fixes changelog: https://ipython.readthedocs.io/en/stable/whatsnew/version8.html --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 616f1a2c42..6710dd4004 100644 --- a/requirements.txt +++ b/requirements.txt @@ -21,7 +21,7 @@ googlemaps~=4.4.5 gunicorn~=20.1.0 html2text==2020.1.16 html5lib~=1.1 -ipython~=7.31.1 +ipython~=8.4.0 Jinja2~=3.0.1 ldap3~=2.9 markdown2~=2.4.0 From e7f2b7c6287d1f80607803c7e227c617dd2868c5 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 11:31:59 +0530 Subject: [PATCH 15/38] chore(deps): bump faker from 8.1.4 to 13.12.1 Faker is a dev dependency installed for tests - to access the frappe.mock API to generate paragraphs, names & emails. --- dev-requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dev-requirements.txt b/dev-requirements.txt index f4045c6bed..b67e915a16 100644 --- a/dev-requirements.txt +++ b/dev-requirements.txt @@ -1,4 +1,4 @@ coverage==5.5 -Faker~=8.1.0 +Faker~=13.12.1 pyngrok~=5.0.5 unittest-xml-reporting~=3.0.4 From 88f27f4af4ce88c0e7de3e0a22115c21867fef45 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 11:36:05 +0530 Subject: [PATCH 16/38] chore: Remove deprecated dependency_links from setup * pdfkit dep is already being fulfilled by requirements.txt * ref: https://stackoverflow.com/questions/12518499/pip-ignores-dependency-links-in-setup-py --- setup.py | 1 - 1 file changed, 1 deletion(-) diff --git a/setup.py b/setup.py index 92ff63baff..ba4034a766 100644 --- a/setup.py +++ b/setup.py @@ -53,7 +53,6 @@ setup( zip_safe=False, include_package_data=True, install_requires=install_requires, - dependency_links=["https://github.com/frappe/python-pdfkit.git#egg=pdfkit"], cmdclass={"clean": CleanCommand}, python_requires=">=3.8", ) From 3aef47200b3e0c1afbbe51e6af89e803118227a8 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 11:53:50 +0530 Subject: [PATCH 17/38] chore(deps): Bumped pdfkit from 0.6.1 to 1.0.0 * Updated from_string API to match latest signature * changelog: https://github.com/JazzCore/python-pdfkit/blob/master/HISTORY.rst --- frappe/utils/pdf.py | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 952717434c..5e8b65a5bd 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -35,7 +35,7 @@ def get_pdf(html, options=None, output=None): try: # Set filename property to false, so no file is actually created - filedata = pdfkit.from_string(html, False, options=options or {}) + filedata = pdfkit.from_string(html, options=options or {}, verbose=True) # https://pythonhosted.org/PyPDF2/PdfFileReader.html # create in-memory binary streams from filedata and create a PdfFileReader object diff --git a/requirements.txt b/requirements.txt index 6710dd4004..b69716675e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -32,7 +32,7 @@ openpyxl~=3.0.7 parse~=1.19.0 passlib~=1.7.4 paytmchecksum~=1.7.0 -pdfkit~=0.6.1 +pdfkit~=1.0.0 Pillow~=9.0.0 premailer~=3.8.0 psutil~=5.8.0 From ef0a92c8499051c4ed4cb73740c1c9ed4ebed43d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 12:08:10 +0530 Subject: [PATCH 18/38] chore(deps): Bump PyPDF2 from 1.26.0 to 2.1.0 * Updated changes in API usages * changelog: https://github.com/py-pdf/PyPDF2/blob/main/CHANGELOG --- frappe/tests/test_pdf.py | 4 ++-- frappe/utils/pdf.py | 20 ++++++++++---------- frappe/utils/print_format.py | 6 +++--- requirements.txt | 2 +- 4 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frappe/tests/test_pdf.py b/frappe/tests/test_pdf.py index 497546ebd5..8f2a2c1cfa 100644 --- a/frappe/tests/test_pdf.py +++ b/frappe/tests/test_pdf.py @@ -3,7 +3,7 @@ import io import unittest -from PyPDF2 import PdfFileReader +from PyPDF2 import PdfReader import frappe import frappe.utils.pdf as pdfgen @@ -42,7 +42,7 @@ class TestPdf(unittest.TestCase): def test_pdf_encryption(self): password = "qwe" pdf = pdfgen.get_pdf(self.html, options={"password": password}) - reader = PdfFileReader(io.BytesIO(pdf)) + reader = PdfReader(io.BytesIO(pdf)) self.assertTrue(reader.isEncrypted) self.assertTrue(reader.decrypt(password)) diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 5e8b65a5bd..811a6511fd 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -5,10 +5,11 @@ import os import re import subprocess from distutils.version import LooseVersion +from typing import Optional import pdfkit from bs4 import BeautifulSoup -from PyPDF2 import PdfFileReader, PdfFileWriter +from PyPDF2 import PdfReader, PdfWriter import frappe from frappe import _ @@ -23,7 +24,7 @@ PDF_CONTENT_ERRORS = [ ] -def get_pdf(html, options=None, output=None): +def get_pdf(html, options=None, output: Optional[PdfWriter] = None): html = scrub_urls(html) html, options = prepare_options(html, options) @@ -37,9 +38,8 @@ def get_pdf(html, options=None, output=None): # Set filename property to false, so no file is actually created filedata = pdfkit.from_string(html, options=options or {}, verbose=True) - # https://pythonhosted.org/PyPDF2/PdfFileReader.html - # create in-memory binary streams from filedata and create a PdfFileReader object - reader = PdfFileReader(io.BytesIO(filedata)) + # create in-memory binary streams from filedata and create a PdfReader object + reader = PdfReader(io.BytesIO(filedata)) except OSError as e: if any([error in str(e) for error in PDF_CONTENT_ERRORS]): if not filedata: @@ -47,8 +47,8 @@ def get_pdf(html, options=None, output=None): frappe.throw(_("PDF generation failed because of broken image links")) # allow pdfs with missing images if file got created - if output: # output is a PdfFileWriter object - output.appendPagesFromReader(reader) + if output: + output.append_pages_from_reader(reader) else: raise finally: @@ -58,11 +58,11 @@ def get_pdf(html, options=None, output=None): password = options["password"] if output: - output.appendPagesFromReader(reader) + output.append_pages_from_reader(reader) return output - writer = PdfFileWriter() - writer.appendPagesFromReader(reader) + writer = PdfWriter() + writer.append_pages_from_reader(reader) if "password" in options: writer.encrypt(password) diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index 028501f306..a48d7ab84f 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -1,6 +1,6 @@ import os -from PyPDF2 import PdfFileWriter +from PyPDF2 import PdfWriter import frappe from frappe import _ @@ -58,7 +58,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options= import json - output = PdfFileWriter() + output = PdfWriter() if isinstance(options, str): options = json.loads(options) @@ -152,7 +152,7 @@ def print_by_server( cups.setServer(print_settings.server_ip) cups.setPort(print_settings.port) conn = cups.Connection() - output = PdfFileWriter() + output = PdfWriter() output = frappe.get_print( doctype, name, print_format, doc=doc, no_letterhead=no_letterhead, as_pdf=True, output=output ) diff --git a/requirements.txt b/requirements.txt index b69716675e..62275b587e 100644 --- a/requirements.txt +++ b/requirements.txt @@ -43,7 +43,7 @@ PyJWT~=2.0.1 PyMySQL~=1.0.2 pyOpenSSL~=20.0.1 pyotp~=2.6.0 -PyPDF2~=1.26.0 +PyPDF2~=2.1.0 PyPika~=0.48.9 pypng~=0.0.20 PyQRCode~=1.2.1 From 8fab5b96b79e134b428cbbda490eb99aa54fbaa0 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 12:15:08 +0530 Subject: [PATCH 19/38] chore(deps): Bump pytz from 2021.1 to 2022.1 Multiple bug fixes & IANA 2022a * changelog: https://github.com/stub42/pytz/blob/master/tz/NEWS * diff: https://github.com/stub42/pytz/compare/release_2021.1...release_2022.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 62275b587e..085b596cea 100644 --- a/requirements.txt +++ b/requirements.txt @@ -48,7 +48,7 @@ PyPika~=0.48.9 pypng~=0.0.20 PyQRCode~=1.2.1 python-dateutil~=2.8.1 -pytz==2021.1 +pytz==2022.1 PyYAML~=5.4.1 rauth~=0.7.3 razorpay~=1.2.0 From 8f2bb4780a98081c841ace8f45e9bdad22d0ae7a Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 12:30:07 +0530 Subject: [PATCH 20/38] chore(deps): Bump rq from 1.8.1 to 1.10.1 Exciting improvements to rq! Cleanup & bugs that made us sccratch heads in prod. changelog: https://github.com/rq/rq/blob/master/CHANGES.md --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 085b596cea..03731d77e5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -56,7 +56,7 @@ redis~=3.5.3 requests-oauthlib~=1.3.0 requests~=2.25.1 RestrictedPython~=5.1 -rq~=1.8.0 +rq~=1.10.1 rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability schedule~=1.1.0 semantic-version~=2.8.5 From d60f228c9029528fd5f8d7e15e4e72f1dced2c00 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 12:43:34 +0530 Subject: [PATCH 21/38] chore(deps): Bump Jinja2 from 3.0.3 to 3.1.2 changelog: https://jinja.palletsprojects.com/en/3.1.x/changes/ --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 03731d77e5..3b99aa06d5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -22,7 +22,7 @@ gunicorn~=20.1.0 html2text==2020.1.16 html5lib~=1.1 ipython~=8.4.0 -Jinja2~=3.0.1 +Jinja2~=3.1.2 ldap3~=2.9 markdown2~=2.4.0 maxminddb-geolite2==2018.703 From 3d1df3525e78e8c0e8033e48aca2083ae901c12f Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 12:46:59 +0530 Subject: [PATCH 22/38] chore(deps): Bump psutil from 5.8.0 to 5.9.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 3b99aa06d5..cc35b594c9 100644 --- a/requirements.txt +++ b/requirements.txt @@ -35,7 +35,7 @@ paytmchecksum~=1.7.0 pdfkit~=1.0.0 Pillow~=9.0.0 premailer~=3.8.0 -psutil~=5.8.0 +psutil~=5.9.1 psycopg2-binary~=2.9.1 pyasn1~=0.4.8 pycryptodome~=3.10.1 From 5680f458c18f4b59bff645231d2bbb8e5c228656 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 10 Jun 2022 12:48:36 +0530 Subject: [PATCH 23/38] chore(deps): Bump Werkzeug from 2.0.3 to 2.1.2 changelog: https://werkzeug.palletsprojects.com/en/2.1.x/changes/ --- requirements.txt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index cc35b594c9..2e3ec3434a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -65,7 +65,7 @@ stripe~=2.56.0 terminaltables~=3.1.0 traceback-with-variables~=2.0.4 urllib3~=1.26.4 -Werkzeug~=2.0.3 +Werkzeug~=2.1.2 Whoosh~=2.7.4 xlrd~=2.0.1 zxcvbn-python~=4.4.24 @@ -73,4 +73,3 @@ tenacity~=8.0.1 cairocffi==1.2.0 WeasyPrint==52.5 phonenumbers==8.12.40 - From 65b011f14147f1e10670888f2424654096972192 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sun, 5 Jun 2022 12:54:03 +0530 Subject: [PATCH 24/38] chore(deps): Bump semantic-version from 2.8.5 to 2.10.0 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2e3ec3434a..6c16a5be85 100644 --- a/requirements.txt +++ b/requirements.txt @@ -59,7 +59,7 @@ RestrictedPython~=5.1 rq~=1.10.1 rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability schedule~=1.1.0 -semantic-version~=2.8.5 +semantic-version~=2.10.0 sqlparse~=0.4.1 stripe~=2.56.0 terminaltables~=3.1.0 From 9bec09e14e20b8482baa867c1c7b793a42471202 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sun, 5 Jun 2022 12:57:04 +0530 Subject: [PATCH 25/38] chore(deps): Bump croniter from 1.0.15 to 1.3.5 changelog: https://pypi.org/project/croniter/#changelog --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 6c16a5be85..53839d9e18 100644 --- a/requirements.txt +++ b/requirements.txt @@ -6,7 +6,7 @@ boto3~=1.17.53 braintree~=4.8.0 chardet~=4.0.0 Click~=7.1.2 -croniter~=1.0.11 +croniter~=1.3.5 cryptography~=37.0.2 dropbox~=11.7.0 email-reply-parser~=0.5.12 From 111ad3cc82de1a7d6d89f5121e5e1dcdb98dd5fe Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 2 Jun 2022 12:58:40 +0530 Subject: [PATCH 26/38] chore(deps): Bump requests from 2.25.1 to 2.27.1 --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 53839d9e18..66a75c99ca 100644 --- a/requirements.txt +++ b/requirements.txt @@ -54,7 +54,7 @@ rauth~=0.7.3 razorpay~=1.2.0 redis~=3.5.3 requests-oauthlib~=1.3.0 -requests~=2.25.1 +requests~=2.27.1 RestrictedPython~=5.1 rq~=1.10.1 rsa>=4.1 # not directly required, pinned by Snyk to avoid a vulnerability From fc422f4a97bd12ceadb05ffbad582e2a921c44f3 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 2 Jun 2022 13:03:57 +0530 Subject: [PATCH 27/38] chore(deps): Bump Pillow from 9.0.1 to 9.1.1 Includes security fixes, API & constants deprecations Changelog: https://pillow.readthedocs.io/en/stable/releasenotes/index.html --- frappe/core/doctype/file/file.py | 4 ++-- frappe/utils/data.py | 2 +- frappe/utils/image.py | 6 ++---- requirements.txt | 2 +- 4 files changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/file/file.py b/frappe/core/doctype/file/file.py index 1bcbaf161a..e8b8da76ab 100755 --- a/frappe/core/doctype/file/file.py +++ b/frappe/core/doctype/file/file.py @@ -341,9 +341,9 @@ class File(Document): size = width, height if crop: - image = ImageOps.fit(image, size, Image.ANTIALIAS) + image = ImageOps.fit(image, size, Image.Resampling.LANCZOS) else: - image.thumbnail(size, Image.ANTIALIAS) + image.thumbnail(size, Image.Resampling.LANCZOS) thumbnail_url = f"{filename}_{suffix}.{extn}" path = os.path.abspath(frappe.get_site_path("public", thumbnail_url.lstrip("/"))) diff --git a/frappe/utils/data.py b/frappe/utils/data.py index 60770ef6a9..49f9ead437 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -1349,7 +1349,7 @@ def get_thumbnail_base64_for_image(src): original_size = image.size size = 50, 50 - image.thumbnail(size, Image.ANTIALIAS) + image.thumbnail(size, Image.Resampling.LANCZOS) base64_string = image_to_base64(image, extn) return { diff --git a/frappe/utils/image.py b/frappe/utils/image.py index 0cbc02fb31..8823ea3dfe 100644 --- a/frappe/utils/image.py +++ b/frappe/utils/image.py @@ -7,8 +7,6 @@ from PIL import Image def resize_images(path, maxdim=700): - from PIL import Image - size = (maxdim, maxdim) for basepath, folders, files in os.walk(path): for fname in files: @@ -16,7 +14,7 @@ def resize_images(path, maxdim=700): if extn in ("jpg", "jpeg", "png", "gif"): im = Image.open(os.path.join(basepath, fname)) if im.size[0] > size[0] or im.size[1] > size[1]: - im.thumbnail(size, Image.ANTIALIAS) + im.thumbnail(size, Image.Resampling.LANCZOS) im.save(os.path.join(basepath, fname)) print("resized {0}".format(os.path.join(basepath, fname))) @@ -56,7 +54,7 @@ def optimize_image( image = Image.open(io.BytesIO(content)) image_format = content_type.split("/")[1] size = max_width, max_height - image.thumbnail(size, Image.LANCZOS) + image.thumbnail(size, Image.Resampling.LANCZOS) output = io.BytesIO() image.save( diff --git a/requirements.txt b/requirements.txt index 66a75c99ca..1a6b6120a3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -33,7 +33,7 @@ parse~=1.19.0 passlib~=1.7.4 paytmchecksum~=1.7.0 pdfkit~=1.0.0 -Pillow~=9.0.0 +Pillow~=9.1.1 premailer~=3.8.0 psutil~=5.9.1 psycopg2-binary~=2.9.1 From 70188934bc143d84c3367a1f3a90628e77cc12b7 Mon Sep 17 00:00:00 2001 From: Jannat Patel <31363128+pateljannat@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:00:32 +0530 Subject: [PATCH 28/38] fix: pick the last signup template from hook (#17118) --- frappe/www/login.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/www/login.py b/frappe/www/login.py index fbb34e43e7..1b9a8c239a 100644 --- a/frappe/www/login.py +++ b/frappe/www/login.py @@ -55,10 +55,10 @@ def get_context(context): ) signup_form_template = frappe.get_hooks("signup_form_template") - if signup_form_template and len(signup_form_template) and signup_form_template[0]: - path = signup_form_template[0] + if signup_form_template and len(signup_form_template): + path = signup_form_template[-1] if not guess_is_path(path): - path = frappe.get_attr(signup_form_template[0])() + path = frappe.get_attr(signup_form_template[-1])() else: path = "frappe/templates/signup.html" if path: From e740ad180d8cdd4804f4bc568412c857f9f2ced9 Mon Sep 17 00:00:00 2001 From: Shariq Ansari <30859809+shariquerik@users.noreply.github.com> Date: Fri, 10 Jun 2022 15:46:50 +0530 Subject: [PATCH 29/38] test: fix for failing Data Control UI test (#17133) --- cypress/support/commands.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/cypress/support/commands.js b/cypress/support/commands.js index c64f0bf469..c168b0c201 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -271,10 +271,9 @@ Cypress.Commands.add('save', () => { cy.get(`button[data-label="Save"]:visible`).click({scrollBehavior: false, force: true}); cy.wait('@api'); }); - Cypress.Commands.add('hide_dialog', () => { - cy.wait(400); - cy.get('.btn-modal-close:visible').click({force: true}); + cy.wait(300); + cy.get_open_dialog().focus().find('.btn-modal-close').click(); cy.get('.modal:visible').should('not.exist'); }); From 076816a7d7d7541b66cc7c3d6a977256b77642dd Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 10 Jun 2022 15:55:34 +0530 Subject: [PATCH 30/38] fix: Allow to set empty values in Bulk Edit - minor UX fixes --- .../public/js/frappe/list/bulk_operations.js | 24 +++++++++++++++---- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index 94ec9d4e67..7c8c515643 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -208,7 +208,7 @@ export default class BulkOperations { const default_field = field_options.find(value => status_regex.test(value)); const dialog = new frappe.ui.Dialog({ - title: __('Edit'), + title: __('Bulk Edit'), fields: [ { 'fieldtype': 'Select', @@ -225,7 +225,9 @@ export default class BulkOperations { 'fieldtype': 'Data', 'label': __('Value'), 'fieldname': 'value', - 'reqd': 1 + onchange() { + show_help_text(); + } } ], primary_action: ({ value }) => { @@ -239,7 +241,7 @@ export default class BulkOperations { docnames: docnames, action: 'update', data: { - [fieldname]: value + [fieldname]: value || null } } }).then(r => { @@ -254,10 +256,11 @@ export default class BulkOperations { frappe.show_alert(__('Updated successfully')); }); }, - primary_action_label: __('Update') + primary_action_label: __('Update {0} records', [docnames.length]), }); if (default_field) set_value_field(dialog); // to set `Value` df based on default `Field` + show_help_text(); function set_value_field (dialogObj) { const new_df = Object.assign({}, @@ -275,9 +278,20 @@ export default class BulkOperations { new_df.default = options[0] || options[1]; } new_df.label = __('Value'); - new_df.reqd = 1; + new_df.onchange = show_help_text; + delete new_df.depends_on; dialogObj.replace_field('value', new_df); + show_help_text(); + } + + function show_help_text() { + let value = dialog.get_value('value'); + if (value == null || value === '') { + dialog.set_df_property('value', 'description', __('You have not entered a value. The field will be set to empty.')); + } else { + dialog.set_df_property('value', 'description', ''); + } } dialog.refresh(); From c2b9197624efbb66a2f0a7743c96d7ed163cd768 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 10 Jun 2022 20:01:33 +0530 Subject: [PATCH 31/38] fix: default tab fieldname conflict --- frappe/public/js/frappe/form/layout.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/layout.js b/frappe/public/js/frappe/form/layout.js index 403abf0981..c1c9967507 100644 --- a/frappe/public/js/frappe/form/layout.js +++ b/frappe/public/js/frappe/form/layout.js @@ -122,7 +122,8 @@ frappe.ui.form.Layout = class Layout { } if (this.is_tabbed_layout()) { - let default_tab = {label: __('Details'), fieldname: 'details', fieldtype: "Tab Break"}; + // add a tab without `fieldname` to avoid conflicts + let default_tab = {label: __('Details'), fieldtype: "Tab Break"}; let first_tab = this.fields[1].fieldtype === "Tab Break" ? this.fields[1] : null; if (!first_tab) { this.fields.splice(1, 0, default_tab); From c157564281a786fba0835fd58310947074e56772 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Fri, 10 Jun 2022 22:19:38 +0530 Subject: [PATCH 32/38] fix: use doctype property instead of name for Customize Form in validate_autoincrement_autoname (#17139) --- frappe/core/doctype/doctype/doctype.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 3e58146ae7..e834b698d5 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -8,6 +8,7 @@ import os # imports - standard imports import re import shutil +from typing import TYPE_CHECKING, Union # imports - module imports import frappe @@ -35,6 +36,9 @@ from frappe.query_builder.functions import Concat from frappe.utils import cint from frappe.website.utils import clear_cache +if TYPE_CHECKING: + from frappe.custom.doctype.customize_form.customize_form import CustomizeForm + DEPENDS_ON_PATTERN = re.compile(r'[\w\.:_]+\s*={1}\s*[\w\.@\'"]+') ILLEGAL_FIELDNAME_PATTERN = re.compile("""['",./%@()<>{}]""") WHITESPACE_PADDING_PATTERN = re.compile(r"^[ \t\n\r]+|[ \t\n\r]+$", flags=re.ASCII) @@ -916,11 +920,11 @@ def validate_series(dt, autoname=None, name=None): frappe.throw(_("Series {0} already used in {1}").format(prefix, used_in[0][0])) -def validate_autoincrement_autoname(dt: DocType) -> bool: +def validate_autoincrement_autoname(dt: Union[DocType, "CustomizeForm"]) -> bool: """Checks if can doctype can change to/from autoincrement autoname""" - def get_autoname_before_save(dt: DocType) -> str: - if dt.name == "Customize Form": + def get_autoname_before_save(dt: Union[DocType, "CustomizeForm"]) -> str: + if dt.doctype == "Customize Form": property_value = frappe.db.get_value( "Property Setter", {"doc_type": dt.doc_type, "property": "autoname"}, "value" ) @@ -943,10 +947,10 @@ def validate_autoincrement_autoname(dt: DocType) -> bool: or (not is_autoname_autoincrement and autoname_before_save == "autoincrement") ): - if frappe.get_meta(dt.name).issingle: - if dt.name == "Customize Form": - frappe.throw(_("Cannot change to/from autoincrement autoname in Customize Form")) + if dt.doctype == "Customize Form": + frappe.throw(_("Cannot change to/from autoincrement autoname in Customize Form")) + if frappe.get_meta(dt.name).issingle: return False if not frappe.get_all(dt.name, limit=1): From 89889753e4dadab618bdbf8101823e8187cc995d Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Sun, 12 Jun 2022 12:11:25 +0200 Subject: [PATCH 33/38] fix: JS error in page.js when the is a quote in button translation --- frappe/public/js/frappe/ui/page.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/page.js b/frappe/public/js/frappe/ui/page.js index 70e0862ba5..eded1aefc5 100644 --- a/frappe/public/js/frappe/ui/page.js +++ b/frappe/public/js/frappe/ui/page.js @@ -510,7 +510,7 @@ frappe.ui.Page = class Page { if (!label || !parent) return false; - const item_selector = `${selector}[data-label='${encodeURIComponent(label)}']`; + const item_selector = `${selector}[data-label="${encodeURIComponent(label)}"]`; const existing_items = $(parent).find(item_selector); return existing_items?.length > 0 && existing_items; From 03f48580ef1baa91b3d1bd7fdb90e20fa3f9adb4 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 12 Jun 2022 16:24:39 +0530 Subject: [PATCH 34/38] test: button with single quote in label --- cypress/integration/custom_buttons.js | 1 + 1 file changed, 1 insertion(+) diff --git a/cypress/integration/custom_buttons.js b/cypress/integration/custom_buttons.js index e2f02668e9..6045d009c2 100644 --- a/cypress/integration/custom_buttons.js +++ b/cypress/integration/custom_buttons.js @@ -4,6 +4,7 @@ const test_button_names = [ "Porcupine Tree (the GOAT)", "AC / DC", `Electronic Dance "music"`, + "l'imperatrice", ]; const add_button = (label, group = "TestGroup") => { From 21442f5cbaa5c7b1a5a0aa1c8a376ddbc6d04ad6 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sun, 12 Jun 2022 17:19:13 +0530 Subject: [PATCH 35/38] perf: disable creating version for new docs Each new doc inserts a version, this contains nothing but creator and creation time.. which is already immutable information on the original document. This was added for cases like data import to track from where document got created, ref: https://github.com/frappe/frappe/commit/b7dfe7969dd6b2851844b1e9c090cbb6447748de Fix: only add a version on creation IF creation info is present on flags --- frappe/core/doctype/version/test_version.py | 13 +++++++++++++ frappe/core/doctype/version/version.py | 19 ++++++++++++++++--- frappe/model/document.py | 7 +++---- frappe/tests/test_form_load.py | 2 +- 4 files changed, 33 insertions(+), 8 deletions(-) diff --git a/frappe/core/doctype/version/test_version.py b/frappe/core/doctype/version/test_version.py index c35430b17b..3e82f30f06 100644 --- a/frappe/core/doctype/version/test_version.py +++ b/frappe/core/doctype/version/test_version.py @@ -32,6 +32,19 @@ class TestVersion(unittest.TestCase): self.assertEqual(get_old_values(diff)[1], "01-01-2014 00:00:00") self.assertEqual(get_new_values(diff)[1], "07-20-2017 00:00:00") + def test_no_version_on_new_doc(self): + from frappe.desk.form.load import get_versions + + t = frappe.get_doc(doctype="ToDo", description="something") + t.save(ignore_version=False) + + self.assertFalse(get_versions(t)) + + t = frappe.get_doc(t.doctype, t.name) + t.description = "changed" + t.save(ignore_version=False) + self.assertTrue(get_versions(t)) + def get_fieldnames(change_array): return [d[0] for d in change_array] diff --git a/frappe/core/doctype/version/version.py b/frappe/core/doctype/version/version.py index 863885e85c..fa6ba0a9cf 100644 --- a/frappe/core/doctype/version/version.py +++ b/frappe/core/doctype/version/version.py @@ -2,6 +2,7 @@ # License: MIT. See LICENSE import json +from typing import Optional import frappe from frappe.model import no_value_fields, table_fields @@ -9,7 +10,15 @@ from frappe.model.document import Document class Version(Document): - def set_diff(self, old, new): + def update_version_info(self, old: Optional[Document], new: Document) -> bool: + """Update changed info and return true if change contains useful data.""" + if not old: + # Check if doc has some information about creation source like data import + return self.for_insert(new) + else: + return self.set_diff(old, new) + + def set_diff(self, old: Document, new: Document) -> bool: """Set the data property with the diff of the docs if present""" diff = get_diff(old, new) if diff: @@ -20,8 +29,11 @@ class Version(Document): else: return False - def for_insert(self, doc): + def for_insert(self, doc: Document) -> bool: updater_reference = doc.flags.updater_reference + if not updater_reference: + return False + data = { "creation": doc.creation, "updater_reference": updater_reference, @@ -29,7 +41,8 @@ class Version(Document): } self.ref_doctype = doc.doctype self.docname = doc.name - self.data = frappe.as_json(data) + self.data = frappe.as_json(data, indent=None, separators=(",", ":")) + return True def get_data(self): return json.loads(self.data) diff --git a/frappe/model/document.py b/frappe/model/document.py index fa1f423d11..a98d49e58c 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -1198,11 +1198,10 @@ class Document(BaseDocument): return version = frappe.new_doc("Version") - if not self._doc_before_save: - version.for_insert(self) - version.insert(ignore_permissions=True) - elif version.set_diff(self._doc_before_save, self): + + if is_useful_diff := version.update_version_info(self._doc_before_save, self): version.insert(ignore_permissions=True) + if not frappe.flags.in_migrate: # follow since you made a change? if frappe.get_cached_value("User", frappe.session.user, "follow_created_documents"): diff --git a/frappe/tests/test_form_load.py b/frappe/tests/test_form_load.py index e92b8c3ff2..22db56eeef 100644 --- a/frappe/tests/test_form_load.py +++ b/frappe/tests/test_form_load.py @@ -181,7 +181,7 @@ class TestFormLoad(unittest.TestCase): self.assertEqual(len(docinfo.comments), 1) self.assertIn("test", docinfo.comments[0].content) - self.assertGreaterEqual(len(docinfo.versions), 2) + self.assertGreaterEqual(len(docinfo.versions), 1) self.assertEqual(set(docinfo.tags.split(",")), {"more_tag", "test_tag"}) From a4f2912fdf5e0945d89fe588f5cb6b02cc3c2582 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 13 Jun 2022 11:11:47 +0530 Subject: [PATCH 36/38] fix: Handle case where document title can be NONE (cherry picked from commit 9b67fc24bc290789158f37a8f2ce10b505878792) --- frappe/www/printview.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 85ffc671fa..c8595a6f2c 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -63,7 +63,7 @@ def get_context(context): "body": body, "print_style": print_style, "comment": frappe.session.user, - "title": frappe.utils.strip_html(doc.get_title()), + "title": frappe.utils.strip_html(doc.get_title() or doc.name), "lang": frappe.local.lang, "layout_direction": "rtl" if is_rtl() else "ltr", "doctype": frappe.form_dict.doctype, From 53a079f1017e883bb07c745135db9506705b3841 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 13 Jun 2022 11:12:53 +0530 Subject: [PATCH 37/38] fix: `doc.get_title` should return empty string if title is not set (cherry picked from commit b244c9148192362de43aa483b328709745f3be82) --- frappe/model/document.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/document.py b/frappe/model/document.py index a98d49e58c..22514df75a 100644 --- a/frappe/model/document.py +++ b/frappe/model/document.py @@ -438,7 +438,7 @@ class Document(BaseDocument): def get_title(self): """Get the document title based on title_field or `title` or `name`""" - return self.get(self.meta.get_title_field()) + return self.get(self.meta.get_title_field()) or "" def set_title_field(self): """Set title field based on template""" From 63a5db94cfa728b6116e2a806ae8f2fc7988e4d8 Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Mon, 13 Jun 2022 12:03:39 +0530 Subject: [PATCH 38/38] chore: Add mergify[bot] to exception list https://user-images.githubusercontent.com/13928957/173293263-4987c494-4524-46dd-996c-36f1ca760c68.png According to this mergify's login id is `mergify[bot]` so am guessing this should work. --- .mergify.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.mergify.yml b/.mergify.yml index f1333362a8..97df91a927 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -7,6 +7,7 @@ pull_request_rules: - author!=gavindsouza - author!=deepeshgarg007 - author!=ankush + - author!=mergify[bot] - or: - base=version-13 - base=version-12