From db101e5ec396ae1562608ed9a159c9f0877eab37 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 30 Nov 2021 16:25:20 +0530 Subject: [PATCH 01/11] fix(ux): ensure max_attachments is more than no of attach fields --- frappe/core/doctype/doctype/doctype.js | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 262a6efd90..291fb237a9 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -129,7 +129,23 @@ frappe.ui.form.on('DocType', { } frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt'); - } + }, + + max_attachments: function(frm) { + if (!frm.doc.max_attachments) { + return; + } + const is_attach_field = (f) => ["Attach", "Attach Image"].includes(f.fieldtype); + const no_of_attach_fields = frm.doc.fields.filter(is_attach_field).length; + + if (no_of_attach_fields > frm.doc.max_attachments) { + frm.set_value("max_attachments", no_of_attach_fields); + const label = frm.get_docfield("max_attachments").label; + frappe.show_alert( + __("Number of attachment fields are more than {}, limit updated to {}.", [label, no_of_attach_fields])); + } + }, + }); frappe.ui.form.on("DocField", { @@ -217,5 +233,9 @@ frappe.ui.form.on("DocField", { $doctype_select.val(curr_value.doctype); update_fieldname_options(); } + }, + + fieldtype: function(frm) { + frm.trigger("max_attachments"); } }); From 2f6b57cc0ae2b87859bdcca8755f0e764f0ea16a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 30 Nov 2021 12:18:52 +0530 Subject: [PATCH 02/11] fix(ux): validate max_attachment on doctype controller --- frappe/core/doctype/doctype/doctype.py | 17 +++++++++++++++++ frappe/model/__init__.py | 5 +++++ 2 files changed, 22 insertions(+) diff --git a/frappe/core/doctype/doctype/doctype.py b/frappe/core/doctype/doctype/doctype.py index 1c8c1f9217..a6a81cb195 100644 --- a/frappe/core/doctype/doctype/doctype.py +++ b/frappe/core/doctype/doctype/doctype.py @@ -75,6 +75,7 @@ class DocType(Document): self.make_repeatable() self.validate_nestedset() self.validate_website() + self.ensure_minimum_max_attachment_limit() validate_links_table_fieldnames(self) if not self.is_new(): @@ -246,6 +247,22 @@ class DocType(Document): # clear website cache clear_cache() + def ensure_minimum_max_attachment_limit(self): + """Ensure that max_attachments is *at least* bigger than number of attach fields.""" + from frappe.model import attachment_fieldtypes + + + if not self.max_attachments: + return + + total_attach_fields = len([d for d in self.fields if d.fieldtype in attachment_fieldtypes]) + if total_attach_fields > self.max_attachments: + self.max_attachments = total_attach_fields + field_label = frappe.bold(self.meta.get_field("max_attachments").label) + frappe.msgprint(_("Number of attachment fields are more than {}, limit updated to {}.") + .format(field_label, total_attach_fields), + title=_("Insufficient attachment limit"), alert=True) + def change_modified_of_parent(self): """Change the timestamp of parent DocType if the current one is a child to clear caches.""" if frappe.flags.in_import: diff --git a/frappe/model/__init__.py b/frappe/model/__init__.py index b460db29a7..b50a0304a5 100644 --- a/frappe/model/__init__.py +++ b/frappe/model/__init__.py @@ -38,6 +38,11 @@ data_fieldtypes = ( 'Icon' ) +attachment_fieldtypes = ( + 'Attach', + 'Attach Image', +) + no_value_fields = ( 'Section Break', 'Column Break', From f33be7592f21de80c55a48824c7e94885a3198cb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 1 Dec 2021 18:59:03 +0530 Subject: [PATCH 03/11] refactor: common JS controller for DocType and customize form --- frappe/core/doctype/doctype/doctype.js | 28 ++----------------- .../doctype/customize_form/customize_form.js | 1 + frappe/public/js/form.bundle.js | 2 +- frappe/public/js/frappe/doctype/index.js | 23 +++++++++++++++ 4 files changed, 27 insertions(+), 27 deletions(-) create mode 100644 frappe/public/js/frappe/doctype/index.js diff --git a/frappe/core/doctype/doctype/doctype.js b/frappe/core/doctype/doctype/doctype.js index 291fb237a9..1c52070063 100644 --- a/frappe/core/doctype/doctype/doctype.js +++ b/frappe/core/doctype/doctype/doctype.js @@ -1,16 +1,6 @@ // Copyright (c) 2015, Frappe Technologies Pvt. Ltd. and Contributors // MIT License. See license.txt -// ------------- -// Menu Display -// ------------- - -// $(cur_frm.wrapper).on("grid-row-render", function(e, grid_row) { -// if(grid_row.doc && grid_row.doc.fieldtype=="Section Break") { -// $(grid_row.row).css({"font-weight": "bold"}); -// } -// }) - frappe.ui.form.on('DocType', { refresh: function(frm) { frm.set_query('role', 'permissions', function(doc) { @@ -130,22 +120,6 @@ frappe.ui.form.on('DocType', { frm.set_df_property('fields', 'reqd', frm.doc.autoname !== 'Prompt'); }, - - max_attachments: function(frm) { - if (!frm.doc.max_attachments) { - return; - } - const is_attach_field = (f) => ["Attach", "Attach Image"].includes(f.fieldtype); - const no_of_attach_fields = frm.doc.fields.filter(is_attach_field).length; - - if (no_of_attach_fields > frm.doc.max_attachments) { - frm.set_value("max_attachments", no_of_attach_fields); - const label = frm.get_docfield("max_attachments").label; - frappe.show_alert( - __("Number of attachment fields are more than {}, limit updated to {}.", [label, no_of_attach_fields])); - } - }, - }); frappe.ui.form.on("DocField", { @@ -239,3 +213,5 @@ frappe.ui.form.on("DocField", { frm.trigger("max_attachments"); } }); + +extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({frm: cur_frm})); diff --git a/frappe/custom/doctype/customize_form/customize_form.js b/frappe/custom/doctype/customize_form/customize_form.js index 4e00456f0d..8ca6e0e54e 100644 --- a/frappe/custom/doctype/customize_form/customize_form.js +++ b/frappe/custom/doctype/customize_form/customize_form.js @@ -332,3 +332,4 @@ frappe.customize_form.clear_locals_and_refresh = function(frm) { frm.refresh(); } +extend_cscript(cur_frm.cscript, new frappe.model.DocTypeController({frm: cur_frm})); diff --git a/frappe/public/js/form.bundle.js b/frappe/public/js/form.bundle.js index 5bed5c2cb8..2719535599 100644 --- a/frappe/public/js/form.bundle.js +++ b/frappe/public/js/form.bundle.js @@ -14,4 +14,4 @@ import "./frappe/form/controls/control.js"; import "./frappe/views/formview.js"; import "./frappe/form/form.js"; import "./frappe/meta_tag.js"; - +import "./frappe/doctype/" diff --git a/frappe/public/js/frappe/doctype/index.js b/frappe/public/js/frappe/doctype/index.js new file mode 100644 index 0000000000..9fe8957c60 --- /dev/null +++ b/frappe/public/js/frappe/doctype/index.js @@ -0,0 +1,23 @@ +frappe.provide("frappe.model"); + +/* + Common class for handling client side interactions that + apply to both DocType form and customize form. +*/ +frappe.model.DocTypeController = class DocTypeController extends frappe.ui.form.Controller { + + max_attachments() { + if (!this.frm.doc.max_attachments) { + return; + } + const is_attach_field = (f) => ["Attach", "Attach Image"].includes(f.fieldtype); + const no_of_attach_fields = this.frm.doc.fields.filter(is_attach_field).length; + + if (no_of_attach_fields > this.frm.doc.max_attachments) { + this.frm.set_value("max_attachments", no_of_attach_fields); + const label = this.frm.get_docfield("max_attachments").label; + frappe.show_alert( + __("Number of attachment fields are more than {}, limit updated to {}.", [label, no_of_attach_fields])); + } + } +} From 2fe2db5f944157c5cc7fe163b9859e04ab63669c Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 2 Dec 2021 11:17:31 +0530 Subject: [PATCH 04/11] fix: form tour field in onboarding step --- .../desk/doctype/onboarding_step/onboarding_step.js | 11 +++++++++++ .../desk/doctype/onboarding_step/onboarding_step.json | 11 ++++++++++- frappe/public/js/frappe/widgets/onboarding_widget.js | 6 ++++-- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.js b/frappe/desk/doctype/onboarding_step/onboarding_step.js index 793e044d98..3c9bbab9ac 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.js +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.js @@ -2,6 +2,17 @@ // For license information, please see license.txt frappe.ui.form.on("Onboarding Step", { + + setup: function(frm) { + frm.set_query("form_tour", function() { + return { + filters: { + reference_doctype: frm.doc.reference_document + } + }; + }); + }, + refresh: function(frm) { frappe.boot.developer_mode && frm.set_intro( diff --git a/frappe/desk/doctype/onboarding_step/onboarding_step.json b/frappe/desk/doctype/onboarding_step/onboarding_step.json index f71e821f65..b5d7851eca 100644 --- a/frappe/desk/doctype/onboarding_step/onboarding_step.json +++ b/frappe/desk/doctype/onboarding_step/onboarding_step.json @@ -20,6 +20,7 @@ "reference_document", "show_full_form", "show_form_tour", + "form_tour", "is_single", "reference_report", "report_reference_doctype", @@ -206,13 +207,21 @@ "fieldname": "show_form_tour", "fieldtype": "Check", "label": "Show Form Tour" + }, + { + "depends_on": "show_form_tour", + "fieldname": "form_tour", + "fieldtype": "Link", + "label": "Form Tour", + "options": "Form Tour" } ], "links": [], - "modified": "2020-10-30 14:54:06.646513", + "modified": "2021-12-02 10:56:04.448580", "modified_by": "Administrator", "module": "Desk", "name": "Onboarding Step", + "naming_rule": "Set by user", "owner": "Administrator", "permissions": [ { diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index 110d617f73..0c06bd3203 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -234,8 +234,9 @@ export default class OnboardingWidget extends Widget { }, }); }; + const tour_name = step.form_tour; frm.tour - .init({ on_finish }) + .init({ tour_name, on_finish }) .then(() => frm.tour.start()); }; @@ -328,8 +329,9 @@ export default class OnboardingWidget extends Widget { this.mark_complete(step); }; }; + const tour_name = step.form_tour frm.tour - .init({ on_finish }) + .init({ tour_name, on_finish }) .then(() => frm.tour.start()); }; From a9b433e81944d626c6a4b18b84a9a619bcf19a20 Mon Sep 17 00:00:00 2001 From: Jannat Patel Date: Thu, 2 Dec 2021 11:42:49 +0530 Subject: [PATCH 05/11] fix: semicolon --- frappe/public/js/frappe/widgets/onboarding_widget.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/widgets/onboarding_widget.js b/frappe/public/js/frappe/widgets/onboarding_widget.js index 0c06bd3203..7d379d4531 100644 --- a/frappe/public/js/frappe/widgets/onboarding_widget.js +++ b/frappe/public/js/frappe/widgets/onboarding_widget.js @@ -329,7 +329,7 @@ export default class OnboardingWidget extends Widget { this.mark_complete(step); }; }; - const tour_name = step.form_tour + const tour_name = step.form_tour; frm.tour .init({ tour_name, on_finish }) .then(() => frm.tour.start()); From 51c4738a04ae5e2aa7bc23bd0fb685f691174cb7 Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Thu, 2 Dec 2021 18:01:43 +0530 Subject: [PATCH 06/11] fix(newsletter): use md_to_html instead of markdown because valid html is valid markdown and markdown method doesn't convert markdown if it encounters some html tags --- frappe/email/doctype/newsletter/newsletter.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/frappe/email/doctype/newsletter/newsletter.py b/frappe/email/doctype/newsletter/newsletter.py index 7c0e2dfe87..12fe160c9d 100644 --- a/frappe/email/doctype/newsletter/newsletter.py +++ b/frappe/email/doctype/newsletter/newsletter.py @@ -186,12 +186,13 @@ class Newsletter(WebsiteGenerator): frappe.db.auto_commit_on_many_writes = is_auto_commit_set def get_message(self) -> str: - if self.content_type == "HTML": - return frappe.render_template(self.message_html, {"doc": self.as_dict()}) + message = self.message if self.content_type == "Markdown": - return frappe.utils.markdown(self.message_md) - # fallback to Rich Text - return self.message + message = frappe.utils.md_to_html(self.message_md) + if self.content_type == "HTML": + message = self.message_html + + return frappe.render_template(message, {"doc": self.as_dict()}) def get_recipients(self) -> List[str]: """Get recipients from Email Group""" From 8a84ae4f0812d2dc5700b7e7011fa96141be204f Mon Sep 17 00:00:00 2001 From: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> Date: Fri, 3 Dec 2021 13:57:57 +0530 Subject: [PATCH 07/11] ci: Use node version 14 to avoid node-sass failure in patch testing build (#15176) --- .github/workflows/patch-mariadb-tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/patch-mariadb-tests.yml b/.github/workflows/patch-mariadb-tests.yml index 52fa987994..c8294886a0 100644 --- a/.github/workflows/patch-mariadb-tests.yml +++ b/.github/workflows/patch-mariadb-tests.yml @@ -32,6 +32,12 @@ jobs: with: python-version: '3.9' + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: 14 + check-latest: true + - name: Check if build should be run id: check-build run: | From e862ae83da1e19abea889fcfe5e6366975201547 Mon Sep 17 00:00:00 2001 From: Aradhya Date: Thu, 2 Dec 2021 21:07:06 +0530 Subject: [PATCH 08/11] fix: fixed list of Field objects as fields in get_values tests: added test for list of field objects --- frappe/database/database.py | 4 ++-- frappe/tests/test_db.py | 28 ++++++++++++++++++++++++---- 2 files changed, 26 insertions(+), 6 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index f489cea7de..64f09c1835 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -583,7 +583,7 @@ class Database(object): if not isinstance(fields, Criterion): for field in fields: - if "(" in field or " as " in field: + if "(" in str(field) or " as " in str(field): field_objects.append(PseudoColumn(field)) else: field_objects.append(field) @@ -842,7 +842,7 @@ class Database(object): cache_count = frappe.cache().get_value('doctype:count:{}'.format(dt)) if cache_count is not None: return cache_count - query = self.query.build_conditions(table=dt, filters=filters).select(Count("*")) + query = self.query.get_sql(table=dt, filters=filters, fields=Count("*")) if filters: count = self.sql(query, debug=debug)[0][0] return count diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 60c8db6ab6..6501d753ff 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -24,10 +24,30 @@ class TestDB(unittest.TestCase): self.assertNotEqual(frappe.db.get_value("User", {"name": ["!=", "Guest"]}), "Guest") self.assertEqual(frappe.db.get_value("User", {"name": ["<", "Adn"]}), "Administrator") self.assertEqual(frappe.db.get_value("User", {"name": ["<=", "Administrator"]}), "Administrator") - self.assertEqual(frappe.db.get_value("User", {}, ["Max(name)"], order_by=None), frappe.db.sql("SELECT Max(name) FROM tabUser")[0][0]) - self.assertEqual(frappe.db.get_value("User", {}, "Min(name)", order_by=None), frappe.db.sql("SELECT Min(name) FROM tabUser")[0][0]) - self.assertIn("for update", frappe.db.get_value("User", Field("name") == "Administrator", for_update=True, run=False).lower()) - + self.assertEqual( + frappe.db.get_value("User", {}, ["Max(name)"], order_by=None), + frappe.db.sql("SELECT Max(name) FROM tabUser")[0][0], + ) + self.assertEqual( + frappe.db.get_value("User", {}, "Min(name)", order_by=None), + frappe.db.sql("SELECT Min(name) FROM tabUser")[0][0], + ) + self.assertIn( + "for update", + frappe.db.get_value( + "User", Field("name") == "Administrator", for_update=True, run=False + ).lower(), + ) + doctype = frappe.qb.DocType("User") + self.assertEqual( + frappe.qb.from_(doctype).select(doctype.name, doctype.email).run(), + frappe.db.get_values( + doctype, + filters={}, + fieldname=[doctype.name, doctype.email], + order_by=None, + ), + ) self.assertEqual(frappe.db.sql("""SELECT name FROM `tabUser` WHERE name > 's' ORDER BY MODIFIED DESC""")[0][0], frappe.db.get_value("User", {"name": [">", "s"]})) From 6f7d030e82ae3b2664f56a6fd9989cfbfc1f1648 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Fri, 3 Dec 2021 16:06:23 +0530 Subject: [PATCH 09/11] fix: IndexError while handling sql timeout error --- frappe/database/database.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index f489cea7de..ea56acff27 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -171,10 +171,10 @@ class Database(object): frappe.errprint(query) elif self.is_deadlocked(e): - raise frappe.QueryDeadlockError + raise frappe.QueryDeadlockError(e) elif self.is_timedout(e): - raise frappe.QueryTimeoutError + raise frappe.QueryTimeoutError(e) if ignore_ddl and (self.is_missing_column(e) or self.is_missing_table(e) or self.cant_drop_field_or_key(e)): pass From db951d2369d8ca3a13f9f5667706b4a002185983 Mon Sep 17 00:00:00 2001 From: Summayya Hashmani <58825865+sumaiya2908@users.noreply.github.com> Date: Fri, 3 Dec 2021 17:22:07 +0530 Subject: [PATCH 10/11] frefactor: padd separate padding (#15178) Co-authored-by: Summayya Co-authored-by: Suraj Shetty <13928957+surajshetty3416@users.noreply.github.com> --- frappe/public/scss/website/index.scss | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frappe/public/scss/website/index.scss b/frappe/public/scss/website/index.scss index 9c84e99a5a..58f5ca79c6 100644 --- a/frappe/public/scss/website/index.scss +++ b/frappe/public/scss/website/index.scss @@ -266,7 +266,8 @@ h5.modal-title { .login-content.container { background-color: var(--fg-color); - padding: 45px 0px; + padding-bottom: 45px; + padding-top: 45px; box-shadow: var(--shadow-base); border-radius: var(--border-radius-md); max-width: 400px; From c5df17e3561c083a68f477ffe56403747aaf4d9c Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Fri, 3 Dec 2021 15:34:41 +0100 Subject: [PATCH 11/11] fix: cannot uninstall app with virtual doctype (#15136) * Update installer.py * fix: Drop table only if it exists * revert: "Update installer.py" This reverts commit 0e8370ede8a9c2b1c0687e5c216ecf67566da0f5. Co-authored-by: Suraj Shetty --- frappe/installer.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/installer.py b/frappe/installer.py index 9eed44ea15..cd6526c788 100755 --- a/frappe/installer.py +++ b/frappe/installer.py @@ -324,7 +324,7 @@ def _delete_doctypes(doctypes: List[str], dry_run: bool) -> None: print(f"* dropping Table for '{doctype}'...") if not dry_run: frappe.delete_doc("DocType", doctype, ignore_on_trash=True) - frappe.db.sql_ddl(f"drop table `tab{doctype}`") + frappe.db.sql_ddl(f"DROP TABLE IF EXISTS `tab{doctype}`") def post_install(rebuild_website=False):