From ec20739feef2acc57c11dfa03812468d2d0d8022 Mon Sep 17 00:00:00 2001 From: Corentin Forler <10946971+cogk@users.noreply.github.com> Date: Tue, 5 Sep 2023 12:20:01 +0200 Subject: [PATCH 01/20] test(email): Add test for accents in subject header --- .../doctype/email_account/test_email_account.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/frappe/email/doctype/email_account/test_email_account.py b/frappe/email/doctype/email_account/test_email_account.py index a1740b0b0a..b9ba3a2bc1 100644 --- a/frappe/email/doctype/email_account/test_email_account.py +++ b/frappe/email/doctype/email_account/test_email_account.py @@ -606,6 +606,20 @@ class TestInboundMail(FrappeTestCase): reference_doc = inbound_mail.reference_document() self.assertEqual(todo.name, reference_doc.name) + def test_reference_document_by_subject_match_with_accents(self): + subject = "Nouvelle tâche à faire 😃" + todo = self.new_todo(sender="test_sender@example.com", description=subject) + + mail_content = ( + self.get_test_mail(fname="incoming-subject-placeholder.raw") + .replace("{{ subject }}", f"RE: {subject}") + .encode("utf-8") + ) # note: encode to bytes because that's what triggered the error + email_account = frappe.get_doc("Email Account", "_Test Email Account 1") + inbound_mail = InboundMail(mail_content, email_account, 12345, 1) + reference_doc = inbound_mail.reference_document() + self.assertEqual(todo.name, reference_doc.name) + def test_create_communication_from_mail(self): # Create email queue record mail_content = self.get_test_mail(fname="incoming-2.raw") From e5dd924cbef2c8fa28404ac8669d94a4a344e6a6 Mon Sep 17 00:00:00 2001 From: Corentin Forler <10946971+cogk@users.noreply.github.com> Date: Tue, 5 Sep 2023 12:20:31 +0200 Subject: [PATCH 02/20] fix(email): Add missing byte-decoding in set_subject --- frappe/email/receive.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index 6af6c3cebe..ecee7d583c 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -412,6 +412,9 @@ class Email: # assume that the encoding is utf-8 self.subject = safe_decode(self.subject)[:140] + if isinstance(self.subject, bytes): + self.subject = self.subject.decode("utf8", "replace") # last resort + if not self.subject: self.subject = "No Subject" From a4a3af5859be89e7f41987a3e882b99ce35ee081 Mon Sep 17 00:00:00 2001 From: Corentin Forler <10946971+cogk@users.noreply.github.com> Date: Tue, 5 Sep 2023 13:46:34 +0200 Subject: [PATCH 03/20] refactor(email): Ensure that subject is a string --- frappe/email/receive.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/frappe/email/receive.py b/frappe/email/receive.py index ecee7d583c..aa73093168 100644 --- a/frappe/email/receive.py +++ b/frappe/email/receive.py @@ -406,14 +406,19 @@ class Email: """Parse and decode `Subject` header.""" _subject = decode_header(self.mail.get("Subject", "No Subject")) self.subject = _subject[0][0] or "" + if _subject[0][1]: + # Encoding is known by decode_header (might also be unknown-8bit) self.subject = safe_decode(self.subject, _subject[0][1]) - else: - # assume that the encoding is utf-8 - self.subject = safe_decode(self.subject)[:140] if isinstance(self.subject, bytes): - self.subject = self.subject.decode("utf8", "replace") # last resort + # Fall back to utf-8 if the charset is unknown or decoding fails + # Replace invalid characters with '' + self.subject = self.subject.decode("utf-8", "replace") + + # Convert non-string (e.g. None) + # Truncate to 140 chars (can be used as a document name) + self.subject = str(self.subject).strip()[:140] if not self.subject: self.subject = "No Subject" From 8b83e051d09b0a97ef5fb6937e1a518f053c3d5b Mon Sep 17 00:00:00 2001 From: saif Date: Sun, 17 Sep 2023 11:20:06 +0300 Subject: [PATCH 04/20] refactor: Improve list_apps function readability and maintainability. - Improve variable naming and reduce redundancy. - Reorganize and simplify code structure. --- frappe/commands/site.py | 40 ++++++++++++++++------------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/frappe/commands/site.py b/frappe/commands/site.py index 263c5438bf..a833431142 100644 --- a/frappe/commands/site.py +++ b/frappe/commands/site.py @@ -481,43 +481,35 @@ def install_app(context, apps, force=False): @click.option("--format", "-f", type=click.Choice(["text", "json"]), default="text") @pass_context def list_apps(context, format): - "List apps in site" + """ + List apps in site. + """ summary_dict = {} - def fix_whitespaces(text): - if site == context.sites[-1]: - text = text.rstrip() - if len(context.sites) == 1: - text = text.lstrip() - return text + def format_app(app): + name_len = max(len(app.app_name) for app in apps) + ver_len = max(len(app.app_version) for app in apps) + template = f"{{0:{name_len}}} {{1:{ver_len}}} {{2}}" + return template.format(app.app_name, app.app_version, app.git_branch) for site in context.sites: frappe.init(site=site) frappe.connect() site_title = click.style(f"{site}", fg="green") if len(context.sites) > 1 else "" + installed_apps_info = [] + apps = frappe.get_single("Installed Applications").installed_applications - if apps: - name_len, ver_len = (max(len(x.get(y)) for x in apps) for y in ["app_name", "app_version"]) - template = f"{{0:{name_len}}} {{1:{ver_len}}} {{2}}" - - installed_applications = [ - template.format(app.app_name, app.app_version, app.git_branch) for app in apps - ] - applications_summary = "\n".join(installed_applications) - summary = f"{site_title}\n{applications_summary}\n" - summary_dict[site] = [app.app_name for app in apps] - + installed_apps_info.extend(format_app(app) for app in apps) else: - installed_applications = frappe.get_installed_apps() - applications_summary = "\n".join(installed_applications) - summary = f"{site_title}\n{applications_summary}\n" - summary_dict[site] = installed_applications + installed_apps_info.extend(frappe.get_installed_apps()) - summary = fix_whitespaces(summary) + installed_apps_info_str = "\n".join(installed_apps_info) + summary = f"{site_title}\n{installed_apps_info_str}\n" + summary_dict[site] = [app.app_name for app in apps] - if format == "text" and applications_summary and summary: + if format == "text" and installed_apps_info and summary: print(summary) frappe.destroy() From df30e1c6f0bbed85efa432e96b8e95f98104643b Mon Sep 17 00:00:00 2001 From: Suraj Shetty Date: Mon, 18 Sep 2023 13:44:09 +0530 Subject: [PATCH 05/20] fix: Only allow user with role set in Dashboard chart If roles are set is Dashboard Chart, only user with those roles can access the chart. If not set, DocType or Report permissions should be used --- .../desk/doctype/dashboard_chart/dashboard_chart.json | 3 ++- .../desk/doctype/dashboard_chart/dashboard_chart.py | 11 +++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json index 6d4a86d535..a649633049 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.json +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.json @@ -280,6 +280,7 @@ "options": "DocType" }, { + "description": "If set, only user with these roles can access this chart. If not set, DocType or Report permissions will be used.", "fieldname": "roles", "fieldtype": "Table", "label": "Roles", @@ -287,7 +288,7 @@ } ], "links": [], - "modified": "2023-08-28 20:20:54.186299", + "modified": "2023-09-18 13:41:05.263676", "modified_by": "Administrator", "module": "Desk", "name": "Dashboard Chart", diff --git a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py index 9fe135d4e1..1c331a7fe8 100644 --- a/frappe/desk/doctype/dashboard_chart/dashboard_chart.py +++ b/frappe/desk/doctype/dashboard_chart/dashboard_chart.py @@ -79,7 +79,11 @@ def has_permission(doc, ptype, user): if "System Manager" in roles: return True - if doc.chart_type == "Report": + if doc.roles: + allowed = [d.role for d in doc.roles] + if has_common(roles, allowed): + return True + elif doc.chart_type == "Report": if doc.report_name in get_allowed_report_names(): return True else: @@ -87,11 +91,6 @@ def has_permission(doc, ptype, user): if doc.document_type in allowed_doctypes: return True - if doc.roles: - allowed = [d.role for d in doc.roles] - if has_common(roles, allowed): - return True - return False From d73a854191e0f6fbb5abb7936765ec1e09868d86 Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Mon, 18 Sep 2023 19:07:32 +0200 Subject: [PATCH 06/20] feat(UX): Add autocomplete for User timezone (#22456) * feat(UX): Add autocomplete for User timezone * Revert "feat(UX): Add autocomplete for User timezone" This reverts commit a721b4a1ef8e0e6b656b6795b832324a955b2f7a. * fix: convert tz to autocomplete --------- Co-authored-by: Ankush Menat --- frappe/core/doctype/user/user.js | 8 ++++---- frappe/core/doctype/user/user.json | 4 ++-- frappe/core/doctype/user/user.py | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/frappe/core/doctype/user/user.js b/frappe/core/doctype/user/user.js index cd89a57dfe..f168ad920f 100644 --- a/frappe/core/doctype/user/user.js +++ b/frappe/core/doctype/user/user.js @@ -1,7 +1,7 @@ frappe.ui.form.on("User", { before_load: function (frm) { - var update_tz_select = function (user_language) { - frm.set_df_property("time_zone", "options", [""].concat(frappe.all_timezones)); + let update_tz_options = function () { + frm.fields_dict.time_zone.set_data(frappe.all_timezones); }; if (!frappe.all_timezones) { @@ -9,11 +9,11 @@ frappe.ui.form.on("User", { method: "frappe.core.doctype.user.user.get_timezones", callback: function (r) { frappe.all_timezones = r.message.timezones; - update_tz_select(); + update_tz_options(); }, }); } else { - update_tz_select(); + update_tz_options(); } }, diff --git a/frappe/core/doctype/user/user.json b/frappe/core/doctype/user/user.json index 0396776183..5ed990a794 100644 --- a/frappe/core/doctype/user/user.json +++ b/frappe/core/doctype/user/user.json @@ -191,7 +191,7 @@ }, { "fieldname": "time_zone", - "fieldtype": "Select", + "fieldtype": "Autocomplete", "label": "Time Zone" }, { @@ -762,7 +762,7 @@ "link_fieldname": "user" } ], - "modified": "2023-06-05 17:26:04.127555", + "modified": "2023-09-18 22:19:49.933972", "modified_by": "Administrator", "module": "Core", "name": "User", diff --git a/frappe/core/doctype/user/user.py b/frappe/core/doctype/user/user.py index 33b1cd3bf5..1af7af72e5 100644 --- a/frappe/core/doctype/user/user.py +++ b/frappe/core/doctype/user/user.py @@ -104,7 +104,7 @@ class User(Document): simultaneous_sessions: DF.Int social_logins: DF.Table[UserSocialLogin] thread_notify: DF.Check - time_zone: DF.Literal + time_zone: DF.Autocomplete | None unsubscribed: DF.Check user_emails: DF.Table[UserEmail] user_image: DF.AttachImage | None From c4afd035e800a7057073d25fe8afa1fa5a224c16 Mon Sep 17 00:00:00 2001 From: Shadrak Gurupnor <30501401+shadrak98@users.noreply.github.com> Date: Tue, 19 Sep 2023 14:38:23 +0530 Subject: [PATCH 07/20] feat: view committed queries in console log (#22466) * feat: show committed queries in console log * chore: typo --- frappe/desk/doctype/console_log/console_log.json | 12 ++++++++++-- frappe/desk/doctype/console_log/console_log.py | 1 + frappe/desk/doctype/system_console/system_console.py | 4 +++- 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/frappe/desk/doctype/console_log/console_log.json b/frappe/desk/doctype/console_log/console_log.json index 7531d97991..3653a26eab 100644 --- a/frappe/desk/doctype/console_log/console_log.json +++ b/frappe/desk/doctype/console_log/console_log.json @@ -7,7 +7,8 @@ "engine": "InnoDB", "field_order": [ "script", - "type" + "type", + "committed" ], "fields": [ { @@ -23,11 +24,18 @@ "hidden": 1, "label": "Type", "read_only": 1 + }, + { + "default": "0", + "fieldname": "committed", + "fieldtype": "Check", + "label": "Committed", + "read_only": 1 } ], "index_web_pages_for_search": 1, "links": [], - "modified": "2023-07-27 22:52:37.239039", + "modified": "2023-09-19 13:02:56.332137", "modified_by": "Administrator", "module": "Desk", "name": "Console Log", diff --git a/frappe/desk/doctype/console_log/console_log.py b/frappe/desk/doctype/console_log/console_log.py index bed829c5b8..cd004745a3 100644 --- a/frappe/desk/doctype/console_log/console_log.py +++ b/frappe/desk/doctype/console_log/console_log.py @@ -14,6 +14,7 @@ class ConsoleLog(Document): if TYPE_CHECKING: from frappe.types import DF + committed: DF.Check script: DF.Code | None type: DF.Data | None # end: auto-generated types diff --git a/frappe/desk/doctype/system_console/system_console.py b/frappe/desk/doctype/system_console/system_console.py index 14576d3860..4969f5a04a 100644 --- a/frappe/desk/doctype/system_console/system_console.py +++ b/frappe/desk/doctype/system_console/system_console.py @@ -40,7 +40,9 @@ class SystemConsole(Document): frappe.db.commit() else: frappe.db.rollback() - frappe.get_doc(dict(doctype="Console Log", script=self.console, type=self.type)).insert() + frappe.get_doc( + dict(doctype="Console Log", script=self.console, type=self.type, committed=self.commit) + ).insert() frappe.db.commit() From b7e35e558e8202d537abac5a30daa6b48c02cf0e Mon Sep 17 00:00:00 2001 From: 14987 Date: Tue, 19 Sep 2023 15:14:51 +0530 Subject: [PATCH 08/20] feat: Add print shortcut --- frappe/public/js/frappe/form/form.js | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/frappe/public/js/frappe/form/form.js b/frappe/public/js/frappe/form/form.js index 2dc180ed8c..57c443ba9d 100644 --- a/frappe/public/js/frappe/form/form.js +++ b/frappe/public/js/frappe/form/form.js @@ -176,6 +176,11 @@ frappe.ui.form.Form = class FrappeForm { page: this.page, description: __("Redo last action"), }); + frappe.ui.keys.add_shortcut({ + shortcut: "ctrl+p", + action: () => this.print_doc(), + description: __("Print document"), + }); let grid_shortcut_keys = [ { From acd1b9d64ea3360abcdc42b3e11ef987f45ecacb Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Tue, 19 Sep 2023 20:41:15 +0530 Subject: [PATCH 09/20] fix: Don't run query if dn is None (#22475) --- frappe/database/database.py | 4 +++- frappe/tests/test_db.py | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/frappe/database/database.py b/frappe/database/database.py index f790aa6c9b..77fcfe73fe 100644 --- a/frappe/database/database.py +++ b/frappe/database/database.py @@ -879,7 +879,9 @@ class Database: """ from frappe.model.utils import is_single_doctype - if (dn is None or dt == dn) and is_single_doctype(dt): + if dn is None or dt == dn: + if not is_single_doctype(dt): + return deprecation_warning( "Calling db.set_value on single doctype is deprecated. This behaviour will be removed in future. Use db.set_single_value instead." ) diff --git a/frappe/tests/test_db.py b/frappe/tests/test_db.py index 3acb6e99f3..655777b39f 100644 --- a/frappe/tests/test_db.py +++ b/frappe/tests/test_db.py @@ -693,6 +693,12 @@ class TestDBSetValue(FrappeTestCase): current_value = frappe.db.get_single_value("System Settings", "deny_multiple_sessions") self.assertEqual(current_value, changed_value) + def test_none_no_set_value(self): + frappe.db.set_value("User", None, "middle_name", "test") + with self.assertQueryCount(0): + frappe.db.set_value("User", None, "middle_name", "test") + frappe.db.set_value("User", "User", "middle_name", "test") + def test_update_single_row_single_column(self): frappe.db.set_value("ToDo", self.todo1.name, "description", "test_set_value change 1") updated_value = frappe.db.get_value("ToDo", self.todo1.name, "description") From 56371f09e075ca174728d0c2cdc4d72bff6dc84a Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Sep 2023 11:43:56 +0530 Subject: [PATCH 10/20] Revert "fix!: Bind development server only to localhost (#22397)" This reverts commit 9244140816b44bb481af5b5627047244d33b9d4b. Breaks docker development installs --- frappe/app.py | 10 ++-------- frappe/commands/utils.py | 3 --- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/frappe/app.py b/frappe/app.py index dc194a7559..e2b42ab2a0 100644 --- a/frappe/app.py +++ b/frappe/app.py @@ -413,13 +413,7 @@ def sync_database(rollback: bool) -> bool: def serve( - host=None, - port=8000, - profile=False, - no_reload=False, - no_threading=False, - site=None, - sites_path=".", + port=8000, profile=False, no_reload=False, no_threading=False, site=None, sites_path="." ): global application, _site, _sites_path _site = site @@ -444,7 +438,7 @@ def serve( log.setLevel(logging.ERROR) run_simple( - host, + "0.0.0.0", int(port), application, exclude_patterns=["test_*"], diff --git a/frappe/commands/utils.py b/frappe/commands/utils.py index 3af7a935dd..6de2885b58 100644 --- a/frappe/commands/utils.py +++ b/frappe/commands/utils.py @@ -922,7 +922,6 @@ def run_ui_tests( @click.command("serve") -@click.option("--host", default="127.0.0.1") @click.option("--port", default=8000) @click.option("--profile", is_flag=True, default=False) @click.option("--noreload", "no_reload", is_flag=True, default=False) @@ -931,7 +930,6 @@ def run_ui_tests( @pass_context def serve( context, - host="127.0.0.1", port=None, profile=False, no_reload=False, @@ -953,7 +951,6 @@ def serve( no_threading = True no_reload = True frappe.app.serve( - host=host, port=port, profile=profile, no_reload=no_reload, From d7f4518de39d72d934d5d730442b87fe9fd28834 Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Sep 2023 12:16:34 +0530 Subject: [PATCH 11/20] fix: handle df without labels in report view --- frappe/public/js/frappe/ui/group_by/group_by.html | 2 +- frappe/public/js/frappe/ui/group_by/group_by.js | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.html b/frappe/public/js/frappe/ui/group_by/group_by.html index 8522b46da8..c51d8495da 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.html +++ b/frappe/public/js/frappe/ui/group_by/group_by.html @@ -23,7 +23,7 @@ data-doctype="{{parent_doctype}}" value="{{group_by_conditions[parent_doctype][val].fieldname}}" > - {{ __(group_by_conditions[parent_doctype][val].label) }} + {{ __(group_by_conditions[parent_doctype][val].label || group_by_conditions[parent_doctype][val].fieldname) }} {% } %} {% } %} diff --git a/frappe/public/js/frappe/ui/group_by/group_by.js b/frappe/public/js/frappe/ui/group_by/group_by.js index a98612b6a0..7b58f9c938 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.js +++ b/frappe/public/js/frappe/ui/group_by/group_by.js @@ -369,7 +369,7 @@ frappe.ui.GroupBy = class { const tag_field = { fieldname: "_user_tags", fieldtype: "Data", label: __("Tags") }; this.group_by_fields[this.doctype] = fields .concat(tag_field) - .sort((a, b) => __(a.label).localeCompare(__(b.label))); + .sort((a, b) => __(cstr(a.label)).localeCompare(cstr(__(b.label)))); this.all_fields[this.doctype] = this.report_view.meta.fields; const standard_fields_filter = (df) => @@ -407,8 +407,9 @@ frappe.ui.GroupBy = class { } get_group_by_field_label() { - return this.group_by_fields[this.group_by_doctype].find( + let field = this.group_by_fields[this.group_by_doctype].find( (field) => field.fieldname == this.group_by_field - ).label; + ); + return field.label || field.fieldname; } }; From 9a36a09beeec75d62855be78b7fa12a2e1bc6f7c Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Sep 2023 12:21:40 +0530 Subject: [PATCH 12/20] fix: remove unnecessary conditional both code branches exactly same, so why bother --- frappe/public/js/frappe/ui/group_by/group_by.html | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/frappe/public/js/frappe/ui/group_by/group_by.html b/frappe/public/js/frappe/ui/group_by/group_by.html index c51d8495da..35f354a129 100644 --- a/frappe/public/js/frappe/ui/group_by/group_by.html +++ b/frappe/public/js/frappe/ui/group_by/group_by.html @@ -10,22 +10,12 @@ {% for (var parent_doctype in group_by_conditions) { %} {% for (var val in group_by_conditions[parent_doctype]) { %} - {% if (parent_doctype !== doctype) { %} - - {% } else { %} - {% } %} {% } %} {% } %} From 086435c0041ad622e2170d741844b77b124dde48 Mon Sep 17 00:00:00 2001 From: David Arnold Date: Wed, 20 Sep 2023 09:45:22 +0200 Subject: [PATCH 13/20] feat: add upstream json argument to integrations make_request (#22462) * feat: add upstream json argument to integrations make_request * style: format [skip ci] --------- Co-authored-by: Ankush Menat --- frappe/integrations/utils.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/frappe/integrations/utils.py b/frappe/integrations/utils.py index 5ae8965c83..0f94d2ee29 100644 --- a/frappe/integrations/utils.py +++ b/frappe/integrations/utils.py @@ -10,14 +10,16 @@ from frappe import _ from frappe.utils import get_request_session -def make_request(method, url, auth=None, headers=None, data=None): +def make_request(method, url, auth=None, headers=None, data=None, json=None): auth = auth or "" data = data or {} headers = headers or {} try: s = get_request_session() - frappe.flags.integration_request = s.request(method, url, data=data, auth=auth, headers=headers) + frappe.flags.integration_request = s.request( + method, url, data=data, auth=auth, headers=headers, json=json + ) frappe.flags.integration_request.raise_for_status() if frappe.flags.integration_request.headers.get("content-type") == "text/plain; charset=utf-8": From 15010cf8a98f2a60d67a06778fcb010a10b2bd57 Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Wed, 20 Sep 2023 09:52:09 +0200 Subject: [PATCH 14/20] fix: Fix typo in form_tour (#22474) --- frappe/desk/doctype/form_tour/form_tour.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/frappe/desk/doctype/form_tour/form_tour.js b/frappe/desk/doctype/form_tour/form_tour.js index 068457c2c6..8201a5d0aa 100644 --- a/frappe/desk/doctype/form_tour/form_tour.js +++ b/frappe/desk/doctype/form_tour/form_tour.js @@ -108,8 +108,8 @@ let add_custom_button = (frm) => { tour_name: frm.doc.name, }, }); - }, - delete frappe.boot.user.onboarding_status[frm.doc.name] + delete frappe.boot.user.onboarding_status[frm.doc.name]; + } ); }); } else { From 67376e26d42e9e8fd3b162254df934e0232e3d3c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bernd=20Oliver=20S=C3=BCnderhauf?= <46800703+bosue@users.noreply.github.com> Date: Wed, 20 Sep 2023 11:56:00 +0200 Subject: [PATCH 15/20] fix: Set system timezone for Administrator and Guest users. (#22440) * fix: Set system timezone for Administrator and Guest users. * refactor: use constant instead of duplicating [skip ci] --------- Co-authored-by: Ankush Menat --- frappe/desk/page/setup_wizard/setup_wizard.py | 7 +++++++ frappe/utils/data.py | 2 +- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/frappe/desk/page/setup_wizard/setup_wizard.py b/frappe/desk/page/setup_wizard/setup_wizard.py index 9504a30df6..3a2b369a23 100755 --- a/frappe/desk/page/setup_wizard/setup_wizard.py +++ b/frappe/desk/page/setup_wizard/setup_wizard.py @@ -108,6 +108,7 @@ def update_global_settings(args): update_system_settings(args) update_user_name(args) + set_timezone(args) def run_post_setup_complete(args): @@ -248,6 +249,12 @@ def update_user_name(args): add_all_roles_to(args.get("name")) +def set_timezone(args): + if args.get("timezone"): + for name in frappe.STANDARD_USERS: + frappe.db.set_value("User", name, "time_zone", args.get("timezone")) + + def parse_args(args): if not args: args = frappe.local.form_dict diff --git a/frappe/utils/data.py b/frappe/utils/data.py index ee092a6a1f..d3cb996c9d 100644 --- a/frappe/utils/data.py +++ b/frappe/utils/data.py @@ -309,7 +309,7 @@ def get_eta(from_time, percent_complete): def _get_system_timezone(): - return frappe.db.get_system_setting("time_zone") or "Asia/Kolkata" # Default to India ?! + return frappe.get_system_settings("time_zone") or "Asia/Kolkata" # Default to India ?! def get_system_timezone(): From 5004374eccd0603a2c6f9a0311997d7ff00b4bea Mon Sep 17 00:00:00 2001 From: Anand Baburajan Date: Wed, 20 Sep 2023 16:44:51 +0530 Subject: [PATCH 16/20] chore: handle invalid connections conf for internal links (#22486) * chore: handle invalid connections conf for internal links * chore: remove unnecessary None --- frappe/desk/notifications.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/frappe/desk/notifications.py b/frappe/desk/notifications.py index f0470566ac..4c728bdee9 100644 --- a/frappe/desk/notifications.py +++ b/frappe/desk/notifications.py @@ -311,7 +311,7 @@ def get_internal_links(doc, link, link_doctype): elif isinstance(link, list): # get internal links in child documents table_fieldname, link_fieldname = link - for row in doc.get(table_fieldname): + for row in doc.get(table_fieldname) or []: value = row.get(link_fieldname) if value and value not in names: names.append(value) From 36c182e72c6383656d1cb16fb09434ad0a6eb9ce Mon Sep 17 00:00:00 2001 From: Psyi Rius <99079175+psyirius@users.noreply.github.com> Date: Wed, 20 Sep 2023 17:39:33 +0530 Subject: [PATCH 17/20] chore(js): improved cryptic looking sanitise regex with info (#22357) --- frappe/public/js/frappe/utils/common.js | 31 +++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 83a6766841..882f159117 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -332,10 +332,37 @@ frappe.utils.sanitise_redirect = (url) => { }; })(); + /* + * Strips out url containing the text `javascript` with or without any HTML Entities in it + **/ const sanitise_javascript = (url) => { - // please do not ask how or why + /* + * Written below split into parts, but actual is in one line regardless of whitespaces + * / + * j + * \s*(&#x.{1,7})? + * a + * \s*(&#x.{1,7})? + * v + * \s*(&#x.{1,7})? + * a + * \s*(&#x.{1,7})? + * s + * \s*(&#x.{1,7})? + * c + * \s*(&#x.{1,7})? + * r + * \s*(&#x.{1,7})? + * i + * \s*(&#x.{1,7})? + * p + * \s*(&#x.{1,7})? + * t + * /gi + * */ + const REGEX_ESC_UNIT = /\s*(&#x.{1,7})?/; const REGEX_SCRIPT = - /j[\s]*(&#x.{1,7})?a[\s]*(&#x.{1,7})?v[\s]*(&#x.{1,7})?a[\s]*(&#x.{1,7})?s[\s]*(&#x.{1,7})?c[\s]*(&#x.{1,7})?r[\s]*(&#x.{1,7})?i[\s]*(&#x.{1,7})?p[\s]*(&#x.{1,7})?t/gi; + new RegExp(Array.from('javascript').join(REGEX_ESC_UNIT.source), 'gi'); return url.replace(REGEX_SCRIPT, ""); }; From ff26528ab06b71845903f06924fdefa985affacd Mon Sep 17 00:00:00 2001 From: Ankush Menat Date: Wed, 20 Sep 2023 18:16:10 +0530 Subject: [PATCH 18/20] style: format --- frappe/public/js/frappe/utils/common.js | 56 +++++++++++++------------ 1 file changed, 29 insertions(+), 27 deletions(-) diff --git a/frappe/public/js/frappe/utils/common.js b/frappe/public/js/frappe/utils/common.js index 882f159117..870d5b7182 100644 --- a/frappe/public/js/frappe/utils/common.js +++ b/frappe/public/js/frappe/utils/common.js @@ -333,36 +333,38 @@ frappe.utils.sanitise_redirect = (url) => { })(); /* - * Strips out url containing the text `javascript` with or without any HTML Entities in it - **/ + * Strips out url containing the text `javascript` with or without any HTML Entities in it + **/ const sanitise_javascript = (url) => { /* - * Written below split into parts, but actual is in one line regardless of whitespaces - * / - * j - * \s*(&#x.{1,7})? - * a - * \s*(&#x.{1,7})? - * v - * \s*(&#x.{1,7})? - * a - * \s*(&#x.{1,7})? - * s - * \s*(&#x.{1,7})? - * c - * \s*(&#x.{1,7})? - * r - * \s*(&#x.{1,7})? - * i - * \s*(&#x.{1,7})? - * p - * \s*(&#x.{1,7})? - * t - * /gi - * */ + * Written below split into parts, but actual is in one line regardless of whitespaces + * / + * j + * \s*(&#x.{1,7})? + * a + * \s*(&#x.{1,7})? + * v + * \s*(&#x.{1,7})? + * a + * \s*(&#x.{1,7})? + * s + * \s*(&#x.{1,7})? + * c + * \s*(&#x.{1,7})? + * r + * \s*(&#x.{1,7})? + * i + * \s*(&#x.{1,7})? + * p + * \s*(&#x.{1,7})? + * t + * /gi + * */ const REGEX_ESC_UNIT = /\s*(&#x.{1,7})?/; - const REGEX_SCRIPT = - new RegExp(Array.from('javascript').join(REGEX_ESC_UNIT.source), 'gi'); + const REGEX_SCRIPT = new RegExp( + Array.from("javascript").join(REGEX_ESC_UNIT.source), + "gi" + ); return url.replace(REGEX_SCRIPT, ""); }; From 42ee7f9b2ddafe1c9b769df4760462be3ee64f12 Mon Sep 17 00:00:00 2001 From: HENRY Florian Date: Wed, 20 Sep 2023 15:22:21 +0200 Subject: [PATCH 19/20] feat: bulk update fields select sorted by translated labels (#22318) * feat: bulk update fields sorted translated alpha sort * chore: implement review from @barredterra * chore: fix linter/prettier * fix: convert to string --------- Co-authored-by: Ankush Menat --- frappe/public/js/frappe/list/bulk_operations.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/frappe/public/js/frappe/list/bulk_operations.js b/frappe/public/js/frappe/list/bulk_operations.js index 5e38c4c25b..a0271967b4 100644 --- a/frappe/public/js/frappe/list/bulk_operations.js +++ b/frappe/public/js/frappe/list/bulk_operations.js @@ -235,7 +235,11 @@ export default class BulkOperations { } edit(docnames, field_mappings, done) { - let field_options = Object.keys(field_mappings).sort(); + let field_options = Object.keys(field_mappings).sort(function (a, b) { + return __(cstr(field_mappings[a].label)).localeCompare( + cstr(__(field_mappings[b].label)) + ); + }); const status_regex = /status/i; const default_field = field_options.find((value) => status_regex.test(value)); From 41d30e721380be6bb225a3888c48c441552dce2a Mon Sep 17 00:00:00 2001 From: Corentin Flr <10946971+cogk@users.noreply.github.com> Date: Wed, 20 Sep 2023 16:53:17 +0200 Subject: [PATCH 20/20] feat: Add custom icons with the `app_include_icons` hook (#22254) * feat: Add custom icons with the `app_include_icons` hook These custom icons are available on the desk only. They can be picked in the `ControlIcon` picker. Co-authored-by: Abraham Kalungi <85731451+kalungia@users.noreply.github.com> * fix: skip conf for including icons * test: Fix test_include_icons --------- Co-authored-by: Abraham Kalungi <85731451+kalungia@users.noreply.github.com> Co-authored-by: Ankush Menat --- frappe/hooks.py | 1 + frappe/public/icons/timeless/test.svg | 5 +++++ frappe/public/js/frappe/form/controls/icon.js | 2 +- frappe/tests/test_website.py | 21 +++++++++++++++++++ frappe/utils/boilerplate.py | 5 +++++ frappe/www/app.html | 6 +++++- frappe/www/app.py | 2 ++ 7 files changed, 40 insertions(+), 2 deletions(-) create mode 100644 frappe/public/icons/timeless/test.svg diff --git a/frappe/hooks.py b/frappe/hooks.py index 28ba377793..070e7f5ba0 100644 --- a/frappe/hooks.py +++ b/frappe/hooks.py @@ -35,6 +35,7 @@ app_include_css = [ "desk.bundle.css", "report.bundle.css", ] +app_include_icons = ["frappe/public/icons/timeless/icons.svg"] doctype_js = { "Web Page": "public/js/frappe/utils/web_template.js", diff --git a/frappe/public/icons/timeless/test.svg b/frappe/public/icons/timeless/test.svg new file mode 100644 index 0000000000..9563c827fd --- /dev/null +++ b/frappe/public/icons/timeless/test.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/frappe/public/js/frappe/form/controls/icon.js b/frappe/public/js/frappe/form/controls/icon.js index 89abb82114..f3386ed813 100644 --- a/frappe/public/js/frappe/form/controls/icon.js +++ b/frappe/public/js/frappe/form/controls/icon.js @@ -10,7 +10,7 @@ frappe.ui.form.ControlIcon = class ControlIcon extends frappe.ui.form.ControlDat get_all_icons() { frappe.symbols = []; - $("#frappe-symbols > symbol[id]").each(function () { + $("#all-symbols > svg > symbol[id]").each(function () { this.id.includes("icon-") && frappe.symbols.push(this.id.replace("icon-", "")); }); } diff --git a/frappe/tests/test_website.py b/frappe/tests/test_website.py index b7b33b3531..279252865c 100644 --- a/frappe/tests/test_website.py +++ b/frappe/tests/test_website.py @@ -391,6 +391,27 @@ class TestWebsite(FrappeTestCase): delattr(frappe.local, "request") frappe.set_user("Guest") + def test_include_icons(self): + from frappe import get_hooks + + TEST_ICONS_PATH = "frappe/public/icons/timeless/test.svg" + + def patched_get_hooks(*args, **kwargs): + return_value = get_hooks(*args, **kwargs) + if isinstance(return_value, dict) and "app_include_icons" in return_value: + return_value.app_include_icons.append(TEST_ICONS_PATH) + return return_value + + with patch.object(frappe, "get_hooks", patched_get_hooks): + frappe.set_user("Administrator") + + set_request(method="GET", path="/app") + content = get_response_content("/app") + # icon is available in a symbol tag + self.assertIn('id="icon-TEST-ONLY"', content) + delattr(frappe.local, "request") + frappe.set_user("Guest") + def patched_get_hooks(hook, value): def wrapper(*args, **kwargs): diff --git a/frappe/utils/boilerplate.py b/frappe/utils/boilerplate.py index 85bf3d6f8c..f3a9d39190 100644 --- a/frappe/utils/boilerplate.py +++ b/frappe/utils/boilerplate.py @@ -365,6 +365,11 @@ app_license = "{app_license}" # doctype_tree_js = {{"doctype" : "public/js/doctype_tree.js"}} # doctype_calendar_js = {{"doctype" : "public/js/doctype_calendar.js"}} +# Svg Icons +# ------------------ +# include app icons in desk +# app_include_icons = "{app_name}/public/icons.svg" + # Home Pages # ---------- diff --git a/frappe/www/app.html b/frappe/www/app.html index ceceaf3219..14399bc2d5 100644 --- a/frappe/www/app.html +++ b/frappe/www/app.html @@ -25,7 +25,11 @@ {%- endfor -%} - {% include "public/icons/timeless/icons.svg" %} +
+ {%- for path in include_icons -%} + {%- include path ignore missing -%} + {%- endfor -%} +
{% include "templates/includes/splash_screen.html" %}
diff --git a/frappe/www/app.py b/frappe/www/app.py index 37628d1ab2..38c97fd481 100644 --- a/frappe/www/app.py +++ b/frappe/www/app.py @@ -44,6 +44,7 @@ def get_context(context): include_js = hooks.get("app_include_js", []) + frappe.conf.get("app_include_js", []) include_css = hooks.get("app_include_css", []) + frappe.conf.get("app_include_css", []) + include_icons = hooks.get("app_include_icons", []) context.update( { @@ -51,6 +52,7 @@ def get_context(context): "build_version": frappe.utils.get_build_version(), "include_js": include_js, "include_css": include_css, + "include_icons": include_icons, "layout_direction": "rtl" if is_rtl() else "ltr", "lang": frappe.local.lang, "sounds": hooks["sounds"],