From b7b92845f0a4d34f91b49b486535ea78e8681b21 Mon Sep 17 00:00:00 2001 From: David Angulo Date: Mon, 3 May 2021 13:38:17 -0500 Subject: [PATCH 01/25] fix: Use docfields from options if no docfields are returned from meta --- frappe/public/js/frappe/form/grid_row.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/form/grid_row.js b/frappe/public/js/frappe/form/grid_row.js index f6da88df57..453b8b5f24 100644 --- a/frappe/public/js/frappe/form/grid_row.js +++ b/frappe/public/js/frappe/form/grid_row.js @@ -7,7 +7,8 @@ export default class GridRow { $.extend(this, opts); if (this.doc && this.parent_df.options) { frappe.meta.make_docfield_copy_for(this.parent_df.options, this.doc.name, this.docfields); - this.docfields = frappe.meta.get_docfields(this.parent_df.options, this.doc.name); + const docfields = frappe.meta.get_docfields(this.parent_df.options, this.doc.name); + this.docfields = docfields.length ? docfields : opts.docfields; } this.columns = {}; this.columns_list = []; From 483cd85eba617624da4fd60c8b3d2a1181ce4ea3 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Thu, 6 May 2021 16:44:18 +0530 Subject: [PATCH 02/25] fix: Revert naming for custom naming series --- frappe/model/naming.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 1a3f90da37..1cfcd56350 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -202,7 +202,12 @@ def revert_series_if_last(key, name, doc=None): if ".#" in key: prefix, hashes = key.rsplit(".", 1) if "#" not in hashes: - return + key = key.rsplit(".") + hash = list(filter(re.compile(".*#").match, key))[0] + if not hash: + return + name = name.replace(hashes, "") + prefix, hashes = key[:key.index(hash)+1] else: prefix = key From 9aa2f366dd6f51adde6fbd9be4fbc2b5c7a85ab9 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Wed, 20 Jan 2021 13:52:05 +0530 Subject: [PATCH 03/25] fix: Don't execute dynamic properties to check if conflicts exist --- frappe/core/doctype/doctype/doctype.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 3588cc553a..6f33221155 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -1176,9 +1176,15 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): def check_if_fieldname_conflicts_with_methods(doctype, fieldname): doc = frappe.get_doc({"doctype": doctype}) - method_list = [method for method in dir(doc) if isinstance(method, str) and callable(getattr(doc, method))] - - if fieldname in method_list: + available_objects = [x for x in dir(doc) if isinstance(x, str)] + property_list = [ + x for x in available_objects if isinstance(getattr(type(doc), x, None), property) + ] + method_list = [ + x for x in available_objects if x not in property_list and callable(getattr(doc, x)) + ] + + if fieldname in method_list + property_list: frappe.throw(_("Fieldname {0} conflicting with meta object").format(fieldname)) def clear_linked_doctype_cache(): From 255a959a3eb706ab25845aa94890708c37fe7573 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 4 Jan 2021 10:41:18 +0530 Subject: [PATCH 04/25] chore: Rename function to validate conflicting methods and properties --- frappe/core/doctype/doctype/doctype.py | 2 +- frappe/custom/doctype/custom_field/custom_field.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 6f33221155..d277a217d1 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -1174,7 +1174,7 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): else: raise -def check_if_fieldname_conflicts_with_methods(doctype, fieldname): +def check_if_fieldname_conflicts_with_properties_and_methods(doctype, fieldname): doc = frappe.get_doc({"doctype": doctype}) available_objects = [x for x in dir(doc) if isinstance(x, str)] property_list = [ diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index 3126326636..ac1213c727 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -64,8 +64,8 @@ class CustomField(Document): self.translatable = 0 if not self.flags.ignore_validate: - from frappe.core.doctype.doctype.doctype import check_if_fieldname_conflicts_with_methods - check_if_fieldname_conflicts_with_methods(self.dt, self.fieldname) + from frappe.core.doctype.doctype.doctype import check_if_fieldname_conflicts_with_properties_and_methods + check_if_fieldname_conflicts_with_properties_and_methods(self.dt, self.fieldname) def on_update(self): frappe.clear_cache(doctype=self.dt) From c652c7b7f52ad8d9a5b00b84af3f1ab33c3d1769 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 6 May 2021 11:58:17 +0530 Subject: [PATCH 05/25] feat: Validate field name conflicts in DocType.validate # Conflicts: # frappe/core/doctype/doctype/doctype.py --- frappe/core/doctype/doctype/doctype.py | 34 ++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index d277a217d1..13ebcd82ac 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -70,6 +70,7 @@ class DocType(Document): validate_series(self) self.validate_document_type() validate_fields(self) + self.validate_field_name_conflicts() if not self.istable: validate_permissions(self) @@ -89,6 +90,39 @@ class DocType(Document): if self.default_print_format and not self.custom: frappe.throw(_('Standard DocType cannot have default print format, use Customize Form')) + if frappe.conf.get('developer_mode'): + self.owner = 'Administrator' + self.modified_by = 'Administrator' + + def validate_field_name_conflicts(self): + """Check if field names dont conflict with controller properties and methods""" + from frappe.model.base_document import get_controller + + controller = get_controller(self.name) + available_objects = {x for x in dir(controller) if isinstance(x, str)} + property_set = { + x for x in available_objects if isinstance(getattr(controller, x, None), property) + } + method_set = { + x for x in available_objects if x not in property_set and callable(getattr(controller, x, None)) + } + + for docfield in self.get("fields") or []: + conflict_type = "" + field = docfield.fieldname + field_label = docfield.label or docfield.fieldname + + if docfield.fieldname in method_set: + conflict_type = "controller method" + if docfield.fieldname in property_set: + conflict_type = "class property" + + if conflict_type: + frappe.throw( + _("Fieldname '{0}' conflicting with a {1} of the name {2} in {3}") + .format(field_label, conflict_type, field, self.name) + ) + def after_insert(self): # clear user cache so that on the next reload this doctype is included in boot clear_user_cache(frappe.session.user) From 78e1297392706eaa209e53d3acb42faae66dfa65 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 22 Jan 2021 18:08:18 +0530 Subject: [PATCH 06/25] chore: Drop dead file --- frappe/email/doctype/newsletter/newsletter..json | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 frappe/email/doctype/newsletter/newsletter..json diff --git a/frappe/email/doctype/newsletter/newsletter..json b/frappe/email/doctype/newsletter/newsletter..json deleted file mode 100644 index e69de29bb2..0000000000 From ce4253c6318546a7249f0edd518ad1f499f8fd88 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Mon, 25 Jan 2021 13:55:10 +0530 Subject: [PATCH 07/25] fix: Ignore 'special' DocTypes to avoid breaking changes eg: Fieldname 'Delete' conflicting with a controller method of the name delete in Custom DocPerm Fieldname 'Delete' conflicting with a controller method of the name delete in DocPerm Fieldname 'Precision' conflicting with a controller method of the name precision in Custom Field Fieldname 'Precision' conflicting with a controller method of the name precision in Customize Form Field Fieldname 'Precision' conflicting with a controller method of the name precision in DocField --- frappe/core/doctype/doctype/doctype.py | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 13ebcd82ac..c1a8527512 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -96,6 +96,17 @@ class DocType(Document): def validate_field_name_conflicts(self): """Check if field names dont conflict with controller properties and methods""" + core_doctypes = [ + "Custom DocPerm", + "DocPerm", + "Custom Field", + "Customize Form Field", + "DocField", + ] + + if self.name in core_doctypes: + return + from frappe.model.base_document import get_controller controller = get_controller(self.name) From a3b79081d64e754d73fad46f26aa24ac42a32a2e Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Sat, 18 Jan 2020 04:11:19 +0530 Subject: [PATCH 08/25] fix: Use Document in case get_controller raises import errors --- frappe/core/doctype/doctype/doctype.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index c1a8527512..d361bb8a98 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -109,7 +109,11 @@ class DocType(Document): from frappe.model.base_document import get_controller - controller = get_controller(self.name) + try: + controller = get_controller(self.name) + except ImportError: + controller = Document + available_objects = {x for x in dir(controller) if isinstance(x, str)} property_set = { x for x in available_objects if isinstance(getattr(controller, x, None), property) From 05712abc60721f4633d6b0166648743154b56cca Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Thu, 18 Feb 2021 19:55:12 +0530 Subject: [PATCH 09/25] fix: Check for db value if cache doesn't exist in get_controller, if cached value doesn't exist for a DocType in the frappe.db.value_cache, then query the db as fallback before the final fallback of assuming module as Core and non custom --- frappe/model/base_document.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 05435482bd..9288d621c9 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -34,8 +34,11 @@ def get_controller(doctype): from frappe.model.document import Document from frappe.utils.nestedset import NestedSet - module_name, custom = frappe.db.get_value("DocType", doctype, ("module", "custom"), cache=True) \ + module_name, custom = ( + frappe.db.get_value("DocType", doctype, ("module", "custom"), cache=True) + or frappe.db.get_value("DocType", doctype, ("module", "custom")) or ["Core", False] + ) if custom: if frappe.db.field_exists("DocType", "is_tree"): From 843c544117e7f661cc97cc78c33349833b12df21 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 16 Mar 2021 12:23:10 +0530 Subject: [PATCH 10/25] refactor: Rename function and add docstring Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/core/doctype/doctype/doctype.py | 4 +++- frappe/custom/doctype/custom_field/custom_field.py | 4 ++-- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index d361bb8a98..2cdccda8e1 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -1223,7 +1223,9 @@ def make_module_and_roles(doc, perm_fieldname="permissions"): else: raise -def check_if_fieldname_conflicts_with_properties_and_methods(doctype, fieldname): +def check_fieldname_conflicts(doctype, fieldname): + """Checks if fieldname conflicts with methods or properties""" + doc = frappe.get_doc({"doctype": doctype}) available_objects = [x for x in dir(doc) if isinstance(x, str)] property_list = [ diff --git a/frappe/custom/doctype/custom_field/custom_field.py b/frappe/custom/doctype/custom_field/custom_field.py index ac1213c727..b3f61c707d 100644 --- a/frappe/custom/doctype/custom_field/custom_field.py +++ b/frappe/custom/doctype/custom_field/custom_field.py @@ -64,8 +64,8 @@ class CustomField(Document): self.translatable = 0 if not self.flags.ignore_validate: - from frappe.core.doctype.doctype.doctype import check_if_fieldname_conflicts_with_properties_and_methods - check_if_fieldname_conflicts_with_properties_and_methods(self.dt, self.fieldname) + from frappe.core.doctype.doctype.doctype import check_fieldname_conflicts + check_fieldname_conflicts(self.dt, self.fieldname) def on_update(self): frappe.clear_cache(doctype=self.dt) From 877f9d08df70a741aa493421a002651ef0098cbc Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Tue, 16 Mar 2021 16:28:38 +0530 Subject: [PATCH 11/25] fix: Use fallback values if doctype values unset --- frappe/model/base_document.py | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 9288d621c9..2724e320c4 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -34,11 +34,15 @@ def get_controller(doctype): from frappe.model.document import Document from frappe.utils.nestedset import NestedSet - module_name, custom = ( - frappe.db.get_value("DocType", doctype, ("module", "custom"), cache=True) - or frappe.db.get_value("DocType", doctype, ("module", "custom")) - or ["Core", False] - ) + if frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_patch: + module_name, custom = ["Core", False] + else: + # this could be simplified in PY3.8 using walrus operators + result = frappe.db.get_value("DocType", doctype, ("module", "custom"), cache=True) + if result: + module_name, custom = result + else: + module_name, custom = ["Core", bool(not frappe.db.exists(doctype))] if custom: if frappe.db.field_exists("DocType", "is_tree"): From 87ed7796ded4053d8c1f04924716b29bc4848a6d Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 23 Apr 2021 12:43:13 +0530 Subject: [PATCH 12/25] fix: Use older logic to set module_name and custom vars --- frappe/model/base_document.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/frappe/model/base_document.py b/frappe/model/base_document.py index 2724e320c4..154a091b8a 100644 --- a/frappe/model/base_document.py +++ b/frappe/model/base_document.py @@ -34,15 +34,9 @@ def get_controller(doctype): from frappe.model.document import Document from frappe.utils.nestedset import NestedSet - if frappe.flags.in_migrate or frappe.flags.in_install or frappe.flags.in_patch: - module_name, custom = ["Core", False] - else: - # this could be simplified in PY3.8 using walrus operators - result = frappe.db.get_value("DocType", doctype, ("module", "custom"), cache=True) - if result: - module_name, custom = result - else: - module_name, custom = ["Core", bool(not frappe.db.exists(doctype))] + module_name, custom = frappe.db.get_value( + "DocType", doctype, ("module", "custom"), cache=True + ) or ["Core", False] if custom: if frappe.db.field_exists("DocType", "is_tree"): From bc08459ca77194bc5853a21b311aebfd125d62e9 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Thu, 6 May 2021 17:22:59 +0530 Subject: [PATCH 13/25] test: Test case for revert series --- frappe/tests/test_naming.py | 98 +++++++++++++++++++++++-------------- 1 file changed, 60 insertions(+), 38 deletions(-) diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py index b47fb809ca..fbfb1e677d 100644 --- a/frappe/tests/test_naming.py +++ b/frappe/tests/test_naming.py @@ -16,50 +16,50 @@ class TestNaming(unittest.TestCase): todo_doctype.autoname = 'hash' todo_doctype.save() - def test_append_number_if_name_exists(self): - ''' - Append number to name based on existing values - if Bottle exists - Bottle -> Bottle-1 - if Bottle-1 exists - Bottle -> Bottle-2 - ''' - - note = frappe.new_doc('Note') - note.title = 'Test' - note.insert() - - title2 = append_number_if_name_exists('Note', 'Test') - self.assertEqual(title2, 'Test-1') - - title2 = append_number_if_name_exists('Note', 'Test', 'title', '_') - self.assertEqual(title2, 'Test_1') - - def test_format_autoname(self): - ''' - Test if braced params are replaced in format autoname - ''' - doctype = 'ToDo' - - todo_doctype = frappe.get_doc('DocType', doctype) - todo_doctype.autoname = 'format:TODO-{MM}-{status}-{##}' - todo_doctype.save() + # def test_append_number_if_name_exists(self): + # ''' + # Append number to name based on existing values + # if Bottle exists + # Bottle -> Bottle-1 + # if Bottle-1 exists + # Bottle -> Bottle-2 + # ''' + + # note = frappe.new_doc('Note') + # note.title = 'Test' + # note.insert() + + # title2 = append_number_if_name_exists('Note', 'Test') + # self.assertEqual(title2, 'Test-1') + + # title2 = append_number_if_name_exists('Note', 'Test', 'title', '_') + # self.assertEqual(title2, 'Test_1') + + # def test_format_autoname(self): + # ''' + # Test if braced params are replaced in format autoname + # ''' + # doctype = 'ToDo' + + # todo_doctype = frappe.get_doc('DocType', doctype) + # todo_doctype.autoname = 'format:TODO-{MM}-{status}-{##}' + # todo_doctype.save() - description = 'Format' + # description = 'Format' - todo = frappe.new_doc(doctype) - todo.description = description - todo.insert() + # todo = frappe.new_doc(doctype) + # todo.description = description + # todo.insert() - series = getseries('', 2) + # series = getseries('', 2) - series = str(int(series)-1) + # series = str(int(series)-1) - if len(series) < 2: - series = '0' + series + # if len(series) < 2: + # series = '0' + series - self.assertEqual(todo.name, 'TODO-{month}-{status}-{series}'.format( - month=now_datetime().strftime('%m'), status=todo.status, series=series)) + # self.assertEqual(todo.name, 'TODO-{month}-{status}-{series}'.format( + # month=now_datetime().strftime('%m'), status=todo.status, series=series)) def test_revert_series(self): from datetime import datetime @@ -95,3 +95,25 @@ class TestNaming(unittest.TestCase): self.assertEqual(count.get('current'), 2) frappe.db.sql("""delete from `tabSeries` where name = %s""", series) + + series = 'TEST1-' + key = 'TEST1-.#####.-2021-22' + name = 'TEST1-00003-2021-22' + frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) + frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) + revert_series_if_last(key, name) + count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] + + self.assertEqual(count.get('current'), 2) + frappe.db.sql("""delete from `tabSeries` where name = %s""", series) + + series = '' + key = '.#####.-2021-22' + name = '00003-2021-22' + frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) + frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) + revert_series_if_last(key, name) + count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] + + self.assertEqual(count.get('current'), 2) + frappe.db.sql("""delete from `tabSeries` where name = %s""", series) From 47d1f3d50abff0d253c157e760a72ad1eff3b696 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Thu, 6 May 2021 17:24:12 +0530 Subject: [PATCH 14/25] test: uncomment --- frappe/tests/test_naming.py | 76 ++++++++++++++++++------------------- 1 file changed, 38 insertions(+), 38 deletions(-) diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py index fbfb1e677d..0c3481ba98 100644 --- a/frappe/tests/test_naming.py +++ b/frappe/tests/test_naming.py @@ -16,50 +16,50 @@ class TestNaming(unittest.TestCase): todo_doctype.autoname = 'hash' todo_doctype.save() - # def test_append_number_if_name_exists(self): - # ''' - # Append number to name based on existing values - # if Bottle exists - # Bottle -> Bottle-1 - # if Bottle-1 exists - # Bottle -> Bottle-2 - # ''' - - # note = frappe.new_doc('Note') - # note.title = 'Test' - # note.insert() - - # title2 = append_number_if_name_exists('Note', 'Test') - # self.assertEqual(title2, 'Test-1') - - # title2 = append_number_if_name_exists('Note', 'Test', 'title', '_') - # self.assertEqual(title2, 'Test_1') - - # def test_format_autoname(self): - # ''' - # Test if braced params are replaced in format autoname - # ''' - # doctype = 'ToDo' - - # todo_doctype = frappe.get_doc('DocType', doctype) - # todo_doctype.autoname = 'format:TODO-{MM}-{status}-{##}' - # todo_doctype.save() + def test_append_number_if_name_exists(self): + ''' + Append number to name based on existing values + if Bottle exists + Bottle -> Bottle-1 + if Bottle-1 exists + Bottle -> Bottle-2 + ''' + + note = frappe.new_doc('Note') + note.title = 'Test' + note.insert() + + title2 = append_number_if_name_exists('Note', 'Test') + self.assertEqual(title2, 'Test-1') + + title2 = append_number_if_name_exists('Note', 'Test', 'title', '_') + self.assertEqual(title2, 'Test_1') + + def test_format_autoname(self): + ''' + Test if braced params are replaced in format autoname + ''' + doctype = 'ToDo' + + todo_doctype = frappe.get_doc('DocType', doctype) + todo_doctype.autoname = 'format:TODO-{MM}-{status}-{##}' + todo_doctype.save() - # description = 'Format' + description = 'Format' - # todo = frappe.new_doc(doctype) - # todo.description = description - # todo.insert() + todo = frappe.new_doc(doctype) + todo.description = description + todo.insert() - # series = getseries('', 2) + series = getseries('', 2) - # series = str(int(series)-1) + series = str(int(series)-1) - # if len(series) < 2: - # series = '0' + series + if len(series) < 2: + series = '0' + series - # self.assertEqual(todo.name, 'TODO-{month}-{status}-{series}'.format( - # month=now_datetime().strftime('%m'), status=todo.status, series=series)) + self.assertEqual(todo.name, 'TODO-{month}-{status}-{series}'.format( + month=now_datetime().strftime('%m'), status=todo.status, series=series)) def test_revert_series(self): from datetime import datetime From f46c3b59a6385f3d470b03712e00ce0ad5cf289c Mon Sep 17 00:00:00 2001 From: shariquerik Date: Thu, 6 May 2021 17:26:54 +0530 Subject: [PATCH 15/25] fix: sider fix --- frappe/tests/test_naming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py index 0c3481ba98..f808d06db3 100644 --- a/frappe/tests/test_naming.py +++ b/frappe/tests/test_naming.py @@ -106,7 +106,7 @@ class TestNaming(unittest.TestCase): self.assertEqual(count.get('current'), 2) frappe.db.sql("""delete from `tabSeries` where name = %s""", series) - + series = '' key = '.#####.-2021-22' name = '00003-2021-22' From 926d13e69ef88309f409446d2dc1c2e3f65ab0f3 Mon Sep 17 00:00:00 2001 From: Gavin D'souza Date: Fri, 7 May 2021 18:02:25 +0530 Subject: [PATCH 16/25] fix: Skip field-method conflicts validation on new docs --- frappe/core/doctype/doctype/doctype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 2cdccda8e1..e0b9d15114 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -70,7 +70,6 @@ class DocType(Document): validate_series(self) self.validate_document_type() validate_fields(self) - self.validate_field_name_conflicts() if not self.istable: validate_permissions(self) @@ -84,6 +83,7 @@ class DocType(Document): if not self.is_new(): self.before_update = frappe.get_doc('DocType', self.name) self.setup_fields_to_fetch() + self.validate_field_name_conflicts() check_email_append_to(self) From e7552413b4212e1daf47660b72838d1713c6a9bd Mon Sep 17 00:00:00 2001 From: shariquerik Date: Fri, 7 May 2021 18:10:59 +0530 Subject: [PATCH 17/25] refactor: minor fix --- frappe/model/naming.py | 1 + frappe/tests/test_naming.py | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/frappe/model/naming.py b/frappe/model/naming.py index 1cfcd56350..dbe77562d0 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -203,6 +203,7 @@ def revert_series_if_last(key, name, doc=None): prefix, hashes = key.rsplit(".", 1) if "#" not in hashes: key = key.rsplit(".") + # get the hash part from the key hash = list(filter(re.compile(".*#").match, key))[0] if not hash: return diff --git a/frappe/tests/test_naming.py b/frappe/tests/test_naming.py index f808d06db3..66d48e3612 100644 --- a/frappe/tests/test_naming.py +++ b/frappe/tests/test_naming.py @@ -70,9 +70,9 @@ class TestNaming(unittest.TestCase): name = 'TEST-{}-00001'.format(year) frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 1)""", (series,)) revert_series_if_last(key, name) - count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] + current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] - self.assertEqual(count.get('current'), 0) + self.assertEqual(current_index.get('current'), 0) frappe.db.sql("""delete from `tabSeries` where name = %s""", series) series = 'TEST-{}-'.format(year) @@ -80,9 +80,9 @@ class TestNaming(unittest.TestCase): name = 'TEST-{}-00002'.format(year) frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 2)""", (series,)) revert_series_if_last(key, name) - count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] + current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] - self.assertEqual(count.get('current'), 1) + self.assertEqual(current_index.get('current'), 1) frappe.db.sql("""delete from `tabSeries` where name = %s""", series) series = 'TEST-' @@ -91,9 +91,9 @@ class TestNaming(unittest.TestCase): frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) revert_series_if_last(key, name) - count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] + current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] - self.assertEqual(count.get('current'), 2) + self.assertEqual(current_index.get('current'), 2) frappe.db.sql("""delete from `tabSeries` where name = %s""", series) series = 'TEST1-' @@ -102,9 +102,9 @@ class TestNaming(unittest.TestCase): frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) revert_series_if_last(key, name) - count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] + current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] - self.assertEqual(count.get('current'), 2) + self.assertEqual(current_index.get('current'), 2) frappe.db.sql("""delete from `tabSeries` where name = %s""", series) series = '' @@ -113,7 +113,7 @@ class TestNaming(unittest.TestCase): frappe.db.sql("DELETE FROM `tabSeries` WHERE `name`=%s", series) frappe.db.sql("""INSERT INTO `tabSeries` (name, current) values (%s, 3)""", (series,)) revert_series_if_last(key, name) - count = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] + current_index = frappe.db.sql("""SELECT current from `tabSeries` where name = %s""", series, as_dict=True)[0] - self.assertEqual(count.get('current'), 2) + self.assertEqual(current_index.get('current'), 2) frappe.db.sql("""delete from `tabSeries` where name = %s""", series) From c84feb16e9e456cdd040f20a6ac154d3a1d760c0 Mon Sep 17 00:00:00 2001 From: gavin Date: Sat, 8 May 2021 12:39:27 +0530 Subject: [PATCH 18/25] fix: Avoid possible whitespace bug * Handles semgrep warning * Changed "" to None as a precaution against future whitespace bugs via human error Co-authored-by: Ankush Menat --- frappe/core/doctype/doctype/doctype.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index e0b9d15114..6eef5a4023 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -123,7 +123,7 @@ class DocType(Document): } for docfield in self.get("fields") or []: - conflict_type = "" + conflict_type = None field = docfield.fieldname field_label = docfield.label or docfield.fieldname From 8995e8e9db83b40e9df19433b77ec7ee853eaf18 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Sun, 9 May 2021 06:24:20 +0530 Subject: [PATCH 19/25] refactor: remove unnecessary code --- frappe/model/naming.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/model/naming.py b/frappe/model/naming.py index dbe77562d0..ade4eaad95 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -202,13 +202,13 @@ def revert_series_if_last(key, name, doc=None): if ".#" in key: prefix, hashes = key.rsplit(".", 1) if "#" not in hashes: - key = key.rsplit(".") + key = key.split(".") # get the hash part from the key hash = list(filter(re.compile(".*#").match, key))[0] if not hash: return name = name.replace(hashes, "") - prefix, hashes = key[:key.index(hash)+1] + prefix = prefix.replace(".{}".format(hash), "") else: prefix = key From 36d6d224df87e03e6247afc4ec873fb8058ba000 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 10 May 2021 12:18:37 +0530 Subject: [PATCH 20/25] fix: Do not skip text in save while using shortcut --- frappe/public/js/frappe/desk.js | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/desk.js b/frappe/public/js/frappe/desk.js index 6bcd20c494..d6cb7f5507 100644 --- a/frappe/public/js/frappe/desk.js +++ b/frappe/public/js/frappe/desk.js @@ -474,14 +474,19 @@ frappe.Application = Class.extend({ $('').appendTo("head"); }, trigger_primary_action: function() { - if(window.cur_dialog && cur_dialog.display) { - // trigger primary - cur_dialog.get_primary_btn().trigger("click"); - } else if(cur_frm && cur_frm.page.btn_primary.is(':visible')) { - cur_frm.page.btn_primary.trigger('click'); - } else if(frappe.container.page.save_action) { - frappe.container.page.save_action(); - } + // to trigger change event on active input before triggering primary action + $(document.activeElement).blur(); + // wait for possible JS validations triggered after blur (it might change primary button) + setTimeout(() => { + if (window.cur_dialog && cur_dialog.display) { + // trigger primary + cur_dialog.get_primary_btn().trigger("click"); + } else if (cur_frm && cur_frm.page.btn_primary.is(':visible')) { + cur_frm.page.btn_primary.trigger('click'); + } else if (frappe.container.page.save_action) { + frappe.container.page.save_action(); + } + }, 100); }, set_rtl: function() { From e0efefd9e5756fb3e17fe2891964c9a6d0831607 Mon Sep 17 00:00:00 2001 From: shariquerik Date: Mon, 10 May 2021 12:54:04 +0530 Subject: [PATCH 21/25] refactor: Used re.search also added examples --- frappe/model/naming.py | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/frappe/model/naming.py b/frappe/model/naming.py index ade4eaad95..c5b2775ec5 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -199,16 +199,39 @@ def getseries(key, digits): def revert_series_if_last(key, name, doc=None): - if ".#" in key: + """ + Reverts the series for particular naming series: + * key is naming series - SINV-.YYYY-.#### + * name is actual name - SINV-2021-0001 + + 1. This function split the key into two parts prefix (SINV-YYYY) & hashes (####). + 2. Use prefix to get the current index of that naming series from Series table + 3. Then revert the current index. + + *For custom naming series:* + 1. hash can exist anywhere, if it exist in hashes then it take normal flow. + 2. If hash doesn't exit in hashes, we get the hash from prefix, then update name and prefix accordingly. + + *Example:* + 1. key = SINV-.YYYY.- + * If key doesn't have hash it will add hash at the end + * prefix will be SINV-YYYY based on this will get current index from Series table. + 2. key = SINV-.####.-2021 + * now prefix = SINV-#### and hashes = 2021 (hash doesn't exist) + * will search hash in key then accordingly get prefix = SINV- + 3. key = ####.-2021 + * prefix = #### and hashes = 2021 (hash doesn't exist) + * will search hash in key then accordingly get prefix = "" + """ + if ".#" in key: prefix, hashes = key.rsplit(".", 1) if "#" not in hashes: - key = key.split(".") # get the hash part from the key - hash = list(filter(re.compile(".*#").match, key))[0] + hash = re.search("#+", key) if not hash: return name = name.replace(hashes, "") - prefix = prefix.replace(".{}".format(hash), "") + prefix = prefix.replace(hash.group(), "") else: prefix = key From 9919ddff2a937555881f43b83b22cd517555a94c Mon Sep 17 00:00:00 2001 From: shariquerik Date: Mon, 10 May 2021 12:56:15 +0530 Subject: [PATCH 22/25] fix: sider fix --- frappe/model/naming.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/model/naming.py b/frappe/model/naming.py index c5b2775ec5..359b8e2367 100644 --- a/frappe/model/naming.py +++ b/frappe/model/naming.py @@ -214,7 +214,7 @@ def revert_series_if_last(key, name, doc=None): *Example:* 1. key = SINV-.YYYY.- - * If key doesn't have hash it will add hash at the end + * If key doesn't have hash it will add hash at the end * prefix will be SINV-YYYY based on this will get current index from Series table. 2. key = SINV-.####.-2021 * now prefix = SINV-#### and hashes = 2021 (hash doesn't exist) From 67ceab88ff827c19e2880f03271e3ba8c254f50f Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Tue, 11 May 2021 08:58:15 +0200 Subject: [PATCH 23/25] fix: translations (#12942) * fix: get_messages_from_include_files * feat: include labels of navbar items * refactor: strip -> lstrip Co-authored-by: gavin --- frappe/translate.py | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/frappe/translate.py b/frappe/translate.py index aeca758a9d..1d8b1234c7 100644 --- a/frappe/translate.py +++ b/frappe/translate.py @@ -98,6 +98,7 @@ def get_dict(fortype, name=None): translation_assets = cache.hget("translation_assets", frappe.local.lang, shared=True) or {} if not asset_key in translation_assets: + messages = [] if fortype=="doctype": messages = get_messages_from_doctype(name) elif fortype=="page": @@ -109,14 +110,12 @@ def get_dict(fortype, name=None): elif fortype=="jsfile": messages = get_messages_from_file(name) elif fortype=="boot": - messages = [] apps = frappe.get_all_apps(True) for app in apps: messages.extend(get_server_messages(app)) - messages = deduplicate_messages(messages) - messages += frappe.db.sql("""select 'navbar', item_label from `tabNavbar Item` where item_label is not null""") - messages = get_messages_from_include_files() + messages += get_messages_from_navbar() + messages += get_messages_from_include_files() messages += frappe.db.sql("select 'Print Format:', name from `tabPrint Format`") messages += frappe.db.sql("select 'DocType:', name from tabDocType") messages += frappe.db.sql("select 'Role:', name from tabRole") @@ -124,6 +123,7 @@ def get_dict(fortype, name=None): messages += frappe.db.sql("select '', format from `tabWorkspace Shortcut` where format is not null") messages += frappe.db.sql("select '', title from `tabOnboarding Step`") + messages = deduplicate_messages(messages) message_dict = make_dict_from_messages(messages, load_user_translation=False) message_dict.update(get_dict_from_hooks(fortype, name)) # remove untranslated @@ -320,10 +320,22 @@ def get_messages_for_app(app, deduplicate=True): # server_messages messages.extend(get_server_messages(app)) + + # messages from navbar settings + messages.extend(get_messages_from_navbar()) + if deduplicate: messages = deduplicate_messages(messages) + return messages + +def get_messages_from_navbar(): + """Return all labels from Navbar Items, as specified in Navbar Settings.""" + labels = frappe.get_all('Navbar Item', filters={'item_label': ('is', 'set')}, pluck='item_label') + return [('Navbar:', label, 'Label of a Navbar Item') for label in labels] + + def get_messages_from_doctype(name): """Extract all translatable messages for a doctype. Includes labels, Python code, Javascript code, html templates""" @@ -490,8 +502,14 @@ def get_server_messages(app): def get_messages_from_include_files(app_name=None): """Returns messages from js files included at time of boot like desk.min.js for desk and web""" messages = [] - for file in (frappe.get_hooks("app_include_js", app_name=app_name) or []) + (frappe.get_hooks("web_include_js", app_name=app_name) or []): - messages.extend(get_messages_from_file(os.path.join(frappe.local.sites_path, file))) + app_include_js = frappe.get_hooks("app_include_js", app_name=app_name) or [] + web_include_js = frappe.get_hooks("web_include_js", app_name=app_name) or [] + include_js = app_include_js + web_include_js + + for js_path in include_js: + relative_path = os.path.join(frappe.local.sites_path, js_path.lstrip('/')) + messages_from_file = get_messages_from_file(relative_path) + messages.extend(messages_from_file) return messages From 17928f2d280ae09f8cec197647e606eb583e253e Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Tue, 11 May 2021 15:30:55 +0530 Subject: [PATCH 24/25] fix: Clear web page cache after web template is updated --- .../website/doctype/web_template/web_template.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frappe/website/doctype/web_template/web_template.py b/frappe/website/doctype/web_template/web_template.py index 3c61807099..2fd5bfa179 100644 --- a/frappe/website/doctype/web_template/web_template.py +++ b/frappe/website/doctype/web_template/web_template.py @@ -9,6 +9,7 @@ from shutil import rmtree import frappe from frappe.model.document import Document +from frappe.website.render import clear_cache from frappe import _ from frappe.modules.export_file import ( write_document_file, @@ -37,6 +38,19 @@ class WebTemplate(Document): if was_standard and not self.standard: self.import_from_files() + def on_update(self): + """Clear cache for all Web Pages in which this template is used""" + routes = frappe.db.get_all( + "Web Page", + filters=[ + ["Web Page Block", "web_template", "=", self.name], + ["Web Page", "published", "=", 1], + ], + pluck="route", + ) + for route in routes: + clear_cache(route) + def on_trash(self): if frappe.conf.developer_mode and self.standard: # delete template html and json files From 095994a86c0fcebc0d72ff2a18460efac7e9a0ff Mon Sep 17 00:00:00 2001 From: casesolved-co-uk Date: Tue, 11 May 2021 10:14:56 +0000 Subject: [PATCH 25/25] fix: connected app auto_refresh credentials and mandatory fields --- .../doctype/connected_app/connected_app.json | 11 +++++++---- .../doctype/connected_app/connected_app.py | 7 +++++++ 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/frappe/integrations/doctype/connected_app/connected_app.json b/frappe/integrations/doctype/connected_app/connected_app.json index e5dbb0472a..b5330f4d4f 100644 --- a/frappe/integrations/doctype/connected_app/connected_app.json +++ b/frappe/integrations/doctype/connected_app/connected_app.json @@ -54,7 +54,8 @@ "fieldname": "client_id", "fieldtype": "Data", "in_list_view": 1, - "label": "Client Id" + "label": "Client Id", + "mandatory_depends_on": "eval:doc.redirect_uri" }, { "fieldname": "redirect_uri", @@ -96,12 +97,14 @@ { "fieldname": "authorization_uri", "fieldtype": "Data", - "label": "Authorization URI" + "label": "Authorization URI", + "mandatory_depends_on": "eval:doc.redirect_uri" }, { "fieldname": "token_uri", "fieldtype": "Data", - "label": "Token URI" + "label": "Token URI", + "mandatory_depends_on": "eval:doc.redirect_uri" }, { "fieldname": "revocation_uri", @@ -136,7 +139,7 @@ "link_fieldname": "connected_app" } ], - "modified": "2020-11-16 16:29:50.277405", + "modified": "2021-05-10 05:03:06.296863", "modified_by": "Administrator", "module": "Integrations", "name": "Connected App", diff --git a/frappe/integrations/doctype/connected_app/connected_app.py b/frappe/integrations/doctype/connected_app/connected_app.py index 95077ece77..449e30f6d0 100644 --- a/frappe/integrations/doctype/connected_app/connected_app.py +++ b/frappe/integrations/doctype/connected_app/connected_app.py @@ -26,20 +26,27 @@ class ConnectedApp(Document): self.redirect_uri = urljoin(base_url, callback_path) def get_oauth2_session(self, user=None, init=False): + """Return an auto-refreshing OAuth2 session which is an extension of a requests.Session()""" token = None token_updater = None + auto_refresh_kwargs = None if not init: user = user or frappe.session.user token_cache = self.get_user_token(user) token = token_cache.get_json() token_updater = token_cache.update_data + auto_refresh_kwargs = {'client_id': self.client_id} + client_secret = self.get_password('client_secret') + if client_secret: + auto_refresh_kwargs['client_secret'] = client_secret return OAuth2Session( client_id=self.client_id, token=token, token_updater=token_updater, auto_refresh_url=self.token_uri, + auto_refresh_kwargs=auto_refresh_kwargs, redirect_uri=self.redirect_uri, scope=self.get_scopes() )