From 4f1d00442c88e487756cb5e3dc642312b177166c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 17 Mar 2022 15:30:22 +0530 Subject: [PATCH 01/28] fix(as_json): Pop None key if exists JSON doesn't allow null key as per spec, it should be a string only: https://datatracker.ietf.org/doc/html/rfc7159#section-4 ref discussions: * https://github.com/frappe/frappe/issues/14292 * https://github.com/frappe/frappe/pull/14504/files#r821526085 --- frappe/__init__.py | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 86f8be35ea..60189a2565 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1,4 +1,4 @@ -# Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors +# Copyright (c) 2022, Frappe Technologies Pvt. Ltd. and Contributors # License: MIT. See LICENSE """ Frappe - Low Code Open Source Framework in Python and JS @@ -20,10 +20,10 @@ if _dev_server: warnings.simplefilter('always', DeprecationWarning) warnings.simplefilter('always', PendingDeprecationWarning) -from werkzeug.local import Local, release_local import sys, importlib, inspect, json -import typing import click +from werkzeug.local import Local, release_local +from typing import TYPE_CHECKING, Dict, List, Union # Local application imports from .exceptions import * @@ -143,15 +143,14 @@ lang = local("lang") # This if block is never executed when running the code. It is only used for # telling static code analyzer where to find dynamically defined attributes. -if typing.TYPE_CHECKING: - from frappe.utils.redis_wrapper import RedisWrapper - +if TYPE_CHECKING: from frappe.database.mariadb.database import MariaDBDatabase from frappe.database.postgres.database import PostgresDatabase from frappe.query_builder.builder import MariaDB, Postgres + from frappe.utils.redis_wrapper import RedisWrapper - db: typing.Union[MariaDBDatabase, PostgresDatabase] - qb: typing.Union[MariaDB, Postgres] + db: Union[MariaDBDatabase, PostgresDatabase] + qb: Union[MariaDB, Postgres] # end: static analysis hack @@ -1522,12 +1521,14 @@ def get_value(*args, **kwargs): """ return db.get_value(*args, **kwargs) -def as_json(obj, indent=1): +def as_json(obj: Union[Dict, List], indent=1) -> str: from frappe.utils.response import json_handler - try: - return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': ')) - except TypeError: - return json.dumps(obj, indent=indent, default=json_handler, separators=(',', ': ')) + + if isinstance(obj, dict) and None in obj: + obj.pop(None) + + return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': ')) + def are_emails_muted(): from frappe.utils import cint From 38c6db06ea1d25d9a13775efc3b2145823ce1bdb Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Mon, 21 Mar 2022 13:13:19 +0530 Subject: [PATCH 02/28] test: Added test script for control type "Dynamic Link" --- cypress/integration/control_dynamiclink.js | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 cypress/integration/control_dynamiclink.js diff --git a/cypress/integration/control_dynamiclink.js b/cypress/integration/control_dynamiclink.js new file mode 100644 index 0000000000..590ab37c3b --- /dev/null +++ b/cypress/integration/control_dynamiclink.js @@ -0,0 +1,34 @@ +context('Dynamic Link', () => { + before(() => { + cy.login(); + cy.visit('/app/doctype'); + return cy.window().its('frappe').then(frappe => { + return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { + name: 'Test Dynamic Link', + fields: [ + { + "label": "Document Type", + "fieldname": "doc_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, + }, + { + "label": "Document ID", + "fieldname": "doc_id", + "fieldtype": "Dynamic Link", + "options": "doc_type", + "in_list_view": 1, + }, + ] + }); + }); + }); + it('Creating a dynamic link and verifying it', () => { + cy.new_form('Test Dynamic Link'); + cy.get('form > [data-fieldname="doc_type"]').type('User'); + cy.get('form > [data-fieldname="doc_id"]').click(); + cy.get('[id="awesomplete_list_4"]').its('length').should('be.gte', 0); + + }); +}); From 9b69f4e08c1811a1b3e9a87b08335ad9c5193fc0 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Mon, 21 Mar 2022 13:32:16 +0530 Subject: [PATCH 03/28] test: fixing sider issues --- cypress/integration/control_dynamiclink.js | 56 +++++++++++----------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/cypress/integration/control_dynamiclink.js b/cypress/integration/control_dynamiclink.js index 590ab37c3b..206d04e5f2 100644 --- a/cypress/integration/control_dynamiclink.js +++ b/cypress/integration/control_dynamiclink.js @@ -1,34 +1,34 @@ context('Dynamic Link', () => { - before(() => { - cy.login(); - cy.visit('/app/doctype'); - return cy.window().its('frappe').then(frappe => { - return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { - name: 'Test Dynamic Link', - fields: [ - { - "label": "Document Type", - "fieldname": "doc_type", - "fieldtype": "Link", - "options": "DocType", - "in_list_view": 1, - }, - { - "label": "Document ID", - "fieldname": "doc_id", - "fieldtype": "Dynamic Link", - "options": "doc_type", - "in_list_view": 1, - }, - ] - }); - }); - }); - it('Creating a dynamic link and verifying it', () => { - cy.new_form('Test Dynamic Link'); + before(() => { + cy.login(); + cy.visit('/app/doctype'); + return cy.window().its('frappe').then(frappe => { + return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { + name: 'Test Dynamic Link', + fields: [ + { + "label": "Document Type", + "fieldname": "doc_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, + }, + { + "label": "Document ID", + "fieldname": "doc_id", + "fieldtype": "Dynamic Link", + "options": "doc_type", + "in_list_view": 1, + }, + ] + }); + }); + }); + it('Creating a dynamic link and verifying it', () => { + cy.new_form('Test Dynamic Link'); cy.get('form > [data-fieldname="doc_type"]').type('User'); cy.get('form > [data-fieldname="doc_id"]').click(); cy.get('[id="awesomplete_list_4"]').its('length').should('be.gte', 0); - }); + }); }); From a139d1d369b5a24e8c4c0e2a7cedcfed7dd9fa90 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Mon, 21 Mar 2022 13:39:22 +0530 Subject: [PATCH 04/28] test: fixing sider issues --- cypress/integration/control_dynamiclink.js | 40 +++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/cypress/integration/control_dynamiclink.js b/cypress/integration/control_dynamiclink.js index 206d04e5f2..c989889ca2 100644 --- a/cypress/integration/control_dynamiclink.js +++ b/cypress/integration/control_dynamiclink.js @@ -2,27 +2,27 @@ context('Dynamic Link', () => { before(() => { cy.login(); cy.visit('/app/doctype'); - return cy.window().its('frappe').then(frappe => { - return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { - name: 'Test Dynamic Link', - fields: [ - { - "label": "Document Type", - "fieldname": "doc_type", - "fieldtype": "Link", - "options": "DocType", - "in_list_view": 1, - }, - { - "label": "Document ID", - "fieldname": "doc_id", - "fieldtype": "Dynamic Link", - "options": "doc_type", - "in_list_view": 1, - }, - ] - }); + return cy.window().its('frappe').then(frappe => { + return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { + name: 'Test Dynamic Link', + fields: [ + { + "label": "Document Type", + "fieldname": "doc_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, + }, + { + "label": "Document ID", + "fieldname": "doc_id", + "fieldtype": "Dynamic Link", + "options": "doc_type", + "in_list_view": 1, + }, + ] }); + }); }); it('Creating a dynamic link and verifying it', () => { cy.new_form('Test Dynamic Link'); From 7fbbf577c90cbb877dc34c42b5e925373b2d34a4 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Mon, 21 Mar 2022 13:44:33 +0530 Subject: [PATCH 05/28] test: fixing sider issues --- cypress/integration/control_dynamiclink.js | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/cypress/integration/control_dynamiclink.js b/cypress/integration/control_dynamiclink.js index c989889ca2..bef4034120 100644 --- a/cypress/integration/control_dynamiclink.js +++ b/cypress/integration/control_dynamiclink.js @@ -4,21 +4,21 @@ context('Dynamic Link', () => { cy.visit('/app/doctype'); return cy.window().its('frappe').then(frappe => { return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { - name: 'Test Dynamic Link', + name: 'Test Dynamic Link', fields: [ { - "label": "Document Type", - "fieldname": "doc_type", - "fieldtype": "Link", - "options": "DocType", - "in_list_view": 1, + "label": "Document Type", + "fieldname": "doc_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, }, { - "label": "Document ID", - "fieldname": "doc_id", - "fieldtype": "Dynamic Link", - "options": "doc_type", - "in_list_view": 1, + "label": "Document ID", + "fieldname": "doc_id", + "fieldtype": "Dynamic Link", + "options": "doc_type", + "in_list_view": 1, }, ] }); From df62607c4324df555ad6c3c0c4e0c961a08ee6ae Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Mon, 21 Mar 2022 17:02:59 +0530 Subject: [PATCH 06/28] test: Corrected the selector --- cypress/integration/control_dynamiclink.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/integration/control_dynamiclink.js b/cypress/integration/control_dynamiclink.js index bef4034120..3df33acc3f 100644 --- a/cypress/integration/control_dynamiclink.js +++ b/cypress/integration/control_dynamiclink.js @@ -28,7 +28,7 @@ context('Dynamic Link', () => { cy.new_form('Test Dynamic Link'); cy.get('form > [data-fieldname="doc_type"]').type('User'); cy.get('form > [data-fieldname="doc_id"]').click(); - cy.get('[id="awesomplete_list_4"]').its('length').should('be.gte', 0); + cy.get_field("doc_id").its('length').should('be.gte', 0); }); }); From 1c8d2fd5369f76700130fd4c33d31bcaed0f779c Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 21 Mar 2022 17:49:26 +0530 Subject: [PATCH 07/28] fix: Sort keys for illegal JSON --- frappe/__init__.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frappe/__init__.py b/frappe/__init__.py index 60189a2565..df8e1fbfb1 100644 --- a/frappe/__init__.py +++ b/frappe/__init__.py @@ -1524,11 +1524,13 @@ def get_value(*args, **kwargs): def as_json(obj: Union[Dict, List], indent=1) -> str: from frappe.utils.response import json_handler - if isinstance(obj, dict) and None in obj: - obj.pop(None) - - return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': ')) - + try: + return json.dumps(obj, indent=indent, sort_keys=True, default=json_handler, separators=(',', ': ')) + except TypeError: + # this would break in case the keys are not all os "str" type - as defined in the JSON + # adding this to ensure keys are sorted (expected behaviour) + sorted_obj = dict(sorted(obj.items(), key=lambda kv: str(kv[0]))) + return json.dumps(sorted_obj, indent=indent, default=json_handler, separators=(',', ': ')) def are_emails_muted(): from frappe.utils import cint From b2fef12b44103f2f990caf07c4171ed388fe159f Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Thu, 24 Mar 2022 19:42:38 +0530 Subject: [PATCH 08/28] test: Renaimg the file and also adding tests --- cypress/integration/control_dynamic_link.js | 103 ++++++++++++++++++++ cypress/integration/control_dynamiclink.js | 34 ------- cypress/support/index.js | 3 + 3 files changed, 106 insertions(+), 34 deletions(-) create mode 100644 cypress/integration/control_dynamic_link.js delete mode 100644 cypress/integration/control_dynamiclink.js diff --git a/cypress/integration/control_dynamic_link.js b/cypress/integration/control_dynamic_link.js new file mode 100644 index 0000000000..7d642b1b12 --- /dev/null +++ b/cypress/integration/control_dynamic_link.js @@ -0,0 +1,103 @@ +context('Dynamic Link', () => { + before(() => { + cy.login(); + cy.visit('/app/doctype'); + return cy.window().its('frappe').then(frappe => { + return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { + name: 'Test Dynamic Link', + fields: [ + { + "label": "Document Type", + "fieldname": "doc_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, + "in_standard_filter": 1, + }, + { + "label": "Document ID", + "fieldname": "doc_id", + "fieldtype": "Dynamic Link", + "options": "doc_type", + "in_list_view": 1, + "in_standard_filter": 1, + }, + ] + }); + }); + }); + + + function get_dialog_with_dynamic_link() { + return cy.dialog({ + title: 'Dynamic Link', + fields: [{ + "label": "Document Type", + "fieldname": "doc_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, + }, + { + "label": "Document ID", + "fieldname": "doc_id", + "fieldtype": "Dynamic Link", + "options": "doc_type", + "in_list_view": 1, + }] + }); + } + + it('Creating a dynamic link and verifying it in a dialog', () => { + //Opening a dialog + get_dialog_with_dynamic_link().as('dialog'); + cy.get_field('doc_type').clear(); + + //Entering User in the Doctype field + cy.fill_field('doc_type','User','Link'); + + //Clicking on the Document ID field + cy.get_field('doc_id').click(); + + //Checking if the listbox have length greater than 0 + cy.get('.awesomplete').find("li").its('length').should('be.gte', 0); + + //Closing the dialog box + cy.get('.btn-modal-close > .icon').click(); + }); + + it('Creating a dynamic link and verifying it', () => { + //Visiting the dynamic link doctype + cy.visit('/app/test-dynamic-link'); + + //Clicking on the Document ID field + cy.get_field('doc_type').clear(); + + //Entering User in the Doctype field + cy.fill_field('doc_type','User','Link', {delay : 500}); + cy.get_field('doc_id').click(); + + //Checking if the listbox have length greater than 0 + cy.get('.awesomplete').find("li").its('length').should('be.gte', 0); + + //Opening a new form for dynamic link doctype + cy.new_form('Test Dynamic Link'); + cy.get_field('doc_type').clear(); + + //Entering User in the Doctype field + cy.fill_field('doc_type','User','Link', {delay : 500}); + cy.get_field('doc_id').click(); + + //Checking if the listbox have length greater than 0 + cy.get('.awesomplete').find("li").its('length').should('be.gte', 0); + cy.get_field('doc_type').clear(); + + //Entering System Settings in the Doctype field + cy.fill_field('doc_type','System Settings','Link', {delay : 500}); + cy.get_field('doc_id').click(); + + //Checking if the system throws error + cy.get('.modal-title').should('have.text','Error'); + cy.get('.msgprint').should('have.text','System Settings is not a valid DocType for Dynamic Link'); + }); +}); \ No newline at end of file diff --git a/cypress/integration/control_dynamiclink.js b/cypress/integration/control_dynamiclink.js deleted file mode 100644 index 3df33acc3f..0000000000 --- a/cypress/integration/control_dynamiclink.js +++ /dev/null @@ -1,34 +0,0 @@ -context('Dynamic Link', () => { - before(() => { - cy.login(); - cy.visit('/app/doctype'); - return cy.window().its('frappe').then(frappe => { - return frappe.xcall('frappe.tests.ui_test_helpers.create_doctype', { - name: 'Test Dynamic Link', - fields: [ - { - "label": "Document Type", - "fieldname": "doc_type", - "fieldtype": "Link", - "options": "DocType", - "in_list_view": 1, - }, - { - "label": "Document ID", - "fieldname": "doc_id", - "fieldtype": "Dynamic Link", - "options": "doc_type", - "in_list_view": 1, - }, - ] - }); - }); - }); - it('Creating a dynamic link and verifying it', () => { - cy.new_form('Test Dynamic Link'); - cy.get('form > [data-fieldname="doc_type"]').type('User'); - cy.get('form > [data-fieldname="doc_id"]').click(); - cy.get_field("doc_id").its('length').should('be.gte', 0); - - }); -}); diff --git a/cypress/support/index.js b/cypress/support/index.js index 9cd770a31e..f0b53ae833 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -17,6 +17,9 @@ import './commands'; import '@cypress/code-coverage/support'; +Cypress.on('uncaught:exception', (err, runnable) => { + return false +}); // Alternatively you can use CommonJS syntax: // require('./commands') From 8516ea6e474c73a97e8e770c852036c0908d7245 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Thu, 24 Mar 2022 20:14:19 +0530 Subject: [PATCH 09/28] test: Corrected the selector --- cypress/integration/control_dynamic_link.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/cypress/integration/control_dynamic_link.js b/cypress/integration/control_dynamic_link.js index 7d642b1b12..d96843c10b 100644 --- a/cypress/integration/control_dynamic_link.js +++ b/cypress/integration/control_dynamic_link.js @@ -60,7 +60,7 @@ context('Dynamic Link', () => { cy.get_field('doc_id').click(); //Checking if the listbox have length greater than 0 - cy.get('.awesomplete').find("li").its('length').should('be.gte', 0); + cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0); //Closing the dialog box cy.get('.btn-modal-close > .icon').click(); @@ -78,7 +78,7 @@ context('Dynamic Link', () => { cy.get_field('doc_id').click(); //Checking if the listbox have length greater than 0 - cy.get('.awesomplete').find("li").its('length').should('be.gte', 0); + cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0); //Opening a new form for dynamic link doctype cy.new_form('Test Dynamic Link'); @@ -89,7 +89,7 @@ context('Dynamic Link', () => { cy.get_field('doc_id').click(); //Checking if the listbox have length greater than 0 - cy.get('.awesomplete').find("li").its('length').should('be.gte', 0); + cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0); cy.get_field('doc_type').clear(); //Entering System Settings in the Doctype field From e1138eaf927b340ffa6f7d9ca352514eefc2e325 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Thu, 24 Mar 2022 20:17:56 +0530 Subject: [PATCH 10/28] test: Fixing sider issues --- cypress/integration/control_dynamic_link.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/cypress/integration/control_dynamic_link.js b/cypress/integration/control_dynamic_link.js index d96843c10b..26f7e0b85e 100644 --- a/cypress/integration/control_dynamic_link.js +++ b/cypress/integration/control_dynamic_link.js @@ -54,7 +54,7 @@ context('Dynamic Link', () => { cy.get_field('doc_type').clear(); //Entering User in the Doctype field - cy.fill_field('doc_type','User','Link'); + cy.fill_field('doc_type', 'User', 'Link'); //Clicking on the Document ID field cy.get_field('doc_id').click(); @@ -74,7 +74,7 @@ context('Dynamic Link', () => { cy.get_field('doc_type').clear(); //Entering User in the Doctype field - cy.fill_field('doc_type','User','Link', {delay : 500}); + cy.fill_field('doc_type', 'User', 'Link', {delay: 500}); cy.get_field('doc_id').click(); //Checking if the listbox have length greater than 0 @@ -85,7 +85,7 @@ context('Dynamic Link', () => { cy.get_field('doc_type').clear(); //Entering User in the Doctype field - cy.fill_field('doc_type','User','Link', {delay : 500}); + cy.fill_field('doc_type', 'User', 'Link', {delay: 500}); cy.get_field('doc_id').click(); //Checking if the listbox have length greater than 0 @@ -93,11 +93,11 @@ context('Dynamic Link', () => { cy.get_field('doc_type').clear(); //Entering System Settings in the Doctype field - cy.fill_field('doc_type','System Settings','Link', {delay : 500}); + cy.fill_field('doc_type', 'System Settings', 'Link', {delay: 500}); cy.get_field('doc_id').click(); //Checking if the system throws error - cy.get('.modal-title').should('have.text','Error'); - cy.get('.msgprint').should('have.text','System Settings is not a valid DocType for Dynamic Link'); + cy.get('.modal-title').should('have.text', 'Error'); + cy.get('.msgprint').should('have.text', 'System Settings is not a valid DocType for Dynamic Link'); }); }); \ No newline at end of file From b79f9918b4b3591d1a64fe98df4de14a935596f7 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Thu, 24 Mar 2022 20:23:46 +0530 Subject: [PATCH 11/28] test: Fixing sider issues --- cypress/support/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cypress/support/index.js b/cypress/support/index.js index f0b53ae833..5980e96677 100644 --- a/cypress/support/index.js +++ b/cypress/support/index.js @@ -18,7 +18,7 @@ import './commands'; import '@cypress/code-coverage/support'; Cypress.on('uncaught:exception', (err, runnable) => { - return false + return false; }); // Alternatively you can use CommonJS syntax: From c07f62a104f1d8373da7cd218a2a01730a77dd57 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Fri, 25 Mar 2022 16:56:52 +0530 Subject: [PATCH 12/28] test: Added test for checking if dynamic link works by passing options as function --- cypress/integration/control_dynamic_link.js | 41 ++++++++++++++++----- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/cypress/integration/control_dynamic_link.js b/cypress/integration/control_dynamic_link.js index 26f7e0b85e..b9e9a7d99e 100644 --- a/cypress/integration/control_dynamic_link.js +++ b/cypress/integration/control_dynamic_link.js @@ -48,26 +48,49 @@ context('Dynamic Link', () => { }); } + function get_dialog_with_dynamic_link_option() { + return cy.dialog({ + title: 'Dynamic Link', + fields: [{ + "label": "Document Type", + "fieldname": "doc_type", + "fieldtype": "Link", + "options": "DocType", + "in_list_view": 1, + }, + { + "label": "Document ID", + "fieldname": "doc_id", + "fieldtype": "Dynamic Link", + "get_options": () => { return "User"; }, + "in_list_view": 1, + }] + }); + } + + it('Creating a dynamic link by passing option as function and verifying it in a dialog', () => { + get_dialog_with_dynamic_link_option().as('dialog'); + cy.get_field('doc_type').clear(); + cy.fill_field('doc_type', 'User', 'Link'); + cy.get_field('doc_id').click(); + + //Checking if the listbox have length greater than 0 + cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0); + cy.get('.btn-modal-close').click({force: true}); + }); + it('Creating a dynamic link and verifying it in a dialog', () => { - //Opening a dialog get_dialog_with_dynamic_link().as('dialog'); cy.get_field('doc_type').clear(); - - //Entering User in the Doctype field cy.fill_field('doc_type', 'User', 'Link'); - - //Clicking on the Document ID field cy.get_field('doc_id').click(); //Checking if the listbox have length greater than 0 cy.get('[data-fieldname="doc_id"]').find('.awesomplete').find("li").its('length').should('be.gte', 0); - - //Closing the dialog box - cy.get('.btn-modal-close > .icon').click(); + cy.get('.btn-modal-close').click({force: true, multiple: true}); }); it('Creating a dynamic link and verifying it', () => { - //Visiting the dynamic link doctype cy.visit('/app/test-dynamic-link'); //Clicking on the Document ID field From 44fb9fae3958f309d9944f4bf3c6fd9959a30047 Mon Sep 17 00:00:00 2001 From: Komal-Saraf0609 Date: Fri, 25 Mar 2022 17:15:57 +0530 Subject: [PATCH 13/28] test: Fixing sider issues --- cypress/integration/control_dynamic_link.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/cypress/integration/control_dynamic_link.js b/cypress/integration/control_dynamic_link.js index b9e9a7d99e..cc1eb0b695 100644 --- a/cypress/integration/control_dynamic_link.js +++ b/cypress/integration/control_dynamic_link.js @@ -62,7 +62,9 @@ context('Dynamic Link', () => { "label": "Document ID", "fieldname": "doc_id", "fieldtype": "Dynamic Link", - "get_options": () => { return "User"; }, + "get_options": () => { + return "User"; + }, "in_list_view": 1, }] }); From cac8dd60cd69d20d56e07752c0820e78ee8e0dcf Mon Sep 17 00:00:00 2001 From: Florian HENRY Date: Fri, 25 Mar 2022 17:26:14 +0100 Subject: [PATCH 14/28] fix: update translation --- frappe/translations/fr.csv | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/frappe/translations/fr.csv b/frappe/translations/fr.csv index 511c590a59..8a3954cbd5 100644 --- a/frappe/translations/fr.csv +++ b/frappe/translations/fr.csv @@ -2164,7 +2164,7 @@ Search for '{0}',Rechercher '{0}', Search for anything,Rechercher tout, Search in a document type,Rechercher dans un type de document, Search or Create a New Chat,Rechercher ou créer un nouveau chat, -Search or type a command,Rechercher ou taper une commande, +Search or type a command (Ctrl + G),Rechercher ou taper une commande (Ctrl + G), Search...,Rechercher..., Searching,Recherche, Searching ...,Recherche ..., @@ -4215,7 +4215,7 @@ since yesterday,depuis hier, since last week,depuis la semaine dernière, since last month,depuis le mois dernier, since last year,depuis l'année dernière, -Show,Spectacle, +Show,Afficher, New Number Card,Nouvelle carte de numéro, Your Shortcuts,Vos raccourcis, You haven't added any Dashboard Charts or Number Cards yet.,Vous n'avez pas encore ajouté de tableaux de bord ou de cartes numériques., @@ -4700,3 +4700,4 @@ Value cannot be negative for {0}: {1},La valeur ne peut pas être négative pour Negative Value,Valeur négative, Authentication failed while receiving emails from Email Account: {0}.,L'authentification a échoué lors de la réception des e-mails du compte de messagerie: {0}., Message from server: {0},Message du serveur: {0}, +{0} edited this {1},{0} à éditer {1}, From 6fc3c30dc1f96d87a6f5a7bad7582fe7c7d3996a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 26 Mar 2022 14:56:18 +0530 Subject: [PATCH 15/28] refactor: make translate regex readable --- frappe/translate.py | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/frappe/translate.py b/frappe/translate.py index 0367d33d3b..04d1feea76 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -23,6 +23,35 @@ from frappe.utils import get_bench_path, is_html, strip, strip_html_tags from frappe.query_builder import Field, DocType from pypika.terms import PseudoColumn +TRANSLATE_PATTERN = re.compile( + r"_\(" # starts with literal `_(` work with both python / JS + + # BEGIN: message search + r"([\"']{,3})" # start of message string identifier - allows: ', ", """, '''; 1st capture group + r"(?P((?!\1).)*)" # Keep matching until string closing identifier is met which is same as 1st capture group + r"\1" # match exact string closing identifier + # END: message search + + # BEGIN: python context search + r"(\s*,\s*context\s*=\s*" # capture `context=` with ignoring whitespace + r"([\"'])" # start of context string identifier; 5th capture group + r"(?P((?!\5).)*)" # capture context string till closing id is found + r"\5" # match context string closure + r")*" # match one or more context string (?wat this should be 0 or 1) + # END: python context search + + # BEGIN: JS context search + r"(\s*,\s*(.)*?\s*(,\s*" # skip message format replacements: ["format", ...] | null | [] + r"([\"'])" # start of context string; 11th capture group + r"(?P((?!\11).)*)" # capture context string till closing id is found + r"\11" # match context string closure + r")*" + r")*" # match one or more context string + # END: JS context search + + r"\)" # Closing function call +) + def get_language(lang_list: List = None) -> str: """Set `frappe.local.lang` from HTTP headers at beginning of request @@ -651,9 +680,8 @@ def extract_messages_from_code(code): frappe.clear_last_message() messages = [] - pattern = r"_\(([\"']{,3})(?P((?!\1).)*)\1(\s*,\s*context\s*=\s*([\"'])(?P((?!\5).)*)\5)*(\s*,\s*(.)*?\s*(,\s*([\"'])(?P((?!\11).)*)\11)*)*\)" - for m in re.compile(pattern).finditer(code): + for m in TRANSLATE_PATTERN.finditer(code): message = m.group('message') context = m.group('py_context') or m.group('js_context') pos = m.start() From cf69b7f084848da2c7363f93442e7f773ecf0e21 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 26 Mar 2022 14:58:55 +0530 Subject: [PATCH 16/28] fix: only match 1 python context string at most --- frappe/translate.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/translate.py b/frappe/translate.py index 04d1feea76..9869a89840 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -37,7 +37,7 @@ TRANSLATE_PATTERN = re.compile( r"([\"'])" # start of context string identifier; 5th capture group r"(?P((?!\5).)*)" # capture context string till closing id is found r"\5" # match context string closure - r")*" # match one or more context string (?wat this should be 0 or 1) + r")?" # match 0 or 1 context strings # END: python context search # BEGIN: JS context search From 92c563d193179d53ae2e9665ca830594244e720e Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 26 Mar 2022 15:09:16 +0530 Subject: [PATCH 17/28] test: refactor translation extraction test --- frappe/tests/test_translate.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/frappe/tests/test_translate.py b/frappe/tests/test_translate.py index 1b96fb62c3..63c4e8928d 100644 --- a/frappe/tests/test_translate.py +++ b/frappe/tests/test_translate.py @@ -36,7 +36,18 @@ class TestTranslate(unittest.TestCase): def test_extract_message_from_file(self): data = frappe.translate.get_messages_from_file(translation_string_file) - self.assertListEqual(data, expected_output) + exp_filename = "apps/frappe/frappe/tests/translation_test_file.txt" + + self.assertEqual(len(data), len(expected_output), + msg=f"Mismatched output:\nExpected: {expected_output}\nFound: {data}") + + for extracted, expected in zip(data, expected_output): + ext_filename, ext_message, ext_context, ext_line = extracted + exp_message, exp_context, exp_line = expected + self.assertEqual(ext_filename, exp_filename) + self.assertEqual(ext_message, exp_message) + self.assertEqual(ext_context, exp_context) + self.assertEqual(ext_line, exp_line) def test_translation_with_context(self): try: @@ -107,13 +118,13 @@ class TestTranslate(unittest.TestCase): expected_output = [ - ('apps/frappe/frappe/tests/translation_test_file.txt', 'Warning: Unable to find {0} in any table related to {1}', 'This is some context', 2), - ('apps/frappe/frappe/tests/translation_test_file.txt', 'Warning: Unable to find {0} in any table related to {1}', None, 4), - ('apps/frappe/frappe/tests/translation_test_file.txt', "You don't have any messages yet.", None, 6), - ('apps/frappe/frappe/tests/translation_test_file.txt', 'Submit', 'Some DocType', 8), - ('apps/frappe/frappe/tests/translation_test_file.txt', 'Warning: Unable to find {0} in any table related to {1}', 'This is some context', 15), - ('apps/frappe/frappe/tests/translation_test_file.txt', 'Submit', 'Some DocType', 17), - ('apps/frappe/frappe/tests/translation_test_file.txt', "You don't have any messages yet.", None, 19), - ('apps/frappe/frappe/tests/translation_test_file.txt', "You don't have any messages yet.", None, 21) + ('Warning: Unable to find {0} in any table related to {1}', 'This is some context', 2), + ('Warning: Unable to find {0} in any table related to {1}', None, 4), + ("You don't have any messages yet.", None, 6), + ('Submit', 'Some DocType', 8), + ('Warning: Unable to find {0} in any table related to {1}', 'This is some context', 15), + ('Submit', 'Some DocType', 17), + ("You don't have any messages yet.", None, 19), + ("You don't have any messages yet.", None, 21) ] From 29ca4d7aaa75c20d0e7f796fdb217a1d5df36e88 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Sat, 26 Mar 2022 15:18:40 +0530 Subject: [PATCH 18/28] feat: allow splitting _() function call on multiple lines --- frappe/tests/test_translate.py | 5 ++++- frappe/tests/translation_test_file.txt | 16 +++++++++++++++- frappe/translate.py | 6 +++--- 3 files changed, 22 insertions(+), 5 deletions(-) diff --git a/frappe/tests/test_translate.py b/frappe/tests/test_translate.py index 63c4e8928d..f5386914f7 100644 --- a/frappe/tests/test_translate.py +++ b/frappe/tests/test_translate.py @@ -125,6 +125,9 @@ expected_output = [ ('Warning: Unable to find {0} in any table related to {1}', 'This is some context', 15), ('Submit', 'Some DocType', 17), ("You don't have any messages yet.", None, 19), - ("You don't have any messages yet.", None, 21) + ("You don't have any messages yet.", None, 21), + ("Long string that needs its own line because of black formatting.", None, 24), + ("Long string with", "context", 28), + ("Long string with", "context on newline", 32), ] diff --git a/frappe/tests/translation_test_file.txt b/frappe/tests/translation_test_file.txt index 45f67a806b..7db71665ad 100644 --- a/frappe/tests/translation_test_file.txt +++ b/frappe/tests/translation_test_file.txt @@ -18,4 +18,18 @@ _('Submit', context="Some DocType") _("""You don't have any messages yet.""") -_('''You don't have any messages yet.''') \ No newline at end of file +_('''You don't have any messages yet.''') + +// allow newline in beginning +_( +"""Long string that needs its own line because of black formatting.""" +).format("blah") + +_( +"Long string with", context="context" +).format("blah") + +_( + "Long string with", + context="context on newline" +).format("blah") diff --git a/frappe/translate.py b/frappe/translate.py index 9869a89840..6e0fefd6fa 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -24,7 +24,7 @@ from frappe.query_builder import Field, DocType from pypika.terms import PseudoColumn TRANSLATE_PATTERN = re.compile( - r"_\(" # starts with literal `_(` work with both python / JS + r"_\([\s\n]*" # starts with literal `_(`, ignore following whitespace/newlines # BEGIN: message search r"([\"']{,3})" # start of message string identifier - allows: ', ", """, '''; 1st capture group @@ -33,7 +33,7 @@ TRANSLATE_PATTERN = re.compile( # END: message search # BEGIN: python context search - r"(\s*,\s*context\s*=\s*" # capture `context=` with ignoring whitespace + r"([\s\n]*,[\s\n]*context\s*=\s*" # capture `context=` with ignoring whitespace r"([\"'])" # start of context string identifier; 5th capture group r"(?P((?!\5).)*)" # capture context string till closing id is found r"\5" # match context string closure @@ -49,7 +49,7 @@ TRANSLATE_PATTERN = re.compile( r")*" # match one or more context string # END: JS context search - r"\)" # Closing function call + r"[\s\n]*\)" # Closing function call ignore leading whitespace/newlines ) From 3e1df641dab5b1dac39cdbb5b67ff393a97756fa Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 21 Mar 2022 14:51:34 +0530 Subject: [PATCH 19/28] fix: force keyword arguments in new ORM args --- frappe/database/database.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index 24dfdd32df..d4d88977d7 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -357,6 +357,7 @@ class Database(object): order_by="KEEP_DEFAULT_ORDERING", cache=False, for_update=False, + *, run=True, pluck=False, distinct=False, @@ -396,7 +397,7 @@ class Database(object): def get_values(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False, debug=False, order_by="KEEP_DEFAULT_ORDERING", update=None, cache=False, for_update=False, - run=True, pluck=False, distinct=False, limit=None): + *, run=True, pluck=False, distinct=False, limit=None): """Returns multiple document properties. :param doctype: DocType name. @@ -487,6 +488,7 @@ class Database(object): as_dict=False, debug=False, update=None, + *, run=True, pluck=False, distinct=False, @@ -621,7 +623,8 @@ class Database(object): filters, doctype, as_dict, - debug, + *, + debug=False, order_by=None, update=None, for_update=False, @@ -661,7 +664,7 @@ class Database(object): ) return r - def _get_value_for_many_names(self, doctype, names, field, order_by, debug=False, run=True, pluck=False, distinct=False, limit=None): + def _get_value_for_many_names(self, doctype, names, field, order_by, *, debug=False, run=True, pluck=False, distinct=False, limit=None): names = list(filter(None, names)) if names: return self.get_all( From 456cba71cd8e3079383e709ce9540b10983f231b Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 21 Mar 2022 15:19:37 +0530 Subject: [PATCH 20/28] refactor: expand unreadable one-liner in `get_value` --- frappe/database/database.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index d4d88977d7..957e096956 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -387,13 +387,23 @@ class Database(object): frappe.db.get_value("System Settings", None, "date_format") """ - ret = self.get_values(doctype, filters, fieldname, ignore, as_dict, debug, + result = self.get_values(doctype, filters, fieldname, ignore, as_dict, debug, order_by, cache=cache, for_update=for_update, run=run, pluck=pluck, distinct=distinct, limit=1) if not run: - return ret + return result + + if not result: + return None + + row = result[0] + + if len(row) > 1 or as_dict: + return row + else: + # single field is requested, send it without wrapping in containers + return row[0] - return ((len(ret[0]) > 1 or as_dict) and ret[0] or ret[0][0]) if ret else None def get_values(self, doctype, filters=None, fieldname="name", ignore=None, as_dict=False, debug=False, order_by="KEEP_DEFAULT_ORDERING", update=None, cache=False, for_update=False, From c4ca58fedfe6ec7d84768d4dde673f57dcc49bcf Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 22 Mar 2022 21:14:22 +0530 Subject: [PATCH 21/28] refactor: query stripping --- frappe/database/database.py | 4 ++-- frappe/tests/test_db.py | 15 +++++++++++++++ 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index 957e096956..511d993aa5 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -119,8 +119,8 @@ class Database(object): if not run: return query - # remove \n \t from start and end of query - query = re.sub(r'^\s*|\s*$', '', query) + # remove whitespace / indentation from start and end of query + query = query.strip() if re.search(r'ifnull\(', query, flags=re.IGNORECASE): # replaces ifnull in query with coalesce diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 10c601db00..33a5006161 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -312,6 +312,21 @@ class TestDB(unittest.TestCase): frappe.db.MAX_WRITES_PER_TRANSACTION = Database.MAX_WRITES_PER_TRANSACTION + def test_transaction_write_counting(self): + note = frappe.get_doc(doctype="Note", title="transaction counting").insert() + + writes = frappe.db.transaction_writes + frappe.db.set_value("Note", note.name, "content", "abc") + self.assertEqual(1, frappe.db.transaction_writes - writes) + writes = frappe.db.transaction_writes + + frappe.db.sql(""" + update `tabNote` + set content = 'abc' + where name = %s + """, note.name) + self.assertEqual(1, frappe.db.transaction_writes - writes) + def test_pk_collision_ignoring(self): # note has `name` generated from title for _ in range(3): From cc942cfef92d17e3538de89ed11cb95009187e34 Mon Sep 17 00:00:00 2001 From: Deepesh Garg <42651287+deepeshgarg007@users.noreply.github.com> Date: Mon, 28 Mar 2022 17:28:20 +0530 Subject: [PATCH 22/28] chore: Add more users to pull request auto close exception list --- .mergify.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.mergify.yml b/.mergify.yml index 63fe1a0086..838ce75835 100644 --- a/.mergify.yml +++ b/.mergify.yml @@ -5,6 +5,7 @@ pull_request_rules: - and: - author!=surajshetty3416 - author!=gavindsouza + - author!=deepeshgarg007 - or: - base=version-13 - base=version-12 From 7eb5274283b2781f1b21f1b4297b5bf28bc9c37d Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Mon, 28 Mar 2022 14:09:05 +0200 Subject: [PATCH 23/28] fix: bad closure on double quote print js (#16420) --- frappe/printing/page/print/print.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/printing/page/print/print.js b/frappe/printing/page/print/print.js index 5d04fbe982..267419a887 100644 --- a/frappe/printing/page/print/print.js +++ b/frappe/printing/page/print/print.js @@ -37,7 +37,7 @@ frappe.ui.form.PrintView = class { this.print_wrapper = this.page.main.empty().html( `