From c1e20c84b4c8a201d7d10224eef48e320787916a Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:47:57 +0100 Subject: [PATCH 01/46] fix: clear all filters when in list view --- frappe/public/js/frappe/ui/filters/filter_list.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 3119ee4b68..69d089ef3a 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -128,7 +128,14 @@ frappe.ui.FilterGroup = class { this.wrapper.find(".clear-filters").on("click", () => { this.toggle_empty_filters(true); - this.clear_filters(); + if (typeof this.base_list !== "undefined") { + // It's a list view. Clear all the filters, also the ones in the + // FilterArea outside this FilterGroup + this.base_list.filter_area.clear(); + } else { + // Not a list view, just clear the filters in this FilterGroup + this.clear_filters(); + } this.on_change(); }); From 8500f61b8a3b387f7f01e94dc4c027c01b12271e Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:51:46 +0100 Subject: [PATCH 02/46] refactor: method hide_popover --- frappe/public/js/frappe/ui/filters/filter_list.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 69d089ef3a..25344b8e57 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -17,6 +17,10 @@ frappe.ui.FilterGroup = class { this.set_popover_events(); } + hide_popover() { + this.filter_button.popover("hide"); + } + init_filter_popover() { this.filter_button.popover({ content: this.get_filter_area_template(), @@ -54,7 +58,7 @@ frappe.ui.FilterGroup = class { !$(e.target).is(this.filter_button) && !in_datepicker ) { - this.wrapper && this.filter_button.popover("hide"); + this.wrapper && this.hide_popover(); } } }); @@ -85,7 +89,7 @@ frappe.ui.FilterGroup = class { // REDESIGN-TODO: (Temporary) Review and find best solution for this frappe.router.on("change", () => { if (this.wrapper && this.wrapper.is(":visible")) { - this.filter_button.popover("hide"); + this.hide_popover(); } }); } @@ -139,9 +143,7 @@ frappe.ui.FilterGroup = class { this.on_change(); }); - this.wrapper.find(".apply-filters").on("click", () => { - this.filter_button.popover("hide"); - }); + this.wrapper.find(".apply-filters").on("click", () => this.hide_popover()); } add_filters(filters) { From eb398097b7defd1fca215d974ddf54344c0cbced Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Tue, 14 Mar 2023 16:54:15 +0100 Subject: [PATCH 03/46] fix: hide popover after clearing filters --- frappe/public/js/frappe/ui/filters/filter_list.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 25344b8e57..1ae26a34fc 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -141,6 +141,7 @@ frappe.ui.FilterGroup = class { this.clear_filters(); } this.on_change(); + this.hide_popover(); }); this.wrapper.find(".apply-filters").on("click", () => this.hide_popover()); From 2cfe3622d4fa34d289c095bafd9f4cd4278fcc23 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:34:11 +0200 Subject: [PATCH 04/46] fix: role perms on Google Contacts --- .../doctype/google_contacts/google_contacts.json | 12 ++++++------ .../doctype/google_contacts/test_google_contacts.py | 9 +++++++++ 2 files changed, 15 insertions(+), 6 deletions(-) create mode 100644 frappe/integrations/doctype/google_contacts/test_google_contacts.py diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.json b/frappe/integrations/doctype/google_contacts/google_contacts.json index 76781fe47f..4a72651e67 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.json +++ b/frappe/integrations/doctype/google_contacts/google_contacts.json @@ -1,4 +1,5 @@ { + "actions": [], "autoname": "format:GC-{email_id}", "creation": "2019-06-14 00:09:39.441961", "doctype": "DocType", @@ -97,10 +98,12 @@ "label": "Push to Google Contacts" } ], - "modified": "2020-09-18 17:26:09.703215", + "links": [], + "modified": "2023-03-30 11:25:48.832384", "modified_by": "Administrator", "module": "Integrations", "name": "Google Contacts", + "naming_rule": "Expression", "owner": "Administrator", "permissions": [ { @@ -116,17 +119,14 @@ { "create": 1, "delete": 1, - "email": 1, - "export": 1, - "print": 1, + "if_owner": 1, "read": 1, - "report": 1, "role": "All", - "share": 1, "write": 1 } ], "sort_field": "modified", "sort_order": "ASC", + "states": [], "track_changes": 1 } \ No newline at end of file diff --git a/frappe/integrations/doctype/google_contacts/test_google_contacts.py b/frappe/integrations/doctype/google_contacts/test_google_contacts.py new file mode 100644 index 0000000000..d7ca08a082 --- /dev/null +++ b/frappe/integrations/doctype/google_contacts/test_google_contacts.py @@ -0,0 +1,9 @@ +# Copyright (c) 2023, Frappe Technologies and Contributors +# See license.txt + +# import frappe +from frappe.tests.utils import FrappeTestCase + + +class TestGoogleContacts(FrappeTestCase): + pass From fcb6fa82339dec1af53b5a812902f2d919b509fb Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:47:52 +0200 Subject: [PATCH 05/46] refactor(Google Contact): authorize_access --- .../doctype/google_contacts/google_contacts.py | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/frappe/integrations/doctype/google_contacts/google_contacts.py b/frappe/integrations/doctype/google_contacts/google_contacts.py index 9a20d5e905..7c845da330 100644 --- a/frappe/integrations/doctype/google_contacts/google_contacts.py +++ b/frappe/integrations/doctype/google_contacts/google_contacts.py @@ -36,10 +36,10 @@ def authorize_access(g_contact, reauthorize=False, code=None): If no Authorization code get it from Google and then request for Refresh Token. Google Contact Name is set to flags to set_value after Authorization Code is obtained. """ + contact = frappe.get_doc("Google Contacts", g_contact) + contact.check_permission("write") - oauth_code = ( - frappe.db.get_value("Google Contacts", g_contact, "authorization_code") if not code else code - ) + oauth_code = code or contact.get_password("authorization_code") oauth_obj = GoogleOAuth("contacts") if not oauth_code or reauthorize: @@ -51,11 +51,9 @@ def authorize_access(g_contact, reauthorize=False, code=None): ) r = oauth_obj.authorize(oauth_code) - frappe.db.set_value( - "Google Contacts", - g_contact, - {"authorization_code": oauth_code, "refresh_token": r.get("refresh_token")}, - ) + contact.authorization_code = oauth_code + contact.refresh_token = r.get("refresh_token") + contact.save() def get_google_contacts_object(g_contact): From 54ec1a764c87deff38a1b7acb30d2312b3766892 Mon Sep 17 00:00:00 2001 From: barredterra <14891507+barredterra@users.noreply.github.com> Date: Thu, 30 Mar 2023 11:55:09 +0200 Subject: [PATCH 06/46] fix(Google Calendar): authorize_access --- frappe/integrations/doctype/google_calendar/google_calendar.py | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/integrations/doctype/google_calendar/google_calendar.py b/frappe/integrations/doctype/google_calendar/google_calendar.py index 65a4a2bccd..d1cdd0d9e7 100644 --- a/frappe/integrations/doctype/google_calendar/google_calendar.py +++ b/frappe/integrations/doctype/google_calendar/google_calendar.py @@ -111,6 +111,7 @@ def authorize_access(g_calendar, reauthorize=None): """ google_settings = frappe.get_doc("Google Settings") google_calendar = frappe.get_doc("Google Calendar", g_calendar) + google_calendar.check_permission("write") redirect_uri = ( get_request_site_address(True) From 1b6b19b38e475ecf306ae65800ce70d580789750 Mon Sep 17 00:00:00 2001 From: vvrithof <98533401+vvrithof@users.noreply.github.com> Date: Tue, 11 Apr 2023 12:23:41 +0200 Subject: [PATCH 07/46] fix: float formatting --- frappe/public/js/frappe/form/controls/float.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/form/controls/float.js b/frappe/public/js/frappe/form/controls/float.js index d6b542804f..0066020a8f 100644 --- a/frappe/public/js/frappe/form/controls/float.js +++ b/frappe/public/js/frappe/form/controls/float.js @@ -6,6 +6,7 @@ frappe.ui.form.ControlFloat = class ControlFloat extends frappe.ui.form.ControlI else { let value = this.get_input_value(); this.parse_validate_and_set_in_model(value, e); + this.refresh(); } }; // convert to number format on focusout since focus converts it to flt. From 7037b408a3624f12343b26d384ba55a38f405d49 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 11 Apr 2023 17:21:06 +0530 Subject: [PATCH 08/46] fix: separate clear all filter button grouped with filter button --- frappe/public/icons/timeless/icons.svg | 10 ++++++-- frappe/public/js/frappe/list/base_list.js | 23 +++++++++++------ .../js/frappe/ui/filters/filter_list.js | 25 +++++++++++++------ frappe/public/scss/desk/filters.scss | 4 +-- frappe/public/scss/desk/list.scss | 5 ++-- 5 files changed, 44 insertions(+), 23 deletions(-) diff --git a/frappe/public/icons/timeless/icons.svg b/frappe/public/icons/timeless/icons.svg index cfaf3ba1d7..b1b5448fee 100644 --- a/frappe/public/icons/timeless/icons.svg +++ b/frappe/public/icons/timeless/icons.svg @@ -237,8 +237,14 @@ - - + + + + + + + + diff --git a/frappe/public/js/frappe/list/base_list.js b/frappe/public/js/frappe/list/base_list.js index 600db57dd1..f581165f0b 100644 --- a/frappe/public/js/frappe/list/base_list.js +++ b/frappe/public/js/frappe/list/base_list.js @@ -830,22 +830,31 @@ class FilterArea { make_filter_list() { $(`
- + + + +
`).appendTo(this.$filter_list_wrapper); this.filter_button = this.$filter_list_wrapper.find(".filter-button"); + this.filter_x_button = this.$filter_list_wrapper.find(".filter-x-button"); this.filter_list = new frappe.ui.FilterGroup({ base_list: this.list_view, parent: this.$filter_list_wrapper, doctype: this.list_view.doctype, filter_button: this.filter_button, + filter_x_button: this.filter_x_button, default_filters: [], on_change: () => this.refresh_list_view(), }); diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 1ae26a34fc..5a8d14e09f 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -14,9 +14,25 @@ frappe.ui.FilterGroup = class { make_popover() { this.init_filter_popover(); + this.set_clear_all_filters_event(); this.set_popover_events(); } + set_clear_all_filters_event() { + this.filter_x_button.on("click", () => { + this.toggle_empty_filters(true); + if (typeof this.base_list !== "undefined") { + // It's a list view. Clear all the filters, also the ones in the + // FilterArea outside this FilterGroup + this.base_list.filter_area.clear(); + } else { + // Not a list view, just clear the filters in this FilterGroup + this.clear_filters(); + } + this.update_filter_button(); + }); + } + hide_popover() { this.filter_button.popover("hide"); } @@ -132,14 +148,7 @@ frappe.ui.FilterGroup = class { this.wrapper.find(".clear-filters").on("click", () => { this.toggle_empty_filters(true); - if (typeof this.base_list !== "undefined") { - // It's a list view. Clear all the filters, also the ones in the - // FilterArea outside this FilterGroup - this.base_list.filter_area.clear(); - } else { - // Not a list view, just clear the filters in this FilterGroup - this.clear_filters(); - } + this.clear_filters(); this.on_change(); this.hide_popover(); }); diff --git a/frappe/public/scss/desk/filters.scss b/frappe/public/scss/desk/filters.scss index ffaea7a9bd..3f197e8278 100644 --- a/frappe/public/scss/desk/filters.scss +++ b/frappe/public/scss/desk/filters.scss @@ -1,7 +1,5 @@ .filter-icon.active { - use { - stroke: var(--text-on-blue); - } + --icon-stroke: var(--text-on-blue); } .filter-popover { diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index 31d1661abb..a9a80a3f06 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -395,9 +395,8 @@ input.list-check-all { padding: 0 var(--padding-xs); } - .filter-button { - margin: 5px; - // padding: 4px 8px; + .filter-selector .btn-group { + margin: var(--margin-xs); } .filter-button.btn-primary-light { From eeedfd0f2c2957f76402aed7b1b52f5ffcd852a1 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Tue, 11 Apr 2023 18:09:26 +0530 Subject: [PATCH 09/46] fix: filter button border when have active filters --- frappe/public/scss/desk/list.scss | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/scss/desk/list.scss b/frappe/public/scss/desk/list.scss index a9a80a3f06..186537f341 100644 --- a/frappe/public/scss/desk/list.scss +++ b/frappe/public/scss/desk/list.scss @@ -401,6 +401,8 @@ input.list-check-all { .filter-button.btn-primary-light { color: var(--text-on-blue); + outline: 1px solid var(--bg-dark-blue); + z-index: 1; } .sort-selector { From 844f25e6461e62c25512caf8077a03cbb708d2a1 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 12 Apr 2023 18:09:07 +0530 Subject: [PATCH 10/46] chore: minor change in the filter icons --- frappe/public/icons/timeless/icons.svg | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/frappe/public/icons/timeless/icons.svg b/frappe/public/icons/timeless/icons.svg index b1b5448fee..aeef96ccfe 100644 --- a/frappe/public/icons/timeless/icons.svg +++ b/frappe/public/icons/timeless/icons.svg @@ -238,13 +238,13 @@
- + - - - + + + From c12406fa3e0507695ce971f3edd64eb0e70e3317 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 17 Apr 2023 14:38:27 +0530 Subject: [PATCH 11/46] test: fixed failing UI test --- cypress/integration/folder_navigation.js | 3 +-- cypress/integration/sidebar.js | 2 +- cypress/support/commands.js | 2 +- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/cypress/integration/folder_navigation.js b/cypress/integration/folder_navigation.js index c5b3a44f0d..1f2f4dfe67 100644 --- a/cypress/integration/folder_navigation.js +++ b/cypress/integration/folder_navigation.js @@ -7,8 +7,7 @@ context("Folder Navigation", () => { it("Adding Folders", () => { //Adding filter to go into the home folder - cy.get(".filter-selector > .btn").findByText("1 filter").click(); - cy.findByRole("button", { name: "Clear Filters" }).click(); + cy.get(".filter-x-button").click(); cy.get(".filter-action-buttons > .text-muted").findByText("+ Add a Filter").click(); cy.get(".fieldname-select-area > .awesomplete > .form-control:last").type("Fol{enter}"); cy.get( diff --git a/cypress/integration/sidebar.js b/cypress/integration/sidebar.js index 0b2a21aa4f..0f97cdc7fe 100644 --- a/cypress/integration/sidebar.js +++ b/cypress/integration/sidebar.js @@ -53,7 +53,7 @@ context("Sidebar", () => { ); //To check if there is no filter added to the listview - cy.get(".filter-selector > .btn").should("contain", "Filter"); + cy.get(".filter-button").should("contain", "Filter"); //To add a filter to display data into the listview cy.get(".group-by-field.show > .dropdown-menu > .group-by-item > .dropdown-item").click(); diff --git a/cypress/support/commands.js b/cypress/support/commands.js index c067974d9f..4b44a24598 100644 --- a/cypress/support/commands.js +++ b/cypress/support/commands.js @@ -479,7 +479,7 @@ Cypress.Commands.add("click_listview_row_item_with_text", (text) => { }); Cypress.Commands.add("click_filter_button", () => { - cy.get(".filter-selector > .btn").click(); + cy.get(".filter-button").click(); }); Cypress.Commands.add("click_listview_primary_button", (btn_name) => { From a2f40290d8f742795e654ad00810b6bd5fc167ae Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 17 Apr 2023 16:52:41 +0530 Subject: [PATCH 12/46] fix: bump poplib limit (#20739) * fix: bump poplib limit * Update receive.py [skip ci] --- frappe/email/receive.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 27b8867f84..a376d8fe53 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -37,7 +37,7 @@ from frappe.utils.html_utils import clean_email_html from frappe.utils.user import is_system_user # fix due to a python bug in poplib that limits it to 2048 -poplib._MAXLINE = 20480 +poplib._MAXLINE = 1_00_000 THREAD_ID_PATTERN = re.compile(r"(?<=\[)[\w/-]+") WORDS_PATTERN = re.compile(r"\w+") From ee97800f8c157170b33deacf2a5e0cc0d7ca8ee7 Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 17 Apr 2023 17:31:55 +0530 Subject: [PATCH 13/46] fix: Load doc before checking permission --- frappe/public/js/frappe/ui/toolbar/toolbar.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/ui/toolbar/toolbar.js b/frappe/public/js/frappe/ui/toolbar/toolbar.js index bef0c19b4e..8e8db5b211 100644 --- a/frappe/public/js/frappe/ui/toolbar/toolbar.js +++ b/frappe/public/js/frappe/ui/toolbar/toolbar.js @@ -137,11 +137,13 @@ frappe.ui.toolbar.Toolbar = class { __("Generate Tracking URL") ); - if (frappe.perm.has_perm("RQ Job")) { - frappe.search.utils.make_function_searchable(function () { - frappe.set_route("List", "RQ Job"); - }, __("Background Jobs")); - } + frappe.model.with_doctype("RQ Job").then(() => { + if (frappe.perm.has_perm("RQ Job", 0, "read")) { + frappe.search.utils.make_function_searchable(function () { + frappe.set_route("List", "RQ Job"); + }, __("Background Jobs")); + } + }); } } From 1bae6a2b0b07494bfaecce6b9e9ec5fd0871992b Mon Sep 17 00:00:00 2001 From: Maharshi Patel <39730881+maharshivpatel@users.noreply.github.com> Date: Mon, 17 Apr 2023 19:03:53 +0530 Subject: [PATCH 14/46] feat: hooks added for print formats / pdf. (#20734) --- frappe/hooks.py | 5 +++++ frappe/utils/pdf.py | 45 +++++++++++++++++++++++++++++++---------- frappe/www/printview.py | 6 ++++-- 3 files changed, 43 insertions(+), 13 deletions(-) diff --git a/frappe/hooks.py b/frappe/hooks.py index b055f9dc8e..a3355d5455 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -83,6 +83,11 @@ on_logout = ( "frappe.core.doctype.session_default_settings.session_default_settings.clear_session_defaults" ) +# PDF +pdf_header_html = "frappe.utils.pdf.pdf_header_html" +pdf_body_html = "frappe.utils.pdf.pdf_body_html" +pdf_footer_html = "frappe.utils.pdf.pdf_footer_html" + # permissions permission_query_conditions = { diff --git a/frappe/utils/pdf.py b/frappe/utils/pdf.py index 678671bce2..0c273854f7 100644 --- a/frappe/utils/pdf.py +++ b/frappe/utils/pdf.py @@ -23,6 +23,31 @@ PDF_CONTENT_ERRORS = [ ] +def pdf_header_html(soup, head, content, styles, html_id, css): + return frappe.render_template( + "templates/print_formats/pdf_header_footer.html", + { + "head": head, + "content": content, + "styles": styles, + "html_id": html_id, + "css": css, + "lang": frappe.local.lang, + "layout_direction": "rtl" if is_rtl() else "ltr", + }, + ) + + +def pdf_body_html(template, args, **kwargs): + return template.render(args, filters={"len": len}) + + +def pdf_footer_html(soup, head, content, styles, html_id, css): + return pdf_header_html( + soup=soup, head=head, content=content, styles=styles, html_id=html_id, css=css + ) + + def get_pdf(html, options=None, output: PdfWriter | None = None): html = scrub_urls(html) html, options = prepare_options(html, options) @@ -196,17 +221,15 @@ def prepare_header_footer(soup): tag.extract() toggle_visible_pdf(content) - html = frappe.render_template( - "templates/print_formats/pdf_header_footer.html", - { - "head": head, - "content": content, - "styles": styles, - "html_id": html_id, - "css": css, - "lang": frappe.local.lang, - "layout_direction": "rtl" if is_rtl() else "ltr", - }, + id_map = {"header-html": "pdf_header_html", "footer-html": "pdf_footer_html"} + hook_func = frappe.get_hooks(id_map.get(html_id)) + html = frappe.get_attr(hook_func[-1])( + soup=soup, + head=head, + content=content, + styles=styles, + html_id=html_id, + css=css, ) # create temp file diff --git a/frappe/www/printview.py b/frappe/www/printview.py index 38a0409e5f..538893d818 100644 --- a/frappe/www/printview.py +++ b/frappe/www/printview.py @@ -208,8 +208,10 @@ def get_rendered_template( "print_settings": print_settings, } ) - - html = template.render(args, filters={"len": len}) + hook_func = frappe.get_hooks("pdf_body_html") + html = frappe.get_attr(hook_func[-1])( + jenv=jenv, template=template, print_format=print_format, args=args + ) if cint(trigger_print): html += trigger_print_script From c083ea3c6219084e7328b3c3587324478a387722 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 17 Apr 2023 19:32:25 +0530 Subject: [PATCH 15/46] fix: route to form if name has slashes in it --- frappe/public/js/frappe/router.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/router.js b/frappe/public/js/frappe/router.js index f6301085d7..c03bfc9b95 100644 --- a/frappe/public/js/frappe/router.js +++ b/frappe/public/js/frappe/router.js @@ -203,7 +203,7 @@ frappe.router = { ? meta.default_view : null ); - } else if (route[1] && route[1] !== "view" && !route[2]) { + } else if (route[1] && route[1] !== "view") { let docname = route[1]; if (route.length > 2) { docname = route.slice(1).join("/"); From 13a7d12452dd918cfbacd2b856f9df81f7e67234 Mon Sep 17 00:00:00 2001 From: Leonard Goertz <49870752+uepselon@users.noreply.github.com> Date: Mon, 17 Apr 2023 18:06:24 +0200 Subject: [PATCH 16/46] fix: check communication subject existence before converting to text (#20738) --- frappe/public/js/frappe/views/communication.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/communication.js b/frappe/public/js/frappe/views/communication.js index 7230de9cf5..3505199d3f 100755 --- a/frappe/public/js/frappe/views/communication.js +++ b/frappe/public/js/frappe/views/communication.js @@ -339,7 +339,7 @@ frappe.views.CommunicationComposer = class { await this.dialog.set_value(fieldname, this[fieldname] || ""); } - const subject = frappe.utils.html2text(this.subject) || ""; + const subject = this.subject ? frappe.utils.html2text(this.subject) : ""; await this.dialog.set_value("subject", subject); await this.set_values_from_last_edited_communication(); From c1af128abae09123ae1761af2f5e38c3bab3aa49 Mon Sep 17 00:00:00 2001 From: Saqib Ansari Date: Mon, 17 Apr 2023 21:40:26 +0530 Subject: [PATCH 17/46] chore: improve insights banner message (#20736) --- frappe/public/js/frappe/list/list_sidebar.js | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/list/list_sidebar.js b/frappe/public/js/frappe/list/list_sidebar.js index fe5f0b810f..fb17b17268 100644 --- a/frappe/public/js/frappe/list/list_sidebar.js +++ b/frappe/public/js/frappe/list/list_sidebar.js @@ -256,19 +256,16 @@ frappe.views.ListSidebar = class ListSidebar { this.insights_banner.remove(); } - const message = "Get more insights from your data with Frappe Insights."; + const message = "Get more insights with"; const link = "https://frappe.io/s/insights"; - const cta = "Get Frappe Insights"; + const cta = "Frappe Insights"; this.insights_banner = $(`
-
- ${message} +
+ ${message} ${cta} →
- -
From af500bb3f59c865a58a46dfbff452fc2e4f6971d Mon Sep 17 00:00:00 2001 From: P-Froggy <60393001+P-Froggy@users.noreply.github.com> Date: Tue, 18 Apr 2023 07:56:18 +0200 Subject: [PATCH 18/46] fix: format filter values in report print view (#20717) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Michael Weißer --- frappe/public/js/frappe/views/reports/query_report.js | 4 ++-- frappe/public/js/frappe/views/reports/report_view.js | 5 ++--- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/views/reports/query_report.js b/frappe/public/js/frappe/views/reports/query_report.js index ee255032bb..9f81f9f6f1 100644 --- a/frappe/public/js/frappe/views/reports/query_report.js +++ b/frappe/public/js/frappe/views/reports/query_report.js @@ -1424,9 +1424,9 @@ frappe.views.QueryReport = class QueryReport extends frappe.views.BaseList { const applied_filters = this.get_filter_values(); return Object.keys(applied_filters) .map((fieldname) => { - const label = frappe.query_report.get_filter(fieldname).df.label; + const docfield = frappe.query_report.get_filter(fieldname).df; const value = applied_filters[fieldname]; - return `
${__(label)}: ${value}
`; + return `
${__(docfield.label)}: ${frappe.format(value, docfield)}
`; }) .join(""); } diff --git a/frappe/public/js/frappe/views/reports/report_view.js b/frappe/public/js/frappe/views/reports/report_view.js index 83411f0ddf..5e76d61c09 100644 --- a/frappe/public/js/frappe/views/reports/report_view.js +++ b/frappe/public/js/frappe/views/reports/report_view.js @@ -1349,9 +1349,8 @@ frappe.views.ReportView = class ReportView extends frappe.views.ListView { .map((f) => { const [doctype, fieldname, condition, value] = f; if (condition !== "=") return ""; - - const label = frappe.meta.get_label(doctype, fieldname); - return `
${__(label)}: ${value}
`; + const docfield = frappe.meta.get_docfield(doctype, fieldname); + return `
${__(docfield.label)}: ${frappe.format(value, docfield)}
`; }) .join(""); } From b483deac3df8613fb155e7ca794e2cfae1d61661 Mon Sep 17 00:00:00 2001 From: phot0n Date: Tue, 18 Apr 2023 13:59:40 +0530 Subject: [PATCH 19/46] refactor(minor): email retreiveing --- frappe/email/receive.py | 158 ++++++++++------------------------------ 1 file changed, 38 insertions(+), 120 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 59d41b543f..80f8d190d6 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -9,6 +9,7 @@ import json import poplib import re import time +from contextlib import suppress from email.header import decode_header import _socket @@ -51,10 +52,6 @@ class EmailTimeoutError(frappe.ValidationError): pass -class TotalSizeExceededError(frappe.ValidationError): - pass - - class LoginLimitExceeded(frappe.ValidationError): pass @@ -67,26 +64,11 @@ class EmailServer: """Wrapper for POP server to pull emails.""" def __init__(self, args=None): - self.setup(args) - - def setup(self, args=None): - # overrride self.settings = args or frappe._dict() - def check_mails(self): - # overrride - return True - - def process_message(self, mail): - # overrride - pass - def connect(self): """Connect to **Email Account**.""" - if cint(self.settings.use_imap): - return self.connect_imap() - else: - return self.connect_pop() + return self.connect_imap() if cint(self.settings.use_imap) else self.connect_pop() def connect_imap(self): """Connect to IMAP""" @@ -150,8 +132,7 @@ class EmailServer: return True except _socket.error: - # log performs rollback and logs error in Error Log - self.log_error("POP: Unable to connect") + frappe.log_error("POP: Unable to connect") # Invalid mail server -- due to refusing connection frappe.msgprint(_("Invalid Mail Server. Please rectify and try again.")) @@ -177,58 +158,25 @@ class EmailServer: return def get_messages(self, folder="INBOX"): - """Returns new email messages in a list.""" - if not (self.check_mails() or self.connect()): - return [] + """Returns new email messages.""" - frappe.db.commit() + self.latest_messages = [] + self.seen_status = {} + self.uid_reindexed = False - uid_list = [] + email_list = self.get_new_mails(folder) - try: - # track if errors arised - self.errors = False - self.latest_messages = [] - self.seen_status = {} - self.uid_reindexed = False - - uid_list = email_list = self.get_new_mails(folder) - - if not email_list: - return - - num = num_copy = len(email_list) - - # WARNING: Hard coded max no. of messages to be popped - if num > 50: - num = 50 - - # size limits - self.total_size = 0 - self.max_email_size = cint(frappe.local.conf.get("max_email_size")) - self.max_total_size = 5 * self.max_email_size - - for i, message_meta in enumerate(email_list[:num]): - try: - self.retrieve_message(message_meta, i + 1) - except (TotalSizeExceededError, EmailTimeoutError, LoginLimitExceeded): - break - # WARNING: Mark as read - message number 101 onwards from the pop list - # This is to avoid having too many messages entering the system - num = num_copy - if not cint(self.settings.use_imap): - if num > 100 and not self.errors: - for m in range(101, num + 1): - self.pop.dele(m) - - except Exception as e: - if not self.has_login_limit_exceeded(e): - raise + for i, message_meta in enumerate(email_list[:100]): + try: + self.retrieve_message(message_meta, i + 1) + except (EmailTimeoutError, LoginLimitExceeded): + # get whatever messages were retrieved + break out = {"latest_messages": self.latest_messages} if self.settings.use_imap: out.update( - {"uid_list": uid_list, "seen_status": self.seen_status, "uid_reindexed": self.uid_reindexed} + {"uid_list": email_list, "seen_status": self.seen_status, "uid_reindexed": self.uid_reindexed} ) return out @@ -248,7 +196,7 @@ class EmailServer: else: email_list = self.pop.list()[1] - return email_list + return email_list or [] def check_imap_uidvalidity(self, folder): # compare the UIDVALIDITY of email account and imap server @@ -294,9 +242,6 @@ class EmailServer: self.settings.email_sync_rule = f"UID {from_uid}:{uidnext}" self.uid_reindexed = True - elif uid_validity == current_uid_validity: - return - def parse_imap_response(self, cmd, response): pattern = rf"(?<={cmd} )[0-9]*" match = re.search(pattern, response.decode("utf-8"), re.U | re.I) @@ -307,10 +252,7 @@ class EmailServer: return None def retrieve_message(self, message_meta, msg_num=None): - incoming_mail = None try: - self.validate_message_limits(message_meta) - if cint(self.settings.use_imap): status, message = self.imap.uid("fetch", message_meta, "(BODY.PEEK[] BODY.PEEK[HEADER] FLAGS)") raw = message[0] @@ -320,35 +262,17 @@ class EmailServer: else: msg = self.pop.retr(msg_num) self.latest_messages.append(b"\n".join(msg[1])) - except (TotalSizeExceededError, EmailTimeoutError): + except EmailTimeoutError: # propagate this error to break the loop - self.errors = True raise except Exception as e: if self.has_login_limit_exceeded(e): - self.errors = True raise LoginLimitExceeded(e) - else: - # log performs rollback and logs error in Error Log - self.log_error("Unable to fetch email", self.make_error_msg(msg_num, incoming_mail)) - self.errors = True - frappe.db.rollback() + frappe.log_error("Unable to fetch email", self.make_error_msg(msg_num)) - if not cint(self.settings.use_imap): - self.pop.dele(msg_num) - else: - # mark as seen if email sync rule is UNSEEN (syncing only unseen mails) - if self.settings.email_sync_rule == "UNSEEN": - self.imap.uid("STORE", message_meta, "+FLAGS", "(\\SEEN)") - else: - if not cint(self.settings.use_imap): - self.pop.dele(msg_num) - else: - # mark as seen if email sync rule is UNSEEN (syncing only unseen mails) - if self.settings.email_sync_rule == "UNSEEN": - self.imap.uid("STORE", message_meta, "+FLAGS", "(\\SEEN)") + self.post_retrieve_cleanup(message_meta, msg_num) def get_email_seen_status(self, uid, flag_string): """parse the email FLAGS response""" @@ -368,6 +292,15 @@ class EmailServer: def has_login_limit_exceeded(self, e): return "-ERR Exceeded the login limit" in strip(cstr(e)) + def post_retrieve_cleanup(self, message_meta, msg_num=None): + with suppress(Exception): + if not cint(self.settings.use_imap): + self.pop.dele(msg_num) + else: + # mark as seen if email sync rule is UNSEEN (syncing only unseen mails) + if self.settings.email_sync_rule == "UNSEEN": + self.imap.uid("STORE", message_meta, "+FLAGS", "(\\SEEN)") + def is_temporary_system_problem(self, e): messages = ( "-ERR [SYS/TEMP] Temporary system problem. Please try again later.", @@ -378,37 +311,22 @@ class EmailServer: return True return False - def validate_message_limits(self, message_meta): - # throttle based on email size - if not self.max_email_size: - return + def make_error_msg(self, message_meta, msg_num): + incoming_mail = None + with suppress(Exception): + # retrieve headers + if not cint(self.settings.use_imap): + partial_message = self.pop.top(msg_num, 5)[1] + else: + partial_message = self.imap.uid("fetch", message_meta, "(BODY.PEEK[HEADER])")[1] - m, size = message_meta.split() - size = cint(size) - - if size < self.max_email_size: - self.total_size += size - if self.total_size > self.max_total_size: - raise TotalSizeExceededError - else: - raise EmailSizeExceededError - - def make_error_msg(self, msg_num, incoming_mail): - error_msg = "Error in retrieving email." - if not incoming_mail: - try: - # retrieve headers - incoming_mail = Email(b"\n".join(self.pop.top(msg_num, 5)[1])) - except Exception: - pass + incoming_mail = Email(b"\n".join(partial_message)) if incoming_mail: - error_msg += "\nDate: {date}\nFrom: {from_email}\nSubject: {subject}\n".format( + return "\nDate: {date}\nFrom: {from_email}\nSubject: {subject}\n".format( date=incoming_mail.date, from_email=incoming_mail.from_email, subject=incoming_mail.subject ) - return error_msg - def update_flag(self, folder, uid_list=None): """set all uids mails the flag as seen""" if not uid_list: From f6606781fb01630aace94284d3122401ffd9aba4 Mon Sep 17 00:00:00 2001 From: phot0n Date: Tue, 18 Apr 2023 14:31:19 +0530 Subject: [PATCH 20/46] chore: take out email_list to function scope --- frappe/email/receive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 1afdb9aa7a..d2b95db4ce 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -183,8 +183,8 @@ class EmailServer: def get_new_mails(self, folder): """Return list of new mails""" + email_list = [] if cint(self.settings.use_imap): - email_list = [] self.check_imap_uidvalidity(folder) readonly = False if self.settings.email_sync_rule == "UNSEEN" else True @@ -196,7 +196,7 @@ class EmailServer: else: email_list = self.pop.list()[1] - return email_list or [] + return email_list def check_imap_uidvalidity(self, folder): # compare the UIDVALIDITY of email account and imap server From 2a5ed2877730ce401b0ad85cdc84b32e57fdba24 Mon Sep 17 00:00:00 2001 From: phot0n Date: Tue, 18 Apr 2023 14:49:09 +0530 Subject: [PATCH 21/46] fix: make_error_msg for imap --- frappe/email/receive.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index d2b95db4ce..e6b7fdda04 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -313,19 +313,26 @@ class EmailServer: def make_error_msg(self, message_meta, msg_num): incoming_mail = None + traceback = frappe.get_traceback(with_context=True) with suppress(Exception): # retrieve headers if not cint(self.settings.use_imap): - partial_message = self.pop.top(msg_num, 5)[1] + partial_message = b"\n".join(self.pop.top(msg_num, 5)[1]) else: - partial_message = self.imap.uid("fetch", message_meta, "(BODY.PEEK[HEADER])")[1] + partial_message = self.imap.uid("fetch", message_meta, "(BODY.PEEK[HEADER])")[1][0][1] - incoming_mail = Email(b"\n".join(partial_message)) + incoming_mail = Email(partial_message) if incoming_mail: - return "\nDate: {date}\nFrom: {from_email}\nSubject: {subject}\n".format( - date=incoming_mail.date, from_email=incoming_mail.from_email, subject=incoming_mail.subject + return ( + "\nDate: {date}\nFrom: {from_email}\nSubject: {subject}\n\n\nTraceback: \n{traceback}".format( + date=incoming_mail.date, + from_email=incoming_mail.from_email, + subject=incoming_mail.subject, + traceback=traceback, + ) ) + return traceback def update_flag(self, folder, uid_list=None): """set all uids mails the flag as seen""" From 97b2adfaf2c69e95f862ea2e2c6e516a089d6aa9 Mon Sep 17 00:00:00 2001 From: phot0n Date: Tue, 18 Apr 2023 19:22:42 +0530 Subject: [PATCH 22/46] chore: rename message_meta -> uid --- frappe/email/receive.py | 36 ++++++++++++++++++------------------ 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index e6b7fdda04..254910c3ca 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -166,9 +166,9 @@ class EmailServer: email_list = self.get_new_mails(folder) - for i, message_meta in enumerate(email_list[:100]): + for i, uid in enumerate(email_list[:100]): try: - self.retrieve_message(message_meta, i + 1) + self.retrieve_message(uid, i + 1) except (EmailTimeoutError, LoginLimitExceeded): # get whatever messages were retrieved break @@ -251,13 +251,13 @@ class EmailServer: else: return None - def retrieve_message(self, message_meta, msg_num=None): + def retrieve_message(self, uid, msg_num): try: if cint(self.settings.use_imap): - status, message = self.imap.uid("fetch", message_meta, "(BODY.PEEK[] BODY.PEEK[HEADER] FLAGS)") + status, message = self.imap.uid("fetch", uid, "(BODY.PEEK[] BODY.PEEK[HEADER] FLAGS)") raw = message[0] - self.get_email_seen_status(message_meta, raw[0]) + self.get_email_seen_status(uid, raw[0]) self.latest_messages.append(raw[1]) else: msg = self.pop.retr(msg_num) @@ -270,9 +270,9 @@ class EmailServer: if self.has_login_limit_exceeded(e): raise LoginLimitExceeded(e) - frappe.log_error("Unable to fetch email", self.make_error_msg(msg_num)) + frappe.log_error("Unable to fetch email", self.make_error_msg(uid, msg_num)) - self.post_retrieve_cleanup(message_meta, msg_num) + self._post_retrieve_cleanup(uid, msg_num) def get_email_seen_status(self, uid, flag_string): """parse the email FLAGS response""" @@ -292,14 +292,14 @@ class EmailServer: def has_login_limit_exceeded(self, e): return "-ERR Exceeded the login limit" in strip(cstr(e)) - def post_retrieve_cleanup(self, message_meta, msg_num=None): + def _post_retrieve_cleanup(self, uid, msg_num): with suppress(Exception): if not cint(self.settings.use_imap): self.pop.dele(msg_num) else: # mark as seen if email sync rule is UNSEEN (syncing only unseen mails) if self.settings.email_sync_rule == "UNSEEN": - self.imap.uid("STORE", message_meta, "+FLAGS", "(\\SEEN)") + self.imap.uid("STORE", uid, "+FLAGS", "(\\SEEN)") def is_temporary_system_problem(self, e): messages = ( @@ -311,24 +311,24 @@ class EmailServer: return True return False - def make_error_msg(self, message_meta, msg_num): - incoming_mail = None + def make_error_msg(self, uid, msg_num): + partial_mail = None traceback = frappe.get_traceback(with_context=True) with suppress(Exception): # retrieve headers if not cint(self.settings.use_imap): - partial_message = b"\n".join(self.pop.top(msg_num, 5)[1]) + headers = b"\n".join(self.pop.top(msg_num, 5)[1]) else: - partial_message = self.imap.uid("fetch", message_meta, "(BODY.PEEK[HEADER])")[1][0][1] + headers = self.imap.uid("fetch", uid, "(BODY.PEEK[HEADER])")[1][0][1] - incoming_mail = Email(partial_message) + partial_mail = Email(headers) - if incoming_mail: + if partial_mail: return ( "\nDate: {date}\nFrom: {from_email}\nSubject: {subject}\n\n\nTraceback: \n{traceback}".format( - date=incoming_mail.date, - from_email=incoming_mail.from_email, - subject=incoming_mail.subject, + date=partial_mail.date, + from_email=partial_mail.from_email, + subject=partial_mail.subject, traceback=traceback, ) ) From cbbbb189ef384883b06e947fe75f6546ea70e757 Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Tue, 18 Apr 2023 12:40:12 -0600 Subject: [PATCH 23/46] fix: translate file uploader buttons (#20763) --- frappe/public/js/frappe/file_uploader/FilePreview.vue | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/file_uploader/FilePreview.vue b/frappe/public/js/frappe/file_uploader/FilePreview.vue index 6cb78142d8..76f7c43b01 100644 --- a/frappe/public/js/frappe/file_uploader/FilePreview.vue +++ b/frappe/public/js/frappe/file_uploader/FilePreview.vue @@ -24,8 +24,8 @@
- - + +
From e5b26b77db6e3f31bcac6f70184667932b761e1a Mon Sep 17 00:00:00 2001 From: Rucha Mahabal Date: Wed, 19 Apr 2023 12:14:18 +0530 Subject: [PATCH 24/46] fix(minor): typo in fixture syncing message (#20768) --- frappe/modules/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/modules/utils.py b/frappe/modules/utils.py index 8488328da4..57d3e8f7ad 100644 --- a/frappe/modules/utils.py +++ b/frappe/modules/utils.py @@ -160,7 +160,7 @@ def sync_customizations_for_doctype(data: dict, folder: str, filename: str = "") if not frappe.db.exists("DocType", doctype): print(_("DocType {0} does not exist.").format(doctype)) - print(_("Skipping fixture syncing for doctyoe {0} from file {1} ").format(doctype, filename)) + print(_("Skipping fixture syncing for doctype {0} from file {1}").format(doctype, filename)) return if data["custom_fields"]: From 8a9a0a643bb4f636ae4728876057b71b4ac69bc2 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 19 Apr 2023 13:36:06 +0530 Subject: [PATCH 25/46] feat: added URL type in workspace shortcut --- .../workspace_shortcut/workspace_shortcut.json | 17 +++++++++++++---- .../js/frappe/widgets/shortcut_widget.js | 11 +++++++++++ .../public/js/frappe/widgets/widget_dialog.js | 18 ++++++++++++++++-- 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json index 8673e93cf7..8832d9e1f4 100644 --- a/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json +++ b/frappe/desk/doctype/workspace_shortcut/workspace_shortcut.json @@ -7,6 +7,7 @@ "field_order": [ "type", "link_to", + "url", "doc_view", "column_break_4", "label", @@ -24,16 +25,16 @@ "fieldtype": "Select", "in_list_view": 1, "label": "Type", - "options": "DocType\nReport\nPage\nDashboard", + "options": "DocType\nReport\nPage\nDashboard\nURL", "reqd": 1 }, { + "depends_on": "eval:doc.type != \"URL\"", "fieldname": "link_to", "fieldtype": "Dynamic Link", "in_list_view": 1, "label": "Link To", - "options": "type", - "reqd": 1 + "options": "type" }, { "depends_on": "eval:doc.type == \"DocType\"", @@ -94,12 +95,20 @@ "fieldname": "format", "fieldtype": "Data", "label": "Format" + }, + { + "depends_on": "eval:doc.type == \"URL\"", + "fieldname": "url", + "fieldtype": "Data", + "in_list_view": 1, + "label": "URL", + "options": "URL" } ], "index_web_pages_for_search": 1, "istable": 1, "links": [], - "modified": "2021-01-12 13:13:17.571324", + "modified": "2023-04-19 13:32:31.005443", "modified_by": "Administrator", "module": "Desk", "name": "Workspace Shortcut", diff --git a/frappe/public/js/frappe/widgets/shortcut_widget.js b/frappe/public/js/frappe/widgets/shortcut_widget.js index d82f63a035..7d340b04e8 100644 --- a/frappe/public/js/frappe/widgets/shortcut_widget.js +++ b/frappe/public/js/frappe/widgets/shortcut_widget.js @@ -20,6 +20,7 @@ export default class ShortcutWidget extends Widget { restrict_to_domain: this.restrict_to_domain, stats_filter: this.stats_filter, type: this.type, + url: this.url, }; } @@ -45,6 +46,16 @@ export default class ShortcutWidget extends Widget { frappe.open_in_new_tab = true; } + if (this.type == "URL") { + if (frappe.open_in_new_tab) { + window.open(this.url, "_blank"); + frappe.open_in_new_tab = false; + } else { + window.location.href = this.url; + } + return; + } + frappe.set_route(route); }); } diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 45f7e5fc47..8f1575a119 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -350,7 +350,7 @@ class ShortcutDialog extends WidgetDialog { fieldname: "type", label: "Type", reqd: 1, - options: "DocType\nReport\nPage\nDashboard", + options: "DocType\nReport\nPage\nDashboard\nURL", onchange: () => { if (this.dialog.get_value("type") == "DocType") { this.dialog.fields_dict.link_to.get_query = () => { @@ -379,7 +379,6 @@ class ShortcutDialog extends WidgetDialog { fieldtype: "Dynamic Link", fieldname: "link_to", label: "Link To", - reqd: 1, options: "type", onchange: () => { const doctype = this.dialog.get_value("link_to"); @@ -404,6 +403,17 @@ class ShortcutDialog extends WidgetDialog { this.hide_filters(); } }, + depends_on: (s) => s.type != "URL", + mandatory_depends_on: (s) => s.type != "URL", + }, + { + fieldtype: "Data", + fieldname: "url", + label: "URL", + options: "URL", + default: "", + depends_on: (s) => s.type == "URL", + mandatory_depends_on: (s) => s.type == "URL", }, { fieldtype: "Select", @@ -500,6 +510,10 @@ class ShortcutDialog extends WidgetDialog { data.label = data.label ? data.label : frappe.model.unscrub(data.link_to); + if (data.url && !data.label) { + data.label = "No Label (URL)"; + } + return data; } } From 6b063cab3c5cc3fbf9744372bf91a486ab863ce8 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 19 Apr 2023 13:37:27 +0530 Subject: [PATCH 26/46] fix: undefined link if no value for url data field in grid --- frappe/public/js/frappe/form/formatters.js | 1 + 1 file changed, 1 insertion(+) diff --git a/frappe/public/js/frappe/form/formatters.js b/frappe/public/js/frappe/form/formatters.js index 14f01b6607..f4371f901b 100644 --- a/frappe/public/js/frappe/form/formatters.js +++ b/frappe/public/js/frappe/form/formatters.js @@ -35,6 +35,7 @@ frappe.form.formatters = { }, Data: function (value, df) { if (df && df.options == "URL") { + if (!value) return; return `${value}`; } value = value == null ? "" : value; From 63531f367f466e76b3ac5fef07738259f22bbc4f Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 19 Apr 2023 14:05:52 +0530 Subject: [PATCH 27/46] fix: validate url string --- frappe/public/js/frappe/widgets/widget_dialog.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/widgets/widget_dialog.js b/frappe/public/js/frappe/widgets/widget_dialog.js index 8f1575a119..db402211df 100644 --- a/frappe/public/js/frappe/widgets/widget_dialog.js +++ b/frappe/public/js/frappe/widgets/widget_dialog.js @@ -510,8 +510,17 @@ class ShortcutDialog extends WidgetDialog { data.label = data.label ? data.label : frappe.model.unscrub(data.link_to); - if (data.url && !data.label) { - data.label = "No Label (URL)"; + if (data.url) { + !validate_url(data.url) && + frappe.throw({ + message: __("{0} is not a valid URL", [data.url]), + title: __("Invalid URL"), + indicator: "red", + }); + + if (!data.label) { + data.label = "No Label (URL)"; + } } return data; From 06c3ce09f6013de7fa2246395ddb0c613172d037 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 19 Apr 2023 15:55:32 +0530 Subject: [PATCH 28/46] fix: letterhead not working for multiple doc print --- frappe/utils/print_format.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/utils/print_format.py b/frappe/utils/print_format.py index cb4467a56c..180edefaf0 100644 --- a/frappe/utils/print_format.py +++ b/frappe/utils/print_format.py @@ -17,7 +17,9 @@ from frappe.www.printview import validate_print_permission @frappe.whitelist() -def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options=None): +def download_multi_pdf( + doctype, name, format=None, no_letterhead=False, letterhead=None, options=None +): """ Concatenate multiple docs as PDF . @@ -76,6 +78,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options= as_pdf=True, output=output, no_letterhead=no_letterhead, + letterhead=letterhead, pdf_options=options, ) frappe.local.response.filename = "{doctype}.pdf".format( @@ -92,6 +95,7 @@ def download_multi_pdf(doctype, name, format=None, no_letterhead=False, options= as_pdf=True, output=output, no_letterhead=no_letterhead, + letterhead=letterhead, pdf_options=options, ) except Exception: From 4ca74abea56ada1b921f3fe1f65a8d6db0bd6e99 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 19 Apr 2023 16:14:39 +0530 Subject: [PATCH 29/46] fix(UX): enable login with link by default (#20772) --- frappe/core/doctype/system_settings/system_settings.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/core/doctype/system_settings/system_settings.json b/frappe/core/doctype/system_settings/system_settings.json index 102a0a76c2..ba3846f8b5 100644 --- a/frappe/core/doctype/system_settings/system_settings.json +++ b/frappe/core/doctype/system_settings/system_settings.json @@ -510,7 +510,7 @@ "label": "Disable Username/Password Login" }, { - "default": "0", + "default": "1", "description": "Allow users to log in without a password, using a login link sent to their email", "fieldname": "login_with_email_link", "fieldtype": "Check", From 6ce73956748af9b60f1aeb6b991507e9722dda64 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 19 Apr 2023 16:51:23 +0530 Subject: [PATCH 30/46] fix: validate route conflict with new title if title is changed --- frappe/desk/doctype/workspace/workspace.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/frappe/desk/doctype/workspace/workspace.py b/frappe/desk/doctype/workspace/workspace.py index 0866795538..7b5970a7d6 100644 --- a/frappe/desk/doctype/workspace/workspace.py +++ b/frappe/desk/doctype/workspace/workspace.py @@ -17,7 +17,10 @@ class Workspace(Document): def validate(self): if self.public and not is_workspace_manager() and not disable_saving_as_public(): frappe.throw(_("You need to be Workspace Manager to edit this document")) - validate_route_conflict(self.doctype, self.name) + if self.has_value_changed("title"): + validate_route_conflict(self.doctype, self.title) + else: + validate_route_conflict(self.doctype, self.name) try: if not isinstance(loads(self.content), list): From 0125af1e3eba280a3911b05c12698fb3af75a726 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 19 Apr 2023 17:17:27 +0530 Subject: [PATCH 31/46] fix: clear cache on delete of prop setters --- frappe/custom/doctype/property_setter/property_setter.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/custom/doctype/property_setter/property_setter.py b/frappe/custom/doctype/property_setter/property_setter.py index 3034904381..bac616356d 100644 --- a/frappe/custom/doctype/property_setter/property_setter.py +++ b/frappe/custom/doctype/property_setter/property_setter.py @@ -21,6 +21,9 @@ class PropertySetter(Document): delete_property_setter(self.doc_type, self.property, self.field_name, self.row_name) frappe.clear_cache(doctype=self.doc_type) + def on_trash(self): + frappe.clear_cache(doctype=self.doc_type) + def validate_fieldtype_change(self): if self.property == "fieldtype" and self.field_name in not_allowed_fieldtype_change: frappe.throw(_("Field type cannot be changed for {0}").format(self.field_name)) From a27fb46212a77b63c057938f4820c15221b4558d Mon Sep 17 00:00:00 2001 From: Ernesto Ruiz Date: Wed, 19 Apr 2023 07:52:07 -0600 Subject: [PATCH 32/46] chore: translate Not Saved message message in web_form --- frappe/website/doctype/web_form/templates/web_form.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/website/doctype/web_form/templates/web_form.html b/frappe/website/doctype/web_form/templates/web_form.html index 9caf1983b3..cbc4c8ac0a 100644 --- a/frappe/website/doctype/web_form/templates/web_form.html +++ b/frappe/website/doctype/web_form/templates/web_form.html @@ -68,7 +68,7 @@

{{ _(title) }}

{% endif %}
- Not Saved + {{ _("Not Saved") }}
{{ header_buttons() }}
From f8563e357095a72008cfdeec308a81ec8e8448d1 Mon Sep 17 00:00:00 2001 From: Mohammad Hussain Nagaria <34810212+NagariaHussain@users.noreply.github.com> Date: Wed, 19 Apr 2023 22:20:27 +0530 Subject: [PATCH 33/46] fix: extra quotation mark in copy (#20781) --- frappe/public/js/frappe/recorder/RequestDetail.vue | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/recorder/RequestDetail.vue b/frappe/public/js/frappe/recorder/RequestDetail.vue index 002e69bbd4..335f9d06fc 100644 --- a/frappe/public/js/frappe/recorder/RequestDetail.vue +++ b/frappe/public/js/frappe/recorder/RequestDetail.vue @@ -53,7 +53,7 @@
{{ __("Query") }}
-
{{ __("Duration (ms)") }}"
+
{{ __("Duration (ms)") }}
{{ __("Exact Copies") }}
From 41d43b2a06edddeb06358baf1186d0dd79e23091 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 19 Apr 2023 23:17:23 +0530 Subject: [PATCH 34/46] fix: allow url type shortcut --- frappe/desk/desktop.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/desk/desktop.py b/frappe/desk/desktop.py index b50cf0357a..8a8fbb54c1 100644 --- a/frappe/desk/desktop.py +++ b/frappe/desk/desktop.py @@ -153,6 +153,8 @@ class Workspace: return True if item_type == "dashboard": return True + if item_type == "url": + return True return False From 91adcc722178f7a4f36b1dc429d6f4b2bd504403 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Wed, 19 Apr 2023 23:24:18 +0530 Subject: [PATCH 35/46] fix: do not show section if it contains only hidden workspaces --- frappe/public/js/frappe/views/workspace/workspace.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/views/workspace/workspace.js b/frappe/public/js/frappe/views/workspace/workspace.js index af02629038..029197d6bd 100644 --- a/frappe/public/js/frappe/views/workspace/workspace.js +++ b/frappe/public/js/frappe/views/workspace/workspace.js @@ -158,7 +158,7 @@ frappe.views.Workspace = class Workspace { } if ( - sidebar_section.find("sidebar-item-container").length && + sidebar_section.find(".sidebar-item-container").length && sidebar_section.find("> [item-is-hidden='0']").length == 0 ) { sidebar_section.addClass("hidden show-in-edit-mode"); From 98e8b6d38921738ad83f9792ca8c2a511d0a88d3 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 19 Apr 2023 23:56:33 +0530 Subject: [PATCH 36/46] fix: correctly return session usertype (#20787) `user_type` is under data dict and not on session itself. --- frappe/realtime.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/realtime.py b/frappe/realtime.py index 4a7cce0f45..e6980ef917 100644 --- a/frappe/realtime.py +++ b/frappe/realtime.py @@ -126,7 +126,7 @@ def can_subscribe_doctype(doctype: str) -> bool: def get_user_info(): return { "user": frappe.session.user, - "user_type": frappe.session.user_type, + "user_type": frappe.session.data.user_type, } From 93f7fa1883fc9fc6012ef38211aeeb918105b469 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Apr 2023 07:17:02 +0530 Subject: [PATCH 37/46] fix: charts on dashboard not loading --- frappe/public/js/frappe/ui/filters/filter_list.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/frappe/public/js/frappe/ui/filters/filter_list.js b/frappe/public/js/frappe/ui/filters/filter_list.js index 5a8d14e09f..4691e05792 100644 --- a/frappe/public/js/frappe/ui/filters/filter_list.js +++ b/frappe/public/js/frappe/ui/filters/filter_list.js @@ -19,6 +19,8 @@ frappe.ui.FilterGroup = class { } set_clear_all_filters_event() { + if (!this.filter_x_button) return; + this.filter_x_button.on("click", () => { this.toggle_empty_filters(true); if (typeof this.base_list !== "undefined") { From 87cf164cac34532131449f637b9d176ef04bb433 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Thu, 20 Apr 2023 17:31:28 +0530 Subject: [PATCH 38/46] feat: get anonymous response from webform --- frappe/website/doctype/web_form/web_form.js | 6 ++++++ frappe/website/doctype/web_form/web_form.json | 20 +++++++++++++++++-- frappe/website/doctype/web_form/web_form.py | 7 +++++++ 3 files changed, 31 insertions(+), 2 deletions(-) diff --git a/frappe/website/doctype/web_form/web_form.js b/frappe/website/doctype/web_form/web_form.js index 28d33a5266..277330e674 100644 --- a/frappe/website/doctype/web_form/web_form.js +++ b/frappe/website/doctype/web_form/web_form.js @@ -42,6 +42,12 @@ frappe.ui.form.on("Web Form", { render_list_settings_message(frm); }, + anonymous: function (frm) { + if (frm.doc.anonymous) { + frm.set_value("login_required", 0); + } + }, + validate: function (frm) { if (!frm.doc.login_required) { frm.set_value("allow_multiple", 0); diff --git a/frappe/website/doctype/web_form/web_form.json b/frappe/website/doctype/web_form/web_form.json index 21e501481b..96749e460d 100644 --- a/frappe/website/doctype/web_form/web_form.json +++ b/frappe/website/doctype/web_form/web_form.json @@ -9,7 +9,7 @@ "title", "route", "published", - "column_break_1", + "column_break_vdhm", "doc_type", "module", "is_standard", @@ -21,6 +21,7 @@ "allow_multiple", "allow_edit", "allow_delete", + "anonymous", "column_break_2", "apply_document_permissions", "allow_print", @@ -96,10 +97,12 @@ "default": "0", "fieldname": "published", "fieldtype": "Check", + "hidden": 1, "label": "Published" }, { "default": "0", + "depends_on": "eval:!doc.anonymous", "fieldname": "login_required", "fieldtype": "Check", "label": "Login Required" @@ -301,6 +304,7 @@ { "collapsible": 1, "collapsible_depends_on": "show_list", + "depends_on": "eval:!doc.anonymous", "fieldname": "section_break_3", "fieldtype": "Section Break", "label": "List Settings" @@ -308,6 +312,7 @@ { "collapsible": 1, "collapsible_depends_on": "show_sidebar", + "depends_on": "eval:!doc.anonymous", "fieldname": "section_break_4", "fieldtype": "Section Break", "label": "Sidebar Settings" @@ -358,13 +363,24 @@ "fieldname": "meta_image", "fieldtype": "Attach Image", "label": "Meta Image" + }, + { + "fieldname": "column_break_vdhm", + "fieldtype": "Column Break" + }, + { + "default": "0", + "description": "Receive anonymous response", + "fieldname": "anonymous", + "fieldtype": "Check", + "label": "Anonymous" } ], "has_web_view": 1, "icon": "icon-edit", "is_published_field": "published", "links": [], - "modified": "2023-01-02 10:19:15.680960", + "modified": "2023-04-20 17:24:42.657731", "modified_by": "Administrator", "module": "Website", "name": "Web Form", diff --git a/frappe/website/doctype/web_form/web_form.py b/frappe/website/doctype/web_form/web_form.py index ac6276c00b..3e2705bdbe 100644 --- a/frappe/website/doctype/web_form/web_form.py +++ b/frappe/website/doctype/web_form/web_form.py @@ -387,6 +387,10 @@ def accept(web_form, data): web_form = frappe.get_doc("Web Form", web_form) doctype = web_form.doc_type + user = frappe.session.user + + if web_form.anonymous and frappe.session.user != "Guest": + frappe.session.user = "Guest" if data.name and not web_form.allow_edit: frappe.throw(_("You are not allowed to update this Web Form Document")) @@ -468,6 +472,9 @@ def accept(web_form, data): if f: remove_file_by_url(f, doctype=doctype, name=doc.name) + if web_form.anonymous and frappe.session.user == "Guest" and user: + frappe.session.user = user + frappe.flags.web_form_doc = doc return doc From cd67ad420fd6f64401efb97548bd3178bb089a6c Mon Sep 17 00:00:00 2001 From: Ritwik Puri Date: Thu, 20 Apr 2023 22:03:11 +0530 Subject: [PATCH 39/46] fix(patch): only run disable_email_accounts_with_oauth if connected user is not set (#20799) --- frappe/patches/v14_0/disable_email_accounts_with_oauth.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/frappe/patches/v14_0/disable_email_accounts_with_oauth.py b/frappe/patches/v14_0/disable_email_accounts_with_oauth.py index d620bf4e3b..2066b5f640 100644 --- a/frappe/patches/v14_0/disable_email_accounts_with_oauth.py +++ b/frappe/patches/v14_0/disable_email_accounts_with_oauth.py @@ -3,7 +3,9 @@ from frappe.desk.doctype.notification_log.notification_log import make_notificat def execute(): - if not frappe.get_value("Email Account", {"auth_method": "OAuth"}): + if frappe.get_all( + "Email Account", {"auth_method": "OAuth", "connected_user": ["is", "set"]}, limit=1 + ): return # Setting awaiting password to 1 for email accounts where Oauth is enabled. From 838919eb428ba2f439f4d7b80b543fc2cd2d652b Mon Sep 17 00:00:00 2001 From: Faris Ansari Date: Fri, 21 Apr 2023 17:43:41 +0530 Subject: [PATCH 40/46] fix: update deprecated deep selector syntax (#20807) `>>>` to `:deep` --- frappe/public/js/print_format_builder/Preview.vue | 2 +- frappe/public/js/print_format_builder/PrintFormatControls.vue | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/print_format_builder/Preview.vue b/frappe/public/js/print_format_builder/Preview.vue index 1603711846..377ec92b6d 100644 --- a/frappe/public/js/print_format_builder/Preview.vue +++ b/frappe/public/js/print_format_builder/Preview.vue @@ -130,7 +130,7 @@ onMounted(() => { margin-top: auto; margin-bottom: 1.2rem; } -.preview-control >>> .form-control { +.preview-control :deep(.form-control) { background: var(--control-bg-on-gray); } diff --git a/frappe/public/js/print_format_builder/PrintFormatControls.vue b/frappe/public/js/print_format_builder/PrintFormatControls.vue index 7d38148d5e..eac5fbe9f6 100644 --- a/frappe/public/js/print_format_builder/PrintFormatControls.vue +++ b/frappe/public/js/print_format_builder/PrintFormatControls.vue @@ -332,7 +332,7 @@ watch(print_format, () => (store.dirty.value = true), { deep: true }); margin-bottom: 0; } -.control-font >>> .frappe-control[data-fieldname="font"] label { +.control-font :deep(.frappe-control[data-fieldname="font"] label) { display: none; } From 6b57a2d35280a07b9b068503020e24ae67931089 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 06:56:43 +0200 Subject: [PATCH 41/46] fix: endless reload in User when timezone is unset (#20815) --- frappe/core/doctype/user/user.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index 918a9ee37c..e389a73cbb 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -114,9 +114,9 @@ frappe.ui.form.on("User", { return; } - function hasChanged(doc_attr, boot_attr) { - return (doc_attr || boot_attr) && doc_attr !== boot_attr; - } + const hasChanged = (doc_attr, boot_attr) => { + return doc_attr && boot_attr && doc_attr !== boot_attr; + }; if ( doc.name === frappe.session.user && From 89186b80570843fbfada945ac15bf7d8b3c14e80 Mon Sep 17 00:00:00 2001 From: Shariq Ansari Date: Mon, 24 Apr 2023 11:30:35 +0530 Subject: [PATCH 42/46] fix: multiple assignments to the same person --- frappe/desk/form/assign_to.py | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/frappe/desk/form/assign_to.py b/frappe/desk/form/assign_to.py index 2b17d38371..ce8bb444a1 100644 --- a/frappe/desk/form/assign_to.py +++ b/frappe/desk/form/assign_to.py @@ -147,35 +147,36 @@ def add_multiple(args=None): def close_all_assignments(doctype, name): assignments = frappe.get_all( "ToDo", - fields=["allocated_to"], + fields=["allocated_to", "name"], filters=dict(reference_type=doctype, reference_name=name, status=("!=", "Cancelled")), ) if not assignments: return False for assign_to in assignments: - set_status(doctype, name, assign_to.allocated_to, status="Closed") + set_status(doctype, name, todo=assign_to.name, assign_to=assign_to.allocated_to, status="Closed") return True @frappe.whitelist() def remove(doctype, name, assign_to): - return set_status(doctype, name, assign_to, status="Cancelled") + return set_status(doctype, name, "", assign_to, status="Cancelled") -def set_status(doctype, name, assign_to, status="Cancelled"): +def set_status(doctype, name, todo=None, assign_to=None, status="Cancelled"): """remove from todo""" try: - todo = frappe.db.get_value( - "ToDo", - { - "reference_type": doctype, - "reference_name": name, - "allocated_to": assign_to, - "status": ("!=", status), - }, - ) + if not todo: + todo = frappe.db.get_value( + "ToDo", + { + "reference_type": doctype, + "reference_name": name, + "allocated_to": assign_to, + "status": ("!=", status), + }, + ) if todo: todo = frappe.get_doc("ToDo", todo) todo.status = status @@ -197,13 +198,17 @@ def clear(doctype, name): Clears assignments, return False if not assigned. """ assignments = frappe.get_all( - "ToDo", fields=["allocated_to"], filters=dict(reference_type=doctype, reference_name=name) + "ToDo", + fields=["allocated_to", "name"], + filters=dict(reference_type=doctype, reference_name=name), ) if not assignments: return False for assign_to in assignments: - set_status(doctype, name, assign_to.allocated_to, "Cancelled") + set_status( + doctype, name, todo=assign_to.name, assign_to=assign_to.allocated_to, status="Cancelled" + ) return True From eeda161e98a73f67c0787f4fd92bc17e0d6d9356 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Mon, 24 Apr 2023 17:34:24 +0530 Subject: [PATCH 43/46] fix: handle `SMTPRecipientsRefused` retries (#20819) * fix: remove trailing and leading quotes from email * chore: typo * fix: dont retry `SMTPRecipientsRefused` If refused once it's unlikely to work again just by retrying. There's no mechanism to prevent infinite retry. This will still attempt for MAX_RETRY_COUNT. * Revert "fix: remove trailing and leading quotes from email" This reverts commit 2676ac2c7fe76c34049da05a209554fee6b3d911. refer https://github.com/frappe/frappe/pull/20819#discussion_r1175166987 --- frappe/email/doctype/email_queue/email_queue.py | 3 +-- .../doctype/email_queue_recipient/email_queue_recipient.py | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/frappe/email/doctype/email_queue/email_queue.py b/frappe/email/doctype/email_queue/email_queue.py index c10494b0d9..d254c87a0a 100644 --- a/frappe/email/doctype/email_queue/email_queue.py +++ b/frappe/email/doctype/email_queue/email_queue.py @@ -203,7 +203,7 @@ class SendMailContext: # Note: smtp session will have to be manually closed self.retain_smtp_session = bool(smtp_server_instance) - self.sent_to = [rec.recipient for rec in self.queue_doc.recipients if rec.is_main_sent()] + self.sent_to = [rec.recipient for rec in self.queue_doc.recipients if rec.is_mail_sent()] def __enter__(self): self.queue_doc.update_status(status="Sending", commit=True) @@ -213,7 +213,6 @@ class SendMailContext: exceptions = [ smtplib.SMTPServerDisconnected, smtplib.SMTPAuthenticationError, - smtplib.SMTPRecipientsRefused, smtplib.SMTPConnectError, smtplib.SMTPHeloError, JobTimeoutException, diff --git a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py index bcb8d9b05d..705075a862 100644 --- a/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py +++ b/frappe/email/doctype/email_queue_recipient/email_queue_recipient.py @@ -11,7 +11,7 @@ class EmailQueueRecipient(Document): def is_mail_to_be_sent(self): return self.status == "Not Sent" - def is_main_sent(self): + def is_mail_sent(self): return self.status == "Sent" def update_db(self, commit=False, **kwargs): From fc103250749575854609a4d959e05a82de416e02 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:21:42 +0200 Subject: [PATCH 44/46] fix: improve delete_contact_and_address (#20381) --- frappe/contacts/address_and_contact.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/frappe/contacts/address_and_contact.py b/frappe/contacts/address_and_contact.py index 4df32c6705..1d3a5d644c 100644 --- a/frappe/contacts/address_and_contact.py +++ b/frappe/contacts/address_and_contact.py @@ -149,18 +149,26 @@ def get_permitted_and_not_permitted_links(doctype): return {"permitted_links": permitted_links, "not_permitted_links": not_permitted_links} -def delete_contact_and_address(doctype, docname): +def delete_contact_and_address(doctype: str, docname: str) -> None: for parenttype in ("Contact", "Address"): - items = frappe.db.sql_list( - """select parent from `tabDynamic Link` - where parenttype=%s and link_doctype=%s and link_name=%s""", - (parenttype, doctype, docname), - ) - - for name in items: + for name in frappe.get_all( + "Dynamic Link", + filters={ + "parenttype": parenttype, + "link_doctype": doctype, + "link_name": docname, + }, + pluck="parent", + ): doc = frappe.get_doc(parenttype, name) if len(doc.links) == 1: doc.delete() + else: + for link in doc.links: + if link.link_doctype == doctype and link.link_name == docname: + doc.remove(link) + doc.save() + break @frappe.whitelist() From e384d94fcc39d37a1a1287d4fc3128dd10352e8b Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:28:22 +0200 Subject: [PATCH 45/46] fix: make role info translatable (#20813) --- frappe/public/js/frappe/roles_editor.js | 14 ++++++++------ frappe/translations/de.csv | 5 +++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/frappe/public/js/frappe/roles_editor.js b/frappe/public/js/frappe/roles_editor.js index efdba7d4d8..312a90a82c 100644 --- a/frappe/public/js/frappe/roles_editor.js +++ b/frappe/public/js/frappe/roles_editor.js @@ -59,7 +59,7 @@ frappe.RoleEditor = class { const $body = $(this.perm_dialog.body); if (!permissions.length) { $body.append(`
- ${__("{0} role does not have permission on any doctype", [role])} + ${__("{0} role does not have permission on any doctype", [__(role)])}
`); } else { $body.append(` @@ -68,7 +68,7 @@ frappe.RoleEditor = class { ${__("Document Type")} ${__("Level")} - ${frappe.perm.rights.map((p) => ` ${frappe.unscrub(p)}`).join("")} + ${frappe.perm.rights.map((p) => ` ${__(frappe.unscrub(p))}`).join("")} @@ -77,7 +77,7 @@ frappe.RoleEditor = class { permissions.forEach((perm) => { $body.find("tbody").append(` - ${perm.parent} + ${__(perm.parent)} ${perm.permlevel} ${frappe.perm.rights .map( @@ -91,7 +91,7 @@ frappe.RoleEditor = class { `); }); } - this.perm_dialog.set_title(role); + this.perm_dialog.set_title(__(role)); this.perm_dialog.show(); }); } @@ -102,8 +102,10 @@ frappe.RoleEditor = class { this.perm_dialog.$wrapper .find(".modal-dialog") - .css("width", "1200px") - .css("max-width", "80vw"); + .css("width", "auto") + .css("max-width", "1200px"); + + this.perm_dialog.$wrapper.find(".modal-body").css("overflow", "overlay"); } show() { this.reset(); diff --git a/frappe/translations/de.csv b/frappe/translations/de.csv index 834281bbab..dae676ee72 100644 --- a/frappe/translations/de.csv +++ b/frappe/translations/de.csv @@ -2290,8 +2290,8 @@ Setup Auto Email,Einstellungen Auto E-Mail, Setup Complete,Einrichtung abgeschlossen, Setup Notifications based on various criteria.,Setup Benachrichtigungen basierend auf verschiedenen Kriterien., Setup Reports to be emailed at regular intervals,Berichte regelmäßig per E-Mail senden, -"Setup of top navigation bar, footer and logo.","Einrichten der oberen Navigationsleiste, der Fußzeile und des Logos", -Share,Aktie, +"Setup of top navigation bar, footer and logo.","Einrichten der oberen Navigationsleiste, der Fußzeile und des Logos", +Share,Freigeben, Share URL,URL teilen, Share With,Freigeben für, Share this document with,Dieses Dokument teilen mit, @@ -4840,3 +4840,4 @@ Non-numeric,Nicht-numerische, Minimal,Minimal, This value is fetched from {0}'s {1} field,Dieser Wert ergibt sich aus dem Feld {1} von {0}, This form is not editable due to a Workflow.,Dieses Formular kann in diesem Workflow-Status nicht bearbeitet werden., +{0} role does not have permission on any doctype,Die Rolle {0} hat auf keinen DocType Zugriff, From f7b2b59f9a3752641c6cae9ff5a5ba57ebd78197 Mon Sep 17 00:00:00 2001 From: Raffael Meyer <14891507+barredterra@users.noreply.github.com> Date: Mon, 24 Apr 2023 15:46:11 +0200 Subject: [PATCH 46/46] fix: don't translate before unscrubbing (#20814) --- frappe/public/js/frappe/model/model.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/frappe/public/js/frappe/model/model.js b/frappe/public/js/frappe/model/model.js index 6c6f617275..7e5eccf26d 100644 --- a/frappe/public/js/frappe/model/model.js +++ b/frappe/public/js/frappe/model/model.js @@ -348,11 +348,9 @@ $.extend(frappe.model, { }, unscrub: function (txt) { - return __(txt || "") - .replace(/-|_/g, " ") - .replace(/\w*/g, function (keywords) { - return keywords.charAt(0).toUpperCase() + keywords.substr(1).toLowerCase(); - }); + return (txt || "").replace(/-|_/g, " ").replace(/\w*/g, function (keywords) { + return keywords.charAt(0).toUpperCase() + keywords.substr(1).toLowerCase(); + }); }, can_create: function (doctype) {