From 3f0b03c80893f037c7e06b0bef29ad206fe3731a Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 26 May 2023 13:23:21 +0530 Subject: [PATCH 01/11] feat: Round QB function --- frappe/query_builder/functions.py | 5 +++++ frappe/tests/test_query_builder.py | 19 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/frappe/query_builder/functions.py b/frappe/query_builder/functions.py index 512df8835c..054e33c31d 100644 --- a/frappe/query_builder/functions.py +++ b/frappe/query_builder/functions.py @@ -41,6 +41,11 @@ class Timestamp(Function): super().__init__("TIMESTAMP", term, alias=alias) +class Round(Function): + def __init__(self, term, decimal=0, **kwargs): + super().__init__("ROUND", term, decimal, **kwargs) + + GroupConcat = ImportMapper({db_type_is.MARIADB: GROUP_CONCAT, db_type_is.POSTGRES: STRING_AGG}) Match = ImportMapper({db_type_is.MARIADB: MATCH, db_type_is.POSTGRES: TO_TSVECTOR}) diff --git a/frappe/tests/test_query_builder.py b/frappe/tests/test_query_builder.py index a16c2a23ae..6d6937038a 100644 --- a/frappe/tests/test_query_builder.py +++ b/frappe/tests/test_query_builder.py @@ -13,6 +13,7 @@ from frappe.query_builder.functions import ( Date, GroupConcat, Match, + Round, UnixTimestamp, ) from frappe.query_builder.utils import db_type_is @@ -153,6 +154,15 @@ class TestCustomFunctionsMariaDB(FrappeTestCase): "SELECT `tabred`.`other`,CONCAT(`tabNote`.`name`,'') FROM `tabred`,`tabNote`", ) + def test_round(self): + note = frappe.qb.DocType("Note") + + query = frappe.qb.from_(note).select(Round(note.price)) + self.assertEqual("select round(`price`,0) from `tabnote`", str(query).lower()) + + query = frappe.qb.from_(note).select(Round(note.price, 3)) + self.assertEqual("select round(`price`,3) from `tabnote`", str(query).lower()) + @run_only_if(db_type_is.POSTGRES) class TestCustomFunctionsPostgres(FrappeTestCase): @@ -283,6 +293,15 @@ class TestCustomFunctionsPostgres(FrappeTestCase): 'SELECT "tabred"."other",CAST("tabNote"."name" AS VARCHAR) FROM "tabred","tabNote"', ) + def test_round(self): + note = frappe.qb.DocType("Note") + + query = frappe.qb.from_(note).select(Round(note.price)) + self.assertEqual('select round("price",0) from "tabnote"', str(query).lower()) + + query = frappe.qb.from_(note).select(Round(note.price, 3)) + self.assertEqual('select round("price",3) from "tabnote"', str(query).lower()) + class TestBuilderBase: def test_adding_tabs(self): From a43ad15eab731863548f201292fc73b8524584a3 Mon Sep 17 00:00:00 2001 From: Sagar Sharma Date: Fri, 26 May 2023 13:23:47 +0530 Subject: [PATCH 02/11] feat: Truncate QB function --- frappe/query_builder/functions.py | 5 +++++ frappe/tests/test_query_builder.py | 11 +++++++++++ 2 files changed, 16 insertions(+) diff --git a/frappe/query_builder/functions.py b/frappe/query_builder/functions.py index 054e33c31d..aa25fa1215 100644 --- a/frappe/query_builder/functions.py +++ b/frappe/query_builder/functions.py @@ -46,6 +46,11 @@ class Round(Function): super().__init__("ROUND", term, decimal, **kwargs) +class Truncate(Function): + def __init__(self, term, decimal, **kwargs): + super().__init__("TRUNCATE", term, decimal, **kwargs) + + GroupConcat = ImportMapper({db_type_is.MARIADB: GROUP_CONCAT, db_type_is.POSTGRES: STRING_AGG}) Match = ImportMapper({db_type_is.MARIADB: MATCH, db_type_is.POSTGRES: TO_TSVECTOR}) diff --git a/frappe/tests/test_query_builder.py b/frappe/tests/test_query_builder.py index 6d6937038a..e3ca63abf1 100644 --- a/frappe/tests/test_query_builder.py +++ b/frappe/tests/test_query_builder.py @@ -14,6 +14,7 @@ from frappe.query_builder.functions import ( GroupConcat, Match, Round, + Truncate, UnixTimestamp, ) from frappe.query_builder.utils import db_type_is @@ -163,6 +164,11 @@ class TestCustomFunctionsMariaDB(FrappeTestCase): query = frappe.qb.from_(note).select(Round(note.price, 3)) self.assertEqual("select round(`price`,3) from `tabnote`", str(query).lower()) + def test_truncate(self): + note = frappe.qb.DocType("Note") + query = frappe.qb.from_(note).select(Truncate(note.price, 3)) + self.assertEqual("select truncate(`price`,3) from `tabnote`", str(query).lower()) + @run_only_if(db_type_is.POSTGRES) class TestCustomFunctionsPostgres(FrappeTestCase): @@ -302,6 +308,11 @@ class TestCustomFunctionsPostgres(FrappeTestCase): query = frappe.qb.from_(note).select(Round(note.price, 3)) self.assertEqual('select round("price",3) from "tabnote"', str(query).lower()) + def test_truncate(self): + note = frappe.qb.DocType("Note") + query = frappe.qb.from_(note).select(Truncate(note.price, 3)) + self.assertEqual('select truncate("price",3) from "tabnote"', str(query).lower()) + class TestBuilderBase: def test_adding_tabs(self): From 43714825b0eb3dbac86845bfec3a74d2e04f416d Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Fri, 26 May 2023 15:58:26 +0530 Subject: [PATCH 03/11] fix: sync importable doctype before documents (#21131) --- frappe/model/sync.py | 51 ++++++++++++++++++++++++-------------------- 1 file changed, 28 insertions(+), 23 deletions(-) diff --git a/frappe/model/sync.py b/frappe/model/sync.py index 6272c9cb7d..0b344b892a 100644 --- a/frappe/model/sync.py +++ b/frappe/model/sync.py @@ -11,6 +11,28 @@ from frappe.modules.import_file import import_file_by_path from frappe.modules.patch_handler import _patch_mode from frappe.utils import update_progress_bar +IMPORTABLE_DOCTYPES = [ + ("core", "doctype"), + ("core", "page"), + ("core", "report"), + ("desk", "dashboard_chart_source"), + ("printing", "print_format"), + ("website", "web_page"), + ("website", "website_theme"), + ("website", "web_form"), + ("website", "web_template"), + ("email", "notification"), + ("printing", "print_style"), + ("desk", "workspace"), + ("desk", "onboarding_step"), + ("desk", "module_onboarding"), + ("desk", "form_tour"), + ("custom", "client_script"), + ("core", "server_script"), + ("custom", "custom_field"), + ("custom", "property_setter"), +] + def sync_all(force=0, reset_permissions=False): _patch_mode(True) @@ -71,6 +93,11 @@ def sync_for(app_name, force=0, reset_permissions=False): ]: files.append(os.path.join(FRAPPE_PATH, "desk", "doctype", desk_module, f"{desk_module}.json")) + for module_name, document_type in IMPORTABLE_DOCTYPES: + file = os.path.join(FRAPPE_PATH, module_name, "doctype", document_type, f"{document_type}.json") + if file not in files: + files.append(file) + for module_name in frappe.local.app_modules.get(app_name) or []: folder = os.path.dirname(frappe.get_module(app_name + "." + module_name).__file__) files = get_doc_files(files=files, start_path=folder) @@ -97,29 +124,7 @@ def get_doc_files(files, start_path): files = files or [] - # load in sequence - warning for devs - document_types = [ - "doctype", - "page", - "report", - "dashboard_chart_source", - "print_format", - "web_page", - "website_theme", - "web_form", - "web_template", - "notification", - "print_style", - "workspace", - "onboarding_step", - "module_onboarding", - "form_tour", - "client_script", - "server_script", - "custom_field", - "property_setter", - ] - for doctype in document_types: + for _module, doctype in IMPORTABLE_DOCTYPES: doctype_path = os.path.join(start_path, doctype) if os.path.exists(doctype_path): for docname in os.listdir(doctype_path): From d099c9376b86c000535fb5b3434663352276c741 Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Fri, 26 May 2023 16:03:28 +0530 Subject: [PATCH 04/11] feat(minor): db.get_column_type for postgres (#21125) --- frappe/database/database.py | 15 --------------- frappe/database/mariadb/database.py | 15 +++++++++++++++ frappe/database/postgres/database.py | 15 +++++++++++++++ 3 files changed, 30 insertions(+), 15 deletions(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index 2d38a6dea8..59c514991a 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -1119,21 +1119,6 @@ class Database: """Returns True if column exists in database.""" return column in self.get_table_columns(doctype) - def get_column_type(self, doctype, column): - """Returns column type from database.""" - information_schema = frappe.qb.Schema("information_schema") - table = get_table_name(doctype) - - return ( - frappe.qb.from_(information_schema.columns) - .select(information_schema.columns.column_type) - .where( - (information_schema.columns.table_name == table) - & (information_schema.columns.column_name == column) - ) - .run(pluck=True)[0] - ) - def has_index(self, table_name, index_name): raise NotImplementedError diff --git a/frappe/database/mariadb/database.py b/frappe/database/mariadb/database.py index 43540956e0..8e52cc7ffd 100644 --- a/frappe/database/mariadb/database.py +++ b/frappe/database/mariadb/database.py @@ -318,6 +318,21 @@ class MariaDBDatabase(MariaDBConnectionUtil, MariaDBExceptionUtil, Database): as_dict=1, ) + def get_column_type(self, doctype, column): + """Returns column type from database.""" + information_schema = frappe.qb.Schema("information_schema") + table = get_table_name(doctype) + + return ( + frappe.qb.from_(information_schema.columns) + .select(information_schema.columns.column_type) + .where( + (information_schema.columns.table_name == table) + & (information_schema.columns.column_name == column) + ) + .run(pluck=True)[0] + ) + def has_index(self, table_name, index_name): return self.sql( """SHOW INDEX FROM `{table_name}` diff --git a/frappe/database/postgres/database.py b/frappe/database/postgres/database.py index d082afceaf..836a689251 100644 --- a/frappe/database/postgres/database.py +++ b/frappe/database/postgres/database.py @@ -394,6 +394,21 @@ class PostgresDatabase(PostgresExceptionUtil, Database): as_dict=1, ) + def get_column_type(self, doctype, column): + """Returns column type from database.""" + information_schema = frappe.qb.Schema("information_schema") + table = get_table_name(doctype) + + return ( + frappe.qb.from_(information_schema.columns) + .select(information_schema.columns.data_type) + .where( + (information_schema.columns.table_name == table) + & (information_schema.columns.column_name == column) + ) + .run(pluck=True)[0] + ) + def get_database_list(self): return self.sql("SELECT datname FROM pg_database", pluck=True) From 4b2730642d012b48e13ed21d9f5af759605e61cf Mon Sep 17 00:00:00 2001 From: Doridel Cahanap Mendez Date: Fri, 12 May 2023 05:26:19 +0000 Subject: [PATCH 05/11] refactor: attach txt file in received emails (cherry picked from commit a36d6a9df02e9a86e3701fcc6861c9c64bd40c66) --- frappe/email/receive.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 382fd2ac99..525703c8a2 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -459,6 +459,10 @@ class Email: if content_type == "text/plain": self.text_content += self.get_payload(part) + # attach txt file from received email as well aside from saving to text_content if it has filename + if part.get_filename(): + self.get_attachment(part) + elif content_type == "text/html": self.html_content += self.get_payload(part) From 28f6ef74a6e7075451bd91c9856fb57851c23a5e Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 26 May 2023 18:12:30 +0530 Subject: [PATCH 06/11] feat: select group button --- frappe/public/js/frappe/utils/utils.js | 60 ++++++++++++++++++++++++++ frappe/public/scss/desk/global.scss | 14 ++++++ 2 files changed, 74 insertions(+) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index ac9a18785b..7c05ccc535 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1440,6 +1440,66 @@ Object.assign(frappe.utils, { prepend && wrapper.prepend(button); }, + add_select_group_button(actions, btn_type, wrapper, prepend) { + // actions = [{ + // label: "Action 1", + // description: "Description 1", (optional) + // action: () => {}, + // }, + // { + // label: "Action 2", + // description: "Description 2", (optional) + // action: () => {}, + // }] + let selected_action = actions[0]; + + let $select_group_button = $(` +
+ + + + + +
+ `); + + actions.forEach((action) => { + $(`
  • + +
    ${frappe.utils.icon("check", "xs")}
    +
    +
    ${action.label}
    +
    ${action.description || ""}
    +
    +
    +
  • `) + .appendTo($select_group_button.find(".dropdown-menu")) + .click((e) => { + selected_action = action; + $select_group_button.find(".selected-button").text(action.label); + $select_group_button.find(".tick-icon").addClass("selected"); + + $(e.currentTarget).siblings().find(".tick-icon").removeClass("selected"); + }); + }); + + $select_group_button.find(".dropdown-menu li:first-child .tick-icon").addClass("selected"); + + $select_group_button.find(".selected-button").click((event) => { + event.stopPropagation(); + selected_action.action && selected_action.action(event); + }); + + !prepend && $select_group_button.appendTo(wrapper); + prepend && wrapper.prepend($select_group_button); + + return $select_group_button; + }, + sleep(time) { return new Promise((resolve) => setTimeout(resolve, time)); }, diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 765e51cab9..80e4e3c9bc 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -249,6 +249,20 @@ h2 { } } +.select-group-btn { + .dropdown-toggle-split::after { + display: none; + } + + .dropdown-item .tick-icon { + visibility: hidden; + + &.selected { + visibility: visible; + } + } +} + .btn-xs { @extend .btn-sm; line-height: 1.2; From b1b05ee05b3bec5e9c8664d1a7e2b991e611b290 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 26 May 2023 18:18:52 +0530 Subject: [PATCH 07/11] fix: clear & add email template using select group btn --- .../public/js/frappe/views/communication.js | 40 ++++++++++++++++--- frappe/public/scss/common/modal.scss | 18 +++++++++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 3505199d3f..21a42988eb 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -77,12 +77,22 @@ frappe.views.CommunicationComposer = class { fieldtype: "MultiSelect", fieldname: "bcc", }, + { + fieldtype: "Section Break", + fieldname: "email_template_section_break", + hidden: 1, + }, { label: __("Email Template"), fieldtype: "Link", options: "Email Template", fieldname: "email_template", }, + { + fieldtype: "HTML", + label: __("Clear & Add template"), + fieldname: "clear_and_add_template", + }, { fieldtype: "Section Break" }, { label: __("Subject"), @@ -170,6 +180,7 @@ frappe.views.CommunicationComposer = class { toggle_more_options(show_options) { show_options = show_options || this.dialog.fields_dict.more_options.df.hidden; this.dialog.set_df_property("more_options", "hidden", !show_options); + this.dialog.set_df_property("email_template_section_break", "hidden", !show_options); const label = frappe.utils.icon(show_options ? "up-line" : "down"); this.dialog.get_field("option_toggle_button").set_label(label); @@ -266,13 +277,14 @@ frappe.views.CommunicationComposer = class { setup_email_template() { const me = this; - this.dialog.fields_dict["email_template"].df.onchange = () => { + const fields = this.dialog.fields_dict; + const clear_and_add_template = $(fields.clear_and_add_template.wrapper); + + function add_template() { const email_template = me.dialog.fields_dict.email_template.get_value(); if (!email_template) return; function prepend_reply(reply) { - if (me.reply_added === email_template) return; - const content_field = me.dialog.fields_dict.content; const subject_field = me.dialog.fields_dict.subject; @@ -280,8 +292,6 @@ frappe.views.CommunicationComposer = class { content_field.set_value(`${reply.message}
    ${content}`); subject_field.set_value(reply.subject); - - me.reply_added = email_template; } frappe.call({ @@ -294,7 +304,25 @@ frappe.views.CommunicationComposer = class { prepend_reply(r.message); }, }); - }; + } + + let email_template_actions = [ + { + label: __("Add Template"), + description: __("Prepend the template to the email message"), + action: () => add_template(), + }, + { + label: __("Clear & Add Template"), + description: __("Clear the email message and add the template"), + action: () => { + me.dialog.fields_dict.content.set_value(""); + add_template(); + }, + }, + ]; + + frappe.utils.add_select_group_button(email_template_actions, "", clear_and_add_template); } setup_last_edited_communication() { diff --git a/frappe/public/scss/common/modal.scss b/frappe/public/scss/common/modal.scss index 8e69a956e5..6909967cdb 100644 --- a/frappe/public/scss/common/modal.scss +++ b/frappe/public/scss/common/modal.scss @@ -228,6 +228,24 @@ body.modal-open[style^="padding-right"] { } } +.modal [data-fieldname="email_template_section_break"] { + form { + display: flex; + align-items: center; + + .frappe-control:first-child { + &[data-fieldname="email_template"] { + margin-right: 10px; + } + flex: 1; + } + + .frappe-control:last-child { + margin-bottom: -8px; + } + } +} + // modal is xs (for grids) .modal .hidden-xs { display: none !important; From f06449adaf46e015382506bc0c3c3e561411e4d0 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 26 May 2023 18:20:05 +0530 Subject: [PATCH 08/11] fix: make option toggle button smaller --- frappe/public/js/frappe/views/communication.js | 4 ++-- frappe/public/scss/common/modal.scss | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 21a42988eb..7b861fe161 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -56,7 +56,7 @@ frappe.views.CommunicationComposer = class { }, { fieldtype: "Button", - label: frappe.utils.icon("down"), + label: frappe.utils.icon("down", "xs"), fieldname: "option_toggle_button", click: () => { this.toggle_more_options(); @@ -182,7 +182,7 @@ frappe.views.CommunicationComposer = class { this.dialog.set_df_property("more_options", "hidden", !show_options); this.dialog.set_df_property("email_template_section_break", "hidden", !show_options); - const label = frappe.utils.icon(show_options ? "up-line" : "down"); + const label = frappe.utils.icon(show_options ? "up-line" : "down", "xs"); this.dialog.get_field("option_toggle_button").set_label(label); } diff --git a/frappe/public/scss/common/modal.scss b/frappe/public/scss/common/modal.scss index 6909967cdb..4b7f028c79 100644 --- a/frappe/public/scss/common/modal.scss +++ b/frappe/public/scss/common/modal.scss @@ -222,7 +222,7 @@ body.modal-open[style^="padding-right"] { margin-bottom: -24px; button { // same as form-control input - height: calc(1.5em + .75rem + 2px); + height: calc(1.5em + .7rem); } } } From 61dcd09172f36b37c7a23807e7cb2e8ab332b9d8 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 26 May 2023 18:35:08 +0530 Subject: [PATCH 09/11] fix: add selected on current dropdown-item --- frappe/public/js/frappe/utils/utils.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 7c05ccc535..b613d895d3 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1481,8 +1481,8 @@ Object.assign(frappe.utils, { .click((e) => { selected_action = action; $select_group_button.find(".selected-button").text(action.label); - $select_group_button.find(".tick-icon").addClass("selected"); + $(e.currentTarget).find(".tick-icon").addClass("selected"); $(e.currentTarget).siblings().find(".tick-icon").removeClass("selected"); }); }); From 78f3d9c2419054abfca13a0fdc61405e2619c5e0 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 26 May 2023 19:55:06 +0530 Subject: [PATCH 10/11] fix: make dropdown item label bold --- frappe/public/js/frappe/utils/utils.js | 4 ++-- frappe/public/scss/desk/global.scss | 14 ++++++++++---- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index b613d895d3..90a19fca40 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1472,8 +1472,8 @@ Object.assign(frappe.utils, {
    ${frappe.utils.icon("check", "xs")}
    -
    ${action.label}
    -
    ${action.description || ""}
    +
    ${action.label}
    +
    ${action.description || ""}
    `) diff --git a/frappe/public/scss/desk/global.scss b/frappe/public/scss/desk/global.scss index 80e4e3c9bc..fa27ef99ad 100644 --- a/frappe/public/scss/desk/global.scss +++ b/frappe/public/scss/desk/global.scss @@ -254,11 +254,17 @@ h2 { display: none; } - .dropdown-item .tick-icon { - visibility: hidden; + .dropdown-item { + .tick-icon { + visibility: hidden; - &.selected { - visibility: visible; + &.selected { + visibility: visible; + } + } + + .item-label { + font-weight: 500; } } } From f91bc1bde6a517a2867089dd91af4f5c0fa7efcc Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Fri, 26 May 2023 19:56:12 +0530 Subject: [PATCH 11/11] fix: added left icon option --- frappe/public/js/frappe/utils/utils.js | 7 ++++--- frappe/public/js/frappe/views/communication.js | 2 +- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/utils/utils.js b/frappe/public/js/frappe/utils/utils.js index 90a19fca40..095b04c931 100644 --- a/frappe/public/js/frappe/utils/utils.js +++ b/frappe/public/js/frappe/utils/utils.js @@ -1440,7 +1440,7 @@ Object.assign(frappe.utils, { prepend && wrapper.prepend(button); }, - add_select_group_button(actions, btn_type, wrapper, prepend) { + add_select_group_button(wrapper, actions, btn_type, icon = "", prepend) { // actions = [{ // label: "Action 1", // description: "Description 1", (optional) @@ -1456,7 +1456,8 @@ Object.assign(frappe.utils, { let $select_group_button = $(`